Парсек не потребляет весь ввод?

Я пишу код для разбора команд простого императивного языка, определенного в Theory of Programming Languages (Reynolds, 1998).

У меня есть модуль лексера, который по заданной строке извлекает из нее токены, если это допустимое языковое выражение, а затем я передаю этот список токенов синтаксическому анализатору, который должен построить внутреннее представление команды (определяемое как алгебраический тип данных).

Это мои токены:

--Tokens for the parser
data Token = Kw Keyword
| Num Int
| Op Operator
| Str String
| Sym Symbol
deriving Show 

У меня проблемы с бинарными операторами. Я приведу в качестве примера сумму, но это происходит одинаково со всеми, будь то логические или целые числа.

Например, если бы я запустил программу parse "x:=2+3", я должен получить следующий список токенов из лексера
[Str "x", Op Colon , Op Equal, Num 2, OP, Plus, Num 3]
что я и получаю.

Но тогда синтаксический анализатор должен вернуть команду
Назначить "x" (Ibin Plus (Const 2) (Const 3)
, что является правильным представлением команды. Но вместо этого я получаю следующее представление:
Назначить "x" (Const 2)

Я предполагаю, что я облажался в какой-то момент в функции pIntExpr, потому что идентификатор переменной и := присваивания анализируются нормально, и он не анализирует последние элементы. Вот соответствующие парсеры для этого примера, чтобы посмотреть, может ли кто-нибудь сориентировать меня в том, что я делаю неправильно.

-- Integer expressions
data IntExpr = Const Int 
         | Var Iden --Iden=String
         | Neg IntExpr
         | IBin OpInt IntExpr IntExpr
         deriving Show

type TParser = Parsec [Token] ()

--Internal representation of the commands
data Comm = Skip 
      | Assign Iden IntExpr
      | If Assert Comm Comm
      | Seq Comm Comm
      | While Assert Comm
      | Newvar Iden IntExpr Comm
        deriving Show

--Parser for non sequential commands
pComm' :: TParser Comm
pComm' = choice [pif,pskip,pAssign,pwhile,pNewVar]

--Parser for the assignment command
pAssign :: TParser Comm
pAssign = do v <- pvar
             _ <- silentOp Colon
             _ <- silentOp Equal
             e <- pIntExp
             return $ Assign v e 

-- Integer expressions parser
pIntExp :: TParser IntExpr
pIntExp = choice [ var'  --An intexp is either a variable
                  , num  --Or a numeric constant
                  , pMul --Or <intexp>x<intexp>
                  , pSum --Or <intexp>+<intexp>
                  , pRes --Or <intexp>-<intexp>
                  , pDiv --Division
                  , pMod --Modulus
                  , pNeg --Unary "-"
                 ]

-- Parser for <intexp>+<intexp>
pSum :: TParser IntExpr
pSum = do 
    e <- pIntExp
    _ <- silentOp Lexer.Plus
    e' <- pIntExp
    return $ IBin Lang.Plus e e'

ОБНОВЛЕНИЕ С УЧЕТОМ ОТВЕТА AndrewC
К сожалению, перемещение синтаксического анализатора var' вниз в списке выбора не сработало, он дает тот же результат. Но я принял во внимание ответ AndrewC и попытался «вручную» отследить выполнение (я не знаком с отладчиком ghci, в итоге сделал много отдельных шагов и в конце концов потерялся). Вот как я это рассуждаю:

Я получил этот список токенов от лексера: [Str "x", Op Colon, Op Equal, Num 2, OP Plus, Num 3]

Таким образом, синтаксический анализатор pComm' терпит неудачу с pif и pskip, но успешно с pAssign потребляет Str "x", Op Colon и Op Equal и попытка проанализировать [Num 2, OP Plus, Num 3] с помощью pIntExp (!!)

Затем синтаксический анализатор pIntExp пытается использовать синтаксический анализатор var' и терпит неудачу, но синтаксический анализатор num использует Num 2. токен и поэтому возвращает ошибочный результат Назначить "x" (Const 2).

Помня о совете AndrewC относительно выбора, я также переместил анализатор num вниз в списке. Для простоты я буду рассматривать pIntExp как выбор [pSum, num, var´], который подходит для данного конкретного примера.

Первая часть рассуждения остается прежней. Итак, я начну с (!!), где у нас было
[Num 2, Op Plus, Num 3] для анализа с помощью pIntExp
pIntExp теперь сначала пытается с pSum, который, в свою очередь, снова "вызывает" pIntExp, который снова пытается использовать pSum, и так далее. программа зависает. Я попробовал это, и это действительно зависает и никогда не заканчивается.

Итак, мне интересно, есть ли форма, позволяющая сделать синтаксический анализатор pSum "упреждающим" для токена Op Plus, а затем проанализировать соответствующие выражения?

ОБНОВЛЕНИЕ 2: После того, как я немного погуглил, теперь, когда я определил проблему, я обнаружил, что комбинационные парсеры chainl1 и/или chainl могут быть именно тем, что мне нужно. Я буду играть с этим, и если я сработаю, выложу решение


person leandrodemarco    schedule 29.04.2013    source источник
comment
См. также: Parsec: использовать все входные данные   -  person hammar    schedule 29.04.2013


Ответы (1)


Функция выбора пробует синтаксический анализатор, указанный ей, в том порядке, в котором они находятся в списке.

Поскольку ваш синтаксический анализатор для переменных появляется перед вашим синтаксическим анализатором для более сложного выражения сложения, он завершается успешно до того, как будет испробован другой.

Чтобы решить эту проблему, поместите синтаксический анализатор переменных после любых выражений, начинающихся с переменной (и продумайте любые другие проблемы с сопоставлением подстрок при использовании выбора.

Аналогичные проблемы включают 3 - 4 + 1, оценивающие до -2. Люди ожидают левой ассоциации при отсутствии других приоритетов (так сумма-термин вместо термин-сумма).

Вы также можете не захотеть, чтобы 1 + 10 * 5 преобразовывалось в 55, поэтому вам нужно быть осторожным с + и * и т. Д., Если вы хотите реализовать приоритет оператора. Этого можно добиться, проанализировав выражение, состоящее из умножения, как терм, а затем аддитивное выражение как сумму термов.

person AndrewC    schedule 29.04.2013
comment
Кроме того, рассмотрите возможность разрешения buildExpressionParser сделать за вас часть тяжелой работы. - person hammar; 29.04.2013
comment
@hammar К сожалению, это университетское задание, и нам дали скелет кода, и мы не должны использовать buildExpressionParser. Спасибо за информацию в любом случае, может быть полезно для людей, которые окажутся здесь в будущем. - person leandrodemarco; 30.04.2013
comment
@AndrewC Я обновил вопрос, указав, что я сделал с вашим предложением. Спасибо за подсказку о выборе, это помогло мне понять, что происходит (я думаю) - person leandrodemarco; 30.04.2013