dust/src/lib.rs

227 lines
7.1 KiB
Rust
Raw Normal View History

2024-02-23 13:23:35 +00:00
use std::{
collections::BTreeMap,
fmt::{self, Display, Formatter},
ops::Range,
};
2024-02-23 12:40:01 +00:00
use chumsky::{prelude::*, Parser};
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
Boolean(bool),
2024-02-23 13:23:35 +00:00
Float(f64),
2024-02-23 12:40:01 +00:00
Integer(i64),
2024-02-23 13:23:35 +00:00
List(Vec<Value>),
Map(BTreeMap<Identifier, Value>),
Range(Range<i64>),
2024-02-23 12:40:01 +00:00
String(String),
}
2024-02-23 13:23:35 +00:00
#[derive(Clone, Debug, PartialEq)]
pub struct Identifier(String);
2024-02-23 12:40:01 +00:00
impl Display for Value {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Value::Boolean(boolean) => write!(f, "{boolean}"),
2024-02-23 13:23:35 +00:00
Value::Float(float) => write!(f, "{float}"),
2024-02-23 12:40:01 +00:00
Value::Integer(integer) => write!(f, "{integer}"),
2024-02-23 13:23:35 +00:00
Value::List(_list) => todo!(),
Value::Map(_map) => todo!(),
Value::Range(range) => write!(f, "{}..{}", range.start, range.end),
2024-02-23 12:40:01 +00:00
Value::String(string) => write!(f, "{string}"),
}
}
2023-09-28 19:58:01 +00:00
}
2024-02-23 12:40:01 +00:00
pub fn parser() -> impl Parser<char, Value, Error = Simple<char>> {
let boolean = just("true")
.or(just("false"))
.map(|s: &str| Value::Boolean(s.parse().unwrap()));
2024-02-23 13:23:35 +00:00
let float_numeric = just('-')
.or_not()
.then(text::int(10))
.then(just('.').then(text::digits(10)))
.map(|((negative, before), (_, after))| {
let combined = before + "." + &after;
if negative.is_some() {
Value::Float(-combined.parse::<f64>().unwrap())
} else {
Value::Float(combined.parse().unwrap())
}
});
let float_other = choice((just("Infinity"), just("-Infinity"), just("NaN")))
.map(|text| Value::Float(text.parse().unwrap()));
let float = choice((float_numeric, float_other));
2024-02-23 12:40:01 +00:00
let integer = just('-')
.or_not()
.then(text::int(10).padded())
.map(|(c, s)| {
if let Some(c) = c {
c.to_string() + &s
} else {
s
}
})
.map(|s: String| Value::Integer(s.parse().unwrap()));
2024-02-23 13:23:35 +00:00
let delimited_string = |delimiter| {
2024-02-23 12:40:01 +00:00
just(delimiter)
.ignore_then(none_of(delimiter).repeated())
.then_ignore(just(delimiter))
.map(|chars| Value::String(chars.into_iter().collect()))
};
let string = choice((
delimited_string('\''),
delimited_string('"'),
delimited_string('`'),
));
2024-02-23 13:23:35 +00:00
boolean.or(float).or(integer).or(string).then_ignore(end())
2023-09-28 19:58:01 +00:00
}
#[cfg(test)]
mod tests {
2024-02-23 12:40:01 +00:00
use super::*;
#[test]
fn parse_true() {
2024-02-23 13:23:35 +00:00
assert_eq!(parser().parse("true"), Ok(Value::Boolean(true)));
2024-02-23 12:40:01 +00:00
}
2023-09-28 19:58:01 +00:00
#[test]
2024-02-23 12:40:01 +00:00
fn parse_false() {
2024-02-23 13:23:35 +00:00
assert_eq!(parser().parse("false"), Ok(Value::Boolean(false)));
}
#[test]
fn parse_positive_float() {
assert_eq!(parser().parse("0.0"), Ok(Value::Float(0.0)));
assert_eq!(parser().parse("42.0"), Ok(Value::Float(42.0)));
assert_eq!(
parser().parse(f64::MAX.to_string() + ".0"),
Ok(Value::Float(f64::MAX))
);
assert_eq!(
parser().parse(f64::MIN_POSITIVE.to_string()),
Ok(Value::Float(f64::MIN_POSITIVE))
);
}
#[test]
fn parse_negative_float() {
assert_eq!(parser().parse("-0.0"), Ok(Value::Float(-0.0)));
assert_eq!(parser().parse("-42.0"), Ok(Value::Float(-42.0)));
assert_eq!(
parser().parse(f64::MIN.to_string() + ".0"),
Ok(Value::Float(f64::MIN))
);
assert_eq!(
parser().parse("-".to_string() + &f64::MIN_POSITIVE.to_string()),
Ok(Value::Float(-f64::MIN_POSITIVE))
);
}
#[test]
fn parse_other_float() {
assert_eq!(parser().parse("Infinity"), Ok(Value::Float(f64::INFINITY)));
assert_eq!(
parser().parse("-Infinity"),
Ok(Value::Float(f64::NEG_INFINITY))
);
if let Value::Float(float) = parser().parse("NaN").unwrap() {
assert!(float.is_nan())
} else {
panic!("Expected a float.")
}
2024-02-23 12:40:01 +00:00
}
#[test]
fn parse_positive_integer() {
let parser = parser();
assert_eq!(parser.parse("0"), Ok(Value::Integer(0)));
assert_eq!(parser.parse("1"), Ok(Value::Integer(1)));
assert_eq!(parser.parse("2"), Ok(Value::Integer(2)));
assert_eq!(parser.parse("3"), Ok(Value::Integer(3)));
assert_eq!(parser.parse("4"), Ok(Value::Integer(4)));
assert_eq!(parser.parse("5"), Ok(Value::Integer(5)));
assert_eq!(parser.parse("6"), Ok(Value::Integer(6)));
assert_eq!(parser.parse("7"), Ok(Value::Integer(7)));
assert_eq!(parser.parse("8"), Ok(Value::Integer(8)));
assert_eq!(parser.parse("9"), Ok(Value::Integer(9)));
assert_eq!(parser.parse("42"), Ok(Value::Integer(42)));
assert_eq!(
parser.parse(i64::MAX.to_string()),
Ok(Value::Integer(i64::MAX))
);
}
#[test]
fn parse_negative_integer() {
let parser = parser();
assert_eq!(parser.parse("-0"), Ok(Value::Integer(-0)));
assert_eq!(parser.parse("-1"), Ok(Value::Integer(-1)));
assert_eq!(parser.parse("-2"), Ok(Value::Integer(-2)));
assert_eq!(parser.parse("-3"), Ok(Value::Integer(-3)));
assert_eq!(parser.parse("-4"), Ok(Value::Integer(-4)));
assert_eq!(parser.parse("-5"), Ok(Value::Integer(-5)));
assert_eq!(parser.parse("-6"), Ok(Value::Integer(-6)));
assert_eq!(parser.parse("-7"), Ok(Value::Integer(-7)));
assert_eq!(parser.parse("-8"), Ok(Value::Integer(-8)));
assert_eq!(parser.parse("-9"), Ok(Value::Integer(-9)));
assert_eq!(parser.parse("-42"), Ok(Value::Integer(-42)));
assert_eq!(
parser.parse(i64::MIN.to_string()),
Ok(Value::Integer(i64::MIN))
);
}
#[test]
fn double_quoted_string() {
let parser = parser();
assert_eq!(parser.parse("\"\""), Ok(Value::String("".to_string())));
assert_eq!(parser.parse("\"1\""), Ok(Value::String("1".to_string())));
assert_eq!(parser.parse("\"42\""), Ok(Value::String("42".to_string())));
assert_eq!(
parser.parse("\"foobar\""),
Ok(Value::String("foobar".to_string()))
);
}
#[test]
fn single_quoted_string() {
let parser = parser();
assert_eq!(parser.parse("''"), Ok(Value::String("".to_string())));
assert_eq!(parser.parse("'1'"), Ok(Value::String("1".to_string())));
assert_eq!(parser.parse("'42'"), Ok(Value::String("42".to_string())));
assert_eq!(
parser.parse("'foobar'"),
Ok(Value::String("foobar".to_string()))
);
}
#[test]
fn grave_quoted_string() {
let parser = parser();
assert_eq!(parser.parse("``"), Ok(Value::String("".to_string())));
assert_eq!(parser.parse("`1`"), Ok(Value::String("1".to_string())));
assert_eq!(parser.parse("`42`"), Ok(Value::String("42".to_string())));
assert_eq!(
parser.parse("`foobar`"),
Ok(Value::String("foobar".to_string()))
);
2023-09-28 19:58:01 +00:00
}
}