Skip to content
This repository was archived by the owner on Apr 2, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
433 changes: 427 additions & 6 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ sync = ["spin"]
# Enable Pratt parsing combinator
pratt = ["unstable"]

# Utilities for debugging parsers
debug = ["unstable", "nightly", "dep:railroad"]

# Allow the use of unstable features (aka features where the API is not settled)
unstable = []

Expand Down Expand Up @@ -83,6 +86,7 @@ serde = { version = "1.0", default-features = false, optional = true, features =
unicode-ident = "1.0.10"
unicode-segmentation = "1"
bytes = { version = "1", default-features = false, optional = true }
railroad = { version = "0.3.3", optional = true }

[dev-dependencies]
ariadne = "0.5"
Expand Down Expand Up @@ -149,3 +153,7 @@ required-features = ["std"]
[[example]]
name = "mini_ml"
required-features = ["pratt"]

[[example]]
name = "debug"
required-features = ["debug"]
104 changes: 104 additions & 0 deletions examples/debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//! This is a Brainfuck parser and interpreter
//! Run it with the following command:
//! cargo run --example debug

use chumsky::prelude::*;
use std::collections::HashMap;

#[derive(Clone, Debug)]
pub enum Json {
Null,
Bool(bool),
Str(String),
Num(f64),
Array(Vec<Json>),
Object(HashMap<String, Json>),
}

fn json<'a>() -> impl Parser<'a, &'a str, Json> {
recursive(|value| {
let digits = text::digits(10).to_slice();

let frac = just('.').then(digits);

let exp = just('e')
.or(just('E'))
.then(one_of("+-").or_not())
.then(digits);

let number = just('-')
.or_not()
.then(text::int(10))
.then(frac.or_not())
.then(exp.or_not())
.to_slice()
.map(|s: &str| s.parse().unwrap());

let escape = just('\\')
.then(choice((
just('\\'),
just('/'),
just('"'),
just('b').to('\x08'),
just('f').to('\x0C'),
just('n').to('\n'),
just('r').to('\r'),
just('t').to('\t'),
just('u').ignore_then(text::digits(16).exactly(4).to_slice().validate(
|digits, _, emitter| {
char::from_u32(u32::from_str_radix(digits, 16).unwrap()).unwrap_or_else(
|| {
emitter.emit(Default::default());
'\u{FFFD}' // unicode replacement character
},
)
},
)),
)))
.ignored();

let string = none_of("\\\"")
.ignored()
.or(escape)
.repeated()
.to_slice()
.map(ToString::to_string)
.delimited_by(just('"'), just('"'));

let array = value
.clone()
.separated_by(just(',').padded())
.allow_trailing()
.collect()
.padded()
.delimited_by(just('['), just(']'));

let member = string.then_ignore(just(':').padded()).then(value);
let object = member
.clone()
.separated_by(just(',').padded())
.collect()
.padded()
.delimited_by(just('{'), just('}'));

choice((
just("null").to(Json::Null),
just("true").to(Json::Bool(true)),
just("false").to(Json::Bool(false)),
number.map(Json::Num).labelled("number"),
string.map(Json::Str).labelled("string"),
array.map(Json::Array).labelled("array"),
object.map(Json::Object).labelled("object"),
))
.padded()
.labelled("JSON value")
})
}

fn main() {
// Generate an eBNF grammar for the parser
println!("{}", json().debug().to_ebnf());

// Generate a railroad diagram for the parser
println!("{}", json().debug().to_railroad_svg());
}
Loading