簡體   English   中英

Haskell Parsec:感動運算符

[英]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時,它的行為異常。

我認為這不喜歡您將方括號用作運算符。 我會嘗試這樣的:

  1. Token.reservedOpNames刪除"[""]"
  2. 將另一個解析器添加到平凡的解析器中: squareBrackets = Token.brackets lexer
  3. 將您的boundFormula函數更改為:

     boundFormula = do v <- squareBrackets var f <- formulaTerm return $ Bound vf 

這是我用Megaparsec( docstutorial )編寫解析器的方式:

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 "&") ]
      ]

一些要點:

  • 盡可能使用可應用的樣式( <$><$$> )。
  • 百萬秒差距更名一些組合子像many1some 有一個教程, 從Parsec切換到Megaparsec ,可以幫助過渡。 (可能有點過時;我在回答這個問題的過程中發送了PR。)
  • BVarFVar在第一個字符$互斥時,您無需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使用來將解析器包裝在bracketsbraces
  • Parsec令牌解析器有點復雜,因為它們要求您指定令牌是什么以及空白是什么,依此類推。 Megaparsec lexeme解析器具有某種復雜性,但是您可以通過使用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.

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