Разбор списка токенов в дереве выражений - PullRequest
4 голосов
/ 27 марта 2011

Я хочу разобрать выражения, подобные тем, что приведены в типичном исходном тексте на 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

Ради простоты я не отношусь к лямбдам особенным, они просто атомы.

Но теперь я совершенно потерян.Как я могу преобразовать поток атомов в дерево?Может кто-нибудь указать мне алгоритм или помочь найти его.

Ответы [ 2 ]

5 голосов
/ 27 марта 2011

Короче говоря, даже если у вас есть список токенов, вам все еще нужен парсер.

Parsec может обрабатывать альтернативные потоки токенов, но вам, вероятно, придется обратиться к руководству - PDF, доступному на "устаревшей" домашней странице Daan Leijen - http://legacy.cs.uu.nl/daan/download/parsec/parsec.pdf. Вы можете свернуть свой собственный анализатор без использования комбинатора библиотека, но вы будете повторно реализовать некоторую часть Parsec. Насколько я помню, UU_parsing рассчитывает работать с отдельным сканером, так что это еще один вариант.

Хотя он не обрабатывает синтаксический анализ, вы можете найти, что «Лямбда-исчисление, приготовленное четырьмя способами» Леннарта Огастсона полезно для других вещей - 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, поэтому отдельные определения короче ср. функция mytoken на стр. 19.

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. Примечание Применить обычно находится на самом высоком уровне приоритета.

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

...

Вам придется расширить синтаксис для обработки IntLiterals и идентификаторов, которые имеют приоритет на уровне 0:

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

Редактировать - для неограниченного приоритета - возможно, если у вас есть только приложение и Atom, может быть, что-то подобное будет работать. Обратите внимание, что вам придется изменить синтаксические анализаторы tokInfixL и tokInfixR, чтобы они больше не соответствовали ассоциативному уровню, и вам, возможно, придется поэкспериментировать с порядком альтернатив.

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 = ??
0 голосов
/ 29 марта 2011

После поиска в Интернете другой темы я обнаружил, что этот прекрасный фрагмент кода делает именно то, что я хочу. Посмотрите:

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)

...