При отладке подобных проблем часто помогает распечатать поток токенов, сгенерированный для данного ввода.Вы можете сделать это, запустив grun
с параметром -tokens
или итерировав stream
в вашей функции main
.
Если вы сделаете это, вы увидите, что main
является токенизированнымкак последовательность из четырех CHAR
токенов, тогда как ваше правило identifier
ожидает ALPHA
токенов, а не CHAR
.Так что это непосредственная проблема, но это не единственная проблема в вашем коде:
Первое, что я заметил, когда попробовал ваш код, это то, что я получил ошибки при переносе строк.Причина, по которой это происходит для меня, а не для вас, (предположительно) в том, что вы используете разрывы строк в Windows (\r\n
), а я нет.Ваш лексер распознает \r\n
как разрыв строки и пропускает его, но просто \n
распознается как CHAR
.
Кроме того, обработка пробелов очень запутанна.Одиночные пробелы являются их собственными токенами.Они должны появляться в определенных местах и не могут появляться где-либо еще.Однако несколько последовательных пробелов пропускаются.Так что что-то вроде int main
будет ошибкой, поскольку не будет обнаруживать пробел между int
и main
.С другой стороны, отступ строки с одним пробелом был бы ошибкой, потому что тогда отступ не был бы пропущен.
Ваши идентификаторы также бесполезны.Идентификаторы могут содержать пробелы (если их больше одного), разрывы строк (если они \r\n
или вы исправили это, так что \n
также пропускается) или комментарии.Таким образом, следующее будет одним действительным идентификатором (при условии, что вы измените лексер, чтобы буквы распознавались как ALPHA
вместо CHAR
):
hel lo //comment
wor
ld
С другой стороны, maintarget
будетне может быть действительным идентификатором, поскольку он содержит ключевое слово int
.
Аналогичным образом пропущенные токены также можно использовать внутри целочисленных литералов и строковых литералов.Для строковых литералов это означает, что "a b"
является допустимой строкой (что хорошо), которая содержит только символы a
и b
(что не хорошо), поскольку двойной пробел пропускается.С другой стороны, " "
будет недопустимой строкой, поскольку
распознается как токен ' '
, а не как CHAR
.Также, если вы исправите свои идентификаторы, сделав буквы распознаваемыми как ALPHA
, они больше не будут действительны внутри строк.Также "la//la"
будет рассматриваться как незамкнутый строковый литерал, поскольку //la"
будет рассматриваться как комментарий.
Все эти проблемы связаны с тем, как работает лексер, поэтому давайте пройдемся почто:
При преобразовании потока символов в поток токенов лексер будет обрабатывать ввод в соответствии с правилом "максимального мунка": он пройдет все правила лексера и проверит, какое из них соответствуетв начале текущего ввода.Из тех, которые совпадают, он выберет тот, который даст самый длинный матч.В случае связей он предпочтет тот, который определен первым в грамматике.Если вы используете строковые литералы непосредственно в правилах синтаксического анализатора, они обрабатываются как правила лексера, которые определены раньше других.
То есть, у вас есть правило CHAR: .;
, которое предшествует ALPHA
, DIGIT
и HEX_DIGIT
означает, что эти правила никогда не будут совпадать.Все эти правила соответствуют одному символу, поэтому, когда более чем одно из них соответствует, CHAR
будет предпочтительным, потому что оно стоит первым в грамматике.Если вы переместитесь на CHAR
до конца, буквы теперь будут соответствовать ALPHA
, десятичные цифры DIGIT
и все остальное CHAR
.Это по-прежнему оставляет HEX_DIGIT
бесполезным (и если вы переместите его вперед, это сделает ALPHA
и DIGIT
бесполезным), а также означает, что CHAR
больше не делает то, что вы хотите, потому что вы хотите цифр и букв, которые следует рассматривать как CHAR
с, но только внутри строк.
Настоящая проблема здесь в том, что ни одна из этих вещей не должна быть токеном.Они должны быть либо fragment
s, либо просто вставлены непосредственно в правила лексера, которые их используют.Вместо этого ваши токены должны быть такими, внутри которых вы не хотите разрешать / игнорировать пробелы или комментарии.Поэтому строковые литералы, литералы int и идентификаторы должны быть токенами.Единственный случай, когда у вас есть несколько правил лексера, которые могут соответствовать одному и тому же вводу, должны быть идентификаторами и ключевыми словами (где ключевые слова имеют приоритет над идентификаторами, потому что вы задаете их как строковые литералы в грамматике, но более длинные идентификаторы могут по-прежнему содержать ключевые слова в качестве подстроки из-замаксимальное правило munch).
Вы также должны удалить из своей грамматики все случаи использования ' '
и вместо этого всегда пропускать пробелы.