Parsec.Expr以不同的优先级重复前缀

时间:2015-10-19 12:16:08

标签: haskell parsec

Parsec.Expr.buildExpressionParser的文档说:

  

相同优先级的前缀和后缀运算符只能出现一次(如果 - 是前缀否定,则不允许--2)。

但是,我想解析这些字符串。

具体而言,请考虑以下语法:

sentence: 
    | identifier
    | "~" sentence
    | sentence & sentence
    | "!" sentence

运算符优先级为:"~"绑定强于"&"绑定强于"!"

例如,我想要句子

! ~a & b

被解析为

! ( (~a) & b )

和句子

~ ! a & b 

as

~( ! ( a & b) )

Parsec允许我这样做(并指定运算符优先级),但是,我希望能够链接前缀,例如~ ~ ! ~ a。 Parsec不允许这样做。 我找到了解决方案for chaining prefixes,但是这个解决方案不允许我为不同的前缀运算符指定不同的运算符优先级(“〜”和“!”绑定都强于“&”,或者没有他们确实)

有人有解决方案吗?

修改

使操作符绑定正确但不允许链接的部分解决方案: http://lpaste.net/143362

带链接的部分解决方案,但“〜”运算符绑定错误: http://lpaste.net/143364

修改:有关latest answer的更多说明。

我实际上希望&是关联的。左或右无所谓。左与右关联性仅在具有相同优先级的运算符之间起作用。 对于您的示例,通过注意&绑定强于!&具有更高的运算符优先级)来解决所有问题

因此,你担心的表达方式是:

a & ! b & c应该成为: (首先绑定&a & ! (b & c)

同样,应该解析! a & ! b & c (第一次绑定&) ! a & ! (b & c)! a & (! (b & c))! (a & (! (b & c)))

4 个答案:

答案 0 :(得分:4)

我对原始答案不满意,因为它没有解决各种优先级的前缀和后缀运算符的一般情况,并且它要求程序员必须考虑语法而不是仅仅依赖{{1做正确的事。

我在网上搜寻并发现了Pratt method for recursive descent parsing of expressions。我能够实现一个替换buildExpressionParser的紧凑型Haskell版本。它与buildExpressionParser具有完全相同的接口,但不要求您使用链接前缀组合器或使用术语解析器。我玩你的语法,改变buildExpressionParser的关联性,并将前缀运算符切换到后缀运算符,这一切似乎都有用......

&

答案 1 :(得分:2)

http://lpaste.net/143362部分解决方案的一个问题是它无法识别~ ! a

但是,如果将操作员表更改为:

table   = [ [ Prefix tilde ]
          , [ Infix amper AssocLeft ]
          , [ Prefix bang ]
          , [ Prefix tilde ]
          ]

它可以正确地解析该表达式以及! ~a & b~ ! a & b。代码位于:http://lpaste.net/143370

所以现在将这个想法与你的链接结合起来并尝试:

table   = [ [ Prefix (chained tilde) ]
          , [ Infix amper AssocLeft ]
          , [ Prefix (chained bang) ]
          , [ Prefix (chained tilde) ]
          ]

chained  p = chainl1 p $ return (.)

代码:http://lpaste.net/143371

答案 2 :(得分:2)

你想要的解析器的左因子语法是:

public void handle(HttpChannel connection) throws IOException,
        ServletException { 
    super.handle(connection); 

    connection.getRequest().getTimeStamp();
} 

可以在EBNF中重写为:

sentence : '!' sentence
         | sentence1

sentence1 : sentence2 '&' sentence1
          | sentence2

sentence2 : '~' sentence2
          | term

term : '!' sentence
     | ident

sentence : '!'* sentence1 sentence1 : sentence2 ('&' sentence2)* sentence2 : '~'* term term : '!' sentence | ident 使用链式前缀运算符生成的解析器几乎生成此解析器,除了它在术语解析器中不包含buildExpressionParser规则;因此在!之后遇到!时出现解析错误。

鉴于以下内容:

~

我们可以手动定义{-# LANGUAGE NoMonomorphismRestriction #-} module Main where import Control.Monad import Text.Parsec import Text.Parsec.Expr import Text.Parsec.Char import Control.Applicative ( (<*), (*>), (<*>), (<$), (<$>) ) data Sentence = Tilde Sentence | Bang Sentence | Amper Sentence Sentence | Ident String deriving ( Eq, Ord, Show ) bangP = Bang <$ lexeme (char '!') amperP = Amper <$ lexeme (char '&') tildeP = Tilde <$ lexeme (char '~') identP = Ident <$> lexeme (many1 alphaNum) lexeme = (<* spaces) parser = spaces *> sentence <* eof main = do let inputs = [ "a", "! a", "~ a", "a & b", "! a & b" , "~ a & b", "! ~ a & b", "~ ! a & b", "! ~ ! a" , "~ a & b", "a & ! b & c & d" ] forM_ inputs $ \input -> do putStr input putStr " -> " parseTest parser input 解析器:

sentence

如果我们将sentence = sentence0 where sentence0 = chainl bangP (return (.)) id <*> sentence1 sentence1 = chainl1 sentence2 amperP sentence2 = chainl tildeP (return (.)) id <*> term term = (bangP <*> sentence0) <|> identP 规则添加到buildExpressionParser解析器中,我们可以使用!

term

答案 3 :(得分:1)

新答案......

您是否考虑过&amp; 运算符的相关性?

这是我想出的另一个想法,假设&amp; 是正确关联的。

  1. 收集术语前的前缀运算符序列。
  2. 解析该术语(一个标识或一个表达式)
  3. 通过从步骤1中收集的序列移动运算符来修正该术语。
  4. 如果下一个标记是&amp; ,则amper运算符的LHS是固定的期限。其余运算符应用于amper表达式。
  5. 否则,结果只是应用于术语的前缀运算符。
  6. 我认为&amp; 的相关性很重要,例如我们有:

    a & ! b & c  -->   a & (! b & c)  --> a & ! (b & c)
    

    a & ! b & c  -->   (a & (! b)) & c
    

    要考虑的另一个案例是! a & ! b & c - 您希望如何解析?

    实施:

     {-# LANGUAGE NoMonomorphismRestriction, FlexibleContexts #-}
    
     import Text.Parsec
     import Control.Monad
     import Text.ParserCombinators.Parsec hiding (runParser, try)
     import Text.Parsec.Char
    
     data Sentence = Ident String | Bang Sentence | Tilde Sentence | Amper Sentence Sentence
       deriving (Show)
    
     lexer p = do x <- p; spaces; return x
     ident = lexer (many1 letter)
     sym ch  = lexer (char ch)
    
     tilde = sym '~'
     bang  = sym '!'
     amper = sym '&'
    
     parens p = between (sym '(') (sym ')') p
    
     term    =  parens expr 
              <|> (fmap Ident ident)
              <?> "simple expression"
    
     prefixOps = many (try tilde <|> bang)
    
     expr = do
       ops <- fmap reverse prefixOps
       lhs <- term
    
       let (ops', lhs') = popTildes ops lhs
           pre = mkPrefixNode ops'
    
       mrhs <- try (fmap Just (amper >> expr)) <|> (return Nothing)
    
       case mrhs of
         Nothing  -> return $ pre lhs'
         Just rhs -> return $ pre (Amper lhs' rhs)
    
     popTildes :: [Char] -> Sentence -> ([Char], Sentence)
     popTildes ('~':rest) s = popTildes rest (Tilde s)
     popTildes ops s        = (ops, s)
    
     mkPrefixNode :: [Char] -> (Sentence -> Sentence)
     mkPrefixNode [] = id
     mkPrefixNode ('~':rest) = mkPrefixNode rest . Tilde
     mkPrefixNode ('!':rest) = mkPrefixNode rest . Bang 
     mkPrefixNode _          = error "can't happen"
    
     check :: String -> IO ()
     check input = do
       let padded = input ++ (replicate (15-length input) ' ')
       case parse expr "-" input of
         Left e  -> do putStrLn $ "FAILED: " ++ input
                       putStrLn $ "  " ++ show e
         Right x -> do putStrLn $ "OK: " ++ padded ++ " -> " ++ show x
    
     inputs = [ "a", "! a", "~ a", "a & b", "! a & b", "~ a & b", "! ~ a & b"
              ,  "~ ! a", "! ~a & b", "~ ! a & b ", "! ~ ! a 2"
              ]
    
     main = mapM_ check inputs