1use anyhow::{Result, anyhow, bail};
12use serde_json::Value as Json;
13
14use crate::mvt::{GeomType, Value};
15
16#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct Rgba {
19 pub r: f64,
21 pub g: f64,
23 pub b: f64,
25 pub a: f64,
27}
28
29impl Rgba {
30 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
32 Self { r: r as f64 / 255.0, g: g as f64 / 255.0, b: b as f64 / 255.0, a: 1.0 }
33 }
34
35 pub fn to_u8(self) -> (u8, u8, u8) {
37 let c = |v: f64| (v.clamp(0.0, 1.0) * 255.0).round() as u8;
38 (c(self.r), c(self.g), c(self.b))
39 }
40
41 pub fn over(self, under: Rgba, alpha: f64) -> Rgba {
43 let a = alpha.clamp(0.0, 1.0);
44 let m = |x: f64, y: f64| x * a + y * (1.0 - a);
45 Rgba { r: m(self.r, under.r), g: m(self.g, under.g), b: m(self.b, under.b), a: 1.0 }
46 }
47
48 fn lerp(self, o: Rgba, t: f64) -> Rgba {
49 let m = |x: f64, y: f64| x + (y - x) * t;
50 Rgba { r: m(self.r, o.r), g: m(self.g, o.g), b: m(self.b, o.b), a: m(self.a, o.a) }
51 }
52}
53
54pub fn parse_color(s: &str) -> Option<Rgba> {
57 let s = s.trim().to_ascii_lowercase();
58 if let Some(hex) = s.strip_prefix('#') {
59 let d: Vec<u8> = hex.chars().map(|c| c.to_digit(16).map(|v| v as u8)).collect::<Option<_>>()?;
60 let (r, g, b, a) = match d.len() {
61 3 => (d[0] * 17, d[1] * 17, d[2] * 17, 255),
62 4 => (d[0] * 17, d[1] * 17, d[2] * 17, d[3] * 17),
63 6 => (d[0] * 16 + d[1], d[2] * 16 + d[3], d[4] * 16 + d[5], 255),
64 8 => (d[0] * 16 + d[1], d[2] * 16 + d[3], d[4] * 16 + d[5], d[6] * 16 + d[7]),
65 _ => return None,
66 };
67 let mut c = Rgba::rgb(r, g, b);
68 c.a = a as f64 / 255.0;
69 return Some(c);
70 }
71 if let Some((func, rest)) = s.split_once('(') {
72 let args: Vec<&str> = rest.trim_end_matches(')').split([',', ' ', '/']).filter(|a| !a.is_empty()).collect();
73 let num = |a: &str| -> Option<f64> {
74 match a.strip_suffix('%') {
75 Some(p) => p.parse::<f64>().ok().map(|v| v / 100.0),
76 None => a.trim_end_matches("deg").parse().ok(),
77 }
78 };
79 let alpha = args.get(3).map_or(Some(1.0), |a| num(a))?;
80 return match func.trim() {
81 "rgb" | "rgba" if args.len() >= 3 => {
82 let ch = |a: &str| -> Option<f64> {
83 if a.ends_with('%') { num(a) } else { num(a).map(|v| v / 255.0) }
84 };
85 Some(Rgba { r: ch(args[0])?, g: ch(args[1])?, b: ch(args[2])?, a: alpha })
86 }
87 "hsl" | "hsla" if args.len() >= 3 => {
88 let (h, sat, l) = (num(args[0])?.rem_euclid(360.0) / 360.0, num(args[1])?, num(args[2])?);
89 let q = if l < 0.5 { l * (1.0 + sat) } else { l + sat - l * sat };
90 let p = 2.0 * l - q;
91 let hue = |mut t: f64| {
92 t = t.rem_euclid(1.0);
93 if t < 1.0 / 6.0 {
94 p + (q - p) * 6.0 * t
95 } else if t < 0.5 {
96 q
97 } else if t < 2.0 / 3.0 {
98 p + (q - p) * (2.0 / 3.0 - t) * 6.0
99 } else {
100 p
101 }
102 };
103 Some(Rgba { r: hue(h + 1.0 / 3.0), g: hue(h), b: hue(h - 1.0 / 3.0), a: alpha })
104 }
105 _ => None,
106 };
107 }
108 let (r, g, b) = match s.as_str() {
109 "transparent" => return Some(Rgba { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
110 "black" => (0, 0, 0),
111 "white" => (255, 255, 255),
112 "red" => (255, 0, 0),
113 "green" => (0, 128, 0),
114 "lime" => (0, 255, 0),
115 "blue" => (0, 0, 255),
116 "yellow" => (255, 255, 0),
117 "cyan" | "aqua" => (0, 255, 255),
118 "magenta" | "fuchsia" => (255, 0, 255),
119 "orange" => (255, 165, 0),
120 "purple" => (128, 0, 128),
121 "gray" | "grey" => (128, 128, 128),
122 "silver" => (192, 192, 192),
123 "navy" => (0, 0, 128),
124 "teal" => (0, 128, 128),
125 "maroon" => (128, 0, 0),
126 "olive" => (128, 128, 0),
127 "brown" => (165, 42, 42),
128 "pink" => (255, 192, 203),
129 _ => return None,
130 };
131 Some(Rgba::rgb(r, g, b))
132}
133
134#[derive(Clone, Debug, PartialEq)]
136pub enum Val {
137 Null,
139 Bool(bool),
141 Num(f64),
143 Str(String),
145 Color(Rgba),
147 Array(Vec<Val>),
149}
150
151impl Val {
152 fn from_json(j: &Json) -> Val {
153 match j {
154 Json::Null => Val::Null,
155 Json::Bool(b) => Val::Bool(*b),
156 Json::Number(n) => n.as_f64().map_or(Val::Null, Val::Num),
157 Json::String(s) => Val::Str(s.clone()),
158 Json::Array(a) => Val::Array(a.iter().map(Val::from_json).collect()),
159 Json::Object(_) => Val::Null,
160 }
161 }
162
163 fn from_prop(v: &Value) -> Val {
164 match v {
165 Value::Str(s) => Val::Str(s.clone()),
166 Value::Num(n) => Val::Num(*n),
167 Value::Bool(b) => Val::Bool(*b),
168 }
169 }
170
171 pub fn as_num(&self) -> Option<f64> {
173 match self {
174 Val::Num(n) => Some(*n),
175 _ => None,
176 }
177 }
178
179 pub fn as_bool(&self) -> bool {
181 matches!(self, Val::Bool(true))
182 }
183
184 pub fn as_color(&self) -> Option<Rgba> {
186 match self {
187 Val::Color(c) => Some(*c),
188 Val::Str(s) => parse_color(s),
189 _ => None,
190 }
191 }
192
193 pub fn to_text(&self) -> String {
195 match self {
196 Val::Null => String::new(),
197 Val::Bool(b) => b.to_string(),
198 Val::Num(n) => Value::Num(*n).to_string(),
199 Val::Str(s) => s.clone(),
200 Val::Color(c) => {
201 let (r, g, b) = c.to_u8();
202 format!("rgba({r},{g},{b},{})", c.a)
203 }
204 Val::Array(a) => format!("[{}]", a.iter().map(Val::to_text).collect::<Vec<_>>().join(",")),
205 }
206 }
207
208 fn truthy(&self) -> bool {
209 !matches!(self, Val::Null | Val::Bool(false))
210 }
211}
212
213pub struct Ctx<'a> {
216 pub zoom: f64,
218 pub geom: GeomType,
220 pub props: &'a [(String, Value)],
222 pub state: &'a [(String, Val)],
224}
225
226impl Ctx<'_> {
227 fn prop(&self, k: &str) -> Option<&Value> {
228 self.props.iter().find(|(key, _)| key == k).map(|(_, v)| v)
229 }
230}
231
232#[derive(Clone, Copy, Debug, PartialEq)]
233pub enum Cmp {
235 Eq,
237 Ne,
239 Lt,
241 Le,
243 Gt,
245 Ge,
247}
248
249#[derive(Clone, Copy, Debug, PartialEq)]
250pub enum Math {
252 Add,
254 Sub,
256 Mul,
258 Div,
260 Rem,
262 Min,
264 Max,
266}
267
268#[derive(Clone, Copy, Debug, PartialEq)]
269pub enum Assert {
271 Str,
273 Num,
275 Bool,
277}
278
279#[derive(Clone, Debug, PartialEq)]
282pub enum Expr {
283 Lit(Val),
285 Get(String),
287 Has(String),
289 Zoom,
291 GeomType,
293 Cmp(Cmp, Box<Expr>, Box<Expr>),
295 All(Vec<Expr>),
297 Any(Vec<Expr>),
299 Not(Box<Expr>),
301 InList(Box<Expr>, Vec<Val>),
303 In(Box<Expr>, Box<Expr>),
305 Match {
307 input: Box<Expr>,
309 arms: Vec<(Vec<Val>, Expr)>,
311 default: Box<Expr>,
313 },
314 Case {
316 arms: Vec<(Expr, Expr)>,
318 default: Box<Expr>,
320 },
321 Coalesce(Vec<Expr>),
323 Interpolate {
325 base: f64,
327 input: Box<Expr>,
329 stops: Vec<(f64, Expr)>,
331 },
332 Step {
334 input: Box<Expr>,
336 default: Box<Expr>,
338 stops: Vec<(f64, Expr)>,
340 },
341 Math(Math, Vec<Expr>),
343 Concat(Vec<Expr>),
345 Upcase(Box<Expr>),
347 Downcase(Box<Expr>),
349 ToStr(Box<Expr>),
351 ToNum(Vec<Expr>),
353 ToBool(Box<Expr>),
355 ToColor(Vec<Expr>),
357 Length(Box<Expr>),
359 TypeOf(Box<Expr>),
361 IndexOf(Vec<Expr>),
363 Slice(Vec<Expr>),
365 Assert(Assert, Vec<Expr>),
367 FeatureState(String),
369 Template(Vec<Part>),
371}
372
373#[derive(Clone, Debug, PartialEq)]
375pub enum Part {
376 Text(String),
378 Prop(String),
380}
381
382fn key_expr(k: &str) -> Expr {
383 match k {
384 "$type" => Expr::GeomType,
385 _ => Expr::Get(k.to_string()),
386 }
387}
388
389fn args(a: &[Json]) -> Result<Vec<Expr>> {
390 a.iter().map(Expr::parse).collect()
391}
392
393fn one(a: &[Json], op: &str) -> Result<Box<Expr>> {
394 match a {
395 [x] => Ok(Box::new(Expr::parse(x)?)),
396 _ => bail!("{op}: expected 1 argument"),
397 }
398}
399
400fn literal_list(j: &Json) -> Option<Vec<Val>> {
402 match j {
403 Json::Array(a) if a.first().and_then(Json::as_str) == Some("literal") => {
404 a.get(1).and_then(Json::as_array).map(|l| l.iter().map(Val::from_json).collect())
405 }
406 _ => None,
407 }
408}
409
410fn stops(a: &[Json], op: &str) -> Result<Vec<(f64, Expr)>> {
411 if !a.len().is_multiple_of(2) {
412 bail!("{op}: stops must come in pairs");
413 }
414 let mut out = Vec::new();
415 for pair in a.chunks(2) {
416 let x = pair[0].as_f64().ok_or_else(|| anyhow!("{op}: stop input must be a number"))?;
417 out.push((x, Expr::parse(&pair[1])?));
418 }
419 Ok(out)
420}
421
422impl Expr {
423 pub fn lit(v: Val) -> Expr {
425 Expr::Lit(v)
426 }
427
428 pub fn parse(j: &Json) -> Result<Expr> {
431 match j {
432 Json::Array(a) => match a.first() {
433 Some(Json::String(op)) => Self::parse_op(op, &a[1..]),
434 _ => Ok(Expr::Lit(Val::from_json(j))),
435 },
436 Json::Object(o) if o.contains_key("stops") => Self::parse_function(j),
437 Json::Object(_) => bail!("objects are not expressions"),
438 _ => Ok(Expr::Lit(Val::from_json(j))),
439 }
440 }
441
442 pub fn parse_text(j: &Json) -> Result<Expr> {
444 match j.as_str() {
445 Some(s) if s.contains('{') => {
446 let mut parts = Vec::new();
447 let mut rest = s;
448 while let Some(open) = rest.find('{') {
449 let Some(close) = rest[open..].find('}') else { break };
450 if open > 0 {
451 parts.push(Part::Text(rest[..open].to_string()));
452 }
453 parts.push(Part::Prop(rest[open + 1..open + close].to_string()));
454 rest = &rest[open + close + 1..];
455 }
456 if !rest.is_empty() {
457 parts.push(Part::Text(rest.to_string()));
458 }
459 Ok(Expr::Template(parts))
460 }
461 _ => Self::parse(j),
462 }
463 }
464
465 fn parse_function(j: &Json) -> Result<Expr> {
467 let input = match j.get("property").and_then(Json::as_str) {
468 Some(p) => Box::new(key_expr(p)),
469 None => Box::new(Expr::Zoom),
470 };
471 let raw = j["stops"].as_array().ok_or_else(|| anyhow!("stops must be an array"))?;
472 let mut st = Vec::new();
473 for s in raw {
474 let (Some(k), Some(v)) = (s.get(0), s.get(1)) else { bail!("a stop must be [input, output]") };
475 if k.is_object() {
476 bail!("zoom-and-property functions are not supported");
477 }
478 st.push((k.clone(), Val::from_json(v)));
479 }
480 let kind = j.get("type").and_then(Json::as_str);
481 let default = Box::new(Expr::Lit(j.get("default").map_or(Val::Null, Val::from_json)));
482 let interpolable = st.first().is_some_and(|(_, v)| matches!(v, Val::Num(_)) || v.as_color().is_some());
483 match kind {
484 Some("categorical") => {
485 Ok(Expr::Match { input, arms: st.into_iter().map(|(k, v)| (vec![Val::from_json(&k)], Expr::Lit(v))).collect(), default })
486 }
487 Some("identity") => Ok(*input),
488 _ => {
489 let nums: Vec<(f64, Expr)> = st
490 .into_iter()
491 .map(|(k, v)| k.as_f64().map(|k| (k, Expr::Lit(v))).ok_or_else(|| anyhow!("stop inputs must be numbers")))
492 .collect::<Result<_>>()?;
493 if nums.is_empty() {
494 bail!("no stops");
495 }
496 if kind == Some("interval") || (kind.is_none() && !interpolable) {
497 let first = nums[0].1.clone();
498 Ok(Expr::Step { input, default: Box::new(first), stops: nums })
499 } else {
500 let base = j.get("base").and_then(Json::as_f64).unwrap_or(1.0);
501 Ok(Expr::Interpolate { base, input, stops: nums })
502 }
503 }
504 }
505 }
506
507 fn parse_op(op: &str, a: &[Json]) -> Result<Expr> {
508 let legacy_key = a.first().and_then(Json::as_str);
509 let legacy = legacy_key.is_some() && a[1..].iter().all(|v| !v.is_array());
511 let cmp = |c: Cmp| -> Result<Expr> {
512 match a {
513 [_, r] if legacy => Ok(Expr::Cmp(c, Box::new(key_expr(legacy_key.unwrap_or_default())), Box::new(Expr::Lit(Val::from_json(r))))),
514 [l, r] => Ok(Expr::Cmp(c, Box::new(Expr::parse(l)?), Box::new(Expr::parse(r)?))),
515 [l, r, _collator] => Ok(Expr::Cmp(c, Box::new(Expr::parse(l)?), Box::new(Expr::parse(r)?))),
516 _ => bail!("{op}: expected 2 arguments"),
517 }
518 };
519 Ok(match op {
520 "literal" => Expr::Lit(a.first().map_or(Val::Null, Val::from_json)),
521 "get" => match a {
522 [Json::String(k)] => Expr::Get(k.clone()),
523 _ => bail!("get: only [\"get\", \"name\"] is supported"),
524 },
525 "has" => match a {
526 [Json::String(k)] => Expr::Has(k.clone()),
527 _ => bail!("has: only [\"has\", \"name\"] is supported"),
528 },
529 "!has" => match a {
530 [Json::String(k)] => Expr::Not(Box::new(Expr::Has(k.clone()))),
531 _ => bail!("!has: expected a property name"),
532 },
533 "zoom" => Expr::Zoom,
534 "feature-state" => match a {
535 [Json::String(k)] => Expr::FeatureState(k.clone()),
536 _ => bail!("feature-state: only [\"feature-state\", \"key\"] is supported"),
537 },
538 "geometry-type" => Expr::GeomType,
539 "==" => cmp(Cmp::Eq)?,
540 "!=" => cmp(Cmp::Ne)?,
541 "<" => cmp(Cmp::Lt)?,
542 "<=" => cmp(Cmp::Le)?,
543 ">" => cmp(Cmp::Gt)?,
544 ">=" => cmp(Cmp::Ge)?,
545 "all" => Expr::All(args(a)?),
546 "any" => Expr::Any(args(a)?),
547 "none" => Expr::Not(Box::new(Expr::Any(args(a)?))),
548 "!" => Expr::Not(one(a, op)?),
549 "in" | "!in" => {
550 let e = if legacy {
551 Expr::InList(Box::new(key_expr(legacy_key.unwrap_or_default())), a[1..].iter().map(Val::from_json).collect())
552 } else {
553 match a {
554 [needle, hay] => match literal_list(hay) {
555 Some(list) => Expr::InList(Box::new(Expr::parse(needle)?), list),
556 None => Expr::In(Box::new(Expr::parse(needle)?), Box::new(Expr::parse(hay)?)),
557 },
558 _ => bail!("in: expected 2 arguments"),
559 }
560 };
561 if op == "!in" { Expr::Not(Box::new(e)) } else { e }
562 }
563 "match" => {
564 if a.len() < 2 || !a.len().is_multiple_of(2) {
565 bail!("match: expected input, label/output pairs and a default");
566 }
567 let input = Box::new(Expr::parse(&a[0])?);
568 let mut arms = Vec::new();
569 for pair in a[1..a.len() - 1].chunks(2) {
570 let labels = match &pair[0] {
571 Json::Array(l) => l.iter().map(Val::from_json).collect(),
572 v => vec![Val::from_json(v)],
573 };
574 arms.push((labels, Expr::parse(&pair[1])?));
575 }
576 Expr::Match { input, arms, default: Box::new(Expr::parse(&a[a.len() - 1])?) }
577 }
578 "case" => {
579 if a.len() % 2 != 1 {
580 bail!("case: expected condition/output pairs and a default");
581 }
582 let mut arms = Vec::new();
583 for pair in a[..a.len() - 1].chunks(2) {
584 arms.push((Expr::parse(&pair[0])?, Expr::parse(&pair[1])?));
585 }
586 Expr::Case { arms, default: Box::new(Expr::parse(&a[a.len() - 1])?) }
587 }
588 "coalesce" => Expr::Coalesce(args(a)?),
589 "interpolate" | "interpolate-hcl" | "interpolate-lab" => {
590 if a.len() < 4 {
591 bail!("interpolate: expected a curve, an input and stops");
592 }
593 let base = match a[0].get(0).and_then(Json::as_str) {
594 Some("linear") => 1.0,
595 Some("exponential") => a[0].get(1).and_then(Json::as_f64).unwrap_or(1.0),
596 Some("cubic-bezier") => 1.0,
598 _ => bail!("interpolate: unknown curve"),
599 };
600 Expr::Interpolate { base, input: Box::new(Expr::parse(&a[1])?), stops: stops(&a[2..], op)? }
601 }
602 "step" => {
603 if a.len() < 2 {
604 bail!("step: expected an input and a default");
605 }
606 Expr::Step { input: Box::new(Expr::parse(&a[0])?), default: Box::new(Expr::parse(&a[1])?), stops: stops(&a[2..], op)? }
607 }
608 "+" => Expr::Math(Math::Add, args(a)?),
609 "-" => Expr::Math(Math::Sub, args(a)?),
610 "*" => Expr::Math(Math::Mul, args(a)?),
611 "/" => Expr::Math(Math::Div, args(a)?),
612 "%" => Expr::Math(Math::Rem, args(a)?),
613 "min" => Expr::Math(Math::Min, args(a)?),
614 "max" => Expr::Math(Math::Max, args(a)?),
615 "concat" => Expr::Concat(args(a)?),
616 "format" => Expr::Concat(a.iter().filter(|v| !v.is_object()).map(Expr::parse).collect::<Result<_>>()?),
618 "upcase" => Expr::Upcase(one(a, op)?),
619 "downcase" => Expr::Downcase(one(a, op)?),
620 "to-string" => Expr::ToStr(one(a, op)?),
621 "to-number" => Expr::ToNum(args(a)?),
622 "to-boolean" => Expr::ToBool(one(a, op)?),
623 "to-color" => Expr::ToColor(args(a)?),
624 "length" => Expr::Length(one(a, op)?),
625 "typeof" => Expr::TypeOf(one(a, op)?),
626 "index-of" if (2..=3).contains(&a.len()) => Expr::IndexOf(args(a)?),
627 "slice" if (2..=3).contains(&a.len()) => Expr::Slice(args(a)?),
628 "string" => Expr::Assert(Assert::Str, args(a)?),
629 "number" => Expr::Assert(Assert::Num, args(a)?),
630 "boolean" => Expr::Assert(Assert::Bool, args(a)?),
631 other => bail!("unsupported expression \"{other}\""),
632 })
633 }
634
635 pub fn is_constant(&self) -> bool {
637 match self {
638 Expr::Lit(_) => true,
639 Expr::Get(_) | Expr::Has(_) | Expr::Zoom | Expr::GeomType | Expr::Template(_) | Expr::FeatureState(_) => false,
640 Expr::Cmp(_, a, b) | Expr::In(a, b) => a.is_constant() && b.is_constant(),
641 Expr::Not(a) | Expr::Upcase(a) | Expr::Downcase(a) | Expr::ToStr(a) | Expr::ToBool(a) | Expr::Length(a) | Expr::TypeOf(a) => a.is_constant(),
642 Expr::InList(a, _) => a.is_constant(),
643 Expr::All(v) | Expr::Any(v) | Expr::Coalesce(v) | Expr::Math(_, v) | Expr::Concat(v) | Expr::ToNum(v) | Expr::ToColor(v) | Expr::Assert(_, v) | Expr::IndexOf(v) | Expr::Slice(v) => {
644 v.iter().all(Expr::is_constant)
645 }
646 Expr::Match { input, arms, default } => input.is_constant() && default.is_constant() && arms.iter().all(|(_, e)| e.is_constant()),
647 Expr::Case { arms, default } => default.is_constant() && arms.iter().all(|(c, e)| c.is_constant() && e.is_constant()),
648 Expr::Interpolate { input, stops, .. } => input.is_constant() && stops.iter().all(|(_, e)| e.is_constant()),
649 Expr::Step { input, default, stops } => input.is_constant() && default.is_constant() && stops.iter().all(|(_, e)| e.is_constant()),
650 }
651 }
652
653 pub fn uses_zoom(&self) -> bool {
655 self.any(&|e| matches!(e, Expr::Zoom))
656 }
657
658 pub fn uses_state(&self) -> bool {
660 self.any(&|e| matches!(e, Expr::FeatureState(_)))
661 }
662
663 fn any(&self, f: &dyn Fn(&Expr) -> bool) -> bool {
665 if f(self) {
666 return true;
667 }
668 match self {
669 Expr::Lit(_) | Expr::Get(_) | Expr::Has(_) | Expr::Zoom | Expr::GeomType | Expr::Template(_) | Expr::FeatureState(_) => false,
670 Expr::Cmp(_, a, b) | Expr::In(a, b) => a.any(f) || b.any(f),
671 Expr::Not(a) | Expr::Upcase(a) | Expr::Downcase(a) | Expr::ToStr(a) | Expr::ToBool(a) | Expr::Length(a) | Expr::TypeOf(a) | Expr::InList(a, _) => a.any(f),
672 Expr::All(v) | Expr::Any(v) | Expr::Coalesce(v) | Expr::Math(_, v) | Expr::Concat(v) | Expr::ToNum(v) | Expr::ToColor(v) | Expr::Assert(_, v) | Expr::IndexOf(v) | Expr::Slice(v) => {
673 v.iter().any(|e| e.any(f))
674 }
675 Expr::Match { input, arms, default } => input.any(f) || default.any(f) || arms.iter().any(|(_, e)| e.any(f)),
676 Expr::Case { arms, default } => default.any(f) || arms.iter().any(|(c, e)| c.any(f) || e.any(f)),
677 Expr::Interpolate { input, stops, .. } => input.any(f) || stops.iter().any(|(_, e)| e.any(f)),
678 Expr::Step { input, default, stops } => input.any(f) || default.any(f) || stops.iter().any(|(_, e)| e.any(f)),
679 }
680 }
681
682 pub fn eval(&self, c: &Ctx) -> Val {
684 match self {
685 Expr::Lit(v) => v.clone(),
686 Expr::Get(k) => c.prop(k).map_or(Val::Null, Val::from_prop),
687 Expr::Has(k) => Val::Bool(c.prop(k).is_some()),
688 Expr::FeatureState(k) => c.state.iter().find(|(key, _)| key == k).map_or(Val::Null, |(_, v)| v.clone()),
689 Expr::Zoom => Val::Num(c.zoom),
690 Expr::GeomType => Val::Str(
691 match c.geom {
692 GeomType::Point => "Point",
693 GeomType::Line => "LineString",
694 GeomType::Polygon => "Polygon",
695 GeomType::Unknown => "Unknown",
696 }
697 .into(),
698 ),
699 Expr::Cmp(op, l, r) => Val::Bool(compare(*op, &l.eval(c), &r.eval(c))),
700 Expr::All(v) => Val::Bool(v.iter().all(|e| e.eval(c).as_bool())),
701 Expr::Any(v) => Val::Bool(v.iter().any(|e| e.eval(c).as_bool())),
702 Expr::Not(e) => Val::Bool(!e.eval(c).as_bool()),
703 Expr::InList(needle, list) => {
704 let n = needle.eval(c);
705 Val::Bool(list.iter().any(|v| loose_eq(v, &n)))
706 }
707 Expr::In(needle, hay) => {
708 let n = needle.eval(c);
709 Val::Bool(match hay.eval(c) {
710 Val::Array(a) => a.iter().any(|v| loose_eq(v, &n)),
711 Val::Str(s) => s.contains(&n.to_text()),
712 _ => false,
713 })
714 }
715 Expr::Match { input, arms, default } => {
716 let v = input.eval(c);
717 arms.iter().find(|(labels, _)| labels.iter().any(|l| loose_eq(l, &v))).map_or_else(|| default.eval(c), |(_, out)| out.eval(c))
718 }
719 Expr::Case { arms, default } => arms.iter().find(|(cond, _)| cond.eval(c).as_bool()).map_or_else(|| default.eval(c), |(_, out)| out.eval(c)),
720 Expr::Coalesce(v) => v.iter().map(|e| e.eval(c)).find(Val::truthy).unwrap_or(Val::Null),
721 Expr::Interpolate { base, input, stops } => {
722 let Some(x) = input.eval(c).as_num() else { return Val::Null };
723 interpolate(*base, x, stops, c)
724 }
725 Expr::Step { input, default, stops } => {
726 let Some(x) = input.eval(c).as_num() else { return default.eval(c) };
727 stops.iter().rev().find(|(s, _)| x >= *s).map_or_else(|| default.eval(c), |(_, e)| e.eval(c))
728 }
729 Expr::Math(op, v) => {
730 let n: Vec<f64> = v.iter().filter_map(|e| e.eval(c).as_num()).collect();
731 if n.len() != v.len() || n.is_empty() {
732 return Val::Null;
733 }
734 Val::Num(match op {
735 Math::Add => n.iter().sum(),
736 Math::Mul => n.iter().product(),
737 Math::Sub if n.len() == 1 => -n[0],
738 Math::Sub => n[0] - n[1..].iter().sum::<f64>(),
739 Math::Div => n[1..].iter().fold(n[0], |a, b| a / b),
740 Math::Rem => n[1..].iter().fold(n[0], |a, b| a % b),
741 Math::Min => n.iter().copied().fold(f64::MAX, f64::min),
742 Math::Max => n.iter().copied().fold(f64::MIN, f64::max),
743 })
744 }
745 Expr::Concat(v) => Val::Str(v.iter().map(|e| e.eval(c).to_text()).collect()),
746 Expr::Upcase(e) => Val::Str(e.eval(c).to_text().to_uppercase()),
747 Expr::Downcase(e) => Val::Str(e.eval(c).to_text().to_lowercase()),
748 Expr::ToStr(e) => Val::Str(e.eval(c).to_text()),
749 Expr::ToNum(v) => v
750 .iter()
751 .find_map(|e| match e.eval(c) {
752 Val::Num(n) => Some(n),
753 Val::Str(s) => s.trim().parse().ok(),
754 Val::Bool(b) => Some(b as u8 as f64),
755 _ => None,
756 })
757 .map_or(Val::Null, Val::Num),
758 Expr::ToBool(e) => Val::Bool(match e.eval(c) {
759 Val::Num(n) => n != 0.0 && !n.is_nan(),
760 Val::Str(s) => !s.is_empty(),
761 v => v.truthy(),
762 }),
763 Expr::ToColor(v) => v.iter().find_map(|e| e.eval(c).as_color()).map_or(Val::Null, Val::Color),
764 Expr::Length(e) => match e.eval(c) {
765 Val::Str(s) => Val::Num(s.chars().count() as f64),
766 Val::Array(a) => Val::Num(a.len() as f64),
767 _ => Val::Null,
768 },
769 Expr::TypeOf(e) => Val::Str(
770 match e.eval(c) {
771 Val::Null => "null",
772 Val::Bool(_) => "boolean",
773 Val::Num(_) => "number",
774 Val::Str(_) => "string",
775 Val::Color(_) => "color",
776 Val::Array(_) => "array",
777 }
778 .into(),
779 ),
780 Expr::IndexOf(v) => {
781 let (needle, hay) = (v[0].eval(c), v[1].eval(c));
782 let from = v.get(2).and_then(|e| e.eval(c).as_num()).unwrap_or(0.0).max(0.0) as usize;
783 Val::Num(match hay {
784 Val::Str(h) => {
785 let (h, n): (Vec<char>, Vec<char>) = (h.chars().collect(), needle.to_text().chars().collect());
786 (from..=h.len().saturating_sub(n.len())).find(|&i| h.len() >= n.len() && h[i..i + n.len()] == n[..]).map_or(-1.0, |i| i as f64)
787 }
788 Val::Array(a) => a.iter().skip(from).position(|x| loose_eq(x, &needle)).map_or(-1.0, |i| (i + from) as f64),
789 _ => -1.0,
790 })
791 }
792 Expr::Slice(v) => {
793 let input = v[0].eval(c);
794 let len = match &input {
795 Val::Str(s) => s.chars().count(),
796 Val::Array(a) => a.len(),
797 _ => return Val::Null,
798 } as f64;
799 let pos = |e: Option<&Expr>, default: f64| {
801 let p = e.and_then(|e| e.eval(c).as_num()).unwrap_or(default);
802 (if p < 0.0 { len + p } else { p }).clamp(0.0, len) as usize
803 };
804 let (start, end) = (pos(v.get(1), 0.0), pos(v.get(2), len));
805 let end = end.max(start);
806 match input {
807 Val::Str(s) => Val::Str(s.chars().skip(start).take(end - start).collect()),
808 Val::Array(a) => Val::Array(a[start..end].to_vec()),
809 _ => Val::Null,
810 }
811 }
812 Expr::Assert(kind, v) => v
813 .iter()
814 .map(|e| e.eval(c))
815 .find(|x| matches!((kind, x), (Assert::Str, Val::Str(_)) | (Assert::Num, Val::Num(_)) | (Assert::Bool, Val::Bool(_))))
816 .unwrap_or(Val::Null),
817 Expr::Template(parts) => Val::Str(
818 parts
819 .iter()
820 .map(|p| match p {
821 Part::Text(t) => t.clone(),
822 Part::Prop(k) => c.prop(k).map(|v| v.to_string()).unwrap_or_default(),
823 })
824 .collect(),
825 ),
826 }
827 }
828}
829
830fn loose_eq(a: &Val, b: &Val) -> bool {
831 match (a, b) {
832 (Val::Num(x), Val::Num(y)) => x == y,
833 _ => a == b,
834 }
835}
836
837fn compare(op: Cmp, l: &Val, r: &Val) -> bool {
838 use std::cmp::Ordering;
839 let ord: Option<Ordering> = match (l, r) {
840 (Val::Num(a), Val::Num(b)) => a.partial_cmp(b),
841 (Val::Str(a), Val::Str(b)) => Some(a.cmp(b)),
842 (Val::Bool(a), Val::Bool(b)) => Some(a.cmp(b)),
843 _ => None,
844 };
845 match op {
846 Cmp::Eq => ord == Some(Ordering::Equal) || (l == &Val::Null && r == &Val::Null),
847 Cmp::Ne => !(ord == Some(Ordering::Equal) || (l == &Val::Null && r == &Val::Null)),
848 Cmp::Lt => ord == Some(Ordering::Less),
849 Cmp::Le => matches!(ord, Some(Ordering::Less | Ordering::Equal)),
850 Cmp::Gt => ord == Some(Ordering::Greater),
851 Cmp::Ge => matches!(ord, Some(Ordering::Greater | Ordering::Equal)),
852 }
853}
854
855fn interpolate(base: f64, x: f64, stops: &[(f64, Expr)], c: &Ctx) -> Val {
856 let Some(first) = stops.first() else { return Val::Null };
857 if x <= first.0 {
858 return first.1.eval(c);
859 }
860 let Some(i) = stops.iter().position(|(s, _)| *s > x) else {
861 return stops[stops.len() - 1].1.eval(c);
862 };
863 let ((x0, lo), (x1, hi)) = (&stops[i - 1], &stops[i]);
864 let span = x1 - x0;
865 let t = if span <= 0.0 {
866 0.0
867 } else if (base - 1.0).abs() < 1e-9 {
868 (x - x0) / span
869 } else {
870 (base.powf(x - x0) - 1.0) / (base.powf(span) - 1.0)
871 };
872 match (lo.eval(c), hi.eval(c)) {
873 (Val::Num(a), Val::Num(b)) => Val::Num(a + (b - a) * t),
874 (a, b) => match (a.as_color(), b.as_color()) {
875 (Some(ca), Some(cb)) => Val::Color(ca.lerp(cb, t)),
876 _ => a,
877 },
878 }
879}
880
881#[cfg(test)]
882mod tests {
883 use super::*;
884 use serde_json::json;
885
886 fn props(p: &[(&str, Value)]) -> Vec<(String, Value)> {
887 p.iter().map(|(k, v)| (k.to_string(), v.clone())).collect()
888 }
889
890 fn eval(e: Json, z: f64, geom: GeomType, p: &[(String, Value)]) -> Val {
891 Expr::parse(&e).unwrap().eval(&Ctx { zoom: z, geom, props: p, state: &[] })
892 }
893
894 #[test]
895 fn colors() {
896 assert_eq!(parse_color("#fff").unwrap().to_u8(), (255, 255, 255));
897 assert_eq!(parse_color("#336699").unwrap().to_u8(), (0x33, 0x66, 0x99));
898 assert!((parse_color("#33669980").unwrap().a - 0.502).abs() < 0.01);
899 assert_eq!(parse_color("rgb(10, 20, 30)").unwrap().to_u8(), (10, 20, 30));
900 assert_eq!(parse_color("rgba(10,20,30,0.5)").unwrap().a, 0.5);
901 assert_eq!(parse_color("hsl(0, 100%, 50%)").unwrap().to_u8(), (255, 0, 0));
902 assert_eq!(parse_color("hsla(120,100%,25%,1)").unwrap().to_u8(), (0, 128, 0));
903 assert_eq!(parse_color("Navy").unwrap().to_u8(), (0, 0, 128));
904 assert!(parse_color("nonsense").is_none());
905 }
906
907 #[test]
908 fn legacy_filters() {
909 let p = props(&[("class", Value::Str("motorway".into())), ("admin_level", Value::Num(2.0))]);
910 let t = |e: Json| eval(e, 10.0, GeomType::Line, &p).as_bool();
911 assert!(t(json!(["==", "class", "motorway"])));
912 assert!(t(json!(["!=", "class", "primary"])));
913 assert!(t(json!(["!=", "missing", "x"])), "a missing property is not equal");
914 assert!(!t(json!(["==", "missing", "x"])));
915 assert!(t(json!(["in", "class", "trunk", "motorway"])));
916 assert!(t(json!(["!in", "class", "path", "track"])));
917 assert!(t(json!(["<=", "admin_level", 4])));
918 assert!(t(json!(["has", "class"])));
919 assert!(t(json!(["!has", "name"])));
920 assert!(t(json!(["==", "$type", "LineString"])));
921 assert!(t(json!(["all", ["==", "class", "motorway"], ["none", ["==", "admin_level", 4]]])));
922 }
923
924 #[test]
925 fn modern_expressions() {
926 let p = props(&[("class", Value::Str("park".into())), ("rank", Value::Num(3.0)), ("name", Value::Str("Mall".into()))]);
927 let e = |j: Json| eval(j, 12.0, GeomType::Polygon, &p);
928 assert!(e(json!(["==", ["get", "class"], "park"])).as_bool());
929 assert!(e(json!(["in", ["get", "class"], ["literal", ["park", "wood"]]])).as_bool());
930 assert!(e(json!(["in", "ar", ["get", "class"]])).as_bool(), "substring");
931 assert_eq!(e(json!(["match", ["get", "class"], ["wood", "park"], "green", "gray"])), Val::Str("green".into()));
932 assert_eq!(e(json!(["case", [">", ["get", "rank"], 5], "big", "small"])), Val::Str("small".into()));
933 assert_eq!(e(json!(["coalesce", ["get", "name:en"], ["get", "name"]])), Val::Str("Mall".into()));
934 assert_eq!(e(json!(["step", ["zoom"], 1, 10, 2, 14, 3])), Val::Num(2.0));
935 assert_eq!(e(json!(["interpolate", ["linear"], ["zoom"], 10, 0, 14, 8])), Val::Num(4.0));
936 assert_eq!(e(json!(["+", ["get", "rank"], 2])), Val::Num(5.0));
937 assert_eq!(e(json!(["concat", ["upcase", ["get", "name"]], "!"])), Val::Str("MALL!".into()));
938 assert_eq!(e(json!(["format", ["get", "name"], {"font-scale": 1.2}])), Val::Str("Mall".into()));
939 assert_eq!(e(json!(["to-number", "x", "7"])), Val::Num(7.0));
940 assert_eq!(e(json!(["typeof", ["get", "rank"]])), Val::Str("number".into()));
941 assert_eq!(e(json!(["index-of", ";", "I 270;US 40"])), Val::Num(5.0));
942 assert_eq!(e(json!(["index-of", "x", "abc"])), Val::Num(-1.0));
943 assert_eq!(e(json!(["index-of", "a", "banana", 2])), Val::Num(3.0));
944 assert_eq!(e(json!(["slice", "I 270;US 40", 0, 5])), Val::Str("I 270".into()));
945 assert_eq!(e(json!(["slice", "I 270", 2])), Val::Str("270".into()));
946 assert_eq!(e(json!(["slice", "abc", -2])), Val::Str("bc".into()));
947 assert_eq!(e(json!(["slice", ["literal", [1, 2, 3]], 1, 2])), Val::Array(vec![Val::Num(2.0)]));
948 let c = e(json!(["interpolate", ["linear"], ["zoom"], 10, "#000000", 14, "#ffffff"])).as_color().unwrap();
949 assert_eq!(c.to_u8(), (128, 128, 128));
950 assert!(Expr::parse(&json!(["distance", 1])).is_err());
951 assert!(Expr::parse(&json!(["interpolate", ["linear"], ["zoom"], 1, 2])).is_ok());
952 }
953
954 #[test]
955 fn exponential_and_functions() {
956 let p = props(&[("kind", Value::Str("a".into()))]);
957 let e = |j: Json, z: f64| eval(j, z, GeomType::Point, &p);
958 assert!((e(json!(["interpolate", ["exponential", 2], ["zoom"], 10, 0, 12, 3]), 11.0).as_num().unwrap() - 1.0).abs() < 1e-9);
960 assert_eq!(e(json!({"stops": [[5, 1], [10, 6]]}), 7.0), Val::Num(3.0));
961 assert_eq!(e(json!({"stops": [[5, "none"], [10, "visible"]]}), 11.0), Val::Str("visible".into()));
962 assert_eq!(e(json!({"property": "kind", "type": "categorical", "stops": [["a", 1], ["b", 2]]}), 0.0), Val::Num(1.0));
963 let c = e(json!({"base": 1, "stops": [[0, "#000"], [10, "#fff"]]}), 5.0).as_color().unwrap();
964 assert_eq!(c.to_u8(), (128, 128, 128));
965 }
966
967 #[test]
968 fn text_templates() {
969 let p = props(&[("name", Value::Str("Georgetown".into())), ("ref", Value::Num(7.0))]);
970 let e = Expr::parse_text(&json!("{name} ({ref}) {missing}")).unwrap();
971 assert_eq!(e.eval(&Ctx { zoom: 0.0, geom: GeomType::Point, props: &p, state: &[] }), Val::Str("Georgetown (7) ".into()));
972 assert_eq!(Expr::parse_text(&json!("plain")).unwrap(), Expr::Lit(Val::Str("plain".into())));
973 assert!(!Expr::parse(&json!(["get", "name"])).unwrap().is_constant());
974 assert!(Expr::parse(&json!(["concat", "a", "b"])).unwrap().is_constant());
975 let st = vec![("hover".to_string(), Val::Bool(true))];
976 let fs = Expr::parse(&json!(["case", ["boolean", ["feature-state", "hover"], false], "on", "off"])).unwrap();
977 assert!(fs.uses_state() && !fs.is_constant());
978 assert_eq!(fs.eval(&Ctx { zoom: 0.0, geom: GeomType::Point, props: &p, state: &st }), Val::Str("on".into()));
979 assert_eq!(fs.eval(&Ctx { zoom: 0.0, geom: GeomType::Point, props: &p, state: &[] }), Val::Str("off".into()));
980 assert!(Expr::parse(&json!(["interpolate", ["linear"], ["zoom"], 5, 1, 10, 2])).unwrap().uses_zoom());
981 assert!(Expr::parse(&json!({"stops": [[5, "#000"], [10, "#fff"]]})).unwrap().uses_zoom());
982 assert!(Expr::parse(&json!(["all", ["==", "class", "x"], [">=", ["zoom"], 14]])).unwrap().uses_zoom());
983 assert!(!Expr::parse(&json!(["match", ["get", "class"], "a", "#fff", "#000"])).unwrap().uses_zoom());
984 }
985}