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日 |
#838 在 Rust 模式
每月 76 次下载
用于 5 个 crate(4 个直接使用)
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 已经提供了 WS
和 s_(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_int
和common_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
- 添加了
eoi
和to_end()
函数以确保您有输入的末尾; - 添加了
common_str()
以获取最常见的字符串形式
依赖项
~0.4–0.9MB
~19K SLoC