Parsing Parsec解析不同类型语句的列表
我正在尝试解析(目前)Dot语言的一个子集。 语法是,我的代码如下Parsing Parsec解析不同类型语句的列表,parsing,haskell,parsec,Parsing,Haskell,Parsec,我正在尝试解析(目前)Dot语言的一个子集。 语法是,我的代码如下 import System.Environment import System.IO import qualified Text.Parsec.Token as P import Text.ParserCombinators.Parsec.Char -- for letter import Text.Parsec import qualified Control.Applicative as App import Lib ty
import System.Environment
import System.IO
import qualified Text.Parsec.Token as P
import Text.ParserCombinators.Parsec.Char -- for letter
import Text.Parsec
import qualified Control.Applicative as App
import Lib
type Id = String
data Dot = Undirected Id Stmts
| Directed Id Stmts
deriving (Show)
data Stmt = NodeStmt Node | EdgeStmt Edges
deriving (Show)
type Stmts = [Stmt]
data Node = Node Id Attributes deriving (Show)
data Edge = Edge Id Id deriving (Show)
type Edges = [Edge]
data Attribute = Attribute Id Id deriving (Show)
type Attributes = [Attribute]
dotDef :: P.LanguageDef st
dotDef = P.LanguageDef
{ P.commentStart = "/*"
, P.commentEnd = "*/"
, P.commentLine = "//"
, P.nestedComments = True
, P.identStart = letter
, P.identLetter = alphaNum
, P.reservedNames = ["node", "edge", "graph", "digraph", "subgraph", "strict" ]
, P.caseSensitive = True
, P.opStart = oneOf "-="
, P.opLetter = oneOf "->"
, P.reservedOpNames = []
}
lexer = P.makeTokenParser dotDef
brackets = P.brackets lexer
braces = P.braces lexer
identifier = P.identifier lexer
reserved = P.reserved lexer
semi = P.semi lexer
comma = P.comma lexer
reservedOp = P.reservedOp lexer
eq_op = reservedOp "="
undir_edge_op = reservedOp "--"
dir_edge_op = reservedOp "->"
edge_op = undir_edge_op <|> dir_edge_op
-- -> Attribute
attribute = do
id1 <- identifier
eq_op
id2 <- identifier
optional (semi <|> comma)
return $ Attribute id1 id2
a_list = many attribute
bracked_alist =
brackets $ option [] a_list
attributes =
do
nestedAttributes <- many1 bracked_alist
return $ concat nestedAttributes
nodeStmt = do
nodeName <- identifier
attr <- option [] attributes
return $ NodeStmt $ Node nodeName attr
dropLast = reverse . tail . reverse
edgeStmt = do
nodes <- identifier `sepBy1` edge_op
return $ EdgeStmt $ fmap (\x -> Edge (fst x) (snd x)) (zip (dropLast nodes) (tail nodes))
stmt = do
x <- nodeStmt <|> edgeStmt
optional semi
return x
stmt_list = many stmt
graphDecl = do
reserved "graph"
varName <- option "" identifier
stms <- braces stmt_list
return $ Undirected varName stms
digraphDecl = do
reserved "digraph"
varName <- option "" identifier
stms <- braces stmt_list
return $ Directed varName stms
topLevel3 = do
spaces
graphDecl <|> digraphDecl
main :: IO ()
main = do
(file:_) <- getArgs
content <- readFile file
case parse topLevel3 "" content of
Right g -> print g
Left err -> print err
如果对第1行或第2行进行了注释,则可以正常工作,但如果同时启用了这两个行,则会失败
(第3行第10列):意外“-”应为标识符或“}”
我的理解是解析器选择第一个匹配规则(带回溯)。在这里,edge和node语句都以and标识符开头,所以它总是选择这个
我尝试在stmt
中反转顺序,但没有任何运气。
我还试图在stmt、nodeStmt和edgeStmt中撒一些try
,但也没有运气
感谢您的帮助。请注意,无论第1行是否被注释掉,我都会收到相同的错误,因此:
digraph PZIFOZBO{
a->b
}
还表示意外“-”
正如我认为您已经正确诊断的那样,这里的问题是stmt
解析器首先尝试nodeStmt
。它成功地解析了“a”
,留下了“->b”
,但->b
不是有效的语句。请注意,Parsec在没有try的情况下不会自动回溯,因此当它“发现”无法解析->b
时,它不会返回并重新考虑该决策
您可以通过交换stmt
中的顺序来“修复”此问题:
x <- edgeStmt <|> nodeStmt
并将stmt
调整为“try
”,首先调整边缘语句,然后回溯到节点语句:
stmt = do
x <- try edgeStmt <|> nodeStmt
optional semi
return x
stmt=do
另外,dropLast
只是库函数init
,zipWith
结合了map
和zip
,所以你可以用return$EdgeStmt$zipWith Edge(init节点)(tail节点)
替换edgeStat
中的最后一句话,我以为dropLast有什么用的,但是我找不到,所以我写了我自己的^^。关于原来的问题,首先,谢谢你的回答。如果我想,我如何进一步扩展解析器来处理stmt中的第四种情况(即ID'='ID)?更多的尝试?是的,我认为x
import Control.Monad (guard)
edgeStmt = do
nodes <- identifier `sepBy1` edge_op
guard $ length nodes > 1
return $ EdgeStmt $ fmap (\x -> Edge (fst x) (snd x)) (zip (dropLast nodes) (tail nodes))
stmt = do
x <- try edgeStmt <|> nodeStmt
optional semi
return x