docs(frontend): explain source readers and PEG grammar rules
This commit is contained in:
@@ -1,20 +1,28 @@
|
||||
// lexer.rs 编译时引入,生成 lexical::next;输入为本轮尚未消费的 UTF-8 后缀,所有 position! 都相对这个后缀。
|
||||
peg::parser! {
|
||||
// Lexer::next 调用的字符串扫描规则;每轮消费 trivia 后至多一个 Token。
|
||||
grammar lexical() for str {
|
||||
// next 首先跳过 ASCII 空白和双斜杠行注释,不产生 Token;注释在 CR/LF 前结束。
|
||||
rule trivia()
|
||||
= ([' ' | '\t' | '\r' | '\n'] / "//" [^'\r' | '\n']*)*
|
||||
|
||||
// name 首字符允许下划线或 Unicode 字母,不允许数字开头。
|
||||
rule name_start()
|
||||
= [character if character == '_' || character.is_alphabetic()]
|
||||
|
||||
// name 后续字符允许下划线或 Unicode 字母/数字;number 也用它识别非法黏连后缀。
|
||||
rule name_continue()
|
||||
= [character if character == '_' || character.is_alphanumeric()]
|
||||
|
||||
// kind 的名称分支;关键字也返回 Name,具体语法上下文稍后才识别。
|
||||
rule name() -> Result<TokenKind, Diagnostic>
|
||||
= name_start() name_continue()* { Ok(TokenKind::Name) }
|
||||
|
||||
// kind 的十进制数分支;无符号整数后可接点和至少一位小数,负号留给单独操作符。
|
||||
rule number() -> Result<TokenKind, Diagnostic>
|
||||
= start:position!() ['0'..='9']+ ("." ['0'..='9']+)?
|
||||
suffix:$(name_continue()*) end:position!() {
|
||||
// 数字后没有名称字符才是完整数 Token;否则将整段作为词法错误,避免误拆成两个合法 Token。
|
||||
if suffix.is_empty() {
|
||||
Ok(TokenKind::Number)
|
||||
} else {
|
||||
@@ -25,32 +33,42 @@ peg::parser! {
|
||||
}
|
||||
}
|
||||
|
||||
// string 只接受换行、制表、双引号、反斜杠四种反斜杠转义,解码在 parser 的 decode_string 中进行。
|
||||
rule escape()
|
||||
= "\\" ['n' | 't' | '"' | '\\']
|
||||
|
||||
// string 的普通字符不能是引号、反斜杠、原始换行或 NUL;其他 Unicode 字符原样消费。
|
||||
rule string_character()
|
||||
= [^'"' | '\\' | '\r' | '\n' | '\0']
|
||||
|
||||
// string 主体停止后检查闭引号或报告具体错误;start 是开引号位置,供未闭合字符串定位。
|
||||
rule string_end(start: usize) -> Result<(), Diagnostic>
|
||||
= "\"" { Ok(()) }
|
||||
// 主体已消费所有合法转义,剩余反斜杠加字符必是未支持的转义。
|
||||
/ position:position!() "\\" [_] end:position!() {
|
||||
Err(Diagnostic::lexical(Span::new(position, end), "unsupported string escape"))
|
||||
}
|
||||
// 反斜杠后直接 EOF,按从开引号到 EOF 的未闭合范围报告。
|
||||
/ "\\" end:position!() ![_] {
|
||||
Err(Diagnostic::lexical(Span::new(start, end), "unterminated string"))
|
||||
}
|
||||
// 原始换行/NUL 不可出现在字符串体内,定位具体违规字符。
|
||||
/ position:position!() ['\r' | '\n' | '\0'] end:position!() {
|
||||
Err(Diagnostic::lexical(Span::new(position, end), "unescaped newline or NUL in string"))
|
||||
}
|
||||
// 普通 EOF 也表示缺少闭引号,范围从开引号覆盖到输入结尾。
|
||||
/ end:position!() ![_] {
|
||||
Err(Diagnostic::lexical(Span::new(start, end), "unterminated string"))
|
||||
}
|
||||
|
||||
// kind 的字符串分支;扫描已验证主体及终结情况,Token 跨度仍包含引号并保持未解码原文。
|
||||
rule string() -> Result<TokenKind, Diagnostic>
|
||||
= start:position!() "\"" (escape() / string_character())* end:string_end(start) {
|
||||
// 闭引号验证通过才确认 String 种类,错误按值继续向上传递。
|
||||
end.map(|()| TokenKind::String)
|
||||
}
|
||||
|
||||
// kind 的操作符/分隔符分支;共享前缀的双字符比较必须放在单字符比较/赋值之前。
|
||||
rule punctuation() -> TokenKind
|
||||
= "(" { TokenKind::LeftParen }
|
||||
/ ")" { TokenKind::RightParen }
|
||||
@@ -71,6 +89,7 @@ peg::parser! {
|
||||
/ "*" { TokenKind::Star }
|
||||
/ "/" { TokenKind::Slash }
|
||||
|
||||
// token 按顺序尝试名称、数字、字符串、标点;未知字符作为错误值返回,避免吞掉后重试别种 Token。
|
||||
rule kind() -> Result<TokenKind, Diagnostic>
|
||||
= name()
|
||||
/ number()
|
||||
@@ -80,6 +99,7 @@ peg::parser! {
|
||||
Err(Diagnostic::lexical(Span::new(start, end), "unexpected source character"))
|
||||
}
|
||||
|
||||
// next 用此包装单词种类及相对字节范围;只在 kind 成功时生成 Token。
|
||||
rule token() -> Result<Token, Diagnostic>
|
||||
= start:position!() kind:kind() end:position!() {
|
||||
kind.map(|kind| Token { kind, span: Span::new(start, end) })
|
||||
@@ -87,9 +107,12 @@ peg::parser! {
|
||||
|
||||
// Scan only one prefix. Errors are returned as values, so a matched
|
||||
// malformed number/string cannot backtrack into another token kind.
|
||||
// 禁用默认整段消费检查,才能逐个前缀扫描;最终 EOF 由规则内的 ![_] 判断。
|
||||
#[no_eof]
|
||||
// Lexer::next 的唯一公开扫描入口;允许剩余后缀尚有内容,返回本轮消费字节数和 Token/EOF/诊断。
|
||||
pub rule next() -> (usize, Result<Option<Token>, Diagnostic>)
|
||||
= trivia() result:(token:token() { token.map(Some) } / ![_] { Ok(None) }) end:position!() {
|
||||
// end 包括前导 trivia 与当前 Token;Lexer 用它推进游标,再把结果跨度加回本轮原文起点。
|
||||
(end, result)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user