17次发布
0.2.0 | 2023年7月22日 |
---|---|
0.1.15 | 2023年7月16日 |
0.1.14 | 2023年6月4日 |
0.1.12 | 2022年11月13日 |
0.1.6 | 2022年9月28日 |
#109 在 解析工具
30 每月下载量
用于 2 个 crate(通过 forester-rs)
85KB
779 行
Parsit
描述
此库提供一个非常简单且轻量级的解析器(递归下降 ll(1)),用于组合和表达文法。
该库使用 Logos 作为词法分析器和分词器。
前提条件
此库的主要动机是
- 轻量级:非常小,无需深入研究
- 透明度:仅有3个结构体和一些方法
- 速度:良好的速度(感谢 Logos)
实现步骤
使用 Logos 创建一组标记
将 logos 添加到依赖项
logos = "*"
use logos::Logos;
#[derive(Logos, Debug, PartialEq)]
enum Token {
// Tokens can be literal strings, of any length.
#[token("fast")]
Fast,
#[token(".")]
Period,
// Or regular expressions.
#[regex("[a-zA-Z]+")]
Text,
// Logos requires one token variant to handle errors,
// it can be named anything you wish.
#[error]
// We can also use this variant to define whitespace,
// or any other matches we wish to skip.
#[regex(r"[ \t\n\f]+", logos::skip)]
Error,
}
创建一个能够解析给定标记集的解析器
该库提供包含一组标记和辅助方法的 Parsit<'a,T>
实例
struct Parser<'a> {
inner: Parsit<'a, Token<'a>>,
}
使用 Parsit
实例和辅助方法实现解析函数
辅助工具
- 宏
token!
,简化单个标记的比较和匹配 - 来自
Step
的方法then
、then_zip
等 - 来自
Parsit
的方法one_or_more
、zero_or_more
将结果转换为 Result<Structure, ParserError<'a>>
fn text(&self, pos: usize) -> Result<Vec<Sentence<'a>>, ParseError<'a>> {
self.inner.zero_or_more(pos, |p| self.sentence(p)).into()
}
完整示例
use crate::parser::Parsit;
use crate::token;
use crate::step::Step;
use crate::parser::EmptyToken;
use crate::error::ParseError;
use logos::Logos;
#[derive(Logos, Debug, Copy, Clone, PartialEq)]
pub enum Token<'a> {
#[regex(r"[a-zA-Z-]+")]
Word(&'a str),
#[token(",")]
Comma,
#[token(".")]
Dot,
#[token("!")]
Bang,
#[token("?")]
Question,
#[regex(r"[ \t\r\n\u000C\f]+", logos::skip)]
Whitespace,
#[error]
Error,
}
#[derive(Debug, Copy, Clone, PartialEq)]
enum Item<'a> {
Word(&'a str),
Comma,
}
#[derive(Debug, Clone, PartialEq)]
enum Sentence<'a> {
Sentence(Vec<Item<'a>>),
Question(Vec<Item<'a>>),
Exclamation(Vec<Item<'a>>),
}
struct Parser<'a> {
inner: Parsit<'a, Token<'a>>,
}
impl<'a> Parser<'a> {
fn new(text: &'a str) -> Parser<'a> {
let delegate: Parsit<Token> = Parsit::new(text).unwrap();
Parser { inner: delegate }
}
fn sentence(&self, pos: usize) -> Step<'a, Sentence<'a>> {
let items = |p| self.inner.one_or_more(p, |p| self.word(p));
let sentence = |p| items(p)
.then_zip(|p| token!(self.inner.token(p) => Token::Dot))
.take_left()
.map(Sentence::Sentence);
let exclamation = |p| items(p)
.then_zip(|p| token!(self.inner.token(p) => Token::Bang))
.take_left()
.map(Sentence::Exclamation);
let question = |p| items(p)
.then_zip(|p| token!(self.inner.token(p) => Token::Question))
.take_left()
.map(Sentence::Question);
sentence(pos)
.or_from(pos)
.or(exclamation)
.or(question)
.into()
}
fn word(&self, pos: usize) -> Step<'a, Item<'a>> {
token!(self.inner.token(pos) =>
Token::Word(v) => Item::Word(v),
Token::Comma => Item::Comma
)
}
fn text(&self, pos: usize) -> Result<Vec<Sentence<'a>>, ParseError<'a>> {
self.inner.zero_or_more(pos, |p| self.sentence(p)).into()
}
}
#[test]
fn test() {
let parser = Parser::new(r#"
I have a strange addiction,
It often sets off sparks!
I really cannot seem to stop,
Using exclamation marks!
Anyone heard of the interrobang?
The poem is for kids.
"#);
let result = parser.text(0).unwrap();
println!("{:?}", result);
}
基本辅助方法
在解析器上
token
- 提供获取当前标记的方法one_or_more
- 提供一个或多个语义zero_or_more
- 提供零个或多个语义validate_eof
- 确保解析器到达输入的末尾
宏
token!
- 解析当前标记。通常,它用于以下代码:token!(p.token(pos) => T::Bang => "!")
wrap!
- 实现类似“左值 右”的简单语法模式,例如:[1,2,3]
或(a,b)
- 可以处理默认值,如:
wrap!(0 => left; value or default; right)
- 可以处理选项值,如:
wrap!(0 => left; value ?; right)
- 可以处理默认值,如:
seq!
- 实现类似“el sep el ...”的简单序列模式,例如:1,2,3
- 可以在末尾有逗号,表示分隔符可以在序列末尾,如:
1,2,3 (,)?
- 可以在末尾有逗号,表示分隔符可以在序列末尾,如:
步骤
交替
or
- 在一个标记的范围内给出一个替代方案or_from
- 给出一个回溯选项
组合
then
- 给出一个省略当前规则的下一个规则的基本组合then_zip
- 将当前结果和下一个结果组合成一对then_skip
- 解析下一个标记,但丢弃结果,只保留当前标记then_or_none
- 将下一个选项与当前选项组合,或者在没有其他选项的情况下返回none
收集
take_left
- 从一对中删除右值take_right
- 从一对中删除左值merge
- 将值合并到列表中to_map
- 将一对列表转换为映射
转换
or_val
- 如果值未提供,则用默认值替换值or_none
- 如果值未提供,则用none替换值
处理值
ok
- 将值转换为选项error
- 将错误转换为选项map
- 转换值combine
- 将值与给定步骤中的另一个值组合validate
- 验证给定的值,如果验证失败则转换为错误
打印
print
- 打印步骤print_with
- 打印带有指定前缀的步骤print_as
- 打印经过值转换的步骤print_with_as
- 打印带有值转换和指定前缀的步骤parsit.env
- 从源文本中打印位置和env(半径为3个标记)
测试
词法分析器
要测试词法分析器,有来自 crate::parsit::test::lexer_test::*
的服务方法
use logos::Logos;
use crate::parsit::test::lexer_test::*;
#[derive(Logos, Debug, PartialEq)]
pub enum T<'a> {
#[regex(r"[a-zA-Z-]+")]
Word(&'a str),
#[token(",")]
Comma,
#[token(".")]
Dot,
#[token("!")]
Bang,
#[token("?")]
Question,
#[regex(r"[ \t\r\n]+", logos::skip)]
Whitespace,
#[error]
Error,
}
#[test]
fn test() {
expect::<T>("abc, bcs!", vec![T::Word("abc"), T::Comma, T::Word("bcs"), T::Bang]);
expect_succeed::<T>("abc, bcs!");
expect_failed::<T>("abc, bcs >> !");
expect_failed_with::<T, _>("abc, bcs > !", |e| e.is_bad_token_on(">"));
}
解析器
要测试解析器,有来自 crate::parsit::test::parser_test::*
的服务方法
- expect : 预期解析给定的值
- expect_or_env : 预期解析给定的值,否则将打印env(
parsit.env
) - expect_pos : 预期解析并获取给定位置的光标
- expect_pos_or_env : 预期解析并获取给定位置的光标,否则将打印env(
parsit.env
) - fail : 应该解析失败
- fail_on : 在给定位置解析失败
use logos::Logos;
use crate::parsit::test::parser_test::fail;
use crate::parsit::test::parser_test::parsit;
use crate::parsit::token;
use crate::parsit::parser::Parsit;
use crate::parsit::step::Step;
#[derive(Logos, Debug, PartialEq)]
pub enum T<'a> {
#[regex(r"[a-zA-Z-]+")]
Word(&'a str),
#[token(",")]
Comma,
#[token(".")]
Dot,
#[token("!")]
Bang,
#[token("?")]
Question,
#[regex(r"[ \t\r\n]+", logos::skip)]
Whitespace,
#[error]
Error,
}
#[test]
fn test_expect() {
let p = parsit("abc!");
let bang = |pos: usize| token!(p.token(pos) => T::Bang => "!");
let word = |pos: usize| token!(p.token(pos) => T::Word(v) => *v);
let step =
word(0)
.then_or_val_zip(bang, "")
.map(|(a, b)| format!("{}{}", a, b));
expect(step, "abc!".to_string());
}
#[test]
fn test_expect_or_env() {
let p = parsit("abc!");
let bang = |pos: usize| token!(p.token(pos) => T::Bang => "!");
let word = |pos: usize| token!(p.token(pos) => T::Word(v) => *v);
let step =
word(0)
.then_or_val_zip(bang, "")
.map(|(a, b)| format!("{}{}", a, b));
expect_or_env(p,step, "abc!".to_string());
}
#[test]
fn test_pos() {
let p = parsit("abc!");
let bang = |pos: usize| token!(p.token(pos) => T::Bang => "!");
let word = |pos: usize| token!(p.token(pos) => T::Word(v) => v);
let step = word(0).then_or_val_zip(bang, "");
expect_pos(step, 2); // the next position to parse
}
#[test]
fn test_pos_or_env() {
let p = parsit("abc!");
let bang = |pos: usize| token!(p.token(pos) => T::Bang => "!");
let word = |pos: usize| token!(p.token(pos) => T::Word(v) => v);
let step = word(0).then_or_val_zip(bang, "");
expect_pos_or_env(p,step, 2); // the next position to parse
}
#[test]
fn test_fail() {
let p = parsit("abc?!");
let bang = |pos: usize| token!(p.token(pos) => T::Bang => "!");
let word = |pos: usize| token!(p.token(pos) => T::Word(v) => v);
let step = word(0).then_zip(bang);
fail(step);
}
#[test]
fn test_fail_on() {
let p = parsit("abc?!");
let bang = |pos: usize| token!(p.token(pos) => T::Bang => "!");
let word = |pos: usize| token!(p.token(pos) => T::Word(v) => v);
let step = word(0).then_zip(bang);
fail_on(step, 1);
}
依赖
~4MB
~72K SLoC