gollum-parser 0.3.0

Parser for the Gollum language
Documentation
use cucumber::{World, gherkin::Step, given, then, when};

#[derive(Debug, Default, World)]
pub struct GollumWorld {
    pub source: String,
    pub parse_result: Option<gollum_parser::Result<Vec<gollum_parser::Item>>>,
    pub lex_tokens: Vec<String>,
}

#[given("a Gollum parser is initialized")]
async fn parser_initialized(_world: &mut GollumWorld) {}

#[given(regex = r"^the following Gollum code:$")]
async fn set_source(world: &mut GollumWorld, #[step] step: &Step) {
    world.source = step
        .docstring
        .as_deref()
        .expect("docstring required")
        .trim()
        .to_string();
}

#[when("I parse the code")]
async fn parse_code(world: &mut GollumWorld) {
    world.parse_result = Some(gollum_parser::parse(&world.source));
}

#[when("I lex the code")]
async fn lex_code(world: &mut GollumWorld) {
    use gollum_parser::lexer::tokenize;
    world.lex_tokens = tokenize(&world.source)
        .into_iter()
        .map(|r| match r {
            Ok(tok) => tok.to_string(),
            Err(()) => "Error".to_string(),
        })
        .collect();
}

#[then(regex = r"^the resulting AST should be:$")]
async fn check_ast(world: &mut GollumWorld, #[step] step: &Step) {
    let items = world
        .parse_result
        .as_ref()
        .expect("parse not yet run")
        .as_ref()
        .expect("parse failed");
    let first = items.first().expect("no items parsed");
    let ron_str = step
        .docstring
        .as_deref()
        .expect("docstring required")
        .trim();
    let expected: gollum_parser::Item = ron::from_str(ron_str)
        .unwrap_or_else(|e| panic!("invalid RON in feature file:\n{e}\n\nRON was:\n{ron_str}"));
    assert_eq!(expected, *first);
}

#[then("parsing should fail with a syntax error")]
async fn check_failure(world: &mut GollumWorld) {
    let result = world.parse_result.as_ref().expect("parse not yet run");
    assert!(result.is_err(), "expected parse failure but got success");
}

#[then(regex = r"^the token stream should be:$")]
async fn check_token_stream(world: &mut GollumWorld, #[step] step: &Step) {
    let expected: Vec<String> = step
        .docstring
        .as_deref()
        .expect("docstring required")
        .split(',')
        .map(|s| s.trim().to_string())
        .filter(|s| !s.is_empty())
        .collect();
    assert_eq!(
        world.lex_tokens,
        expected,
        "\nExpected: {}\nActual:   {}",
        expected.join(", "),
        world.lex_tokens.join(", ")
    );
}

#[tokio::main]
async fn main() {
    // Ugly hack to prevent failure with `cargo nextest` due to lack
    // of support for `nextest` in https://github.com/cucumber-rs/cucumber/issues/370
    if std::env::args().any(|x| x == "--list") {
        std::process::exit(0);
    }

    GollumWorld::cucumber()
        .run_and_exit(concat!(env!("CARGO_MANIFEST_DIR"), "/../../tests/language"))
        .await;
}