// 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 = name_start() name_continue()* { Ok(TokenKind::Name) } // kind 的十进制数分支;无符号整数后可接点和至少一位小数,负号留给单独操作符。 rule number() -> Result = start:position!() ['0'..='9']+ ("." ['0'..='9']+)? suffix:$(name_continue()*) end:position!() { // 数字后没有名称字符才是完整数 Token;否则将整段作为词法错误,避免误拆成两个合法 Token。 if suffix.is_empty() { Ok(TokenKind::Number) } else { Err(Diagnostic::lexical( Span::new(start, end), "unsupported numeric suffix or missing token separator", )) } } // 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 = start:position!() "\"" (escape() / string_character())* end:string_end(start) { // 闭引号验证通过才确认 String 种类,错误按值继续向上传递。 end.map(|()| TokenKind::String) } // kind 的操作符/分隔符分支;共享前缀的双字符比较必须放在单字符比较/赋值之前。 rule punctuation() -> TokenKind = "(" { TokenKind::LeftParen } / ")" { TokenKind::RightParen } / "{" { TokenKind::LeftBrace } / "}" { TokenKind::RightBrace } / ":" { TokenKind::Colon } / "," { TokenKind::Comma } / "." { TokenKind::Dot } / "==" { TokenKind::EqualEqual } / "!=" { TokenKind::NotEqual } / "<=" { TokenKind::LessEqual } / ">=" { TokenKind::GreaterEqual } / "<" { TokenKind::Less } / ">" { TokenKind::Greater } / "=" { TokenKind::Equal } / "+" { TokenKind::Plus } / "-" { TokenKind::Minus } / "*" { TokenKind::Star } / "/" { TokenKind::Slash } // token 按顺序尝试名称、数字、字符串、标点;未知字符作为错误值返回,避免吞掉后重试别种 Token。 rule kind() -> Result = name() / number() / string() / kind:punctuation() { Ok(kind) } / start:position!() [_] end:position!() { Err(Diagnostic::lexical(Span::new(start, end), "unexpected source character")) } // next 用此包装单词种类及相对字节范围;只在 kind 成功时生成 Token。 rule token() -> Result = start:position!() kind:kind() end:position!() { kind.map(|kind| Token { kind, span: Span::new(start, end) }) } // 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, Diagnostic>) = trivia() result:(token:token() { token.map(Some) } / ![_] { Ok(None) }) end:position!() { // end 包括前导 trivia 与当前 Token;Lexer 用它推进游标,再把结果跨度加回本轮原文起点。 (end, result) } } }