1 个不稳定版本
0.1.0 | 2023 年 11 月 19 日 |
---|
#102 在 解析工具
29KB
263 行
lexr
Lexr 是一个简单且灵活的 Rust 词法分析库。它旨在独立使用,或与 parsr 一起使用。
语法由一个宏组成,即 lex_rule!
,用于定义词法规则。该宏生成一个函数,可以调用以生成词法分析器。词法分析器是输入字符串的迭代器,在遍历过程中生成标记和位置。
如果您遇到任何问题或有所建议,请在此处报告:here。
以下是一个简单的词法分析器示例,它可以识别标记 A
、B
和 C
use lexr::lex_rule;
#[derive(Debug, PartialEq)]
enum Token {
A, B, C,
}
use Token::*;
lex_rule!{lex -> Token {
"a" => |_| A,
"b" => |_| B,
"c" => |_| C,
}}
let tokens = lex("abc").into_token_vec();
assert_eq!(tokens, vec![A, B, C])
宏语法
lex_rule!
宏用于定义词法分析器。词法规则有一个名称、一个标记类型以及任何数量的与关联动作相关的模式。语法如下
lex_rule!{NAME(ARGS) -> TOKEN {
PATTERN => ACTION,
...
}}
NAME
是由宏生成的函数名称。此函数可以调用以生成词法分析器。- ARGS 是传递给词法分析器的一个可选参数列表。
TOKEN
是词法分析器生成的标记类型。这可以是任何类型,包括 void。- PATTERN 是词法分析器与输入匹配的模式。如果模式匹配,则执行动作。
- ACTION 是一个在模式匹配时执行的表达式。表达式必须生成一个标记或
continue
或break
。
规则由一个模式和生成标记的动作组成。
模式的顺序很重要,因为第一个匹配的模式将被选择。
模式
模式按定义的顺序与输入的开始匹配。
模式可以是以下之一
- 一个或多个字符串切片字面量或常量。这些字符串连接在一起,并用于正则表达式匹配。
- 通配符
_
,匹配任何单个字符。这不匹配文件结束。 eof
,匹配输入的末尾。这是可选的,如果没有提供,则忽略文件结束。ws
,匹配任何空白字符。
以下是一个示例,展示了不同的合法模式
use lexr::lex_rule;
#[derive(Debug, PartialEq)]
enum Token {
A, B, C, D, Num, Eof
}
use Token::*;
const A_RULE: &str = "a";
lex_rule!{lex -> Token {
ws => |_| continue, // Matches whitespace
"a" => |_| A, // Matches "a"
"b" "a" => |_| B, // Matches "bc"
"c" A_RULE => |_| C, // Matches "ba"
r"[0-9]+" => |_| Num, // Matches any number of digits
_ => |_| D, // Matches any single character
eof => |_| Eof, // Matches the end of the input
}}
let tokens = lex("a ba ca S 42").into_token_vec();
assert_eq!(tokens, vec![A, B, C, D, Num, Eof])
动作
动作是一个返回宏定义中提供的标记类型的闭包。
当模式匹配时将执行,可用于生成令牌、跳过或完全停止词法分析。
签名
闭包有3种不同的签名,可以用来向动作提供不同的参数。
|s|
- 动作提供匹配到的字符串。|s, buf|
- 动作提供匹配到的字符串和缓冲区。缓冲区可以用来词法分析子规则。|s, buf, loc|
- 动作提供匹配到的字符串、缓冲区和位置。位置是匹配字符串在输入中的位置。
只需要第一个参数,其余都是可选的。它们都可以用下划线_
来忽略。
这意味着如果没有参数需要,签名可以写成|_|
。
例如,如果只有位置是感兴趣的,其他参数可以用下划线忽略:|_, _, loc|
。
动作
动作本身可以是任何返回令牌或continues
或breaks
的表达式。
continue和break的工作方式如下
continue
- 这将跳过当前令牌,并返回下一个令牌。break
- 这将停止词法分析,因此当遇到此情况时迭代器将返回None。
值得注意的是,可以从动作中调用[子规则](# Sub Rules)。
以下是一个显示不同合法动作的示例
use lexr::lex_rule;
#[derive(Debug, PartialEq)]
enum Token {
A, Num(i32), Eof
}
use Token::*;
lex_rule!{lex -> Token {
// Returns A
"a" => |_| A,
// Matches any whitespace and skips it
r"[ \n\t\r]" => |_| continue,
// Stops the lexer
"x" => |_| break,
// Calls the sub rule and runs it until it it is done
"#" => |_, buf| { comment(buf).deplete(); continue },
// Parses the number and returns it
r"[0-9]+" => |s| Num(s.parse().unwrap()),
// Detects and returns Eof
eof => |_| Eof, // Returns Eof
}}
// A simple rule that ignores all characters until a '#' is encountered
lex_rule!{comment -> () {
"#" => |_| break,
_ => |_| continue,
}}
let tokens = lex("a # comment # 42 a").into_token_vec();
assert_eq!(tokens, vec![A, Num(42), A, Eof]);
let tokens = lex("aa 12 x aa").into_token_vec();
assert_eq!(tokens, vec![A, A, Num(12)]);
参数
参数传递给词法分析函数,可以用来向词法分析器传递参数。这些可以用来例如传递上下文信息,或向子规则传递参数。
以下是一个显示如何向词法分析器传递参数的示例
use lexr::lex_rule;
#[derive(Debug, PartialEq)]
enum Token {
A, B(i32), Eof
}
use Token::*;
lex_rule!{lex(arg: i32) -> Token {
"a" => |_| A,
"b" => |_| B(arg),
eof => |_| Eof,
}}
let tokens = lex("ab", 12).into_token_vec();
assert_eq!(tokens, vec![A, B(12), Eof]);
子规则
子规则是从另一个词法分析动作中调用的词法规则。
此调用将在相同的缓冲区上操作,因此子规则将修改缓冲区。
这可以用来词法分析例如注释,甚至整个子语言。
请注意,调用子规则不是尾递归的,所以请谨慎使用,不要作为词法分析的主要方式。
此外,请确保运行子规则,否则不会发生任何事情。这可以通过调用deplete
(运行到结束)或next
(单个令牌)来实现。
以下是一个显示如何调用子规则的示例
use lexr::lex_rule;
#[derive(Debug, PartialEq)]
enum Token {
A, Eof
}
use Token::*;
lex_rule!{lex -> Token {
ws => |_| continue,
"a" => |_| A,
r"\(\*" => |_, buf| { comment(buf, 0).next(); continue },
eof => |_| Eof,
}}
lex_rule!{comment(depth: u16) -> () {
r"\(\*" => |_, buf| {comment(buf, depth + 1).next(); break},
r"\*\)" => |_, buf|
if depth == 0 {
break
} else {
comment(buf, depth - 1).next();
break
},
eof => |_| panic!("Unclosed comment!"),
_ => |_| continue,
}}
let tokens = lex("a (* comment (* inner *) comment *) aa").into_token_vec();
assert_eq!(tokens, vec![A, A, A, Eof]);
许可证:MIT
依赖项
~2.4–4MB
~71K SLoC