将令牌列表解析为表达式树

4

我希望能够解析类似于Haskell源代码中的表达式。我已经获得了一个输入流,该流已经被标记并附有优先级和结合性。运算符集在编译时不知道,可能是任意的。输出应该是一个表示表达式的树形结构。以下是我尝试过的一部分内容:

-- A single token of the input stream
data Token a
  = Prefix a
  | Infix a Int Fixity -- The Int parameter represents the precedence
  | LBrace
  | RBrace
  deriving (Show,Eq)

data Fixity
  = InfixL
  | InfixR
  | InfixC
  deriving (Show,Eq)

data Expression a
  = Atom a
  | Apply a a
  deriving Show

-- Wrapped into either, if expression is malformed.
exprToTree :: [Token a] -> Either String (Expression a)
exprToTree = undefined

为了简化问题,我不把lambda表达式看作特殊的元素,它们只是普通的原子。

但现在,我完全迷失了。如何将原子流转换成树形结构?请有经验的人士指点一下算法或者帮我找一个算法。


嗨,FUZxxl - 你知道目前还没有数值字面量和运算符吗?也许这就是类型参数 a 的抽象,但我想自己将它们建模为具体的数据类型。 - stephen tetley
@Stephen Tetley:我选择 a,因为我想之后能够对树应用一些操作(比如打印)。在开始时,a 是一个代数数据类型,可以是名称、数字文字或其他内容。但对于这个算法来说,这并不重要,因为不需要检查原子的任何属性。 - fuz
我已经回复为“答案”,因为它太长了,不能作为跟进评论。 - stephen tetley
2个回答

5
简而言之,即使您有一个令牌列表,您仍然需要解析器。
Parsec可以处理替代令牌流,但您可能需要参考手册 - 可在Daan Leijen的“遗留”主页上获得PDF文件 - http://legacy.cs.uu.nl/daan/download/parsec/parsec.pdf。 您可以自己编写解析器而不使用组合库,但是您将重新实现Parsec的一部分。 就我所记得的,UU_parsing希望与单独的扫描程序一起使用,因此这是另一个选项。
虽然它无法处理解析,但您可能会发现Lennart Augustsson的“Lambda calculus cooked four ways”对其他事情有所帮助 - http://www.augustsson.net/Darcs/Lambda/top.pdf 编辑 - 这里是使用Parsec进行操作的部分计划,有关详细信息,您需要查阅手册的第2.11节。
假设您拥有用于具体“内部”令牌的数据类型:
data InternalTok = Ident String
                 | BinOpPlus
                 | BinOpMinus
                 | UnaryOpNegate
                 | IntLiteral Int
  deriving (Show)

然后您将获得这些类型的Parsec令牌并进行解析:

type MyToken = Token InternalTok

type MyParser a = GenParser MyToken () a

根据 Parsec 手册,定义一个辅助函数 - 这个函数处理 show 和 pos,使得单独的定义更短,参见第 19 页上的 mytoken 函数。

mytoken :: (MyToken -> Maybe a) -> MyParser a
mytoken test = token showToken posToken testToken
  where
    showToken tok = show tok
    posToken tok = no_pos
    testToken tok = test tok

目前,您的令牌类型不跟踪源位置,因此:

no_pos :: SourcePos
no_pos = newPos "" 0 0 0 

对于每个终端,您都需要定义一个令牌函数:

identifier :: MyParser MyToken
identifier =  mytoken (\tok -> case tok of
                         a@(Prefix (Ident _)) -> Just a
                         _                    -> Nothing)

intLiteral :: MyParser MyToken
intLiteral =  mytoken (\tok -> case tok of
                         a@(Prefix (IntLiteral _)) -> Just a
                         _                         -> Nothing)

binPlus :: MyParser MyToken
binPlus =  mytoken (\tok -> case tok of
                      a@(Infix BinOpPlus _ _) -> Just a
                      _                       -> Nothing)


binMinus :: MyParser MyToken
binMinus =  mytoken (\tok -> case tok of
                      a@(Infix BinOpMinus _ _) -> Just a
                      _                        -> Nothing)

unaryNegate :: MyParser MyToken
unaryNegate =  mytoken (\tok -> case tok of
                        a@(Prefix UnaryNegate _ _) -> Just a
                        _                          -> Nothing)

编辑 - 要处理自定义中缀运算符,您需要使用这些令牌解析器:

tokInfixL :: Int -> MyParser MyToken
tokInfixL n = mytoken $ \tok -> case tok of
    a@(Infix _ i InfixL) | i == n -> Just a
    _                             -> Nothing)


tokInfixR :: Int -> MyParser MyToken
tokInfixR n = mytoken $ \tok -> case tok of
    a@(Infix _ i InfixR) | i == n -> Just a
    _                             -> Nothing)

tokInfixC :: Int -> MyParser MyToken
tokInfixC n = mytoken $ \tok -> case tok of
    a@(Infix _ i InfixC) | i == n -> Just a
    _                             -> Nothing)


tokPrefix :: MyParser MyToken
tokPrefix = mytoken (\tok -> case tok of
                       a@(Prefix _) -> Just a
                       _            -> Nothing)

现在您可以定义解析器-您需要事先确定优先级的层数,因为您需要为每个层次编写解析器,顶层表达式解析仅调用最高优先级解析器。
pExpression :: Parser Expersion
pExpression = expression10

每个优先级别需要一个类似于这样的解析器,你需要自己解决非关联性。此外,你可能需要对chainl / chainr进行一些工作 - 我只是用UU_Parsing在这种风格下编写了一个解析器,使用Parsec可能会稍有不同。 注意Apply通常处于最高优先级。
expression10 :: Parser Expression
expression10 = 
        Apply   <$> identifier <*> pExpression
    <|> Prefix  <$> tokPrefix  <*> pExpression
    <|> chainl (Infix <$> tokInfixL 10) expression9
    <|> chainr (Infix <$> tokInfixR 10) expression9

expression9 :: Parser Expression
expression9 = 
        Prefix  <$> tokPrefix  <*> pExpression
    <|> chainl (Infix <$> tokInfixL 9) expression8
    <|> chainr (Infix <$> tokInfixR 9) expression8

...

你需要扩展语法以处理优先级为0的IntLiterals和Identifiers:

expression0 :: Parser Expression
expression0 = 
        IntLit  <$> intLiteral 
    <|> Ident   <$> identifier
    <|> ...

编辑 - 为了无限优先级 - 如果你只有应用程序和Atom,可能类似这样的东西会起作用。请注意,您必须更改tokInfixL和tokInfixR解析器,以不再匹配assoc-level,并且您可能需要尝试使用替代方案的顺序。

expression :: Parser Expression
expression = 
        Apply   <$> identifier <*> expression
    <|> Prefix  <$> tokPrefix  <*> expression
    <|> chainl (Infix <$> tokInfixL) expression
    <|> chainr (Infix <$> tokInfixR) expression
    <|> intLiteral
    <|> identifier

intLiteral :: Parser Expression
intLiteral = Atom . convert <$> intLiteral
  where
    convert = ??

identifier :: Parser Expression
identifier = Atom . convert <$> intLiteral
  where
    convert = ??

据我所知,您的输入已经被词法分析(转换为令牌流),而不是被解析成一个答案——一个语法树,在这种情况下是一个表达式树。如果令牌流以前缀或后缀形式存在,则可以使用堆栈机构建树,而无需解析器。 - stephen tetley
@Stephen Tetley:从我给出的数据类型示例中可以看出,它可能包含具有不同优先级和结合性的中缀和前缀表达式。对于术语使用不当,我很抱歉,是的,它已经进行了词法分析,但尚未进行语法分析。 - fuz
你还是不理解我的问题。中缀操作符集在编译时是未知的。运算符优先级和结合性将在运行时传递。 - fuz
尝试一下Norman Ramsey的“使用前缀和后缀运算符进行解析”的第三部分。 - stephen tetley
只是一个提示,UUlib和UU-parsing没有限制输入需要先进行词法分析。它们可以很好地处理字符串。 - Phyx
显示剩余6条评论

0
在搜索了网络上的其他主题后,我找到了这个很好的代码片段,可以完全满足我的需求。看一下:
data Op     = Op String Prec Fixity          deriving Eq
data Fixity = Leftfix | Rightfix | Nonfix    deriving Eq
data Exp    = Var Var | OpApp Exp Op Exp     deriving Eq
type Prec   = Int
type Var    = String

data Tok = TVar Var | TOp Op

parse :: [Tok] -> Exp
parse (TVar x : rest) = fst (parse1 (Var x) (-1) Nonfix rest)

parse1 :: Exp -> Int -> Fixity -> [Tok] -> (Exp, [Tok])
parse1 e p f [] = (e, [])
parse1 e p f inp@(TOp op@(Op _ p' f') : TVar x : rest) 
  | p' == p && (f /= f' || f == Nonfix)
  = error "ambiguous infix expression"
  | p' < p  ||  p' == p && (f == Leftfix || f' == Nonfix)
  = (e, inp)
  | otherwise
  = let (r,rest') = parse1 (Var x) p' f' rest in
    parse1 (OpApp e op r) p f rest'

-- Printing

instance Show Exp where
  showsPrec _ (Var x) = showString x
  showsPrec p e@(OpApp l (Op op _ _) r) = 
        showParen (p > 0) $ showsPrec 9 l . showString op . showsPrec 9 r

-- Testing

plus   = TOp (Op "+" 6 Leftfix)
times  = TOp (Op "*" 7 Leftfix)
divide = TOp (Op "/" 7 Leftfix)
gt     = TOp (Op ">" 4 Nonfix)
ex     = TOp (Op "^" 8 Rightfix)

lookupop '+' = plus
lookupop '*' = times
lookupop '/' = divide
lookupop '>' = gt
lookupop '^' = ex

fromstr [x]     = [TVar [x]]
fromstr (x:y:z) = TVar [x] : lookupop y : fromstr z

test1 = fromstr "a+b+c"
test2 = fromstr "a+b+c*d"
test3 = fromstr "a/b/c"
test4 = fromstr "a/b+c"
test5 = fromstr "a/b*c"
test6 = fromstr "1^2^3+4"
test7 = fromstr "a/1^2^3"
test8 = fromstr "a*b/c"

(我从这个页面上获取的:http://hackage.haskell.org/trac/haskell-prime/attachment/wiki/FixityResolution/resolve.hs


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接