chronlang-parser 0.1.7

A parser for Chronlang, a computer language for crafting constructed languages.
Documentation
use chumsky::{prelude::*, text::ident};

use crate::ast::Stmt;

pub fn parser() -> impl Parser<char, Stmt, Error = Simple<char>> {

  let name = just("@")
    .then(ident::<char, Simple<char>>())
    .map(|(at, id)| at.to_owned() + &id);

  let segment = name
    .or(ident())
    .or(just(".").map(|s| s.to_string()))
    .or(just("..").map(|s| s.to_string()));

  let path = just("/")
    .or_not()
    .then(
      segment
        .map_with_span(|seg, span| (span, seg))
        .separated_by(just("/"))
        .at_least(1)
    )
    .map(|(slash, segs)| (slash.is_some(), segs));

  let names = ident()
    .map_with_span(|name, span| (span, name))
    .separated_by(just(",").padded())
    .allow_trailing()
    .delimited_by(just("(").padded(), just(")").padded())
    .or(just("*").map_with_span(|star, span| vec![(span, star.to_string())]));

  just("import")
    .padded()
    .ignore_then(names)
    .then_ignore(just("from").padded())
    .then(path)
    .map(|(names, (absolute, path))| Stmt::Import { path, absolute, names })
}

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

  #[test]
  fn it_parses_a_relative_star_import() {
    let src = "import * from ./my/phonology";
    assert_eq!(
      parser().parse(src.to_string()),
      Ok(Stmt::Import {
        path: vec![
          (14..15, ".".into()),
          (16..18, "my".into()),
          (19..28, "phonology".into()),
        ],
        absolute: false,
        names: vec![(7..8, "*".into())]
      })
    )
  }

  #[test]
  fn it_parses_an_absolute_star_import() {
    let src = "import * from /my/phonology";
    assert_eq!(
      parser().parse(src.to_string()),
      Ok(Stmt::Import {
        path: vec![
          (15..17, "my".into()),
          (18..27, "phonology".into()),
        ],
        absolute: true,
        names: vec![(7..8, "*".into())]
      })
    )
  }

  #[test]
  fn it_parses_an_external_import() {
    let src = "import * from @core/ipa";
    assert_eq!(
      parser().parse(src.to_string()),
      Ok(Stmt::Import {
        path: vec![
          (14..19, "@core".into()),
          (20..23, "ipa".into()),
        ],
        absolute: false,
        names: vec![(7..8, "*".into())]
      })
    )
  }

  #[test]
  fn it_parses_named_imports() {
    let src = "import (C, V) from @core/ipa";
    assert_eq!(
      parser().parse(src.to_string()),
      Ok(Stmt::Import {
        path: vec![
          (19..24, "@core".into()),
          (25..28, "ipa".into()),
        ],
        absolute: false,
        names: vec![(8..9, "C".into()), (11..12, "V".into())]
      })
    )
  }

  #[test]
  fn it_parses_multiline_imports() {
    let src = "import (
      Place,
      Manner,
    ) from @core/ipa";
    assert_eq!(
      parser().parse(src.to_string()),
      Ok(Stmt::Import {
        path: vec![
          (47..52, "@core".into()),
          (53..56, "ipa".into()),
        ],
        absolute: false,
        names: vec![(15..20, "Place".into()), (28..34, "Manner".into())]
      })
    )
  }
}