将标记列表解析为表达式树

时间:2011-03-27 15:17:08

标签: algorithm haskell expression-trees

我想解析典型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是特殊的,它们只是原子。

但现在,我完全迷失了。如何将原子流转换为树?有人可以指点我一个算法或帮我找一个。

2 个答案:

答案 0 :(得分:5)

简而言之,即使你有一个令牌列表,你仍然需要一个解析器。

Parsec可以处理替代令牌流,但您可能需要参考手册 - 在Daan Leijen的“遗留”主页 - http://legacy.cs.uu.nl/daan/download/parsec/parsec.pdf中提供的PDF。您可以在不使用组合器库的情况下滚动自己的解析器,但是您将重新实现Parsec的某些部分。据我所知,UU_parsing希望使用单独的扫描仪,这是另一种选择。

虽然它没有处理解析,但你可能会发现Lennart Augustsson的“Lambda calculus以四种方式烹饪”对其他事情有帮助 - 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级别匹配,您可能必须尝试替代的顺序。

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 = ??

答案 1 :(得分: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