Rust Nom take_until使用解析器而不是模式

k5hmc34c  于 2022-11-24  发布在  其他
关注(0)|答案(1)|浏览(165)

使用最新的(v7)标称板条箱。
尝试构建一个能够从markdown中提取代码块的解析器。在我需要支持的markdown风格中,代码块只有在一行中有三个抑音符/反勾号字符时才结束,除非后面可能有空格。
下面是一个例子,我用单引号(')替换反勾号,使StackOverflow减价编辑变得合理:

'''python
print("""
'''")
// this is all still a code block
'''

显而易见的解决方案是只使用take_until("'''"),然而,这将提前结束take,因为这只是搜索第一次出现的''',这是不准确的。我需要终止条件为tuple((tag(code_end), space0, newline))
下一个明显的解决方案是使用正则表达式作为take_until中的模式......但我更愿意避免这样做。是否有任何预构建的解析器(或在另一个机箱中可用)可以接受所有内容,直到解析器返回Ok

use nom::IResult;
use nom::combinator::opt;
use nom::sequence::{terminated, tuple};
use nom::bytes::complete::{tag, take_until};
use nom::character::complete::{newline, space0, alpha1};

fn code(i: &[u8]) -> IResult<&[u8], &[u8]> {
    let (input, _) = tuple((tag("'''"), opt(alpha1), tag("\n")))(i)?;
    let terminator = tuple((tag("'''"), space0, newline));
    let (input, contents) = terminated(take_until("'''"), terminator)(input)?;
    Ok((input, contents))
}

fn main() {
    let test = &b"'''python
print(\"\"\"
'''\"\"\"
// this is all still a code block
'''
";
    assert!(code(&test[..]).is_ok());
}

上面的Assert将失败。但是,如果删除带有三个(''')单引号的行,它将通过。这是因为terminatortake_all("'''")之间的差异。解决此问题的最佳模式是什么?
谢谢你的帮助。我有一种感觉,我错过了一些明显的东西,或者只是做错了什么。如果有什么不清楚的地方,请告诉我。
为方便起见,下面是Rust Playground中上述示例的链接:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d5459edded1e4258ba3e034658ea4acf

gblwokeq

gblwokeq1#

我认为正确的组合符应该是many_till
应用解析器f,直到解析器g产生结果。
它与anychar结合将为代码块返回Vec<char>
我认为nom中没有anybyte,但是如果你更喜欢得到Vec<u8>,你可以很容易地自己写。
或者,如果你想避免分配,并且想要一个引用原始切片的切片,并且不介意有一点不安全,你可以忽略使用的字符,并从开始和结束指针(playground)开始计算切片:

fn code(i: &[u8]) -> IResult<&[u8], &[u8]> {
    let (input, _) = tuple((tag("'''"), opt(alpha1), tag("\n")))(i)?;
    let terminator = tuple((tag("'''"), space0, newline));
    let start = input;
    let (input, (_, (end, _, _))) = many_till(map(anychar, drop), terminator)(input)?;
    let len = unsafe { end.as_ptr().offset_from(start.as_ptr()) as usize};
    Ok((input, &start[..len]))
}

相关问题