18个版本 (破坏性更新)
| 0.15.0 | 2023年9月3日 |
|---|---|
| 0.14.0 | 2023年4月23日 |
| 0.13.0 | 2023年4月10日 |
| 0.12.0 | 2022年8月12日 |
| 0.4.0 | 2021年5月30日 |
#325 in 解析工具
每月721次下载
在 6 个crate中(直接使用3个) 使用
250KB
8K SLoC
lexgen: 一个功能齐全的词法分析器生成器,实现为过程宏
lexer! {
// First line specifies name of the lexer and the token type returned by
// semantic actions
Lexer -> Token;
// Regular expressions can be named with `let` syntax
let init = ['a'-'z'];
let subseq = $init | ['A'-'Z' '0'-'9' '-' '_'];
// Rule sets have names. Each rule set is compiled to a separate DFA.
// Switching between rule sets is done explicitly in semantic actions.
rule Init {
// Rules without a right-hand side for skipping whitespace,
// comments, etc.
[' ' '\t' '\n']+,
// Rule for matching identifiers
$init $subseq* => |lexer| {
let token = Token::Id(lexer.match_().to_owned());
lexer.return_(token)
},
}
}
// The token type
#[derive(Debug, PartialEq, Eq)]
enum Token {
// An identifier
Id(String),
}
// Generated lexers are initialized with a `&str` for the input
let mut lexer = Lexer::new(" abc123Q-t z9_9");
// Lexers implement `Iterator<Item=Result<(Loc, T, Loc), LexerError>>`,
// where `T` is the token type specified in the lexer definition (`Token` in
// this case), and `Loc`s indicate line, column, and byte indices of
// beginning and end of the lexemes.
assert_eq!(
lexer.next(),
Some(Ok((
Loc { line: 0, col: 1, byte_idx: 1 },
Token::Id("abc123Q-t".to_owned()),
Loc { line: 0, col: 10, byte_idx: 10 }
)))
);
assert_eq!(
lexer.next(),
Some(Ok((
Loc { line: 0, col: 12, byte_idx: 12 },
Token::Id("z9_9".to_owned()),
Loc { line: 0, col: 16, byte_idx: 16 }
)))
);
assert_eq!(lexer.next(), None);
另请参阅
动机
实现词法分析通常是(与解析一起)实现语言中最枯燥的部分。词法分析生成器使这变得更加容易,但在Rust中现有的词法分析生成器缺少实际使用中的基本功能,并且/或者需要构建时的预处理步骤。
lexgen的目标是拥有一个功能齐全且易于使用的词法分析器生成器。
用法
lexgen不需要构建步骤。在您的Cargo.toml中将相同版本的lexgen和lexgen_util添加为依赖项。
词法分析器语法
lexgen词法分析器以生成的词法分析器结构的名称开头,可选的用户状态部分,以及令牌类型(语义动作返回的值类型)。示例
lexer! {
Lexer(LexerState) -> Token;
...
}
这里生成的词法分析器类型将被命名为Lexer。用户状态类型是LexerState(此类型应由用户定义)。令牌类型是Token。
在词法分析器名称、用户状态和令牌类型之后,我们定义规则
rule Init {
...
}
rule SomeOtherRule {
...
}
第一个规则集将定义词法分析器的初始状态,需要命名为Init。
在rule块的主体中,我们定义该词法状态的规则。规则的语法为:<regex> => <语义动作>,。正则表达式的语法描述如下。语义动作是任何具有类型fn(LexerHandle) -> LexerAction的Rust代码,其中LexerHandle和LexerAction是源自词法器名称(例如,我们示例中的Lexer)生成的名称。下面将详细介绍这些类型。
可以使用let <name> = <regex>;语法为正则表达式命名。例如
let init = ['a'-'z'];
let subseq = $init | ['A'-'Z' '0'-'9' '-' '_'];
// Named regexes can be used with the `$` prefix
$init $subseq* => |lexer| { ... }
如果您不需要规则集,可以省略rule Init { ... }部分,并将所有规则放在顶层。
总之
-
第一行形式为
<词法器名称>(<用户状态类型类型>) -> <令牌类型名称>。对于无状态的词法器,可以省略(<用户状态类型类型>)部分。 -
接下来是规则集。应该至少有一个名为
Init的规则集,这是初始状态的名字。 -
可以在顶层或在
rule中添加let绑定。
正则表达式语法
正则表达式语法可以用于let绑定右侧和规则左侧。语法是
-
$var用于在let绑定部分定义的变量。变量在使用前需要定义。 -
$$var用于内置正则表达式(见下文“内置正则表达式”部分)。 -
Rust字符语法用于字符,例如
'a'。 -
Rust字符串语法用于字符串,例如
"abc"。 -
[...]用于字符集。在括号内可以有一个或多个- 字符
- 字符范围:例如
'a'-'z'
以下是一个ASCII字母数字字符集的示例:
['a'-'z' 'A'-'Z' '0'-'9'] -
_用于匹配任何字符 -
$用于匹配输入结束 -
<regex>*用于匹配0次或多次重复的<regex> -
<regex>+用于匹配1次或多次重复的<regex> -
<regex>?用于匹配0次或1次重复的<regex> -
<regex> <regex>用于连接 -
<regex> | <regex>用于选择:匹配第一个或第二个。 -
<regex> # <regex>用于差集:匹配第一个正则表达式中的字符,但不包含在第二个正则表达式中。注意,在#左右的正则表达式应该是“字符集”,即*、+、?、"..."、$和连接是不允许的。绑定到字符集的变量是允许的。
绑定优先级(从高到低)
*,+,?#- 连接
|
您可以使用括号进行分组,例如:('a' | 'b')*。
示例:'a' 'b' | 'c'+ 等价于 (('a' 'b') | ('c'+))。
正确上下文(前瞻)
规则集中的一条规则可以使用> <regex>语法跟随另一个正则表达式,用于正确上下文。正确上下文基本上是前瞻的有限形式:它们只能出现在规则顶层正则表达式之后。它们不能在正则表达式中嵌套使用。
例如,规则左侧'a' > (_ # 'b')与'a'匹配,只要其后不是'b'。
有关更多示例,请参阅正确上下文测试。
内置正则表达式
lexgen自带一组内置正则表达式。以下列出的正则表达式与它们的Rust对应物匹配相同的字符集。例如,$$alphabetic与Rust的char::is_alphabetic匹配相同的字符集。
$$alphabetic$$alphanumeric$$ascii$$ascii_alphabetic$$ascii_alphanumeric$$ascii_control$$ascii_digit$$ascii_graphic$$ascii_hexdigit$$ascii_lowercase$$ascii_punctuation$$ascii_uppercase$$ascii_whitespace$$control$$lowercase$$numeric$$uppercase$$whitespace
(请注意,在生成的代码中,我们不使用Rust char方法。对于简单的案例,如$$ascii,我们生成简单的范围检查。对于更复杂的案例,如$$lowercase,我们生成一个二分搜索表,并在检查字符时执行二分搜索)
此外,这两个内置正则表达式匹配Unicode XID_Start和XID_Continue
$$XID_Start$$XID_Continue
规则语法
-
<regex> => <语义动作>,:如下所述的<regex>语法。<语义动作>是任何具有类型fn(&mut Lexer) -> SemanticActionResult<Token>的 Rust 代码。有关SemanticActionResult类型更详细的内容,请参阅下一节。 -
<regex> =? <语义动作>,:可能失败的动作。此语法类似于上面的语法,但<语义动作>的类型为fn(&mut Lexer) -> LexerAction<Result<Token, UserError>>。使用此类规则时,需要在词法分析器的开头声明错误类型,使用type Error = UserError;语法。当此类规则返回错误时,错误会被返回到词法分析器
next方法的调用者。 -
<regex>,:省略了<regex> => |lexer| { lexer.reset_match(); lexer.continue_() },的语法糖。对于跳过字符(例如空白字符)非常有用。 -
<regex> = <token>,:省略了<regex> => |lexer| lexer.return_(<token>),的语法糖。对于匹配关键字、标点(运算符)和分隔符(括号、方括号)非常有用。
规则集中的输入结束处理
Init 规则集在输入结束(即 lexer.next() 返回 None)时成功结束词法分析。其他规则集在输入结束时失败(即返回 Some(<Err(<...)<))。这是因为通常除了初始状态之外的状态是为复杂标记(字符串、原始字符串、多行注释)而设计的,这些标记需要结束和处理,而这些状态中的输入结束通常意味着标记没有正确结束。
(要在规则集中处理输入结束,可以使用如上所述的“正则表达式语法”部分中描述的 $。)
处理、规则、错误和动作类型
lexer 宏生成一个结构体,其名称由用户在词法分析定义的第一行指定。在开头的示例中(Lexer <-> Token;),结构体的名称是 Lexer。
将此类型的可变引用传递给语义动作函数。在语义动作的实现中,您应使用以下方法之一驱动词法分析器并返回标记
fn match_(&self) -> &str:返回当前匹配。请注意,当使用new_from_iter或new_from_iter_with_state构造词法分析器时,此方法会引发恐慌。它仅在用new或new_with_state初始化词法分析器时调用。fn match_loc(&self) -> (lexgen_util::Loc, lexgen_util::Loc):返回当前匹配的边界fn peek(&mut self) -> Option<char>:向前看一个字符fn state(&mut self) -> &mut <user state type>:返回对用户状态的可变引用fn return_(&self, token: <用户令牌类型>) -> SemanticActionResult:返回传递的令牌作为匹配项。fn continue_(&self) -> SemanticActionResult:忽略当前匹配项并继续在同一词法分析器状态下进行词法分析。用于跳过字符。fn switch(&mut self, rule: LexerRule) -> SemanticActionResult:用于在词法分析器状态之间切换。`LexerRule`(其中`Lexer`部分是用户指定的词法分析器名称)是一个枚举,具有每个规则集名称的变体,例如,`LexerRule::Init`。请参见下面的状态性词法分析器示例。fn switch_and_return(&mut self, rule: LexerRule, token: <用户令牌类型>) -> SemanticActionResult:切换到给定的词法分析器状态并返回给定的令牌。fn reset_match(&mut self):重置当前匹配项。例如,如果在调用`reset_match()`后立即调用`match_()`,它将返回一个空字符串。
语义动作函数应返回从上述方法之一获得的`SemanticActionResult`值。
初始化词法分析器
lexgen生成4个构造函数
-
fn new(input: &str) -> Self:当词法分析器没有用户状态或用户状态实现Default时使用。 -
fn new_with_state(input: &str, user_state: S) -> Self:当词法分析器具有不实现Default的用户状态,或者您希望使用非默认值初始化状态时使用。S是在词法分析器定义中指定的用户状态类型。请参阅下面的状态化词法分析器示例。 -
fn new_from_iter<I: Iterator<Item = char> + Clone>(iter: I) -> Self:当输入不是平面字符串,而是类似绳索或拉链的东西时使用。请注意,当使用此构造函数时,match_方法会恐慌。相反,使用match_loc来获取当前匹配的位置。 -
fn new_from_iter_with_state<I: Iterator<Item = char> + Clone, S>(iter: I, user_state: S) -> Self:与上面相同,但不需要用户状态实现Default。
状态化词法分析器示例
这是一个示例词法分析器,用于计算两个=之间出现的数量
lexer! {
// `usize` in parenthesis is the user state type, `usize` after the arrow
// is the token type
Lexer(usize) -> usize;
rule Init {
$$ascii_whitespace, // line 7
'[' => |lexer| {
*lexer.state() = 0; // line 10
lexer.switch(LexerRule::Count) // line 11
},
}
rule Count {
'=' => |lexer| {
*lexer.state() += 1; // line 17
lexer.continue_() // line 18
},
'[' => |lexer| {
let n = *lexer.state();
lexer.switch_and_return(LexerRule::Init, n) // line 23
},
}
}
let mut lexer = Lexer::new("[[ [=[ [==[");
assert_eq!(
lexer.next(),
Some(Ok((
Loc { line: 0, col: 0, byte_idx: 0 },
0,
Loc { line: 0, col: 2, byte_idx: 2 },
)))
);
assert_eq!(
lexer.next(),
Some(Ok((
Loc { line: 0, col: 3, byte_idx: 3 },
1,
Loc { line: 0, col: 6, byte_idx: 6 },
)))
);
assert_eq!(
lexer.next(),
Some(Ok((
Loc { line: 0, col: 7, byte_idx: 7 },
2,
Loc { line: 0, col: 11, byte_idx: 11 },
)))
);
assert_eq!(lexer.next(), None);
最初(Init规则集)我们跳过空格(第7行)。当我们看到[时,我们初始化用户状态(第10行)并切换到Count状态(第11行)。在Count中,每个=将用户状态增加1(第17行)并跳过匹配(第18行)。在Count状态中的[返回当前数量并切换到Init状态(第23行)。
依赖关系
~270–710KB
~17K SLoC