#lexer #lexing #lex #lex-macro

lexr

在 Rust 中的灵活、强大且简单的词法分析

1 个不稳定版本

0.1.0 2023 年 11 月 19 日

#102解析工具

MIT 许可证

29KB
263

lexr

Lexr 是一个简单且灵活的 Rust 词法分析库。它旨在独立使用,或与 parsr 一起使用。

语法由一个宏组成,即 lex_rule!,用于定义词法规则。该宏生成一个函数,可以调用以生成词法分析器。词法分析器是输入字符串的迭代器,在遍历过程中生成标记和位置。

如果您遇到任何问题或有所建议,请在此处报告:here

以下是一个简单的词法分析器示例,它可以识别标记 ABC

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 是一个在模式匹配时执行的表达式。表达式必须生成一个标记或 continuebreak

规则由一个模式和生成标记的动作组成。
模式的顺序很重要,因为第一个匹配的模式将被选择。

模式

模式按定义的顺序与输入的开始匹配。

模式可以是以下之一

  • 一个或多个字符串切片字面量或常量。这些字符串连接在一起,并用于正则表达式匹配。
  • 通配符 _,匹配任何单个字符。这不匹配文件结束。
  • 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|

动作

动作本身可以是任何返回令牌或continuesbreaks的表达式。

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