Как интерпретировать цикл "do" из схемы в Python PLY - PullRequest
1 голос
/ 09 мая 2019

Я делаю интерпретатор для 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]

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

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

, который должен отображать: 01 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'

Может кто-нибудь помочь мне в этом?Я был бы очень признателен

Ответы [ 2 ]

4 голосов
/ 09 мая 2019

Ваша 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's 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_expressioncomparison_expression, и expression относятся к связанной переменной i (что является естественным для конструкции итерации), но эта переменная не привязывается при оценке этих семантических действий.Отсюда и сообщение об ошибке.

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

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

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

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

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

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

1 голос
/ 09 мая 2019

Синтаксис do немного ошибочен,

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

(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))
...