rust Nom分析器无法使用无效输入

3vpjnl9f  于 2023-08-05  发布在  其他
关注(0)|答案(2)|浏览(85)

我的测试parse_line_repeat在最后一行崩溃了(这是无效的),而不是像测试本身那样将其作为余数返回。
我试过用tuple代替pair,用many0代替many1,第一个问题用this blogpost on nom解决,第二个问题在再次阅读documentation on choosing a combinator后解决。
我想做的可能被描述为错误恢复,但通过这个article说,否则,因此我出了什么搜索的想法。
这里是lib.rs

use nom::{
    branch::alt,
    bytes::complete::tag,
    character::complete::space0,
    multi::{many1, many_till},
    sequence::pair,
    IResult,
};

fn parse_token(input: &str) -> IResult<&str, bool> {
    let (remaining, token) = alt((tag("0"), tag("1")))(input)?;
    if token == "1" {
        return Ok((remaining, true));
    } else {
        return Ok((remaining, false));
    }
}

#[allow(dead_code)]
fn parse_line(input: &str) -> IResult<&str, Vec<bool>> {
    let (remaining, (tokens_raw, _)) = pair(
        many1(many_till(space0, parse_token)),
        many_till(space0, tag("\n")),
    )(input)?;

    let mut tokens = Vec::new();

    for (_, token) in tokens_raw {
        let token: bool = token;
        tokens.push(token);
    }

    Ok((remaining, tokens))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_token_test() {
        assert_eq!(parse_token("0"), Ok(("", false)));
        assert_eq!(parse_token("1"), Ok(("", true)));
    }

    #[test]
    fn parse_line_test() {
        assert_eq!(parse_line("1 \n"), Ok(("", vec![true])));
        assert_eq!(parse_line("0 \n"), Ok(("", vec![false])));
        assert_eq!(
            parse_line("1 1 1 1\n"),
            Ok(("", vec![true, true, true, true]))
        );
    }

    #[test]
    fn parse_line_repeat() {
        let rtn = parse_line("1 \n 1\n");
        assert_eq!(rtn, Ok((" 1\n", vec![true])));
        let rtn = parse_line(rtn.unwrap().0);
        assert_eq!(rtn, Ok(("", vec![true])));
        let rtn = parse_line("1\n 1\n\n");
        assert_eq!(rtn, Ok((" 1\n\n", vec![true])));
        let rtn = parse_line(rtn.unwrap().0);
        assert_eq!(rtn, Ok(("\n", vec![true])));
        let rtn = parse_line(rtn.unwrap().0);
        assert_eq!(rtn, Ok(("\n", vec![])));
    }    
}

字符串
这里是Cargo.toml

[package]
name = "minimal"
version = "0.1.0"
edition = "2021"

[lib]
name = "minimal"
path = "src/lib.rs"

[dependencies]
nom = "7.1.3"


错误代码:

$ cargo test
    Finished test [unoptimized + debuginfo] target(s) in 0.94s
     Running unittests minimal/main.rs

running 3 tests
test tests::parse_line_test ... ok
test tests::parse_token_test ... ok
test tests::parse_line_repeat ... FAILED

failures:

---- tests::parse_line_repeat stdout ----
thread 'tests::parse_line_repeat' panicked at 'assertion failed: `(left == right)`
  left: `Err(Error(Error { input: "\n", code: ManyTill }))`,
 right: `Ok(("\n", []))`', minimal/main.rs:67:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    tests::parse_line_repeat

test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--lib`


Playground

dgsult0t

dgsult0t1#

我认为真实的的原因是你的测试/预期行为有缺陷。
换句话说,您的最后一个测试检查parse_line的以下行为:

  • 1 \n 1\n -> 1\n + [true]
  • 1\n -> <empty> + [true]

然后,您检查:

  • 1\n 1\n\n -> 1\n\n + [true]
  • 1\n\n -> \n + [true]

最后:

  • \n -> \n + []

最后一个对我来说没什么意义。如果1\n应该变成<empty>,那么为什么\n不应该也变成<empty>呢?为什么一个使用换行符,而另一个不使用?如果是因为没有1 s的空行不应该被解析器使用,那么为什么它返回错误呢?返回错误是无法使用对象的解析器的预期结果。
所以你可以选择的方法是:

  • 空行(\n不能被您的代码消耗。在这种情况下,你的代码已经是正确的,测试是有缺陷的,应该期待一个错误。
  • 空行(\n可由您的代码使用。在这种情况下,你的代码和测试都有缺陷。代码应该是many0而不是many1,测试应该在最后检查Ok(("", vec![]))

您的原始代码表明您希望空行被消耗。在这种情况下,我会将测试修改为:

#[test]
fn parse_line_repeat() {
    let rtn = parse_line("1 \n 1\n");
    assert_eq!(rtn, Ok((" 1\n", vec![true])));
    let rtn = parse_line(rtn.unwrap().0);
    assert_eq!(rtn, Ok(("", vec![true])));
    let rtn = parse_line("1\n 1\n\n");
    assert_eq!(rtn, Ok((" 1\n\n", vec![true])));
    let rtn = parse_line(rtn.unwrap().0);
    assert_eq!(rtn, Ok(("\n", vec![true])));
    let rtn = parse_line(rtn.unwrap().0);
    assert_eq!(
        rtn,
        Err(nom::Err::Error(nom::error::Error {
            input: "\n",
            code: nom::error::ErrorKind::ManyTill
        }))
    );
}

字符串

zf9nrax1

zf9nrax12#

您可以直接执行此操作,而不是在parse_line中的错误情况下立即使用?失败:

let Ok((remaining, (tokens_raw, _))) = pair(
        many1(many_till(space0, parse_token)),
        many_till(space0, tag("\n")),
    )(input) else {
        return Ok((input, Vec::new()))
    };

字符串
这并不总是做什么写在可以,它不parse_line,它忽略了一行,什么也不做。
当然,在更复杂的场景中,您可能希望更深入地研究错误细节,然后您可能希望使用match而不是简单的let ... else

相关问题