Skip to content

Commit 38ff66a

Browse files
committed
feat: Use chumsky for expression parsing
Implement a new expression parser using chumsky crate to produce better error messages on parsing failure.
1 parent 1d7b012 commit 38ff66a

5 files changed

Lines changed: 408 additions & 412 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ bitcode = "0.6"
4444
bytemuck = "1.14"
4545
bytes = "1.4"
4646
cfg-if = "1"
47+
chumsky = "0.12"
4748
console_log = "1"
4849
console_error_panic_hook = "0.1"
4950
csv = "1.3"
@@ -68,13 +69,13 @@ geo-types = "0.7"
6869
getrandom = "0.3"
6970
image = { version = "0.24", default-features = false }
7071
insta = "1.41"
72+
itertools = "0.14"
7173
js-sys = "0.3"
7274
las = "0.8"
7375
log = "0.4"
7476
lyon = "1"
7577
maybe-sync = "0.1"
7678
nalgebra = "0.32"
77-
nom = "7"
7879
num-traits = "0.2"
7980
ordered-float = "5"
8081
ordered_hash_map = "0.5"

galileo/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,18 @@ base64 = { workspace = true }
3030
bytemuck = { workspace = true, features = ["derive"] }
3131
bytes = { workspace = true }
3232
cfg-if = { workspace = true }
33+
chumsky = { workspace = true }
3334
either = { workspace = true }
3435
futures-intrusive = { workspace = true }
3536
galileo-mvt = { workspace = true }
3637
galileo-types = { workspace = true }
3738
geojson = { workspace = true, optional = true }
3839
geozero = { workspace = true, features = ["with-geojson", "with-geo"] }
3940
image = { workspace = true, default-features = false, features = ["png", "jpeg"], optional = true }
41+
itertools = { workspace = true }
4042
log = { workspace = true }
4143
lyon = { workspace = true, features = ["serialization"] }
4244
nalgebra = { workspace = true }
43-
nom = { workspace = true }
4445
num-traits = { workspace = true }
4546
ordered-float = { workspace = true }
4647
ordered_hash_map = { workspace = true }

galileo/src/expr/mod.rs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use std::marker::PhantomData;
44

5+
use itertools::Itertools;
56
use serde::{Deserialize, Deserializer, Serialize};
67

78
mod interpolation;
@@ -10,6 +11,7 @@ mod match_expr;
1011
pub use match_expr::*;
1112

1213
mod value;
14+
use serde_json::Value;
1315
pub use value::{ExprGeometryType, ExprValue};
1416

1517
use crate::Color;
@@ -118,20 +120,42 @@ impl From<f64> for Expr {
118120
}
119121
}
120122

123+
impl From<String> for Expr {
124+
fn from(value: String) -> Self {
125+
Expr::Literal(value.into())
126+
}
127+
}
128+
129+
impl From<bool> for Expr {
130+
fn from(value: bool) -> Self {
131+
Expr::Literal(value.into())
132+
}
133+
}
134+
121135
impl<'de, Out> Deserialize<'de> for TypedExpr<Out> {
122136
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
123137
// Buffer into a generic value so we can inspect the shape without consuming the input.
124138
let value = serde_json::Value::deserialize(deserializer)?;
125139

126-
if let serde_json::Value::String(ref s) = value {
127-
return parser::parse_expr(s)
128-
.map(|(_, expr)| expr.into())
129-
.map_err(|e| serde::de::Error::custom(format!("expression parse error: {e}")));
130-
}
140+
let expr = match value {
141+
Value::Number(v) => v
142+
.as_f64()
143+
.ok_or_else(|| serde::de::Error::custom(format!("invalid number value: {v}")))?
144+
.into(),
145+
Value::Bool(v) => v.into(),
146+
Value::Null => ExprValue::Null.into(),
147+
Value::String(s) => parser::parse_expr(&s).map_err(|e| {
148+
let errors = e.into_iter().map(|v| v.to_string());
149+
let errors = Itertools::intersperse(errors, ", ".to_string());
150+
serde::de::Error::custom(format!(
151+
"expression parse error: {}",
152+
errors.collect::<String>(),
153+
))
154+
})?,
155+
v => Expr::deserialize(v).map_err(serde::de::Error::custom)?,
156+
};
131157

132-
Expr::deserialize(value)
133-
.map(Into::into)
134-
.map_err(serde::de::Error::custom)
158+
Ok(expr.into())
135159
}
136160
}
137161

@@ -236,6 +260,13 @@ mod tests {
236260
);
237261
}
238262

263+
#[test]
264+
fn deserialize_expr_from_num() {
265+
let json = "42";
266+
let expr: NumExpr = serde_json::from_str(json).unwrap();
267+
assert_eq!(expr.0, Expr::Literal(ExprValue::Number(42.0)));
268+
}
269+
239270
#[test]
240271
fn deserialize_expr_from_object() {
241272
let json = r#"{"Get": "kind"}"#;

0 commit comments

Comments
 (0)