chronlang-parser 0.1.7

A parser for Chronlang, a computer language for crafting constructed languages.
Documentation
use chumsky::{prelude::*, text::{ident, whitespace}};
use crate::parser::common::*;
use crate::ast::{
  Stmt,
  PhonemeDef,
};

pub fn parser() -> impl Parser<char, Stmt, Error = Simple<char>> {
  let label = class()
    .map_with_span(|c, span| (span, c.to_string()));

  let start = just("class")
    .padded();

  let encodes = just("encodes")
    .padded()
    .ignore_then(
      ident()
        .map_with_span(|id, span| (span, id))
        .separated_by(whitespace())
        .allow_leading()
        .allow_trailing()
        .delimited_by(just("("), just(")"))
    );
  
  let annotates = just("annotates")
    .padded()
    .ignore_then(
      ident()
        .map_with_span(|id, span| (span, id))
        .separated_by(whitespace())
        .allow_leading()
        .allow_trailing()
        .delimited_by(just("("), just(")"))
    )
    .or_not()
    .map(|a| match a {
      Some(v) => v,
      None => vec![],
    });

  let phoneme_definition = word_chars()
    .map_with_span(|phoneme, span| (span, phoneme))
    .then_ignore(just("=").padded())
    .then(
      ident()
        .map_with_span(|traits, span| (span, traits))
        .separated_by(inline_whitespace())
        .allow_leading()
        .allow_trailing()
    )
    .map(|(label, traits)| PhonemeDef { label, traits });
  
  let body = phoneme_definition
  .map_with_span(|ph, span| (span, ph))
    .separated_by(just(",").padded())
    .allow_trailing()
    .at_least(1)
    .then_ignore(whitespace())
    .delimited_by(just("{").padded(), just("}"));

  start
    .ignore_then(label)
    .then(encodes)
    .then(annotates)
    .then(body)
    .map(|(((label, encodes), annotates), phonemes)| Stmt::Class { label, encodes, annotates, phonemes })
}

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

  #[test]
  fn it_parses_a_full_class_definition() {
    assert_eq!(
      parser().parse(
        "class C encodes (place manner) {
          p = bilabial plosive,
          t = alveolar plosive,
          k = velar plosive,
          t͡s = alveolar affricate,
        }"
      ),
      Ok(Stmt::Class {
        label: (6..7, "C".into()),
        encodes: vec![(17..22, "place".into()), (23..29, "manner".into())],
        annotates: vec![],
        phonemes: vec![
          (43..63, PhonemeDef { label: (43..44, "p".into()), traits: vec![(47..55, "bilabial".into()), (56..63, "plosive".into())] }),
          (75..95, PhonemeDef { label: (75..76, "t".into()), traits: vec![(79..87, "alveolar".into()), (88..95, "plosive".into())] }),
          (107..124, PhonemeDef { label: (107..108, "k".into()), traits: vec![(111..116, "velar".into()), (117..124, "plosive".into())] }),
          (136..160, PhonemeDef { label: (136..139, "t͡s".into()), traits: vec![(142..150, "alveolar".into()), (151..160, "affricate".into())] }),
        ]
      })
    )
  }
}