Как сказано в заголовке, какой тип данных должен возвращать / передавать синтаксический анализатор? Читая статью о лексическом анализе, которая есть в Википедии, она заявила, что:
В информатике лексический анализ - это процесс преобразования последовательности символов (например, в компьютерной программе или веб-странице) в последовательность токенов ( строк с определенным «значением»).
Однако, в полном противоречии с приведенным выше утверждением, когда на другой вопрос, который я задал на другом сайте ( Code Code, если вам интересно), ответивший сказал, что:
Лексер обычно читает строку и преобразует ее в поток ... лексем. Лексемы должны быть только потоком чисел .
и он дал это визуальное:
nl_output => 256
output => 257
<string> => 258
Позже в статье он упомянул Flex
уже существующий лексер и сказал, что написание «правил» с ним будет проще, чем написание лексера вручную. Он продолжил давать мне этот пример:
Space [ \r\n\t]
QuotedString "[^"]*"
%%
nl_output {return 256;}
output {return 257;}
{QuotedString} {return 258;}
{Space} {/* Ignore */}
. {error("Unmatched character");}
%%
Чтобы глубже понять и получить больше информации, я прочитал статью о Flex в Википедии . статья о Flex показала, что вы можете определить набор синтаксических правил с токенами следующим образом:
digit [0-9]
letter [a-zA-Z]
%%
"+" { return PLUS; }
"-" { return MINUS; }
"*" { return TIMES; }
"/" { return SLASH; }
"(" { return LPAREN; }
")" { return RPAREN; }
";" { return SEMICOLON; }
"," { return COMMA; }
"." { return PERIOD; }
":=" { return BECOMES; }
"=" { return EQL; }
"<>" { return NEQ; }
"<" { return LSS; }
">" { return GTR; }
"<=" { return LEQ; }
">=" { return GEQ; }
"begin" { return BEGINSYM; }
"call" { return CALLSYM; }
"const" { return CONSTSYM; }
"do" { return DOSYM; }
"end" { return ENDSYM; }
"if" { return IFSYM; }
"odd" { return ODDSYM; }
"procedure" { return PROCSYM; }
"then" { return THENSYM; }
"var" { return VARSYM; }
"while" { return WHILESYM; }
Мне кажется, что лексер Flex возвращает строки ключевых слов \ токенов. Но это может быть возвращение констант, которые равны определенным числам.
Если лексер собирался возвращать числа, как бы он читал строковые литералы? возвращение числа хорошо для отдельных ключевых слов, но как бы вы справились со строкой? Если бы лексеру не пришлось преобразовывать строку в двоичные числа, то парсер преобразовывал бы числа обратно в строку. Для лексера кажется гораздо более логичным (и более простым) возвращать строки, а затем позволить анализатору преобразовать любые числовые строковые литералы в реальные числа.
Или может ли лексер вернуть оба? Я пытался написать простой лексер на c ++, который позволяет вам иметь только один тип возврата для ваших функций. Таким образом, побуждает меня задать свой вопрос.
Чтобы сжать мой вопрос в абзаце: при написании лексера и предположении, что он может возвращать только один тип данных (строки или числа), что было бы более логичным выбором?
источник
Ответы:
Как правило, если вы обрабатываете язык через лексизм и синтаксический анализ, у вас есть определение ваших лексических токенов, например:
и у вас есть грамматика для парсера:
Ваш лексер принимает входной поток и генерирует поток токенов. Поток токенов используется синтаксическим анализатором для создания дерева синтаксического анализа. В некоторых случаях достаточно просто знать тип токена (например, LPAREN, RBRACE, FOR), но в некоторых случаях вам потребуется фактическое значение , связанное с токеном. Например, когда вы сталкиваетесь с токеном идентификатора, вам понадобятся настоящие символы, из которых будет составлен идентификатор, когда вы будете пытаться выяснить, на какой идентификатор вы пытаетесь ссылаться.
Итак, у вас обычно есть что-то более или менее похожее на это:
Поэтому, когда лексер возвращает токен, вы знаете, какой это тип (который вам нужен для разбора), и последовательность символов, из которой он был сгенерирован (который вам понадобится позже для интерпретации строковых и числовых литералов, идентификаторов, и т.д.). Может показаться, что вы возвращаете два значения, поскольку вы возвращаете очень простой агрегатный тип, но вам действительно нужны обе части. В конце концов, вы бы хотели по-разному относиться к следующим программам:
Они производят одинаковую последовательность типов токенов : IF, LPAREN, NUMBER, GREATER_THAN, NUMBER, RPAREN, LBRACE, ID, LPAREN, STRING, RPAREN, SEMICOLON, RBRACE. Это значит, что они тоже разбираются . Но когда вы действительно что-то делаете с деревом разбора, вы позаботитесь о том, чтобы значение первого числа было «2» (или «0»), а значение второго числа было «0» (или «2»). '), и что значение строки' 2> 0 '(или' 0> 2 ').
источник
String value
будет наполнено? он будет заполнен строкой или числом? А также, как бы я определитьString
тип?parse(inputStream).forEach(token -> print(token.string); print(' '))
(т. Е. Просто вывести строковые значения токенов, разделенные пробелом). Это довольно быстро. И даже если LPAREN может быть получен только из "(", это может быть постоянная строка в памяти, поэтому включение ссылки на него в токене может быть не дороже, чем включение нулевой ссылки. В общем, я бы лучше написал код, который не делает меня особым случаем, никакой кодекс«Токен», очевидно. Лексер создает поток токенов, поэтому он должен возвращать поток токенов .
Сгенерированные машиной лексеры имеют то преимущество, что вы можете генерировать их быстро, что особенно удобно, если вы думаете, что ваша лексическая грамматика сильно изменится. У них есть недостаток в том, что вы часто не получаете большой гибкости при выборе вариантов реализации.
Тем не менее, кого это волнует, если это «проще»? Написание лексера обычно не самая сложная часть!
Ни. У лексера обычно есть «следующая» операция, которая возвращает токен, поэтому он должен вернуть токен . Токен не является строкой или числом. Это знак.
Последний лексер, который я написал, был лексером «полной верности», что означало, что он вернул токен, который отслеживал местоположение всех пробелов и комментариев - которые мы называем «пустяками» - в программе, а также токен. В моем лексере токен был определен как:
Общая информация была определена как:
Так что, если бы у нас было что-то вроде
что бы LEX в виде четырех маркеров с лексемами видов
Identifier
,Plus
,Identifier
,Semicolon
, и шириной 3, 1, 3, 1. Первым идентификатор имеет ведущую мелочь , состоящую изWhitespace
шириной 4 и задняя мелочьWhitespace
с шириной 1.Plus
не имеют ведущие и мелочей завершающие пустяки, состоящие из одного пробела, комментария и новой строки. Финальный идентификатор имеет начальные мелочи из комментария, пробела и так далее.При такой схеме каждый символ в файле учитывается в выводе лексера, который является удобным свойством для таких вещей, как раскраска синтаксиса.
Конечно, если вам не нужны пустяки, вы можете просто сделать токен две вещи: вид и ширина.
Вы можете заметить, что токен и пустяки содержат только их ширину, а не их абсолютную позицию в исходном коде. Это умышленно. Такая схема имеет преимущества:
Если вас не волнует ни один из этих сценариев, то токен может быть представлен как вид и смещение, а не как вид и ширина.
Но главное здесь: программирование - это искусство создания полезных абстракций . Вы манипулируете токенами, поэтому сделайте полезную абстракцию над токенами, и тогда вы сами сможете выбрать, какие детали реализации лежат в основе этого.
источник
Как правило, вы возвращаете небольшую структуру, у которой есть число, обозначающее токен (или значение enum для простоты использования) и необязательное значение (строка или, возможно, общее / шаблонное значение). Другой подход заключается в возвращении производного типа для элементов, которые должны содержать дополнительные данные. Оба являются слегка неприятными, но достаточно хорошими решениями практической проблемы.
источник
Token *
или a просто aToken
, или a,TokenPtr
который является общим указателемToken
класса. Но я также вижу, что некоторые лексеры возвращают просто TokenType и сохраняют строковое или числовое значение в других глобальных или статических переменных. Другой вопрос - как мы можем хранить информацию о местоположении, нужно ли мне иметь структуру Token с полями TokenType, String и Location? Спасибо.struct Token {TokenType id; std::string lexeme; int line; int column;}
, верно? Для общедоступной функции Lexer, такой какPeekToken()
, функция может вернуть aToken *
илиTokenPtr
. Я думаю, какое-то время, если функция просто возвращает TokenType, как Parser пытается получить другую информацию о Token? Таким образом, указатель типа datatype предпочтителен для возврата из такой функции. Есть комментарии по поводу моей идеи? Спасибо