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