3 个版本
0.1.2 | 2022年5月24日 |
---|---|
0.1.1 | 2022年5月24日 |
0.1.0 | 2022年5月23日 |
1071 在 数据结构
每月 24 次下载
34KB
647 行
安装
[dependencies]
parsable = "0.1"
示例
实现一个仅适用于正整数且不包含操作符优先级的简单操作解释器。
use parsable::{parsable, Parsable, ParseOptions};
#[parsable]
struct Operation {
first_operand: Operand,
other_operands: Vec<(Operator, Operand)>
}
impl Operation {
fn process(&self) -> i32 {
let mut result = self.first_operand.process();
for (operator, operand) in &self.other_operands {
let value = operand.process();
result = match operator {
Operator::Plus => result + value,
Operator::Minus => result - value,
Operator::Mult => result * value,
Operator::Div => result / value,
Operator::Mod => result % value,
}
}
result
}
}
#[parsable]
enum Operand {
Number(NumberLiteral),
Wrapped(WrappedOperation)
}
impl Operand {
fn process(&self) -> i32 {
match self {
Operand::Number(number) => number.process(),
Operand::Wrapped(wrapped) => wrapped.process(),
}
}
}
#[parsable]
struct NumberLiteral {
#[parsable(regex=r"\d+")]
value: String
}
impl NumberLiteral {
fn process(&self) -> i32 {
self.value.parse().unwrap()
}
}
#[parsable]
struct WrappedOperation {
#[parsable(brackets="()")]
operation: Box<Operation>
}
impl WrappedOperation {
fn process(&self) -> i32 {
self.operation.process()
}
}
#[parsable]
enum Operator {
Plus = "+",
Minus = "-",
Mult = "*",
Div = "/",
Mod = "%"
}
fn main() {
let operation_string = "3 + (4 * 5)".to_string();
let parse_options = ParseOptions::default();
match Operation::parse(operation_string, parse_options) {
Ok(operation) => {
println!("result: {}", operation.process());
},
Err(error) => {
dbg!(error);
}
}
}
#[parsable]
宏
使用 #[parsable]
宏标记结构体或枚举将实现项目的 Parsable
特性,条件是所有字段也必须实现 Parsable
特性。它还可以用于字段以调整其解析方式。
结构体
- 所有字段逐个解析。只有当所有字段都成功解析时,解析才会成功。
枚举
- 解析会在成功解析的第一个变体上停止。
- 如果变体包含多个字段,它们将依次解析,并且必须全部成功才能匹配变体。
- 如果变体不包含字段,则必须指定一个字符串以指示如何解析它。
#[parsable]
enum MyOperation {
BinaryOperation(NumerLiteral, Operator, NumerLiteral),
Number(NumberLiteral),
Zero = "zero"
}
// If the first two variants are swapped, then the parsing will never reach the `SimpleOperation` variant
内置类型
字符串
字符串字段必须使用 #[parsable(regex="<pattern>")]
或 #[parsable(value="<pattern>")]
宏选项指定如何解析。
// Matches at least one digit
#[parsable]
struct NumberLiteral {
#[parsable(regex=r"\d+")]
value: String
}
#[parsable]
// Only matches the string "+"
struct PlusSign {
#[parsable(value="+")]
value: String
}
可选<T>
匹配 T
。如果失败,则返回 None
,但字段的解析仍然被认为是成功的。
#[parsable]
enum Sign {
Plus = "+",
Minus = "-"
}
// Matches a number with an optional sign
#[parsable]
struct NumberLiteral {
sign: Option<Sign>,
#[parsable(regex=r"\d+")]
value: String
}
Vec<T>
尽可能多地连续匹配 T
。以下选项可以指定:
min=X
:只有解析至少 X 项时解析才有效。separator=<string>
:在每一项之后,解析器将尝试消费分隔符。如果没有找到分隔符,则解析失败。
// Matches a non-empty list of numbers separated by a comma
#[parsable]
struct NumberList {
#[parsable(separator=",", min=1)]
numbers: Vec<NumberLiteral>
}
其他类型
()
:不匹配任何内容,总是成功。(T, U)
:先匹配T
,然后匹配U
。Box<T>
:匹配T
。
运行解析器
Parsable
特性提供了一个 parse()
方法,它接受两个参数
content: String
:要解析的字符串options: ParseOptions
:解析选项
ParseOptions
类型有以下字段
comment_start: Option<&'static str>
:当匹配到指定的模式时,忽略该行其余部分。常见的实例是"//"
或"#"
。file_path: Option<String>
:正在解析的字符串的文件路径。package_root_path: Option<String>
:包含正在解析的文件的包或模块的根路径。
字段 file_path
和 package_root_path
被转发到 FileInfo
结构中,并且库实际上从不使用它们。
在解析过程中始终忽略空白字符(空格、换行和制表符)。
FileInfo
FileInfo
结构在整个库中都被使用。它有以下字段
content: String
:正在解析的字符串path: String
:正在解析的文件的路径,如ParseOptions
中指定package_root_path: String
:包含正在解析的文件的包的路径,如ParseOptions
中指定
它还提供了以下方法
get_line_col(index: usize) -> Option<(usize, usize)>
: 返回与指定字符索引相关联的行和列号(从1开始)。此方法假设每个字符占用一个字节,因此当文件包含非ASCII字符时不能正常工作。
ItemLocation
使用 #[parsable]
标注结构体将在其中添加类型为 ItemLocation
的 location
字段,并具有以下字段和方法
file: Rc<FileInfo>
: 包含项目的文件信息start: usize
: 项在文件中的起始索引end: usize
: 项在文件中的结束索引get_start_line_col() -> (usize, usize)
: 获取位置起始的行和列号(从1开始)
Parsable
特性还提供了一个结构体上的 location()
方法
- 枚举上的
location()
方法返回其location
字段 - 枚举上的
location()
方法返回匹配的变体的location()
方法 - 在没有任何字段的变体上调用
location()
会引发恐慌
为了防止恐慌,可以将具有单元变体的枚举封装在结构体中
#[parsable]
enum Operator {
Plus = "+",
Minus = "-",
Mult = "*",
Div = "/",
Mod = "%"
}
#[parsable]
struct WrappedOperator {
operator: Operator
}
fn main() {
let string = "+".to_string();
let options = ParseOptions::default();
let result = WrappedOperator::parse(string, options).unwrap();
dbg!(result.location()); // It works!
}
ParseError
在失败时,Parsable::parse()
返回 Err(ParseError)
。此结构体具有以下字段
file: Rc<FileInfo>
: 错误发生的文件。index: usize
: 错误发生的索引。expected: Vec<String>
: 在此索引处期望的项名称列表。
宏选项
根属性
located=<bool>
: 在结构体上指示是否应生成location
字段。默认值:true
。cascade=<bool>
:如果在结构体上为true
,则表示如果Option
字段不匹配,则解析器不应尝试匹配其他Option
字段。这不会使整体结构解析无效。默认值:false
。name=<string>
:指示结构体或枚举的名称,当发生解析错误时使用。默认值:结构体或枚举的名称。
#[parsable(located=false)] // The `location` field will not be added
struct Operation {
first_operand: Operand,
other_operands: Vec<(Operator, Operand)>
}
字段属性
prefix=<string>
:在解析字段之前尝试解析指定的字符串。如果前缀解析失败,则字段解析失败。suffix=<string>
:在解析字段之后尝试解析指定的字符串。如果后缀解析失败,则字段解析失败。brackets=<string>
:通过使用指定字符串的前两个字符来同时指定前缀和后缀的快捷方式。exclude=<string>
:指示只有当项目不匹配指定的正则表达式时,解析才是有效的。followed_by=<string>
:指示只有当项目后面跟有指定的正则表达式时,解析才是有效的。not_followed_by=<string>
:指示只有当项目后面不跟有指定的正则表达式时,解析才是有效的。value=<string>
:在一个String
字段上,指示该字段仅匹配指定的字符串。regex=<string>
:在一个String
字段上,指示该字段仅匹配具有指定模式的正则表达式(使用regex
crate)。separator=<string>
:在一个Vec
字段上,指定项目之间的分隔符。min=<integer>
:在一个Vec
字段上,指定解析有效的最小项目数。cascade=false
:指示该字段忽略根cascade
选项
手动实现 Parsable
特性
有时 #[parsable]
不足以实现自己的解析机制,您需要实现自己的解析机制。这通过实现 parse_item
、get_item_name
和 location
方法来完成。
use parsable::{Parsable, StringReader};
struct MyInteger {
value: u32,
location: ItemLocation,
}
impl Parsable for MyInteger {
fn parse_item(reader: &mut StringReader) -> Option<Self> {
let start = reader.get_index();
match reader.read_regex(r"\d+") {
Some(string) => Some(MyInteger {
value: string.parse().unwrap(),
location: reader.get_item_location(start),
}),
None => None,
}
}
// Only used in errors
fn get_item_name() -> String {
"integer".to_string()
}
// Not required, but convenient
fn location(&self) -> &ItemLocation {
&self.location
}
}
fn main() {
let number_string = "56";
let number = MyInteger::parse(number_string.to_string(), ParseOptions::default()).unwrap();
println!("{}", number.value);
}
StringReader
使用索引封装要解析的字符串,随着解析的进行,索引会增加。它具有以下方法
content() -> &str
:返回整个字符串get_index() -> usize
:返回字符串中的当前索引set_index(index: usize) -> usize
:设置字符串中的当前索引as_str() -> &str
:返回尚未解析的字符串部分(等同于&self.content()[self.get_index()..]
)as_char() -> char
:返回当前字符(等同于&self.content().as_bytes()[self.get_index()]
)is_finished() -> bool
:指示是否已到达字符串的末尾advance(length: usize) -> Option<&str>
:将当前索引向前推进length
,并返回相应的子字符串。如果length
为0
,则返回None
eat_spaces()
:将当前索引向前推进,直到遇到非空白和非注释字符read_string(string: &str) -> Option<&str>
:如果字符串以string
开头,则将当前索引向前推进string
的长度,并返回它,否则返回None
read_regex(pattern: &'static str) -> Option<&str>
:如果字符串以指定的正则表达式模式开头,则将当前索引向前移动到解析字符串的长度,并返回它,否则返回None
peek_regex(pattern: &'static str) -> bool
:指示字符串是否以指定的正则表达式模式开头,而不移动当前索引
如果 parse_item
返回 None
,则它必须确保函数退出时的索引与开始时的索引相同。
许可证
MIT
依赖关系
~3.5–4.5MB
~89K SLoC