use serde_json::Value as Json;
use crate::ast::{Expr, FormatArg, InterpKind, InterpSpace};
use crate::distance::SimpleGeom;
use crate::error::{ParseError, ParseErrorKind};
use crate::ext::{Options, MAX_MACRO_DEPTH};
use crate::value::Value;
const VERTICAL_ALIGN: [&str; 3] = ["bottom", "center", "top"];
type Result<T> = std::result::Result<T, ParseError>;
pub fn parse(json: &Json, opts: &Options) -> Result<Expr> {
match json {
Json::Array(items) => parse_array(items, opts),
Json::Object(_) if opts.convert_legacy && crate::convert::is_function(json) => {
parse(&crate::convert::convert_function(json, &Json::Null), opts)
}
Json::Object(_) => Err(ParseError::of(ParseErrorKind::BareObject)),
_ => Ok(Expr::Literal(Value::from_json(json))),
}
}
fn parse_all(args: &[Json], opts: &Options) -> Result<Vec<Expr>> {
args.iter()
.enumerate()
.map(|(i, a)| parse(a, opts).map_err(|e| e.at(i + 1)))
.collect()
}
fn parse_array(items: &[Json], opts: &Options) -> Result<Expr> {
let first = items
.first()
.ok_or_else(|| ParseError::of(ParseErrorKind::EmptyArray))?;
let op = first.as_str().ok_or_else(|| {
ParseError::of(ParseErrorKind::ExpressionNameNotString {
found: js_typeof(first),
})
.at(0)
})?;
let args = &items[1..];
if opts.macros.contains_key(op) {
return expand_macro(op, args, opts);
}
if let Some(f) = opts.functions.get(op) {
if args.len() != f.params.len() {
return Err(ParseError::of(ParseErrorKind::ExtArgCount {
kind: "Function",
op: op.to_string(),
expected: f.params.len(),
found: args.len(),
}));
}
return Ok(Expr::Call {
op: op.to_string(),
args: parse_all(args, opts)?,
});
}
if let Some((arity, _)) = opts.natives.get(op) {
if args.len() != *arity {
return Err(ParseError::of(ParseErrorKind::ExtArgCount {
kind: "Function",
op: op.to_string(),
expected: *arity,
found: args.len(),
}));
}
return Ok(Expr::Call {
op: op.to_string(),
args: parse_all(args, opts)?,
});
}
match op {
"literal" => {
expect_arity(op, args, 1)?;
Ok(Expr::Literal(Value::from_json(&args[0])))
}
"let" => parse_let(args, opts),
"var" => {
expect_arity(op, args, 1)?;
let name = args[0]
.as_str()
.ok_or_else(|| ParseError::of(ParseErrorKind::VarBindingName))?;
Ok(Expr::Var(name.to_string()))
}
"match" => parse_match(args, opts),
"step" => parse_step(args, opts),
"interpolate" => parse_interpolate(InterpSpace::Rgb, args, opts),
"interpolate-hcl" => parse_interpolate(InterpSpace::Hcl, args, opts),
"interpolate-lab" => parse_interpolate(InterpSpace::Lab, args, opts),
"format" => parse_format(args, opts),
"collator" => parse_collator(args, opts),
"number-format" => parse_number_format(args, opts),
"within" => parse_within(args),
"distance" => parse_distance(args),
"global-state" => {
check_generic_arity(op, args.len())?;
if !args[0].is_string() {
return Err(ParseError::of(ParseErrorKind::GlobalStateProperty {
found: js_typeof(&args[0]).to_string(),
}));
}
Ok(Expr::Call {
op: op.to_string(),
args: parse_all(args, opts)?,
})
}
"array" => {
check_generic_arity(op, args.len())?;
validate_array_type_args(args)?;
let parsed = parse_all(args, opts)?;
Ok(Expr::Call {
op: op.to_string(),
args: parsed,
})
}
_ => {
if let Some(e) = signature_arity_error(op, args) {
return Err(e);
}
check_generic_arity(op, args.len())?;
let args = parse_all(args, opts)?;
Ok(Expr::Call {
op: op.to_string(),
args,
})
}
}
}
fn expand_macro(op: &str, args: &[Json], opts: &Options) -> Result<Expr> {
let m = &opts.macros[op];
if args.len() != m.params.len() {
return Err(ParseError::of(ParseErrorKind::ExtArgCount {
kind: "Macro",
op: op.to_string(),
expected: m.params.len(),
found: args.len(),
}));
}
use std::sync::atomic::Ordering::Relaxed;
let depth = opts.depth.load(Relaxed);
if depth >= MAX_MACRO_DEPTH {
return Err(ParseError::of(ParseErrorKind::MacroDepth {
op: op.to_string(),
}));
}
opts.depth.store(depth + 1, Relaxed);
let result = (|| {
let arg_exprs = parse_all(args, opts)?;
let body = parse(&m.body, opts)?;
let bindings = m.params.iter().cloned().zip(arg_exprs).collect();
Ok(Expr::Let {
bindings,
body: Box::new(body),
})
})();
opts.depth.store(depth, Relaxed);
result
}
fn js_typeof(v: &Json) -> &'static str {
match v {
Json::Number(_) => "number",
Json::Bool(_) => "boolean",
Json::String(_) => "string",
_ => "object",
}
}
fn signature_arity_error(op: &str, args: &[Json]) -> Option<ParseError> {
let (sig, ok) = match op {
"e" | "pi" | "ln2" => ("()", args.is_empty()),
"typeof" => ("(value)", args.len() == 1),
"-" => ("(number, number) | (number)", (1..=2).contains(&args.len())),
_ => return None,
};
if ok {
return None;
}
let found: Vec<&str> = args.iter().map(js_typeof).collect();
Some(ParseError::of(ParseErrorKind::ExpectedArgsOfType {
sig: sig.to_string(),
found: found.join(", "),
}))
}
fn check_generic_arity(op: &str, argc: usize) -> Result<()> {
if op == "case" {
if argc < 3 || argc.is_multiple_of(2) {
return Err(ParseError::of(ParseErrorKind::ExpectedOddArgsCase));
}
return Ok(());
}
let range = arity(op).ok_or_else(|| {
ParseError::of(ParseErrorKind::UnknownExpression(op.to_string())).at(0)
})?;
let (min, max) = range;
if argc < min || max.is_some_and(|m| argc > m) {
if op == "to-boolean" || op == "to-string" {
return Err(ParseError::of(ParseErrorKind::ExpectedOneArgument));
}
let plural = |n: usize| if n == 1 { "argument" } else { "arguments" };
let expected = match max {
Some(m) if m == min => format!("{min} {}", plural(min)),
Some(m) if m == min + 1 => format!("{min} or {m} arguments"),
Some(m) => format!("{min} to {m} arguments"),
None => format!("at least {min} arguments"),
};
return Err(ParseError::of(ParseErrorKind::WrongArgCount {
op: op.to_string(),
expected,
found: argc,
}));
}
Ok(())
}
fn arity(op: &str) -> Option<(usize, Option<usize>)> {
Some(match op {
"get" => (1, Some(2)),
"has" => (1, Some(2)),
"properties"
| "id"
| "geometry-type"
| "zoom"
| "heatmap-density"
| "line-progress"
| "accumulated"
| "e"
| "pi"
| "ln2"
| "raster-value"
| "sky-radial-progress"
| "measure-light"
| "elevation" => (0, Some(0)),
"at" => (2, Some(2)),
"in" => (2, Some(2)),
"index-of" => (2, Some(3)),
"slice" => (2, Some(3)),
"length" => (1, Some(1)),
"feature-state" | "config" => (1, Some(2)),
"global-state" => (1, Some(1)),
"!" => (1, Some(1)),
"all" | "any" | "coalesce" => (0, None),
"error" => (1, Some(1)),
"==" | "!=" | "<" | ">" | "<=" | ">=" => (2, Some(3)),
"+" | "*" | "min" | "max" => (0, None),
"-" => (1, Some(2)),
"/" | "%" | "^" => (2, Some(2)),
"abs" | "acos" | "asin" | "atan" | "ceil" | "cos" | "floor" | "ln" | "log10" | "log2"
| "round" | "sin" | "sqrt" | "tan" => (1, Some(1)),
"distance" => (1, Some(1)),
"concat" => (0, None),
"upcase" | "downcase" => (1, Some(1)),
"join" => (2, Some(2)),
"split" => (2, Some(2)),
"is-supported-script" => (1, Some(1)),
"resolved-locale" => (1, Some(1)),
"number-format" => (2, Some(2)),
"format" | "image" => (1, None),
"array" => (1, None),
"boolean" | "number" | "string" | "object" | "to-number" | "to-color" => (1, None),
"to-boolean" | "to-string" | "to-rgba" | "typeof" => (1, Some(1)),
"rgb" => (3, Some(3)),
"rgba" => (4, Some(4)),
"within" => (1, Some(1)),
_ => return None,
})
}
fn parse_let(args: &[Json], opts: &Options) -> Result<Expr> {
if args.is_empty() || args.len().is_multiple_of(2) {
return Err(ParseError::of(ParseErrorKind::ExpectedOddArgsLet));
}
let mut bindings = Vec::new();
let mut i = 0;
while i + 1 < args.len() {
let name = args[i]
.as_str()
.ok_or_else(|| ParseError::of(ParseErrorKind::LetBindingNameString))?;
bindings.push((name.to_string(), parse(&args[i + 1], opts)?));
i += 2;
}
let body = parse(&args[args.len() - 1], opts)?;
Ok(Expr::Let {
bindings,
body: Box::new(body),
})
}
fn parse_match(args: &[Json], opts: &Options) -> Result<Expr> {
if args.len() < 4 {
return Err(ParseError::of(ParseErrorKind::MatchAtLeast4 {
found: args.len(),
}));
}
if !args.len().is_multiple_of(2) {
return Err(ParseError::of(ParseErrorKind::ExpectedEvenArgs {
op: "match",
}));
}
let input = parse(&args[0], opts).map_err(|e| e.at(1))?;
let mut arms = Vec::new();
let mut i = 1;
while i + 1 < args.len() {
let labels = parse_match_labels(&args[i]).map_err(|e| e.at(i + 1))?;
let output = parse(&args[i + 1], opts).map_err(|e| e.at(i + 2))?;
arms.push((labels, output));
i += 2;
}
let default = parse(&args[args.len() - 1], opts).map_err(|e| e.at(args.len()))?;
Ok(Expr::Match {
input: Box::new(input),
arms,
default: Box::new(default),
})
}
fn parse_match_labels(json: &Json) -> Result<Vec<Value>> {
match json {
Json::Array(items) => Ok(items.iter().map(Value::from_json).collect()),
Json::Number(_) | Json::String(_) => Ok(vec![Value::from_json(json)]),
_ => Err(ParseError::of(ParseErrorKind::BranchLabelsType)),
}
}
fn parse_step(args: &[Json], opts: &Options) -> Result<Expr> {
if args.len() < 3 || args.len() % 2 == 1 {
return Err(ParseError::of(ParseErrorKind::ExpectedEvenArgs {
op: "step",
}));
}
let input = parse(&args[0], opts).map_err(|e| e.at(1))?;
let output0 = parse(&args[1], opts).map_err(|e| e.at(2))?;
let mut stops = Vec::new();
let mut i = 2;
while i + 1 < args.len() {
let stop = args[i]
.as_f64()
.ok_or_else(|| ParseError::of(ParseErrorKind::StepStopNumber))?;
stops.push((stop, parse(&args[i + 1], opts).map_err(|e| e.at(i + 2))?));
i += 2;
}
check_ascending("step", &stops)?;
Ok(Expr::Step {
input: Box::new(input),
output0: Box::new(output0),
stops,
})
}
fn parse_interpolate(space: InterpSpace, args: &[Json], opts: &Options) -> Result<Expr> {
if args.len() < 4 || args.len() % 2 == 1 {
return Err(ParseError::of(ParseErrorKind::ExpectedEvenArgs {
op: "interpolate",
}));
}
let kind = parse_interp_kind(&args[0]).map_err(|e| e.at(1))?;
let input = parse(&args[1], opts).map_err(|e| e.at(2))?;
let mut stops = Vec::new();
let mut i = 2;
while i + 1 < args.len() {
let stop = args[i]
.as_f64()
.ok_or_else(|| ParseError::of(ParseErrorKind::InterpolationStopNumber))?;
stops.push((stop, parse(&args[i + 1], opts).map_err(|e| e.at(i + 2))?));
i += 2;
}
check_ascending("interpolate", &stops)?;
Ok(Expr::Interpolate {
kind,
space,
input: Box::new(input),
stops,
projection: false,
})
}
fn parse_collator(args: &[Json], opts: &Options) -> Result<Expr> {
if args.len() != 1 {
return Err(ParseError::of(ParseErrorKind::CollatorOneArg));
}
let obj = args[0]
.as_object()
.ok_or_else(|| ParseError::of(ParseErrorKind::CollatorOptions))?;
let opt = |key: &str| -> Result<Option<Box<Expr>>> {
match obj.get(key) {
Some(v) => Ok(Some(Box::new(parse(v, opts)?))),
None => Ok(None),
}
};
Ok(Expr::Collator {
case_sensitive: opt("case-sensitive")?,
diacritic_sensitive: opt("diacritic-sensitive")?,
locale: opt("locale")?,
})
}
fn parse_number_format(args: &[Json], opts: &Options) -> Result<Expr> {
if args.len() != 2 {
return Err(ParseError::of(ParseErrorKind::NumberFormatTwoArgs));
}
let value = Box::new(parse(&args[0], opts)?);
let obj = args[1]
.as_object()
.ok_or_else(|| ParseError::of(ParseErrorKind::NumberFormatOptionsObject))?;
if obj.contains_key("currency") && obj.contains_key("unit") {
return Err(ParseError::of(ParseErrorKind::NumberFormatExclusive));
}
let opt = |key: &str| -> Result<Option<Box<Expr>>> {
match obj.get(key) {
Some(v) => Ok(Some(Box::new(parse(v, opts)?))),
None => Ok(None),
}
};
Ok(Expr::NumberFormat {
value,
locale: opt("locale")?,
currency: opt("currency")?,
min_fraction_digits: opt("min-fraction-digits")?,
max_fraction_digits: opt("max-fraction-digits")?,
unit: opt("unit")?,
})
}
fn parse_within(args: &[Json]) -> Result<Expr> {
let err = || {
ParseError::of(ParseErrorKind::GeojsonPolygon {
op: "within".to_string(),
})
};
if args.len() != 1 {
return Err(ParseError::of(ParseErrorKind::RequiresExactlyOneArg {
op: "within".to_string(),
found: args.len(),
}));
}
let geojson = &args[0];
let mut polygons: Vec<Vec<Vec<(f64, f64)>>> = Vec::new();
let mut add_geometry =
|ty: Option<&str>, coords: Option<&Json>| match (ty, coords.and_then(Json::as_array)) {
(Some("Polygon"), Some(c)) => {
if let Some(p) = parse_polygon(c) {
polygons.push(p);
}
}
(Some("MultiPolygon"), Some(c)) => {
for poly in c.iter().filter_map(Json::as_array) {
if let Some(p) = parse_polygon(poly) {
polygons.push(p);
}
}
}
_ => {}
};
match geojson.get("type").and_then(Json::as_str) {
Some("FeatureCollection") => {
for feat in geojson
.get("features")
.and_then(Json::as_array)
.into_iter()
.flatten()
{
let g = feat.get("geometry");
add_geometry(
g.and_then(|g| g.get("type")).and_then(Json::as_str),
g.and_then(|g| g.get("coordinates")),
);
}
}
Some("Feature") => {
let g = geojson.get("geometry");
add_geometry(
g.and_then(|g| g.get("type")).and_then(Json::as_str),
g.and_then(|g| g.get("coordinates")),
);
}
Some(t @ ("Polygon" | "MultiPolygon")) => {
add_geometry(Some(t), geojson.get("coordinates"));
}
_ => {}
}
if polygons.is_empty() {
return Err(err());
}
Ok(Expr::Within(polygons))
}
fn parse_distance(args: &[Json]) -> Result<Expr> {
let err = || {
ParseError::of(ParseErrorKind::GeojsonPolygon {
op: "distance".to_string(),
})
};
if args.len() != 1 {
return Err(ParseError::of(ParseErrorKind::RequiresExactlyOneArg {
op: "distance".to_string(),
found: args.len(),
}));
}
let mut geoms: Vec<SimpleGeom> = Vec::new();
match args[0].get("type").and_then(Json::as_str) {
Some("FeatureCollection") => {
for feat in args[0]
.get("features")
.and_then(Json::as_array)
.into_iter()
.flatten()
{
if let Some(g) = feat.get("geometry") {
add_simple_geometry(g, &mut geoms);
}
}
}
Some("Feature") => {
if let Some(g) = args[0].get("geometry") {
add_simple_geometry(g, &mut geoms);
}
}
Some(_) => add_simple_geometry(&args[0], &mut geoms),
None => {}
}
if geoms.is_empty() {
return Err(err());
}
Ok(Expr::Distance(geoms))
}
fn parse_point(c: &Json) -> Option<(f64, f64)> {
let a = c.as_array()?;
Some((a.first()?.as_f64()?, a.get(1)?.as_f64()?))
}
fn parse_line(c: &Json) -> Vec<(f64, f64)> {
c.as_array()
.map(|a| a.iter().filter_map(parse_point).collect())
.unwrap_or_default()
}
fn add_simple_geometry(geom: &Json, out: &mut Vec<SimpleGeom>) {
let coords = geom.get("coordinates");
match geom.get("type").and_then(Json::as_str) {
Some("Point") => {
if let Some(p) = coords.and_then(parse_point) {
out.push(SimpleGeom::Point(p));
}
}
Some("MultiPoint") => {
for p in coords.and_then(Json::as_array).into_iter().flatten() {
if let Some(p) = parse_point(p) {
out.push(SimpleGeom::Point(p));
}
}
}
Some("LineString") => {
if let Some(c) = coords {
out.push(SimpleGeom::Line(parse_line(c)));
}
}
Some("MultiLineString") => {
for l in coords.and_then(Json::as_array).into_iter().flatten() {
out.push(SimpleGeom::Line(parse_line(l)));
}
}
Some("Polygon") => {
if let Some(c) = coords.and_then(Json::as_array) {
if let Some(p) = parse_polygon(c) {
out.push(SimpleGeom::Polygon(p));
}
}
}
Some("MultiPolygon") => {
for poly in coords.and_then(Json::as_array).into_iter().flatten() {
if let Some(p) = poly.as_array().and_then(|r| parse_polygon(r)) {
out.push(SimpleGeom::Polygon(p));
}
}
}
_ => {}
}
}
fn parse_polygon(rings: &[Json]) -> Option<Vec<Vec<(f64, f64)>>> {
let mut out = Vec::new();
for ring in rings.iter().filter_map(Json::as_array) {
let mut r = Vec::new();
for pt in ring.iter().filter_map(Json::as_array) {
let lng = pt.first().and_then(Json::as_f64)?;
let lat = pt.get(1).and_then(Json::as_f64)?;
r.push((lng, lat));
}
out.push(r);
}
Some(out)
}
fn parse_format(args: &[Json], opts: &Options) -> Result<Expr> {
if args.is_empty() {
return Err(ParseError::of(ParseErrorKind::FormatAtLeastOne));
}
if args[0].is_object() {
return Err(ParseError::of(ParseErrorKind::FormatFirstSection));
}
let mut sections: Vec<FormatArg> = Vec::new();
let mut next_may_be_object = false;
let mut content_pos = 1;
for (j, arg) in args.iter().enumerate() {
let pos = j + 1;
if next_may_be_object && arg.is_object() {
next_may_be_object = false;
let obj = arg.as_object().unwrap();
let sec = content_pos;
let section = sections.last_mut().unwrap();
if let Some(v) = obj.get("font-scale") {
section.scale = Some(parse(v, opts).map_err(|e| e.at(sec))?);
}
if let Some(v) = obj.get("text-font") {
section.font = Some(parse(v, opts).map_err(|e| e.at(sec))?);
}
if let Some(v) = obj.get("text-color") {
section.text_color = Some(parse(v, opts).map_err(|e| e.at(sec))?);
}
if let Some(v) = obj.get("vertical-align") {
if let Some(s) = v.as_str() {
if !VERTICAL_ALIGN.contains(&s) {
return Err(ParseError::of(ParseErrorKind::VerticalAlign {
found: s.to_string(),
}));
}
}
section.vertical_align = Some(parse(v, opts).map_err(|e| e.at(sec))?);
}
} else {
content_pos = pos;
sections.push(FormatArg {
content: parse(arg, opts).map_err(|e| e.at(pos))?,
scale: None,
font: None,
text_color: None,
vertical_align: None,
});
next_may_be_object = true;
}
}
Ok(Expr::Format(sections))
}
fn parse_interp_kind(json: &Json) -> Result<InterpKind> {
let items = json
.as_array()
.ok_or_else(|| ParseError::of(ParseErrorKind::InterpolationTypeArray))?;
let name = items
.first()
.and_then(Json::as_str)
.ok_or_else(|| ParseError::of(ParseErrorKind::InterpolationTypeName))?;
match name {
"linear" => Ok(InterpKind::Linear),
"exponential" => {
let base = items
.get(1)
.and_then(Json::as_f64)
.ok_or_else(|| ParseError::of(ParseErrorKind::ExponentialBase).at(1))?;
Ok(InterpKind::Exponential(base))
}
"cubic-bezier" => {
let cubic_err = || ParseError::of(ParseErrorKind::CubicBezier);
if items.len() != 5 {
return Err(cubic_err());
}
let n = |i: usize| items.get(i).and_then(Json::as_f64);
match (n(1), n(2), n(3), n(4)) {
(Some(a), Some(b), Some(c), Some(d))
if [a, b, c, d].iter().all(|v| (0.0..=1.0).contains(v)) =>
{
Ok(InterpKind::CubicBezier(a, b, c, d))
}
_ => Err(cubic_err()),
}
}
other => Err(ParseError::of(ParseErrorKind::UnknownInterpolationType {
name: other.to_string(),
})),
}
}
fn validate_array_type_args(args: &[Json]) -> Result<()> {
if args.len() < 2 {
return Ok(());
}
match args[0].as_str() {
Some("string" | "number" | "boolean") => {}
_ => {
return Err(ParseError::of(ParseErrorKind::ArrayItemType).at(1));
}
}
if args.len() >= 3 {
if !args[1].is_null() {
match args[1].as_f64() {
Some(n) if n >= 0.0 && n.fract() == 0.0 => {}
_ => {
return Err(ParseError::of(ParseErrorKind::ArrayLength).at(2));
}
}
}
}
Ok(())
}
fn check_ascending(kind: &str, stops: &[(f64, Expr)]) -> Result<()> {
for (j, pair) in stops.windows(2).enumerate() {
if pair[1].0 <= pair[0].0 {
return Err(ParseError::of(ParseErrorKind::AscendingStops {
kind: kind.to_string(),
})
.at(3 + 2 * (j + 1)));
}
}
Ok(())
}
fn expect_arity(op: &str, args: &[Json], n: usize) -> Result<()> {
if args.len() == n {
Ok(())
} else if n == 1 {
Err(ParseError::of(ParseErrorKind::RequiresExactlyOneArg {
op: op.to_string(),
found: args.len(),
}))
} else {
Err(ParseError::of(ParseErrorKind::ExpectedNArgs {
n,
found: args.len(),
}))
}
}