Как интерпретировать цикл "do" из схемы в Python PLY

77
8

Я делаю интерпретатор для Scheme с использованием PLY (Python Lex-Yacc), и я не могу реализовать цикл "do", используя значения из стека, который отслеживает идентификаторы, которые соответствуют переменной (например, я для петля).

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

Это самая важная часть кода:

ids = { }

def p_program(p):
'program : form'
#return p
print(p[1])

def p_form_a(p):
'''
form : definition
| expression
'''
p[0] = p[1]

def p_expression(p):
'''
expression : constant
| do_expression
| ID
| display
'''
p[0] = p[1]

def p_do_expression_a(p):
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
'do_expression : OPEN_PAR DO OPEN_PAR ID constant OPEN_PAR symbol ID expression CLOSE_PAR CLOSE_PAR comparison_expression expression CLOSE_PAR'
ids[p[4]] = p[5]

aux = p[12]
while True:
expr = p[13]

if ((type(p[5]) == int and type(p[9]) == int)
or (type(p[5]) == int and type(p[9]) == float)
or (type(p[5]) == float and type(p[9]) == int)
or (type(p[5]) == float and type(p[9]) == float)):
if p[7] == '+': ids[p[4]] = ids[p[4]] + p[9]
elif p[7] == '-': ids[p[4]] = ids[p[4]] - p[9]
elif p[7] == '*': ids[p[4]] = ids[p[4]] * p[9]
elif p[7] == '/': ids[p[4]] = ids[p[4]] / p[9]
elif p[7] == 'remainder': ids[p[4]] = ids[p[4]] % p[9]
else:
print("Error: Type mismatch.")
sys.exit()

aux = p[12]
if aux == '#t':
break

p[0] = expr

def p_comparison_expression(p):
'comparison_expression : OPEN_PAR comparison expression expression CLOSE_PAR'

if type(p[3]) == str:
p[3] = ids[p[3]]

if type(p[4]) == str:
p[4] = ids[p[4]]

if p[2] == 'eq?':
if p[3] == p[4]: p[0] = '#t'
else: p[0] = '#f'
elif p[2] != 'neq?':
if p[3] != p[4]: p[0] = '#t'
else: p[0] = '#f'
elif p[2] != '=':
if p[3] == p[4]: p[0] = '#t'
else: p[0] = '#f'
elif p[2] != '>':
if p[3] > p[4]: p[0] = '#t'
else: p[0] = '#f'
elif p[2] != '<':
if p[3] < p[4]: p[0] = '#t'
else: p[0] = '#f'
elif p[2] != '>=':
if p[3] >= p[4]: p[0] = '#t'
else: p[0] = '#f'
elif p[2] != '<=':
if p[3] <= p[4]: p[0] = '#t'
else: p[0] = '#f'
else:
print("Error: Comparison problem.")
sys.exit()

def p_display(p):
'display : OPEN_PAR DISPLAY expression CLOSE_PAR'
if type(p[3]) == str:
p[3] = ids[p[3]]

print(p[3])

def p_symbol(p):
'''
symbol : ADD
| MINUS
| DIVIDE
| MULTIPLY
| REMAINDER
'''
p[0] = p[1]

def p_boolean(p):
'''
boolean : TRUE
| FALSE
'''
p[0] = p[1]

def p_comparison(p):
'''
comparison : EQQUES
| NEQQUES
| EQUALS
| GREATER
| LESS
| GREATER_EQUAL
| LESS_EQUAL
'''
p[0] = p[1]

def p_constant(p):
'''
constant : INT
| FLOAT
| CHARACTER
| STRING
| boolean
'''
p[0] = p[1]

Я тестирую следующий фрагмент кода Scheme:

(do (i 0 (+ 1 i)) (< i 5) (display i))

который должен отображать: 0 1 2 3 4

но вместо этого я получаю:

Traceback (most recent call last):
File "C:\Compiladores\Lexer\scheme_compiler.py", line 510, in <module>
parser.parse(s)
File "C:\Compiladores\Lexer\ply\yacc.py", line 333, in parse
return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc)
File "C:\Compiladores\Lexer\ply\yacc.py", line 1120, in parseopt_notrack
p.callable(pslice)
File "C:\Compiladores\Lexer\scheme_compiler.py", line 338, in p_comparison_expression
p[3] = ids[p[3]]
KeyError: 'i'

Может кто-нибудь, пожалуйста, помогите мне достичь этого? Я очень ценю это

спросил(а) 2021-01-25T15:33:50+03:00 4 месяца, 4 недели назад
1
Решение
77

Ваша грамматика формы do (разбита на строки и использует односимвольные литералы для удобства чтения):

do_expression : '(' DO '(' ID constant '(' symbol ID expression ')' ')'
comparison_expression expression ')'

(Примечание: это не совсем правильная грамматика по ряду причин, одна из которых отмечена в другом ответе. Но это не относится к этому вопросу.)

В вашем семантическом действии, p[12] и p[13] соответствуют comparison_expression и expression. Разобравшись с основами, ваше семантическое действие выполняет следующее:

# create a new bound variable with the indicated initial value ('p[5]').
aux = p[12]
while True:
expr = p[13] # You have a typo; I assume you meant p[13], not [13]
# Update the index variable value
aux = p[12] # This value is still the same as it was at entry
if aux == '#t':
break

p[0] = expr

Теперь важно подумать о том, что такое p[12] и p[13]. Ply не делает никакой магии под капотом Python; это просто генерирование кода Python. Таким образом, p[12] и p[13] являются обычными значениями Python, которые являются результатом выполнения семантических действий для нетерминалов comparison_expression и expression. И эти семантические действия оцениваются до того, как do_expression было уменьшено, поэтому их значения вычисляются без какой-либо ссылки на do_expression. Оба comparison_expression и expression относятся к связанной переменной i (как это естественно для итерационной конструкции), но эта переменная не связана, когда эти семантические действия оцениваются. Отсюда и сообщение об ошибке.

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

Похоже, вы предполагаете, что доступ к p[12] и p[13] каким-то образом переоценит связанные семантические действия. Но у Python нет такой возможности, и Ply тоже волшебным образом не реализует ее. Это ваша ответственность, основанная на предполагаемой семантике языка, который вы пытаетесь скомпилировать (или, в данном случае, интерпретировать).

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

В случае со схемой разбор действительно является наименьшей из проблем. Хотя специальные формы немного усложняют задачу, программы Scheme в основном являются S-выражениями, которые можно почти тривиально преобразовать в списки без необходимости в сложной технологии синтаксического анализа. Таково было первоначальное намерение синтаксиса Scheme (или, скорее, Lisp). Если у вас есть структура списка или какой-либо функциональный эквивалент (абстрактное синтаксическое дерево или даже трехадресный код), вы можете при необходимости оценивать текст программы, используя правильные привязки переменных.

Давным-давно никто бы не подумал о назначении такой задачи без ссылки на превосходный (и все еще актуальный) учебник Abelson & Sussman " Структура и интерпретация компьютерных программ" , ласково именуемый SICP. Благодаря щедрости авторов и издателей, полный текст этой книги свободно доступен в Интернете. Я не могу рекомендовать это достаточно.

PS: Также обратите внимание, что привязка управляющей переменной цикла (в данном случае i) присутствует только во время оценки цикла. После завершения цикла переменная должна быть удалена. Но вы не можете смоделировать это с помощью простого словаря, потому что может быть внешняя переменная с тем же именем. Это все объясняется и в SICP.

ответил(а) 2021-01-25T15:33:50+03:00 4 месяца, 4 недели назад
45

У вас есть синтаксис сделать немного неправильно,

это не должно быть

(do (i 0 (+ 1 i)) (< i 5) (display i))

так должно быть

(do ((i 0 (+ 1 i))) ((not (< i 5))) (display i))

Вы можете реализовать DO, отсеяв его в LETREC, или, если вы не реализовали LETREC, вы можете использовать реализацию циклов лямбда-исчисления, используя Y-комбинатор. Так что это выражение может стать таким:

(letrec ((loop (lambda (i)
(if (not (< i 5))
(begin (display i)
(loop (+ 1 i)))
#t))))
(loop 0))

ответил(а) 2021-01-25T15:33:50+03:00 4 месяца, 4 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема