Проблемы парсера Lark с приоритетом токенизацииPython

Программы на Python
Ответить
Anonymous
 Проблемы парсера Lark с приоритетом токенизации

Сообщение Anonymous »

У меня возникли проблемы с анализом ввода. Ниже приведен оскорбительный фрагмент:

Код: Выделить всё

?start: forstmt

forstmt: "for" ID "in" range FOO  -> forstmt

?range: "[" atom DOTDOT atom "]" -> rangetuple

DOTDOT.2: ".."

?atom: NUM            -> num

%import common.NUMBER -> NUM
%import common.WS
%ignore WS
Когда я ввожу что-то вроде for i в [1..10] print(i), я ожидаю токенизацию, похожую на:
< р> 1 .. 10 ]
Но даже при назначении центральным точкам собственного именованного терминала и назначении что приоритет выше, чем у чисел, он все равно всегда анализируется как: 1. .10 ]
Я пробовал изменить приоритет терминала, я пытался изменить приоритет правила в разделе анализатор Earley, и я попробовал запустить анализатор Earley с ambiguity='explicit', и все это без каких-либо изменений в текущем ошибочном выводе.
Вот ошибка, которую я получаю при работе с parser(grammar, parser='lalr'):

Код: Выделить всё

Unexpected token Token('NUM', '.10') at line 1, column 13.
Expected one of:
* DOTDOT
* MINUS
* STAR
* SLASH
* PLUS
Previous tokens: [Token('NUM', '1.')]
...а вот полная грамматика, которую я использую на случай, если пропущу что-то важное.

Код: Выделить всё

?start: stmtlist

?stmtlist: stmt (";" stmt?)* -> stmtlist

stmt: "var" ID "=" expr         -> decl
| ID "=" expr               -> assign
| "if" "(" expr ")" stmt ("else" stmt)? -> ifstmt
| "while" "(" expr ")" stmt -> whstmt
| "for" ID "in" range stmt  -> forstmt
| "print" "(" expr ")"      -> prstmt
| "{" stmtlist "}"          -> block

?range: "[" expr DOTDOT expr "]" -> rangetuple

DOTDOT.2: ".."

?expr: expr "+" term  -> add
|  expr "-" term  -> sub
|  term

?term: term "*" atom  -> mul
|  term "/" atom  -> div
|  atom

?atom: "(" expr ")"
|  ID             -> var
|  NUM            -> num

%import common.CNAME  -> ID
%import common.NUMBER -> NUM
%import common.WS
%ignore WS
Кроме того, я знаю, что использование целых чисел вместо всех числовых типов ввода для NUM решает проблему, я просто надеялся не быть настолько строгим с грамматикой, если вообще буду возможный. Я также знаю, что наличие действительных числовых значений в параметре диапазона — это нонсенс, но я надеюсь принять в значении диапазона выражения типа i+3, которые смогут повторно использовать мои функции, которые могут принять поплавки. Я надеялся выполнить проверку типа для домена диапазона в функции rangetuple; для меня более разумно иметь возможность повторно использовать мои функции и просто выполнять защиту типов/проверку ошибок в функциях с использованием выражений.
Минимальный воспроизводимый пример:
Жаворонок: 1.1.9
Python: 3.12.3

Код: Выделить всё

import sys
from lark import Lark, v_args
from lark.visitors import Interpreter
from typing import Any

grammar = """
?start: stmtlist

?stmtlist: stmt? (";" stmt?)* -> stmtlist

stmt: "for" ID "in" range stmt  -> forstmt
| "print" "(" expr ")"      -> prstmt
| "{" stmtlist "}"          -> block

?range: "[" expr ".." expr "]" -> rangetuple

?expr: ID             -> var
|  NUM            -> num

%import common.CNAME  -> ID
%import common.NUMBER -> NUM
%import common.WS
%ignore WS
"""

parser = Lark(grammar)

class Env():
'''Keeps track of variables and manages scopes for those variables.'''

def __init__(self) -> None:
# initializes with one scope open by default; global scope
self._scopes: list[dict] = [dict()]

def extend(self, x: str, val: Any | None = None) -> None:
'''Declares `x` as a variable in the
current scope, with `x = val`.'''
location = self._findScope(x)
# variable is undefined or has not been declared in current scope
if (location is None) or (location != (len(self._scopes) - 1)):
self._scopes[-1][x] = val
# variable is defined in current scope
else:
raise NameError(f'{x} was redeclared', name=x)

def update(self, x: str, val: Any) -> None:
'''Assigns `val` to current visible `x`.'''
location = self._findScope(x)

if location is not None:
self._scopes[location][x] = val  # overwrite nearest x
else:
raise NameError(f'{x} is undefined', name=x)

def _findScope(self, x) -> int | None:
'''Return the scope index that `x` exists in, if any.
Returns index in order by environment recency.'''
for index in range(len(self._scopes)-1, -1, -1):
if x in self._scopes[index]:
return index

return None

def lookup(self, x: str) -> Any:
'''returns `x`'s value from the most recently assigned scope.'''
location = self._findScope(x)

if location is not None:
return self._scopes[location][x]
else:
raise NameError(f"{x} is undefined", name=x)

def openScope(self) -> None:
'''Increases variable stack height.'''
self._scopes.append(dict())

def closeScope(self) -> None:
'''Decreases variable stack height.'''
if len(self._scopes) >= 1:
del self._scopes[-1]
else:
raise IndexError("attempted to close scope when none were open")

@v_args(inline=True)
class Eval(Interpreter):
def __init__(self, env: Env | None = None):
super().__init__()
# if no environment is supplied, it will rely on its own
self.env = env if env is not None else Env()

def num(self, val) -> int | float:
for char in ".e":  # signifiers for float
if char in val:
return float(val)

return int(val)

def var(self, var: str) -> int | float:
return self.env.lookup(var)

def block(self, statements) -> None:
self.env.openScope()
self.visit(statements)
self.env.closeScope()

def stmtlist(self, *statements) -> None:
for statement in statements:
self.visit(statement)

def forstmt(self, id, rangetuple, stmt) -> None:
self.env.openScope()
self.env.extend(id)
for i in range(*self.visit(rangetuple)):
self.env.update(id, i)
self.visit(stmt)
self.env.closeScope()

def rangetuple(self, expr1, expr2) ->  tuple[int, int]:
return (self.visit(expr1), self.visit(expr2))

def main():
try:
prog = sys.stdin.read()
tree = parser.parse(prog)
Eval().visit(tree)
except Exception as e:
print(e)

if __name__ == "__main__":
main()
и я передаю этот файл на стандартный ввод в качестве входных данных:

Код: Выделить всё

for i in [1..3] print(i)
результат:

Код: Выделить всё

No terminal matches '.' in the current parser context, at line 1 col 13

for i in [1..3] print(i)
^
Expected one of:
* __ANON_0
А изменение используемых парсеров дает аналогичные результаты.


Подробнее здесь: https://stackoverflow.com/questions/791 ... g-priority
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»