求和型无限递归解析左递归文法
Parsing left-recursive grammar in sum-type inifinite recursion
我正在尝试从 ML 中的现代编译器实现 为 Tiger 语言编写解析器,但我卡在其中一种递归类型上。
我有以下类型
data LValue =
Id Atom
| RecordAccess LValue Atom
| ArraySubscript LValue Expression
使用以下语法:
lvalue -> id
-> lvalue.id
-> lvalue[exp]
id -> atom
exp -> (the big, overarching, everything-is-an-expression type)
我正在尝试用 Parsec 解析它,但我陷入了无限递归循环。这是我当前的基本解析器:
lvalueParser :: Parsec String () LValue
lvalueParser =
try (Id <$> (atomParser <* (notFollowedBy (char '.'))))
<|> try recordAccessParser
where recordAccessParser = (uncurry RecordAccess) <$> do {
record <- lvalueParser;
char '.';
atom <- atomParser;
return (record, atom)
}
(注意:我还没有尝试执行任何操作来处理 ArrayAccess
部分)
显然,无限循环发生在 recordAccessParser
回调到 lvalueParser
时。
我可以这样改变recordAccessParser
:
recordAccessParser = (uncurry RecordAccess) <$> do {
record <- atomParser;
char '.';
atom <- atomParser;
return (Id record, atom)
}
它终止了。但是,它不会解析超过单层深度的记录访问:
Parsec.parse lvalueParser "" "record_1.field_1.field_2"
#=> RecordAccess (Id record_1) (Id field_1)
我预计
#=> RecordAccess (RecordAccess (Id record_1) (Id field_1)) (Id field_2)
我看了chainl1
, but the type of the chaining parser is a -> a -> a
, and that doesn't match the type of LValue
that reflects the grammar. I also looked at many
;但是我没有每个术语的常量前缀 - 左递归术语是我试图解析为结果类型的一部分。
我想我错过了 Parsec/parsing 的一个特定概念,希望有人指出正确的方向。我正在为其编写解析器的语言中有更多类型将具有类似的结构。
用不支持左递归的工具解析左递归文法的常用方法确实是用重复代替左递归(即many
)。对于记录访问,这意味着替换
之类的规则
lvalue ::= lvalue '.' ID
| primaryLValue
和
lvalue ::= primaryLValue ('.' ID)*
以秒差距表示:
record <- atomParser
fields <- many (char '.' >> idParser)
现在你有一个 LValue
和一个包含 0 个或更多字段名称的列表,这不适合你的 AST 类型,但你可以通过折叠列表上的 RecordAccess
构造函数来解决这个问题.
虽然你不能在这里使用chainl1
,
您可以像这样定义 chainl1
-like 组合器:
leftRec :: (Stream s m t)
=> ParsecT s u m a -> ParsecT s u m (a -> a) -> ParsecT s u m a
leftRec p op = rest =<< p
where
rest x = do f <- op
rest (f x)
<|> return x
我参考了 here 来实现这个。
通过使用这个组合器,lvalueParser
可以定义如下:
lvalueParser :: Parser LValue
lvalueParser = leftRec idParser (recordAccessModifier <|> arraySubscriptModifier)
where
idParser = Id <$> atomParser
recordAccessModifier = do
a <- char '.' *> atomParser
return (\l -> RecordAccess l a)
arraySubscriptModifier = do
e <- between (char '[') (char ']') expParser
return (\l -> ArraySubscript l e)
示例:
main = parseTest lvalueParser "x.y[2].z"
-- => RecordAccess (ArraySubscript (RecordAccess (Id 'x') 'y') (ENat 2)) 'z'
其中Atom
、Expression
及其解析器定义如下:
type Atom = Char
atomParser :: Parser Atom
atomParser = letter <?> "atom"
data Expression = ENat Int
deriving Show
expParser :: Parser Expression
expParser = (ENat . read) <$> many digit
我正在尝试从 ML 中的现代编译器实现 为 Tiger 语言编写解析器,但我卡在其中一种递归类型上。
我有以下类型
data LValue =
Id Atom
| RecordAccess LValue Atom
| ArraySubscript LValue Expression
使用以下语法:
lvalue -> id
-> lvalue.id
-> lvalue[exp]
id -> atom
exp -> (the big, overarching, everything-is-an-expression type)
我正在尝试用 Parsec 解析它,但我陷入了无限递归循环。这是我当前的基本解析器:
lvalueParser :: Parsec String () LValue
lvalueParser =
try (Id <$> (atomParser <* (notFollowedBy (char '.'))))
<|> try recordAccessParser
where recordAccessParser = (uncurry RecordAccess) <$> do {
record <- lvalueParser;
char '.';
atom <- atomParser;
return (record, atom)
}
(注意:我还没有尝试执行任何操作来处理 ArrayAccess
部分)
显然,无限循环发生在 recordAccessParser
回调到 lvalueParser
时。
我可以这样改变recordAccessParser
:
recordAccessParser = (uncurry RecordAccess) <$> do {
record <- atomParser;
char '.';
atom <- atomParser;
return (Id record, atom)
}
它终止了。但是,它不会解析超过单层深度的记录访问:
Parsec.parse lvalueParser "" "record_1.field_1.field_2"
#=> RecordAccess (Id record_1) (Id field_1)
我预计
#=> RecordAccess (RecordAccess (Id record_1) (Id field_1)) (Id field_2)
我看了chainl1
, but the type of the chaining parser is a -> a -> a
, and that doesn't match the type of LValue
that reflects the grammar. I also looked at many
;但是我没有每个术语的常量前缀 - 左递归术语是我试图解析为结果类型的一部分。
我想我错过了 Parsec/parsing 的一个特定概念,希望有人指出正确的方向。我正在为其编写解析器的语言中有更多类型将具有类似的结构。
用不支持左递归的工具解析左递归文法的常用方法确实是用重复代替左递归(即many
)。对于记录访问,这意味着替换
lvalue ::= lvalue '.' ID
| primaryLValue
和
lvalue ::= primaryLValue ('.' ID)*
以秒差距表示:
record <- atomParser
fields <- many (char '.' >> idParser)
现在你有一个 LValue
和一个包含 0 个或更多字段名称的列表,这不适合你的 AST 类型,但你可以通过折叠列表上的 RecordAccess
构造函数来解决这个问题.
虽然你不能在这里使用chainl1
,
您可以像这样定义 chainl1
-like 组合器:
leftRec :: (Stream s m t)
=> ParsecT s u m a -> ParsecT s u m (a -> a) -> ParsecT s u m a
leftRec p op = rest =<< p
where
rest x = do f <- op
rest (f x)
<|> return x
我参考了 here 来实现这个。
通过使用这个组合器,lvalueParser
可以定义如下:
lvalueParser :: Parser LValue
lvalueParser = leftRec idParser (recordAccessModifier <|> arraySubscriptModifier)
where
idParser = Id <$> atomParser
recordAccessModifier = do
a <- char '.' *> atomParser
return (\l -> RecordAccess l a)
arraySubscriptModifier = do
e <- between (char '[') (char ']') expParser
return (\l -> ArraySubscript l e)
示例:
main = parseTest lvalueParser "x.y[2].z"
-- => RecordAccess (ArraySubscript (RecordAccess (Id 'x') 'y') (ENat 2)) 'z'
其中Atom
、Expression
及其解析器定义如下:
type Atom = Char
atomParser :: Parser Atom
atomParser = letter <?> "atom"
data Expression = ENat Int
deriving Show
expParser :: Parser Expression
expParser = (ENat . read) <$> many digit