[英]Haskell Parsec: Touching Operators
我有以下BNF定義的邏輯語言。
formula ::= true
| false
| var
| formula & formula
| [binder] formula
binder ::= var
| $var
本質上,這允許諸如x & true
, [x]x
和[$x](x & true)
。 這里的語義並不重要; 但是最重要的是,我在公式之前出現了這些方括號表達式,並且在這些方括號表達式中,標識符可以或可以不以美元符號( $
)開頭。 現在,我已經使用Haskell的Parsec庫來幫助我構造這種語言的解析器,下面將詳細介紹。
module LogicParser where
import System.IO
import Control.Monad
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Expr
import Text.ParserCombinators.Parsec.Language
import qualified Text.ParserCombinators.Parsec.Token as Token
-- Data Structures
data Formula = LVar String
| TT
| FF
| And Formula Formula
| Bound Binder Formula
deriving Show
data Binder = BVar String
| FVar String
deriving Show
-- Language Definition
lang :: LanguageDef st
lang =
emptyDef{ Token.identStart = letter
, Token.identLetter = alphaNum
, Token.reservedOpNames = ["&", "$", "[", "]"]
, Token.reservedNames = ["tt", "ff"]
}
-- Lexer for langauge
lexer =
Token.makeTokenParser lang
-- Trivial Parsers
identifier = Token.identifier lexer
keyword = Token.reserved lexer
op = Token.reservedOp lexer
roundBrackets = Token.parens lexer
whiteSpace = Token.whiteSpace lexer
-- Main Parser, takes care of trailing whitespaces
formulaParser :: Parser Formula
formulaParser = whiteSpace >> formula
-- Parsing Formulas
formula :: Parser Formula
formula = andFormula
<|> formulaTerm
-- Term in a Formula
formulaTerm :: Parser Formula
formulaTerm = roundBrackets formula
<|> ttFormula
<|> ffFormula
<|> lvarFormula
<|> boundFormula
-- Conjunction
andFormula :: Parser Formula
andFormula =
buildExpressionParser [[Infix (op "&" >> return And) AssocLeft]] formulaTerm
-- Bound Formula
boundFormula :: Parser Formula
boundFormula =
do op "["
v <- var
op "]"
f <- formulaTerm
return $ Bound v f
-- Truth
ttFormula :: Parser Formula
ttFormula = keyword "tt" >> return TT
-- Falsehood
ffFormula :: Parser Formula
ffFormula = keyword "ff" >> return FF
-- Logical Variable
lvarFormula :: Parser Formula
lvarFormula =
do v <- identifier
return $ LVar v
-- Variable
var :: Parser Binder
var = try bvar <|> fvar
-- Bound Variable
bvar :: Parser Binder
bvar =
do op "$"
v <- identifier
return $ BVar v
-- Free Variable
fvar :: Parser Binder
fvar =
do v <- identifier
return $ FVar v
-- For testing
main :: IO ()
main = interact (unlines . (map stringParser) . lines)
stringParser :: String -> String
stringParser s =
case ret of
Left e -> "Error: " ++ (show e)
Right n -> "Interpreted as: " ++ (show n)
where
ret = parse formulaParser "" s
我的問題如下。 當美元符號運算符( $
) 觸摸方括號時,出現錯誤,而如果添加空格,則解析器可以正常工作:
我如何使解析器識別[$x](x & true)
? 請注意,只有在兩個運算符[
和$
接觸時, &
觸及其操作數才沒有問題。
我對Parsec的令牌方面不是很熟悉,但是從其文檔中,我認為您需要提供opLetter
,可能還需要opStart
,因為您要提供reservedOp
:
opLetter :: ParsecT sum Char
該解析器應接受運算符的任何合法尾字符。 請注意,如果該語言不支持用戶定義的運算符,則甚至應定義該解析器,否則reserveOp解析器將無法正常工作。
特別是默認opLetter
不包含[
或]
,因此當您認為其中之一應該是op時,它的行為異常。
我認為這不喜歡您將方括號用作運算符。 我會嘗試這樣的:
Token.reservedOpNames
刪除"["
和"]"
squareBrackets = Token.brackets lexer
將您的boundFormula
函數更改為:
boundFormula = do v <- squareBrackets var f <- formulaTerm return $ Bound vf
這是我用Megaparsec( docs , tutorial )編寫解析器的方式:
import Text.Megaparsec
import qualified Text.Megaparsec.Char as C
import qualified Text.Megaparsec.Char.Lexer as L
import Control.Monad.Combinators.Expr
import Data.Void
type Parser = Parsec Void String
data Formula = LVar String
| TT
| FF
| Not Formula -- Added to demonstrate `Prefix` of `makeExprParser`
| And Formula Formula
| Bound Binder Formula
deriving Show
data Binder = BVar String
| FVar String
deriving Show
Megaparsec還具有makeExprParser
,但已將其移動到一個單獨的parser-combinators
包中:
formula :: Parser Formula
formula = makeExprParser term operators
where
term = choice
[ TT <$ symbol "true"
, FF <$ symbol "false"
, LVar <$> var
, Bound <$> brackets binder <*> parens formula
]
binder = choice
[ BVar <$> (C.char '$' >> var)
, FVar <$> var
]
var = lexeme $ some C.letterChar
operators :: [[Operator Parser Formula]]
operators =
[ [ Prefix (Not <$ symbol "¬") ]
, [ InfixL (And <$ symbol "&") ]
]
一些要點:
<$>
, <$
, $>
)。 many1
到some
。 有一個教程, 從Parsec切換到Megaparsec ,可以幫助過渡。 (可能有點過時;我在回答這個問題的過程中發送了PR。) BVar
和FVar
在第一個字符$
互斥時,您無需try
。 僅列出BVar
解析器就足夠了。 既然你不會找到開頭任何其他有效的表達式$
,這消耗了失敗的解析器$
是沒有問題的。 您的語法沒有提及括號或括號后的內容。 因此,為了解析"[$x](x & true)"
您需要為語法添加明確的括號,或者
formula ::= ... | '(' formula ')'
或作為
formula ::= ... | '[' binder ']' '(' formula ')'
如果只允許他們在那里。 我已經放棄了后者,但這可能是錯誤的。
繼續,
lexeme :: Parser a -> Parser a
lexeme = L.lexeme spaceConsumer
symbol :: String -> Parser String
symbol = L.symbol spaceConsumer
spaceConsumer :: Parser ()
spaceConsumer = L.space C.space1 empty empty
brackets, parens :: Parser a -> Parser a
brackets = between (symbol "[") (symbol "]")
parens = between (symbol "(") (symbol ")")
最后一點
between
使用來將解析器包裝在brackets
或braces
。 empty :: Alternative f => fa
來忽略不相關的部分(例如,行注釋和塊注釋)。 解析器組合器中的空格非常棘手。 確保所有解析器都是lexeme解析器(例如, symbol "foo"
, lexeme $ some C.letterChar
)或lexeme解析器的組合。 如果在已經是lexeme解析器的對象上使用lexeme
,則說明操作有誤,如果忘記在某些對象上使用lexeme
,則可能會在允許的地方使用空格。
我沒有在C.char '$'
周圍使用lexeme
。
總有一些極端的情況,例如開頭是空格:
> parseTest formula " [$x](x & true) " 1:1: | 1 | [$x](x & true) | ^^^^^ unexpected " [$x]" expecting "false", "true", '[', '¬', or letter
如果要徹底斷言解析器在所有正確的位置都允許空白,則可以構建一個“ ugly-printer”,對於任意語法樹,該打印機在允許的位置插入任意空白。 然后,您的屬性是,解析丑陋的語法樹應該產生相同的語法樹。
Megaparsec解析錯誤看起來超級好。
如果使用<?>
運算符(在Parsec中也可用),它們看起來會更好。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.