#parser-combinator #parser #combinator #iterator #simple #version

gobble

一种基于组合器的字符串解析器,它让代码看起来更像语法

23 个版本 (5 个重大更新)

0.6.3 2020年8月19日
0.6.2 2020年8月14日
0.5.3 2020年6月29日
0.4.4 2020年6月12日
0.1.2 2020年4月10日

#838Rust 模式

Download history 25/week @ 2024-03-11 28/week @ 2024-03-18 17/week @ 2024-03-25 53/week @ 2024-04-01 19/week @ 2024-04-08 21/week @ 2024-04-15 23/week @ 2024-04-22 23/week @ 2024-04-29 22/week @ 2024-05-06 22/week @ 2024-05-13 22/week @ 2024-05-20 14/week @ 2024-05-27 23/week @ 2024-06-03 14/week @ 2024-06-10 17/week @ 2024-06-17 22/week @ 2024-06-24

每月 76 次下载
用于 5 个 crate(4 个直接使用)

MIT 许可证

93KB
2.5K SLoC

吞咽是一个简单的字符串解析器组合系统。

*注意:它运行良好,但目前仍在积极开发中,因此 API 可能会在版本之间发生重大变化。如果 "0.b.c" 中的 'b' 发生变化,将会有破坏性更改。尽管我相信我现在已经接近确定 API

我非常欢迎在 GitHub 上收到反馈*

在 Rust 中创建解析器应该相当简单。例如解析函数调用

use gobble::*;
parser!{
    (Ident->String)
    string((Alpha.one(),(Alpha,NumDigit,'_').istar()))
}

parser!{
    (FSig->(String,Vec<String>))
    (first(Ident,"("),sep_until_ig(Ident,",",")"))
}
let (nm, args) = FSig.parse_s("loadFile1(fname,ref)").unwrap();
assert_eq!(nm, "loadFile1");
assert_eq!(args, vec!["fname", "ref"]);
//Idents can't begin with numbers
assert!(FSig.parse_s("23file(fname,ref)").is_err());

如果你不希望使用宏,你也可以不使用

use gobble::*;
let ident = || string((Alpha.one(),(Alpha,NumDigit,'_').istar()));

let fsig = (first(ident(),"("),sep_until_ig(ident(),",",")"));
 
 let (nm, args) = fsig.parse_s("loadFile1(fname,ref)").unwrap();
 assert_eq!(nm, "loadFile1");
 assert_eq!(args, vec!["fname", "ref"]);
 //identifiers cant start with numbers,
 assert!(fsig.parse_s("23file(fname,ref)").is_err());
 

但是宏保证了零大小类型,这对于组合它们来说很好

为了使用这个库,它依赖于以下内容

pub enum ParseError {
   //...
}

// In the OK Case the value mean
//   LCChars = copy of original, but moved forward,
//   V = The resulting type
//   Option<ParserError> Only "Some" if the parser could have contined with more data
//   --This is useful for tracking what values would have been expected at a certain point
//
pub type ParseRes<'a, V> = Result<(LCChars<'a>, V,Option<ParseError>), ParseError>;

//implements Iterator and can be cloned relatively cheaply
pub struct LCChars<'a>{
   it:std::str::Chars<'a>,
   line:usize,
   col:usize,
}

pub trait Parser<V> {
   // Takes a non-mut pointer to the iterator, so that the caller
   // may try something else if this doesn't work
   // clone it before reading next
   fn parse<'a>(&self,it:&LCChars<'a>)->ParseRes<'a,V>;
   
   //...helper methods
}
pub trait CharBool {
   fn char_bool(&self,c:char)->bool;
   //....helper methods
   //
}

解析器会自动实现以下类型

  • Fn<'a>(&LCChars<'a>)->ParseRes<'a,String>
  • &'static str 如果匹配将返回自身
  • char 如果匹配下一个字符将返回自身
  • 最多 6 个解析器的元组。返回一个元组,其中包含依次匹配的所有解析器。

通常,可以通过组合其他解析器来简单地构建解析器。

use gobble::*;

// map can be used to convert one result to another
// keyval is now a function that returns a parser
let keyval = || (common::Ident,":",common::Quoted).map(|(a,_,c)|(a,c));

//this can also be written as below for better type safety
fn keyval2()->impl Parser<Out=(String,String)>{
   (common::Ident,":",common::Quoted).map(|(a,_,c)|(a,c))
}

// or as a macro KeyVal is now a struct like:
// pub struct KeyVal;
parser!{
   (KeyVal->(String,String))
   (common::Ident,":",common::Quoted).map(|(a,_,c)|(a,c))
}

//parse_s is a helper on Parsers
let (k,v) = keyval().parse_s(r#"car:"mini""#).unwrap();
assert_eq!(k,"car");
assert_eq!(v,"mini");

//this can now be combined with other parsers.
// 'ig_then' combines 2 parsers and drops the result of the first
// 'then_ig' drops the result of the second
// 'sep_until will repeat the first term into a Vec, separated by the second
//    until the final term.
let obj = || "{".ig_then(sep_until_ig(keyval(),",","}"));

let obs = obj().parse_s(r#"{cat:"Tiddles",dog:"Spot"}"#).unwrap();
assert_eq!(obs[0],("cat".to_string(),"Tiddles".to_string()));

CharBool

CharBool 是布尔字符检查的 trait。它会自动实现为

  • Fn(char)->bool
  • char -- 如果输入匹配该字符则返回 true
  • &'static str -- 如果字符串包含输入则返回 true
  • 几个零大小类型 - Alpha、NumDigit、HexDigit、WS、WSL、Any
  • 最多 6 个 CharBool 的元组 -- 如果任何成员成功则返回 true

这意味着您可以将它们组合在元组中 (Alpha,NumDigit,"_").char_bool(c) 如果其中任何一个匹配则返回 true

CharBool 还提供了一些辅助方法,每个方法都返回一个解析器

  • one(self) 匹配并返回恰好 1 个字符
  • plus(self) '+' 至少匹配1次并返回一个字符串
  • min_n(self,n:usize) 至少匹配n次并返回一个字符串
  • star(self) '*' 匹配任意数量的字符并返回一个字符串
  • exact(self,n:usize) '*' 匹配恰好n个字符并返回一个字符串
  • iplus(self) '+' 至少匹配1次并返回一个空元组
  • istar(self) '*' 匹配任意数量的字符并返回一个空元组
  • iexact(self,n:usize) 匹配恰好n个字符并返回一个空元组

一个返回 CharBool 的辅助函数

  • except(self,cb:CharBool) 如果 self 通过,但 cb 不通过则通过
use gobble::*;
let s = |c| c > 'w' || c == 'z';
let xv = s.one().parse_s("xhello").unwrap();
assert_eq!(xv,'x');

let id = (Alpha,"_*").min_n(4).parse_s("sm*shing_game+you").unwrap();
assert_eq!(id,"sm*shing_game");

// not enough matches
assert!((NumDigit,"abc").min_n(4).parse_s("23fflr").is_err());

// any succeeds even with no matches equivilent to min_n(0) but "Zero Size"
assert_eq!((NumDigit,"abc").star().parse_s("23fflr"),Ok("23".to_string()));
assert_eq!((NumDigit,"abc").star().parse_s("fflr"),Ok("".to_string()));

空白字符

空白字符的处理相对直接

use gobble::*;
let my_ws = || " \t".star();
// middle takes three parsers and returns the result of the middle
// this could also be done easily with 'map' or 'then_ig'
let my_s = |p| middle(my_ws(),p,my_ws());

let sp_id = my_s(common::Ident);
let v = sp_id.parse_s("   \t  doggo  ").unwrap();
assert_eq!(v,"doggo");

话虽如此,gobble 已经提供了 WSs_(p)

use gobble::*;
//eoi = end of input
let p = repeat_until_ig(s_("abc".plus()),eoi);
let r = p.parse_s("aaa \tbbb bab").unwrap();
assert_eq!(r,vec!["aaa","bbb","bab"]);

递归结构

一些结构,如 Json 或编程语言,需要能够处理递归。然而,使用我们迄今为止使用的技术,这会导致无限大小的结构。

处理此问题的方法是确保循环中的一个成员不是
构建到结构中。相反,使用 'Fn' 或宏来创建它,这将返回特定情况下的零大小结构。

use gobble::*;
#[derive(Debug,PartialEq)]
enum Expr {
    Val(isize),
    Add(Box<Expr>,Box<Expr>),
    Paren(Box<Expr>),
}

fn expr_l()->impl Parser<Out=Expr>{
    or(
        middle("(",s_(expr),")").map(|e|Expr::Paren(Box::new(e))),
        common::Int.map(|v|Expr::Val(v))
    )
}

// using the full fn def we avoid the recursive structure
fn expr<'a>(it:&LCChars<'a>)->ParseRes<'a,Expr> {
    //note that expr_l has brackets but expr doesnt.
    //expr is a reference to a static function
    let p = (expr_l(),maybe(s_("+").ig_then(expr)))
        .map(|(l,opr)|match opr{
            Some(r)=>Expr::Add(Box::new(l),Box::new(r)),
            None=>l,
        });
    

    p.parse(it)
}

let r = expr.parse_s("45 + (34+3 )").unwrap();

//recursive structures are never fun to write manually
assert_eq!(r,Expr::Add(
                Box::new(Expr::Val(45)),
                Box::new(Expr::Paren(Box::new(Expr::Add(
                    Box::new(Expr::Val(34)),
                    Box::new(Expr::Val(3))
                ))))
            ));

变更日志

v 0.6.3

  • 添加了一个仅导出特质的模块;

v 0.6.2

  • 修复了非ASCII字符的bug;

v 0.6.1

  • 将 'longer' 方法放回 ParseErr,这样 Or 就不会给出疯狂长的错误信息;

v 0.6.0

  • 更新了 Error 以包含 &strs 以包含结果的 Found 部分;此更改在技术上具有破坏性,但我不期望它会破坏任何真实代码。

v 0.5.3

  • 将 Hash 添加到错误类型;
  • 添加了 Exists 方法;
  • 为 CharBool 添加了 not 方法;
  • 添加了 or_ig!() 以 ig 所有内部解析器的结果。

v 0.5.2

  • 为空长度解析结果添加了 catch;
  • StrungErr 现在实现了 PartialEq;

v 0.5.1

  • 为宏添加了自动文档,希望很快会改进这些文档。

v 0.5.0

  • 添加了宏 -- 意外地,有些意外。请参阅文档。
  • 将 skip_star 等交换为 istar 等,用于 CharBool;
  • 添加了 StrungError 和 StrError,可以打印出大量信息;

v 0.4.4

  • 添加了 skip_star(p)
  • 添加了 skip_plus(p)
  • 添加了 skip_exact(p,n)

v 0.4.3

  • 添加了 string<A:Parser>(a:A)->impl Parser<String> 以创建一个读取内部解析器但返回整个匹配字符串的解析器

v 0.4.2

  • 添加 Skip2Star 以跳过不同类型的 2 个解析器

v 0.4.1

  • 为 Error 导出 Hash 和 Eq

v 0.4.0

  • 现在使用 "star" 和 "plus" 而不是 "min_n" 和 "any" 来表示 CharBool 重复
  • 现在要求成功时声明它们是否可以用正确输入继续
  • 现在具有更清晰的错误,包含有关如何找到它们以及它们期望的内容的信息

v 0.3.0: 破坏性变更

* 现在解析器输出是一个与 trait 关联的关联类型 (Out),使用 impl Parser<Out=V> 而不是 impl Parser<V>,并且大多数事情应该可以正常工作

  • read_fs 已移除 - 使用 CharBool.min_n(usize) 代替
  • Esc 已移除 - 有关处理转义字符的方法,请参阅 common::common_str

v 0.2.1

  • 添加 StringRepeat
  • 添加 SkipRepeat
  • 将 LCChars 更改为使用 CharIndices
  • 现在具有索引解析器
  • 向 CharBool 添加 skip 和 skip_min
  • 添加 StrPos 解析器 str_pos

v 0.2.0 -- 主要更新

  • 创建了一个新的 trait 叫做 CharBool
  • 移除了 is_alpha_num
  • 添加了字符读取器,它们使用 CharBool trait 获取所需内容

v 0.1.6

  • 添加了获取结果行和列的 line_col 包装器
  • 添加了 one_char(&str) 解析器以检查下一个字符是否是该集合的成员。

v 0.1.5

  • 添加了 common_float 方法
  • 为 char 和 &'static str 实现了 Parser
  • 使元组作为组合解析器工作

v 0.1.4

  • 添加了关键字以确保关键字末尾没有字母数字字符
  • 修复了错误显示方法,使其更容易阅读。
  • 添加了 'common' 模块和 common_intcommon_bool 解析器

v 0.1.3

  • 添加了用于需要再次向上和向下计数时的 reflect 功能

v 0.1.2

  • 添加了 sep_until(main,sep,close)
  • 添加了 repeat_until(main,close)
  • 修复了 Or Error 以包括两个错误,使其更容易在分支迭代器中找到问题

v 0.1.1

  • 添加了 eoito_end() 函数以确保您有输入的末尾;
  • 添加了 common_str() 以获取最常见的字符串形式

依赖项

~0.4–0.9MB
~19K SLoC