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 中使用

GPL-3.0 许可证

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_ruleprocess_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方法中,也有一种测试标记是否具有某些标签的习惯用法。我们可以使用它分别测试ab标签的两种情况。

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;
"":

没有运行时依赖