Chomp中的序列组合器是什么?

时间:2015-12-05 06:38:50

标签: parsing rust

我正在尝试解析仅包含单个非嵌套对象的JSON子集,该对象仅包含可能包含转义序列的字符串值。 E.g。

{
  "A KEY": "SOME VALUE",
  "Another key": "Escape sequences \n \r \\ \/ \f \t \u263A"
}

在Rust中使用Chomp解析器组合器。我解析了这个结构而忽略了转义序列但是我在解决如何处理转义序列方面遇到了麻烦。查看使用组合器的其他引用字符串解析器,例如:

它们每个都使用序列组合子,Chomp中的等价物是什么?

2 个答案:

答案 0 :(得分:4)

Chomp基于Attoparsec和Parsec,因此对于解析转义字符串,我会使用scan解析器获取"个字符之间的切片,同时保留任何转义的"个字符。< / p>

序列组合器只是ParseResult::bind方法,用于链接"字符与转义字符串本身的匹配,以便它能够解析"foo\\"bar"而不仅仅是foo\\"bar。当您使用parse!宏时,您可以免费获得此内容,每个;都会隐式转换为bind调用以将解析器链接在一起。

链接的解析器使用manyor组合器,并为结果字符分配矢量。 Paka似乎没有对结果数组进行任何转换,PHP正在使用带回调的正则表达式来转换字符串。

这是从Attoparsec's Aeson benchmark翻译的代码,用于解析JSON字符串,而不会转义任何转义字符。

#[macro_use]
extern crate chomp;

use chomp::*;
use chomp::buffer::IntoStream;
use chomp::buffer::Stream;

pub fn json_string(i: Input<u8>) -> U8Result<&[u8]> {
    parse!{i;
                          token(b'"');
        let escaped_str = scan(false, |s, c| if s { Some(false) }
                                             else if c == b'"' { None }
                                             else { Some(c == b'\\') });
                          token(b'"');

        ret escaped_str
    }
}

#[test]
fn test_it() {
    let r = "\"foo\\\"bar\\tbaz\"".as_bytes().into_stream().parse(json_string);

    assert_eq!(r, Ok(&b"foo\\\"bar\\tbaz"[..]));
}

上面的解析器不等效,它产生从源缓冲区/切片借来的u8切片。如果您想拥有Vec个数据,最好使用[T]::to_vecString::from_utf8,而不是使用manyor构建解析器,因为它不会与scan一样快,结果相同。

如果要解析UTF-8和转义序列,可以过滤生成的切片,然后在String::from_utf8上调用Vec(Rust字符串为UTF-8,使用包含无效的字符串UTF-8可能导致未定义的行为)。如果性能是一个问题,那么你最有可能将它构建到解析器中。

答案 1 :(得分:1)

documentation州(强调我的):

  

使用解析器几乎完全使用parse!宏完成,这使我们可以做三件不同的事情:

     
      
  • 对剩余输入进行序列解析
  •   
  • 将中间结果存储到数据类型
  •   
  • 在结尾处返回一个数据类型,这可能是对中间结果进行任意计算的结果。
  •   

然后它提供了解析两个数字序列后跟一个常量字符串的示例:

fn f(i: Input<u8>) -> U8Result<(u8, u8, u8)> {
    parse!{i;
        let a = digit();
        let b = digit();
                string(b"missiles");
        ret (a, b, a + b)
    }
}

fn digit(i: Input<u8>) -> U8Result<u8> {
    satisfy(i, |c| b'0' <= c && c <= b'9').map(|c| c - b'0')
}

还记录了ParseResult::bindParseResult::then,以便通过第二个动作顺序撰写结果。

因为我总是对解析感兴趣,所以我继续玩了一下,看看它看起来如何。我对嵌套or调用会发生的深度缩进感到不满意,但可能还有更好的事情可做。这只是一种可能的解决方案:

#[macro_use]
extern crate chomp;

use chomp::*;
use chomp::ascii::is_alpha;
use chomp::buffer::{Source, Stream, ParseError};

use std::str;
use std::iter::FromIterator;

#[derive(Debug)]
pub enum StringPart<'a> {
    String(&'a [u8]),
    Newline,
    Slash,
}

impl<'a> StringPart<'a> {
    fn from_bytes(s: &[u8]) -> StringPart {
        match s {
            br#"\\"# => StringPart::Slash,
            br#"\n"# => StringPart::Newline,
            s => StringPart::String(s),
        }
    }
}

impl<'a> FromIterator<StringPart<'a>> for String {
    fn from_iter<I>(iterator: I) -> Self
        where I: IntoIterator<Item = StringPart<'a>>
    {
        let mut s = String::new();
        for part in iterator {
            match part {
                StringPart::String(p) => s.push_str(str::from_utf8(p).unwrap()),
                StringPart::Newline => s.push('\n'),
                StringPart::Slash => s.push('\\'),
            }
        }
        s
    }
}

fn json_string_part(i: Input<u8>) -> U8Result<StringPart> {
    or(i,
       |i| parse!{i; take_while1(is_alpha)},
       |i| or(i,
              |i| parse!{i; string(br"\\")},
              |i| parse!{i; string(br"\n")}),
    ).map(StringPart::from_bytes)
}

fn json_string(i: Input<u8>) -> U8Result<String> {
    many1(i, json_string_part)
}

fn main() {
    let input = br#"\\stuff\n"#;

    let mut i = Source::new(input as &[u8]);

    println!("Input has {} bytes", input.len());

    loop {
        match i.parse(json_string) {
            Ok(x)                       => {
                println!("Result has {} bytes", x.len());
                println!("{:?}", x);
            },
            Err(ParseError::Retry)      => {}, // Needed to refill buffer when necessary
            Err(ParseError::EndOfInput) => break,
            Err(e)                      => { panic!("{:?}", e); }
        }
    }
}