Я нахожусь в процессе написания лексера для пользовательского языка программирования.Прежде всего, я хочу сказать, что это личное упражнение, и я хочу сделать это рукописным способом, а не использовать любые инструменты генератора, такие как Lex или Flex .
Один из синтаксисов в моем языке состоит в том, что есть три типа комментариев: однострочные, многострочные и документы:
- однострочные комментарии:
code(x, y, z); %% a comment that ends at the end of the line
moreCode(x, y, z);
- многострочные комментарии:
code(x, %- a comment that starts and ends on the same line. -% y, z);
moreCode(x, %- a comment that starts, contains a line break,
and then ends. -% y, z);
- комментарии к документу:
%%%
a doc comment. the delimiters must be on their own line
%%%
code(x, y, z);
Проблема
Этот вопрос касается токенизации комментария типа документа(# 3).Прямо сейчас я могу успешно токенизировать одно- и многострочные, и я могу токенизировать комментарии к документам , как если бы они были многострочными.Но это приводит к проблеме:
Неправильное поведение
code(); %%% This text
is commented
out. %%% notCommentedOut();
«Комментарий к документу» рассматривается как многострочный комментарий.Исходя из вышесказанного, мой токенизатор неправильно создает следующие токены:
code
- идентификатор (
- символ )
- символ ;
- символ %%% This text
is commented
out. %%%
- комментарий notCommentedOut
- идентификатор (
- символ )
- символ ;
- символ
Ожидаемое поведение
Указанный выше токенизация неверна, потому что я хочу включить разделители %%%
свою собственную строку для регистрации в качестве комментария к документу, и я хочу, чтобы любые %%%
, то есть , а не в отдельной строке, рассматривались как однострочный комментарий (потому что он начинается с %%
).Это означает, что правильный токенизация должна быть:
code(); %%% This is commented out.
notCommentedOut();
'also' + !commentedOut; %%% also commented out
code
- идентификатор (
- символ )
- символ ;
- символ %%% This is commented out.
- комментарий notCommentedOut
- идентификатор (
- символ )
- символ ;
- символ 'also'
- строка +
- символ !
- символ commentedOut
- идентификатор ;
- символ %%% also commented out
- комментарий
сходства
Другие языки имеют аналогичные конструкции, например, в Markdown, заголовках и изолированных блоках кода:
# this is a heading
foobar # this is not a heading
```
this is a fenced code block
```
foobar ``` this is not
a fenced code block ```
В LaTeX мы можем поместить уравнения блоков:
$$
f(x) = 2^{x + 1}
$$
My Approach
Код
(TypeScript и сокращен для ясности.)
// advance the scanner `n` number of characters
function advance(n: number = 1): void {
if (n === 1) {
// reassign c0 to the next character
// reassign c1 to lookahead(1)
// reassign c2 to lookahead(2)
} else {
advance(n - 1)
advance()
}
}
while (!character.done) {
if (whitespace.includes(c0)) {
const wstoken = new Token(character.value)
wstoken.type = TokenType.WHITESPACE
advance()
while (!character.done && whitespace.includes(c0)) {
wstoken.cargo += c0
advance()
}
// yield wstoken // only if we want the lexer to return whitespace
break;
}
const token = new Token(character.value)
if (c0 === ENDMARK) {
token.type = TokenType.EOF
advance()
} else if (c0 + c1 + c2 === comment_doc_start) { // we found a doc comment: `%%%`
token.type = TokenType.COMMENT
token.cargo += comment_doc_start
advance(comment_doc_start.length)
while (!character.done && c0 + c1 + c2 !== comment_doc_end) {
if (c0 === ENDMARK) throw new Error("Found end of file before end of comment")
token.cargo += c0
advance()
}
// add comment_doc_end to token
token.cargo += comment_doc_end
advance(comment_doc_end.length)
} else if (c0 + c1 === comment_multi_start) { // we found a multi-line comment: `%- -%`
token.type = TokenType.COMMENT
token.cargo += comment_multi_start
advance(comment_multi_start.length)
while (!character.done && c0 + c1 !== comment_multi_end) {
if (c0 === ENDMARK) throw new Error("Found end of file before end of comment")
token.cargo += c0
advance()
}
// add comment_multi_end to token
token.cargo += comment_multi_end
advance(comment_multi_end.length)
} else if (c0 + c1 === comment_line) { // we found a single-line comment: `%%`
token.type = TokenType.COMMENT
token.cargo += comment_line
advance(comment_line.length)
while (!character.done && c0 !== '\n') {
if (c0 === ENDMARK) throw new Error("Found end of file before end of comment")
token.cargo += c0
advance()
}
// do not add '\n' to token
} else {
throw new Error(`I found a character or symbol that I do not recognize: ${c0}`)
}
yield token
}
Процесс мысли
Я думаю, что есть два варианта, оба из которых являютсяне желательно.
Один из вариантов - иметь глобальныйпеременная вне цикла while
, логический флаг, указывающий, является ли предыдущий токен пробелом и содержит \n
.Затем используйте этот флаг, чтобы сообщить следующий токен, который начинается с %%%
.Если флаг имеет значение true, комментарий должен закрыться в следующем %%%
;иначе это должно закрыться в следующем \n
.Я не уверен, нравится ли мне эта опция, потому что она включает установку флага для каждого токена кода.Он также не учитывает конечный разделитель, который также должен находиться в отдельной строке.
Другой вариант - возврат.Когда лексер достигает токена, начинающегося с %%%
, проверьте предыдущий токен, чтобы увидеть, является ли он пробелом и содержит \n
.Если это так, токен %%%
является комментарием к документу и должен закрыться на следующем %%%
.Если нет, то это встроенный комментарий, который должен закрыться на \n
.Мне действительно не нравится этот параметр, так как он включает в себя возврат, который увеличивает сложность и время.
Являются ли эти параметры даже удаленно правильными?Они выполнимы?Рекомендуемые?Или есть другой подход, который я должен использовать?