2

我部分地从SO中获取了下面的示例并将其更改为我的需要。它几乎适合,但我想做的是始终将 commaSep expr 中的第一个字符串解析为标识符,而所有后续字符串应该只是字符串。

目前它们都被解析为标识符。

*Parser> parse expr "" "rd (isFib, test2, 100.1, ?BOOL)"
Right (FuncCall "rd" [Identifier "isFib",Identifier "test2",Number 100.1,Query "?BOOL"])

我尝试了许多解决方案,最终所有解决方案都会分解为在不使用 commaSep 的情况下解析整个输入。意味着我将不得不忽略结构并做类似的事情

expr_parse = do
    name <- resvd_cmd
    char '('
    skipMany space
    worker <- ident
    char ','
    skipMany1 space
    args <- commaSep expr --not fully worked this out yet
    query <- theQuery
    skipMany space
    char ')'
    return (name, worker, args, query)

这对我来说看起来不太理想而且非常笨重。有没有办法重构expr下面的代码,实现我需要的并保持简单?

module Parser where

import Control.Monad (liftM)
import Text.Parsec
import Text.Parsec.String (Parser)
import Lexer
import AST

expr = ident <|>  astring <|> number <|> theQuery <|> callOrIdent

astring = liftM String stringLiteral <?> "String"

number = liftM Number float <?> "Number"

ident = liftM Identifier identifier <?> "WorkerName"

questionm :: Parser Char
questionm = oneOf "?"

theQuery :: Parser AST
theQuery = do first <- questionm
              rest <- many1 letter
              let query = first:rest
              return ( Query query )

resvd_cmd = do { reserved "rd"; return ("rd") }
            <|> do { reserved "eval"; return ("eval") }
            <|> do { reserved "read"; return ("read") }
            <|> do { reserved "in"; return ("in") }
            <|> do { reserved "out"; return ("out") }
            <?> "LINDA-like Tuple"

callOrIdent = do
    name <- resvd_cmd
    liftM (FuncCall name)(parens $ commaSep expr) <|> return (Identifier name)

AST.hs

{-# LANGUAGE DeriveDataTypeable #-}

module AST where

import Data.Typeable

data AST
    = Number Double
    | Identifier String
    | String String
    | FuncCall String [AST]
    | Query String
    deriving (Show, Eq, Typeable)

词法分析器

module Lexer (
            identifier, reserved, operator, reservedOp, charLiteral, stringLiteral,
            natural, integer, float, naturalOrFloat, decimal, hexadecimal, octal,
            symbol, lexeme, whiteSpace, parens, braces, angles, brackets, semi,
            comma, colon, dot, semiSep, semiSep1, commaSep, commaSep1
    )where

import Text.Parsec
import qualified Text.Parsec.Token as P
import Text.Parsec.Language (haskellStyle)

lexer = P.makeTokenParser ( haskellStyle
                            {P.reservedNames = ["rd", "in", "out", "eval", "take"]}
                         )


identifier = P.identifier lexer
reserved = P.reserved lexer
operator = P.operator lexer
reservedOp = P.reservedOp lexer
charLiteral = P.charLiteral lexer
stringLiteral = P.stringLiteral lexer
natural = P.natural lexer
integer = P.integer lexer
float = P.float lexer
naturalOrFloat = P.naturalOrFloat lexer
decimal = P.decimal lexer
hexadecimal = P.hexadecimal lexer
octal = P.octal lexer
symbol = P.symbol lexer
lexeme = P.lexeme lexer
whiteSpace = P.whiteSpace lexer
parens = P.parens lexer
braces = P.braces lexer
angles = P.angles lexer
brackets = P.brackets lexer
semi = P.semi lexer
comma = P.comma lexer
colon = P.colon lexer
dot = P.dot lexer
semiSep = P.semiSep lexer
semiSep1 = P.semiSep1 lexer
commaSep = P.commaSep lexer
commaSep1 = P.commaSep1 lexer
4

1 回答 1

4

首先,我想向您介绍lexeme改变解析器以吃尾随空格的函数。鼓励您使用它而不是明确地吃空白。困难在于,commaSep因为它吃了,然后失败了。写一个不太乐观的东西会很好commaSep,但让我们直接解决你的问题。

lexeme让我们申请comma

acomma = lexeme comma

您的代码的问题之一是您希望它看到test2asString "test2"astring解析器希望它的字符串以 . 开头和结尾"。让我们为秃头字符串做一个解析器,但要确保它们不以空格或逗号开头?并且不包含空格或逗号:

baldString = lexeme $ do
   x <- noneOf "? ,)"
   xs <- many (noneOf " ,)")   -- problematic - see comment below
   return . String $ x:xs

当我意识到因为最后必须有一个查询时,突破就来了,所以在一个 baldString 之后总是有一个逗号:

baldStringComma = do 
        s <- baldString
        acomma
        return s

现在让我们为元组末尾的一个或多个查询创建一个解析器:

queries = commaSep1 (lexeme theQuery)

现在我们可以获取标识符、baldStrings 和查询

therest = do
   name <- lexeme ident 
   acomma
   args <- many baldStringComma
   qs <- queries
   return (name,args,qs)

终于给

tuple = do
    name <- lexeme resvd_cmd
    stuff <- parens therest
    return (name,stuff)

所以你得到

*Parser> parseTest tuple "rd (isFib, test2, 100.1, ?BOOL)"
("rd",(Identifier "isFib",[String "test2",String "100.1"],[Query "?BOOL"]))

但是,如果您想将字符串与查询混为一谈,您可以return (name,args++qs)therest.

Applicative 不那么难看

我发现绑定到 Monad 界面很令人沮丧,当有可爱的东西时<$><*>等等,所以首先

import Control.Applicative hiding (many, (<|>))

然后

baldString = lexeme . fmap String $
   (:) <$> noneOf "? ,)"   
       <*> many (noneOf " ,)")   -- problematic - see comment below

<$>是 的中缀版本fmap,因此(:)将应用于 的输出noneOf "? ,",给出返回类似 的解析器('c':)。然后可以将其应用于many (noneOf " ,")using的输出<*>以提供我们想要的字符串。

baldStringComma = baldString <* acomma

这很好,因为我们让<*>操作员忽略 的输出,acomma只返回 的输出baldString,使用<*. 如果我们想要它反过来,我们可以这样做*>,但你也可以使用>>它,它已经忽略了第一个解析器的输出。

therest = (,,) <$> 
   lexeme ident <* acomma
   <*> many baldStringComma
   <*> queries

tuple = (,) <$> lexeme resvd_cmd 
            <*> parens therest

但是如果我们这样做会不会更好

data Tuple = Tuple {cmd :: String, 
                    id :: AST,
                    argumentList :: [AST],
                    queryList :: [AST]} deriving Show

所以我们可以做

niceTuple = Tuple <$> lexeme resvd_cmd <* lexeme (char '(')
                  <*> lexeme ident <* acomma
                  <*> many baldStringComma
                  <*> queries <* lexeme (char ')')

这给出了(用一点手动漂亮的打印来让它进入宽度)

*Parser> parseTest niceTuple "rd (isFib, test2, 100.1, ?BOOL)"
Tuple {cmd = "rd", 
       id = Identifier "isFib", 
       argumentList = [String "test2",String "100.1"], 
       queryList = [Query "?BOOL"]}

我还认为您的 currentAST更像是一个抽象语法存储而不是抽象语法树,并且您可能会从设计自己的 Tuple 类型并使用它中获得更多的好处。采用

newtype Command = Cmd String  deriving Show

诸如此类以确保类型安全,然后使用解析器将它们一起滚动到您的 Tuple 类型中以生成它们。

于 2012-11-25T23:50:41.820 回答