簡體   English   中英

使用 Haskell 創建解析器時的左遞歸算法

[英]Left recursive arithmetic when creating parser with Haskell

我正在嘗試使用在其(算術)輸出中保持關聯的readP 在 Haskell 中創建一個解析器。 在下面的簡化代碼中,如果在表達式的左側部分調用pOp (請參閱注釋掉的代碼),我顯然會得到一個無限循環,或者我會得到一個正確的關聯輸出,例如2+(4+(6+8))相當於:

ghci> parseString "2+4+6+8"
[(Oper Plus (Const (IntVal 2)) (Oper Plus (Const (IntVal 4)) (Oper Plus (Const (IntVal 6)) (Const (IntVal 8)))),"")]

MVE:

import Data.Char

import Text.ParserCombinators.ReadP
--import Text.Parser.Char
import Control.Applicative ((<|>))


type Parser a = ReadP a 

data Value =
  IntVal Int
  deriving (Eq, Show, Read)

data Exp =
    Const Value
  | Oper Op Exp Exp
  deriving (Eq, Show, Read)

data Op = Plus
  deriving (Eq, Show, Read)

space :: Parser Char
space = satisfy isSpace


spaces :: Parser String 
spaces = many space


space1 :: Parser String
space1 = many1 space

symbol :: String -> Parser String
symbol = token . string

token :: Parser a -> Parser a
token combinator = (do spaces
                       combinator)


parseString input = readP_to_S (do                         
                        e <- pExpr 
                        token eof
                        return e) input


pExpr :: Parser Exp 
pExpr = 
    (do
            pv <- pOp
            return pv)
        <|>
    (do         
        pv <- numConst 
        skipSpaces       
        return pv) 


numConst :: Parser Exp
numConst = 
        (do  
            skipSpaces
            y <- munch isDigit            
            return (Const (IntVal (read y)))
        )

pOp :: Parser Exp 
pOp = (do 
        e1 <- numConst -- pExpr 
        skipSpaces          
        op <- symbol "+"                           
        e2 <- pExpr
        pv <- pOper op e1 e2 --
        return pv)
        

pOper :: String -> Exp -> Exp -> Parser Exp 
pOper "+" exp1 exp2 = (do return (Oper Plus exp1 exp2))

我嘗試了不同的策略,例如使用上述文檔中的look來展望未來,然后使用"("++ e ++ ")"其中e是表達式)獲取返回的字符串並在其周圍應用括號,然后有一個單獨的函數處理對括號表達式的調用以避免循環。 但這不是一個可行的解決方案,因為您不能像在原始輸入( look )上使用 readP 庫函數一樣在look的結果值上使用它。

任何想法如何解決這個問題。 我不知道我是否一開始就錯誤地表述了語法 (BNF),我真的只是從錯誤的角度來解決問題。 但我不這么認為。

考慮chainl1:: ReadP a -> ReadP (a -> a -> a) -> ReadP a 大多數解析器組合器庫都有chainlchainr函數族,用於以左關聯或右關聯方式解析運算符。 你可以像這樣應用它:

pExpr = chainl1 numConst (Oper Plus <$ symbol "+")

chainl1的實現正是 chi 在評論中推薦的以及您的答案實現的:它解析p ,然后解析任意數量的op + p對。

這解決了您當前的問題,但是一旦您添加了第二個運算符,您就會對優先級做出一些決定。 如果您希望+*具有相同的優先級(不尋常),您可以只使用這種方法但豐富分隔符解析器。 如果您希望*具有更高的優先級,則需要將pExpr分解為多個解析器:而不是Expr ,解析ProductSum ,諸如此類。 然后你需要一個chainl1來解析Sum的條款,其中每個條款本身都用chainl1解析以產生Product

由於上述評論,我現在能夠根據語法的變化提出一個解決方案。

parseString input = readP_to_S (do                         
                        e <- pExpr 
                        token eof
                        return e) input


pExpr :: Parser Exp 
pExpr = 
    (do
        pv <- numConst
        pv2 <- pOpRight pv               
        return pv2
        )
        <|>
    (do         
        pv <- numConst 
        skipSpaces       
        return pv)
      
   
numConst :: Parser Exp
numConst = 
        (do  
            skipSpaces
            y <- munch isDigit            
            return (Const (IntVal (read y)))
        )

pOpRight :: Exp -> Parser Exp 
pOpRight e1 = (do                 
        op <- symbol "+"
        skipSpaces
        e2 <- pExpr        
        pv <- pOper op e1 e2 --
        return pv)

pOper :: String -> Exp -> Exp -> Parser Exp 
pOper "+" exp1 exp2 = (do return (Oper Plus exp1 exp2))

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM