use ariadne::{Color, Label, Report, ReportKind, Source};
use chumsky::prelude::*;
use std::{collections::HashMap, env, fs};
#[derive(Clone, Debug)]
pub enum Json {
Invalid,
Null,
Bool(bool),
Str(String),
Num(f64),
Array(Vec<Json>),
Object(HashMap<String, Json>),
}
fn parser<'a>() -> impl Parser<'a, &'a str, Json, extra::Err<Rich<'a, char>>> {
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())
.boxed();
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, e, emitter| {
char::from_u32(u32::from_str_radix(digits, 16).unwrap()).unwrap_or_else(
|| {
emitter.emit(Rich::custom(e.span(), "invalid unicode character"));
'\u{FFFD}' },
)
},
)),
)))
.ignored()
.boxed();
let string = none_of("\\\"")
.ignored()
.or(escape)
.repeated()
.to_slice()
.map(ToString::to_string)
.delimited_by(just('"'), just('"'))
.boxed();
let array = value
.clone()
.separated_by(just(',').padded().recover_with(skip_then_retry_until(
any().ignored(),
one_of(",]").ignored(),
)))
.allow_trailing()
.collect()
.padded()
.delimited_by(
just('['),
just(']')
.ignored()
.recover_with(via_parser(end()))
.recover_with(skip_then_retry_until(any().ignored(), end())),
)
.boxed();
let member = string.clone().then_ignore(just(':').padded()).then(value);
let object = member
.clone()
.separated_by(just(',').padded().recover_with(skip_then_retry_until(
any().ignored(),
one_of(",}").ignored(),
)))
.collect()
.padded()
.delimited_by(
just('{'),
just('}')
.ignored()
.recover_with(via_parser(end()))
.recover_with(skip_then_retry_until(any().ignored(), end())),
)
.boxed();
choice((
just("null").to(Json::Null),
just("true").to(Json::Bool(true)),
just("false").to(Json::Bool(false)),
number.map(Json::Num),
string.map(Json::Str),
array.map(Json::Array),
object.map(Json::Object),
))
.recover_with(via_parser(nested_delimiters(
'{',
'}',
[('[', ']')],
|_| Json::Invalid,
)))
.recover_with(via_parser(nested_delimiters(
'[',
']',
[('{', '}')],
|_| Json::Invalid,
)))
.recover_with(skip_then_retry_until(
any().ignored(),
one_of(",]}").ignored(),
))
.padded()
})
}
fn main() {
let src = fs::read_to_string(env::args().nth(1).expect("Expected file argument"))
.expect("Failed to read file");
let (json, errs) = parser().parse(src.trim()).into_output_errors();
println!("{json:#?}");
errs.into_iter().for_each(|e| {
Report::build(ReportKind::Error, ((), e.span().into_range()))
.with_config(ariadne::Config::new().with_index_type(ariadne::IndexType::Byte))
.with_message(e.to_string())
.with_label(
Label::new(((), e.span().into_range()))
.with_message(e.reason().to_string())
.with_color(Color::Red),
)
.finish()
.print(Source::from(&src))
.unwrap()
});
}