Crate chasa

Crate chasa 

Source
Expand description

§chasa

Parser combinators for the YuLang workspace.

§TL;DR

use chasa::prelude::*;

// Parse "let x" and extract the identifier
let mut input = "let x";
let name = parse_ok_once(&mut input, tag("let").right(ws1).right(any)).unwrap();
assert_eq!(name, 'x');

Key combinators:

  • tag("...") – match an exact string
  • ws1 / ws – match whitespace (one-or-more / zero-or-more)
  • any – match any single character
  • right(q) – parse both, return right result
  • many() – repeat zero or more times
  • sep(s) – parse separated list

Most combinators are available as methods (via ParserOnce / ParserMut / Parser), and the prelude imports those traits so you can write p.right(q) / p.many() / p.sep(...).

§Showcase

This crate supports both combinator style and imperative style via In.

§Example 1: S-expressions (combinator style)

use chasa::prelude::*;

#[derive(Debug, PartialEq, Eq)]
enum SExp {
    Atom(String),
    List(Vec<SExp>),
}

fn sexp(mut i: In<&str>) -> Option<SExp> {
    let atom_char = none_of(SPACE.and("()"));
    let atom = atom_char.many1().map(SExp::Atom);
    let list = sexp
        .sep(ws1)
        .map(SExp::List)
        .between(ws, ws)
        .between(item('('), item(')'));
    i.choice((list, atom))
}

let mut input = "(a (b c) d)";
let out = parse_ok_once(&mut input, sexp).unwrap();
assert_eq!(
    out,
    SExp::List(vec![
        SExp::Atom("a".into()),
        SExp::List(vec![SExp::Atom("b".into()), SExp::Atom("c".into())]),
        SExp::Atom("d".into()),
    ])
);

§Example 2: key = value (imperative style)

use chasa::prelude::*;

#[derive(Debug, PartialEq, Eq)]
enum Value {
    Bool(bool),
    Number(i64),
    Str(String),
}

fn kv(mut i: In<&str>) -> Option<(String, Value)> {
    let ident = one_of(ASCII_ALPHA.and("_")).bind(|h| {
        one_of(ASCII_ALPHANUM.and("_"))
            .many_map(move |it| std::iter::once(h).chain(it).collect::<String>())
    });

    let eq = item('=').between(ws, ws);

    let digit = one_of(ASCII_DIGIT);
    let number = choice((
        item('-').right(digit.many1::<String>()).map_once(|s: String| {
            let n = s.parse::<i64>().unwrap();
            Value::Number(-n)
        }),
        digit.many1::<String>().map_once(|s: String| {
            let n = s.parse::<i64>().unwrap();
            Value::Number(n)
        }),
    ));

    let str_body = none_of("\"\\").many::<String>();
    let string = str_body.between(item('\"'), item('\"')).map(Value::Str);

    let key = i.run(ident)?;
    i.run(eq)?;
    let value = i.choice((
        tag("true").to(Value::Bool(true)),
        tag("false").to(Value::Bool(false)),
        number,
        string,
    ))?;
    Some((key, value))
}

let mut input = "port = 8080";
assert_eq!(
    parse_ok_once(&mut input, kv).unwrap(),
    ("port".into(), Value::Number(8080))
);
let mut input = "name = \"alice\"";
assert_eq!(
    parse_ok_once(&mut input, kv).unwrap(),
    ("name".into(), Value::Str("alice".into()))
);

§Quick API tour

Entry points:

  • parse_once(&mut input, parser) – run a ParserOnce with a fresh Merger
  • parse_ok_once(&mut input, parser) – run a ParserOnce and return Result<T, Error>

Building blocks:

  • Items: any, item(c), one_of("abc"), none_of("xyz")
  • Tags: tag("keyword")
  • Whitespace: ws, ws1
  • Sequencing: then, right, left, between
  • Choice: or, choice
  • Repetition: many, many1, many_map
  • Separated lists: sep, sep1, sep_map, sep_reduce
  • Lookahead: lookahead, not
  • Control: cut, maybe, label

§Where to look next

  • prelude: start here for imports
  • parser: combinators and traits
  • input: input abstractions and streaming inputs
  • parse: helpers like parse_ok_once

Re-exports§

pub use input::inner::Back;
pub use input::inner::Input;
pub use input::inner::Offset;

Modules§

char
char-level convenience parsers and sets.
input
Input abstraction (playground core).
parse
High-level entry points to run parsers.
parser
prelude
A convenience prelude for chasa.