chasa, procedural invariant parser combinator
A parser combinator with additional operations on procedures.
A parser combinator is a mechanism that allows you to combine small syntactic elements to define a larger syntax, which can then be parse directly.
```
use chasa::{Parser, EasyParser, prim, prim::{oneof, char}};
// It reads a number of letters (numbers) from 0 to 9,
let num = oneof('0'..='9').many::()
// Interpreted as a number, errors are reported as a message.
.andthen(|str| str.parse::().maperr(prim::Error::Message));
// Multiply by something separated by '' and
let prod = num.sep1(1, |a, b| a * b, char(''));
// Then add the '+' separator to it.
let sum = prod.sep1(0, |a, b| a + b, char('+'));
// Can parse simple addition and multiplication expressions.
asserteq!(sum.parseeasy("1+2*3+4".chars()).ok(), Some(11));
```
Rust doesn't allow you to branch different functions, which prevents you from writing procedural parsers. This hampers the writing of procedural parsers, which can be replaced by a procedural chain for better visibility.
For example, the JSON parser is procedural, but you can write it in procedural form:
```
use chasa::;
use chasa::char::;
[derive(Debug,PartialEq)]
enum JSON {
Object(Vec<(String, JSON)>),
Array(Vec),
String(String),
Number(f64),
True,
False,
Null,
}
fn jsonparser + Clone>() -> impl EasyParser {
any.case(|c, k| match c {
'{' => k
.then(
char('"')
.right(stringchar.manywith(|iter| iter.mapwhile(|x| x).collect()))
.between(whitespace, whitespace)
.bind(|key| char(':').right(jsonparser).maponce(move |value| (key, value)))
.extendsep(vec![], char(',')),
).left(char('}'))
.map(JSON::Object),
'[' => k.then(jsonparser.extendsep(vec![], char(','))).left(char(']')).map(JSON::Array),
'"' => k.then(stringchar.manywith(|iter| iter.mapwhile(|x| x).collect())).map(JSON::String),
'-' => k.then(any).bind(numparser).map(|n| JSON::Number(-n)),
c @ '0'..='9' => k.then(numparser(c)).map(JSON::Number),
't' => k.then(string("rue".chars(), JSON::True)),
'f' => k.then(string("alse".chars(), JSON::False)),
'n' => k.then(string("ull".chars(), JSON::Null)),
c => k.fail(prim::Error::Unexpect(c)),
})
.between(whitespace, whitespace)
}
fn whitespace + Clone>() -> impl EasyParserof("\t\r\n ".chars()).skipmany()
}
fn stringchar + Clone>() -> impl EasyParser> {
let hex = satisfy(|c: &char| matches!(c, '0'..='9' | 'a'..='f' | 'A'..='F'));
any.case(move |c, k| match c {
'\' => k.then(any.case(|c, k| {
match c {
'"' => k.pure(Some('\"')),
'\' => k.pure(Some('\')),
'/' => k.pure(Some('/')),
'b' => k.pure(Some('\x08')),
'f' => k.pure(Some('\x0C')),
'n' => k.pure(Some('\n')),
'r' => k.pure(Some('\r')),
't' => k.pure(Some('\t')),
'u' => k
.then(
hex.manywith(|iter| iter.take(4).collect::())
.andthen(|str| if str.len() < 4 { Err(prim::Error::Unexpect("4 hex digits")) } else { Ok(str) })
.andthen(|str| u32::fromstrradix(&str, 16).maperr(prim::Error::Message))
.andthen(|u| char::fromu32(u).okor(prim::Error::Unexpect("invalid unicode char"))),
)
.map(Some),
c => k.fail(prim::Error::Unexpect(c)),
}
})),
'"' => k.pure(None),
c => k.pure(Some(c)),
})
}
fn numparser + Clone>(c: char) -> impl EasyParser {
let digit = satisfy(|c: &char| ('0'..='9').contains(c));
parseronce(move |k| match c {
'0' => k.then(char('.').ornot().case(|c,k| if c.issome() {
k.then(digit.extend("0.".tostring()))
} else {
k.pure("0".tostring())
})),
c @ '1'..='9' => k.then(digit.extend(c.tostring())).bind(|mut str| char('.').ornot().caseonce(move |c,k|
if c.issome() {
str.push('.');
k.then(digit.extend(str))
}
else {
k.pure(str)
}
)),
c => k.fail(prim::Error::Unexpect(c)),
})
.bindonce(move |mut str| {
oneof("eE".chars()).right(oneof("+-".chars()).ornot()).bindonce(move |pm| {
str.push('e');
str.extend(pm);
digit.extend(str)
})
})
.andthenonce(|str| str.parse::().maperr(prim::Error::Message))
}
asserteq!(
jsonparser.parseeasy("{\"key1\": \"value1\", \"key2\": [ true, \"value3\" ], \"key3\": { \"key4\": 15e1 }}".chars()).ok(),
Some(JSON::Object(vec![
("key1".tostring(), JSON::String("value1".tostring())),
("key2".tostring(), JSON::Array(vec![JSON::True, JSON::String("value3".tostring())])),
("key3".tostring(), JSON::Object(vec![("key4".to_string(), JSON::Number(150.0))]))
]))
);
```