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, Time, Spanned};

use super::common::integer;

fn time() -> impl Parser<char, Spanned<Time>, Error = Simple<char>>  {
  integer()
    .then(
      just("-")
        .padded()
        .ignore_then(integer())
        .or_not()
    )
    .map_with_span(|(from, to), span| (span, match to {
      Some(to) => Time::Range(from, to),
      None => Time::Instant(from),
    }))
}

fn language() -> impl Parser<char, Spanned<String>, Error = Simple<char>> {
  ident().map_with_span(|lang, span| (span, lang))
}

pub fn parser() -> impl Parser<char, Stmt, Error = Simple<char>> {
  let start = just("@")
    .padded();

  let time_only = start
    .ignore_then(time())
    .map(|t| Stmt::Milestone { time: Some(t), language: None });

  let language_only = start
    .ignore_then(language())
    .map(|l| Stmt::Milestone { time: None, language: Some(l) });

  let both = start
    .ignore_then(time())
    .then_ignore(just(",").padded())
    .then(language())
    .map(|(t, l)| Stmt::Milestone { time: Some(t), language: Some(l) });

  choice([
    both.boxed(),
    time_only.boxed(),
    language_only.boxed(),
  ])
  
}

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

  use crate::ast::{
    Stmt,
    Time,
  };

  #[test]
  fn it_parses_an_instant_milestone() {
    let src = "@ 42";
    let res = parser().parse(src.to_string());
    assert_eq!(
      res,
      Ok(
        Stmt::Milestone {
          time: Some((2..4, Time::Instant(42))),
          language: None
        }
      )
    )
  }

  #[test]
  fn it_parses_a_range_milestone() {
    let src = "@ 0-100";
    let res = parser().parse(src.to_string());
    assert_eq!(
      res,
      Ok(
        Stmt::Milestone {
          time: Some((2..7, Time::Range(0, 100))),
          language: None
        }
      )
    )
  }

  #[test]
  fn it_parses_a_milestone_with_a_language() {
    let src = "@ 42, TokiPona";
    let res = parser().parse(src.to_string());
    assert_eq!(
      res,
      Ok(
        Stmt::Milestone {
          time: Some((2..4, Time::Instant(42))),
          language: Some((6..14, "TokiPona".into())),
        }
      )
    )
  }

  #[test]
  fn it_parses_a_milestone_with_only_a_language() {
    let src = "@ TokiPona";
    let res = parser().parse(src.to_string());
    assert_eq!(
      res,
      Ok(
        Stmt::Milestone {
          time: None,
          language: Some((2..10, "TokiPona".into())),
        }
      )
    )
  }

  #[test]
  fn it_does_not_parse_a_milestone_without_a_time_or_a_language() {
    let src = "@ ";
    let res = parser().parse(src.to_string());
    assert!(res.is_err())
  }
}