4个版本
0.2.2 | 2023年3月24日 |
---|---|
0.2.1 | 2023年3月22日 |
0.2.0 | 2023年3月22日 |
0.1.0 | 2023年3月11日 |
#181 in 解析工具
每月 43 次下载
在 blarse 中使用
22KB
355 行
Blex 是一个基于 Rust 和 T-Lex 框架构建的轻量级词法分析框架。Blex 围绕构建一组规则来处理一组标记的概念。每个规则都依次运行在整个输入字符串上,逐渐将其转换为标记的输出字符串。
标记
标记本身的实现是从 Rustuck 严重借鉴的想法。Blex 中的标记表示为从原始字符串中连续的字符范围以及一组标签,标签本身也以借用字符串的形式表示。例如,字符串 let x: int = 2;
可能表示为
"let": var_dec, keyword
"x": ident
":": colon, keyword
"int": ident
"=": eq, keyword
"2": int
注意空白字符的缺失:关键字不必代表整个字符串。
规则
Blex 中的规则是指任何将标记向量转换为可选标记向量的函数。在 Rust 中,这指的是这个特质约束:Fn(Vec<Token>) -> Option<Vec<Token>>
。规则可以修改输入标记,而不必担心修改原始字符串:在应用规则之前,标记被克隆(幸运的是,对于标记来说,克隆是一个非常便宜的操作,因为标记只是一个范围和一些指针)。
规则通过 process_rule
和 process_rules
函数进行处理。process_rule
函数从列表中的每个标记开始应用规则。规则从单个标记开始应用,该标记被包裹在一个 Vec
中,传递给函数并处理。如果函数返回 None
,则起始标记和下一个标记组合成一个 Vec
并传递给函数。如果函数返回 Some
,则传递的标记被替换为返回的 Vec
。
规则处理
规则是在标记的字符串上处理的,但字符串字符通过使用str_to_tokens
函数转换为标记。字符串中的每个字符都被转换成相应的标记,并且添加了一个带有字符内容的标签,这是Rustuck的遗留问题。
示例
让我们创建一个规则,该规则读取连续的两个标记ab
并将它们转换为一个带有标签c
的标记。规则一开始是这样的
fn ab_rule(tokens: Vec<Token>) -> Option<Vec<Token>> {
}
假设我们输入字符串a b blex ab abab
。该字符串将被转换成这些标记
"a": a;
" ": ;
"b": b;
" ": ;
"b": b;
"l": l;
"e": e;
"x": x;
" ": ;
"a": a;
"b": b;
" ": ;
"a": a;
"b": b;
"a": a;
"b": b;
"":
注意末尾的空标记。我们没有输入它:它是自动添加的,以便为一些规则提供缓冲,例如那些测试单词的规则。我们的规则将从逐个扫描每个标记开始。记住,我们在扫描“ab”的模式。让我们列出规则可能采取的每条可能的路径。
- 它将从包含一个字符的一个标记开始。
- 如果标记具有标签
a
,我们应该继续规则以包含下一个标记。 - 否则,我们应该停止规则并返回未更改的标记。
- 如果标记具有标签
- 如果找到多个标记,根据最后一个规则,它必须是一对标记,第一个标记是
a
。了解这一点后,我们可以采取两条路径- 如果第二个标记具有标签
b
,我们应该将这两个标记合并并给它标签c
。 - 否则,我们应该停止规则并返回未更改的标记。
- 如果第二个标记具有标签
幸运的是,blex有表达这种含义的习惯用法。我们将首先使用match
来匹配token_structure(&tokens)
,它有两个选项
fn ab_rule(tokens: Vec<Token>) -> Option<Vec<Token>> {
match tokens_structure(&tokens) {
TokenStructure::Single(tok) => {
},
TokenStructure::Multiple => {
}
}
}
token_structure
函数接受一个标记的借用Vec
。如果它发现该Vec
只包含一个标记,它返回一个包裹在Single
中的标记。否则,它返回Multiple
。这是一种保证存在一个标记的安全方式。在has_tag
方法中,也有一种测试标记是否具有某些标签的习惯用法。我们可以使用它分别测试a
和b
标签的两种情况。
fn ab_rule(tokens: Vec<Token>) -> Option<Vec<Token>> {
match tokens_structure(&tokens) {
TokenStructure::Single(tok) => {
if tok.has_tag("a") {
// case 1
} else {
// case 2
}
},
TokenStructure::Multiple => {
if tokens[1].has_tag("b") {
// case 3
} else {
// case 4
}
}
}
}
剩下的是返回值
- 在情况1中,我们希望继续规则到下一个标记。我们通过返回
None
来做到这一点。 - 在情况2和4中,我们希望结束规则并返回未更改的标记。我们简单地通过返回
Some(tokens)
来完成。 - 在情况3中,我们希望将两个标记合并并给它们标签
c
。当然,也有一个习惯用法来做这件事:wrap
。函数wrap
接受一个标记的Vec
(假定来自同一标记字符串),并将它们的全部内容合并到一个标记中。函数wrap
的第二个参数是一个Vec<&str>
,其中包含要添加到新标记中的所有标签。
fn ab_rule(tokens: Vec<Token>) -> Option<Vec<Token>> {
match tokens_structure(&tokens) {
TokenStructure::Single(tok) => {
if tok.has_tag("a") {
None
} else {
Some(tokens)
}
},
TokenStructure::Multiple => {
if tokens[1].has_tag("b") {
Some(vec![wrap(tokens, vec!["c"])])
} else {
Some(tokens)
}
}
}
}
这样我们的规则就完成了!我们可以用这样的脚本测试它
#[test]
fn apply_ab() {
let text = "a b blex ab abab";
let mut body = str_to_tokens(text);
process_rule(ab_rule, &mut body);
print_tokens(body);
}
它给出了
"a": a;
" ": ;
"b": b;
" ": ;
"b": b;
"l": l;
"e": e;
"x": x;
" ": ;
"ab": c;
" ": ;
"ab": c;
"ab": c;
"":