Написание лексического анализатора Sebesda на python. Не работает для последней лексемы во входном файле

Я должен перевести лексический анализатор кода в Sebesda's Concpets of Programming Languages ​​(глава 4, раздел 2) на python. Вот что у меня есть до сих пор:

# Character classes #
LETTER = 0
DIGIT = 1
UNKNOWN = 99

# Token Codes #
INT_LIT = 10
IDENT = 11
ASSIGN_OP = 20
ADD_OP= 21
SUB_OP = 22
MULT_OP = 23
DIV_OP = 24
LEFT_PAREN = 25
RIGHT_PAREN = 26

charClass = ''
lexeme = ''
lexLen = 0
token = ''
nextToken = ''

### lookup - function to lookup operators and parentheses ###
###          and return the token                         ###
def lookup(ch):
    def left_paren():
        addChar()
        globals()['nextToken'] = LEFT_PAREN

    def right_paren():
        addChar()
        globals()['nextToken'] = RIGHT_PAREN

    def add():
        addChar()
        globals()['nextToken'] = ADD_OP

    def subtract():
        addChar()
        globals()['nextToken'] = SUB_OP

    def multiply():
        addChar()
        globals()['nextToken'] = MULT_OP

    def divide():
        addChar()
        globals()['nextToken'] = DIV_OP
    options = {')': right_paren, '(': left_paren, '+': add,
               '-': subtract, '*': multiply , '/': divide}

    if ch in options.keys():
        options[ch]()
    else:
        addChar()

### addchar- a function to add next char to lexeme ###
def addChar():
    #lexeme = globals()['lexeme']
    if(len(globals()['lexeme']) <=98):
        globals()['lexeme'] += nextChar
    else:
        print("Error. Lexeme is too long")

### getChar- a function to get the next Character of input and determine its character class ###
def getChar():
    globals()['nextChar'] = globals()['contents'][0]
    if nextChar.isalpha():
        globals()['charClass'] = LETTER
    elif nextChar.isdigit():
        globals()['charClass'] = DIGIT
    else:
        globals()['charClass'] = UNKNOWN
    globals()['contents'] = globals()['contents'][1:]


## getNonBlank() - function to call getChar() until it returns a non whitespace character ##
def getNonBlank():
    while nextChar.isspace():
        getChar()

## lex- simple lexical analyzer for arithmetic functions ##
def lex():
    globals()['lexLen'] = 0
    getNonBlank()
    def letterfunc():
        globals()['lexeme'] = ''
        addChar()
        getChar()
        while(globals()['charClass'] == LETTER or globals()['charClass'] == DIGIT):
            addChar()
            getChar()
        globals()['nextToken'] = IDENT

    def digitfunc():
        globals()['lexeme'] = ''
        addChar()
        getChar()
        while(globals()['charClass'] == DIGIT):
            addChar()
            getChar()
        globals()['nextToken'] = INT_LIT

    def unknownfunc():
        globals()['lexeme'] = ''
        lookup(nextChar)
        getChar()

    lexDict = {LETTER: letterfunc, DIGIT: digitfunc, UNKNOWN: unknownfunc}
    if charClass in lexDict.keys():
        lexDict[charClass]()
    print('The next token is: '+ str(globals()['nextToken']) + ' The next lexeme is: ' + globals()['lexeme'])

with open('input.txt') as input:
    contents = input.read()
    getChar()
    lex()
    while contents:
        lex()

Я использую строку sum + 1 / 33 в качестве примера входной строки. Насколько я понимаю, первый вызов getChar() на верхнем уровне устанавливает characterClass в LETTER и contents в um + 1 / 33.

Затем программа входит в цикл while и вызывает lex(). lex(), в свою очередь, накапливает сумму слов в lexeme. Когда цикл while внутри letterfunc встречает первый символ пробела, он прерывается, выходя из lex()

Поскольку contents не пусто, программа снова проходит цикл while на верхнем уровне. На этот раз вызов getNonBlank() внутри lex() «выбрасывает пробелы в contents и повторяется тот же процесс, что и раньше.

Где я встречаю ошибку, так это в последней лексеме. Мне сказали, что globals()['contents'][0] находится вне диапазона, когда звонит getChar(). Я не ожидаю, что будет сложно найти ошибку, но я пытался отследить ее вручную и, похоже, не смог обнаружить проблему. Любая помощь будет принята с благодарностью.


person FaizHasan123    schedule 11.12.2015    source источник


Ответы (1)


Это просто потому, что после успешного чтения последней 3 входной строки функция digitfunc выполняет еще одну итерацию getchar. Но в этот момент content исчерпан и пуст, поэтому contents[0] передается конец буфера, отсюда и ошибка.

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

Причина этого в том, что когда последним символом является UNKNOWN, вы немедленно возвращаетесь из lex и выходите из цикла, потому что content пусто, но если вы обрабатываете число или символ, вы выполняете цикл, вызывая getchar, не проверяя конец ввода. Кстати, если ваша входная строка заканчивается правой скобкой, ваш лексер съедает ее и забывает показать, что нашел ее.

Итак, вы должны как минимум:

  • проверить конец ввода в getchar:

    def getchar():
        if len(contents) == 0:
            # print "END OF INPUT DETECTED"
            globals()['charClass'] = UNKNOWN
            globals()['nextChar'] = ''
            return
        ...
    
  • отобразить последний токен, если он остался:

    ...
    while contents:
        lex()
    lex()
    
  • контролировать наличие лексемы (в конце ввода могут происходить странные вещи)

    ...
    if charClass in lexDict.keys():
        lexDict[charClass]()
    if lexeme != '':
        print('The next token is: '+ str(globals()['nextToken']) +
              ' The next lexeme is: >' + globals()['lexeme'] + '<')
    

Но вы используете глобальные переменные плохо. Обычная идиома использования глобальной функции внутри функции состоит в том, чтобы объявить ее перед использованием:

a = 5

def setA(val):
    global a
    a = val   # sets the global variable a

Но глобальные переменные в Python — это запах кода. Лучшее, что вы можете сделать, это правильно инкапсулировать парсер в классе. Объекты лучше глобальных

person Serge Ballesta    schedule 11.12.2015