解析sum-type inifinite递归中的左递归语法

时间:2017-11-24 14:40:16

标签: parsing haskell parsec left-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,但链接解析器的类型是a -> a -> a,并且它与反映语法的LValue的类型不匹配。我也看了many;但是我没有为每个术语提供一个常量前缀 - 左递归术语是我尝试解析为结果类型的一部分。

我想我错过了Parsec /解析的特定概念,并希望指向正确的方向。我编写解析器的语言中有更多类型,它们具有相似的结构。

2 个答案:

答案 0 :(得分:3)

使用不支持左递归的工具解析左递归语法的常用方法确实是用重复替换左递归(即many)。对于记录访问,这意味着替换像

这样的规则
lvalue ::= lvalue '.' ID
         | primaryLValue

lvalue ::= primaryLValue ('.' ID)*

就Parsec而言意味着:

record <- atomParser                                                                                                       
fields <- many (char '.' >> idParser)

现在你有一个LValue和一个包含0个或更多字段名称的列表,它们不适合你的AST类型,但你可以通过在列表上折叠RecordAccess构造函数来解决这个问题。 / p>

答案 1 :(得分:1)

虽然你不能在这里使用chainl1, 您可以定义chainl1 - 像这样的组合器:

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'

其中AtomExpression及其解析器的定义如下:

type Atom = Char
atomParser :: Parser Atom
atomParser = letter <?> "atom"

data Expression = ENat Int
  deriving Show
expParser :: Parser Expression
expParser = (ENat . read) <$> many digit