use crate::types::{
Equation, Expr, ExpressionNode, JoinClause, OverlapClause, RangeSpec, RegionBound,
};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("{message} (at character {pos})")]
pub struct ExpressionParseError {
pub message: String,
pub pos: usize,
}
impl ExpressionParseError {
fn new(message: impl Into<String>, pos: usize) -> Self {
Self {
message: message.into(),
pos,
}
}
}
type PResult<T> = Result<T, ExpressionParseError>;
const INFIX: &[&str] = &[
"or", "and", "==", "!=", "<", ">", "<=", ">=", "+", "-", "*", "/", "^",
];
const FUNCTION_PRECEDENCE: i32 = 8;
fn op_precedence(op: &str) -> i32 {
match op {
"or" => 1,
"and" => 2,
">" | "<" | ">=" | "<=" | "==" | "!=" | "=" => 3,
"+" | "-" => 4,
"*" | "/" => 5,
"not" => 6,
"^" => 7,
_ => FUNCTION_PRECEDENCE,
}
}
const UMINUS_MIN: i32 = 5;
const NOT_MIN: i32 = 6;
const TEMPLATE_ARG_MIN: i32 = 4;
const STRUCTURAL_OPS: &[&str] = &["table_lookup", "broadcast", "enum"];
const AGG_SYMS: &[&str] = &["sum", "prod", "max", "min", "any"];
const ARGWITNESS_SYMS: &[&str] = &["argmin", "argmax"];
fn reduce_by_sym(sym: &str) -> Option<&'static str> {
match sym {
"prod" => Some("*"),
"max" => Some("max"),
"min" => Some("min"),
_ => None, }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Kind {
Num,
Name,
Op,
LParen,
RParen,
LBrack,
RBrack,
LBrace,
RBrace,
Colon,
Semi,
Comma,
Eq,
Eof,
}
#[derive(Debug, Clone)]
struct Tok {
kind: Kind,
text: String,
num: f64,
pos: usize,
}
impl Tok {
fn simple(kind: Kind, pos: usize) -> Self {
Tok {
kind,
text: String::new(),
num: 0.0,
pos,
}
}
fn is(&self, kind: Kind, v: &str) -> bool {
self.kind == kind && self.text == v
}
}
const MULTI_OPS: &[&str] = &[">=", "<=", "==", "!="];
const WORD_OPS: &[&str] = &["and", "or", "not"];
fn is_single_op(c: char) -> bool {
matches!(c, '+' | '-' | '*' | '/' | '^' | '>' | '<')
}
fn is_name_start(c: char) -> bool {
c == '_' || c == '∂' || c == '∇' || c.is_alphabetic()
}
fn is_name_continue(c: char) -> bool {
c == '_' || c == '.' || c == '∂' || c == '∇' || c.is_alphanumeric()
}
fn match_number(src: &[char], i: usize) -> Option<usize> {
let mut j = i;
let digits = |j: &mut usize| {
let start = *j;
while *j < src.len() && src[*j].is_ascii_digit() {
*j += 1;
}
*j > start
};
if digits(&mut j) {
if j < src.len() && src[j] == '.' {
j += 1;
digits(&mut j);
}
} else if j < src.len() && src[j] == '.' {
j += 1;
if !digits(&mut j) {
return None;
}
} else {
return None;
}
if j < src.len() && (src[j] == 'e' || src[j] == 'E') {
let mut k = j + 1;
if k < src.len() && (src[k] == '+' || src[k] == '-') {
k += 1;
}
let mut m = k;
if digits(&mut m) {
j = m;
}
}
Some(j - i)
}
fn tokenize(src: &str) -> PResult<Vec<Tok>> {
let chars: Vec<char> = src.chars().collect();
let mut toks: Vec<Tok> = Vec::new();
let mut i = 0usize;
while i < chars.len() {
let c = chars[i];
if matches!(c, ' ' | '\t' | '\n' | '\r') {
i += 1;
continue;
}
let simple = match c {
'(' => Some(Kind::LParen),
')' => Some(Kind::RParen),
'[' => Some(Kind::LBrack),
']' => Some(Kind::RBrack),
'{' => Some(Kind::LBrace),
'}' => Some(Kind::RBrace),
':' => Some(Kind::Colon),
';' => Some(Kind::Semi),
',' => Some(Kind::Comma),
_ => None,
};
if let Some(kind) = simple {
toks.push(Tok::simple(kind, i));
i += 1;
continue;
}
if i + 1 < chars.len() {
let two: String = chars[i..i + 2].iter().collect();
if MULTI_OPS.contains(&two.as_str()) {
toks.push(Tok {
kind: Kind::Op,
text: two,
num: 0.0,
pos: i,
});
i += 2;
continue;
}
}
if c == '=' {
toks.push(Tok::simple(Kind::Eq, i));
i += 1;
continue;
}
if is_single_op(c) {
toks.push(Tok {
kind: Kind::Op,
text: c.to_string(),
num: 0.0,
pos: i,
});
i += 1;
continue;
}
let num_len = if c == '.' || c.is_ascii_digit() {
match_number(&chars, i)
} else {
None
};
if let Some(len) = num_len {
let text: String = chars[i..i + len].iter().collect();
let num: f64 = text
.parse()
.map_err(|_| ExpressionParseError::new(format!("Malformed number {text:?}"), i))?;
toks.push(Tok {
kind: Kind::Num,
text,
num,
pos: i,
});
i += len;
continue;
}
if is_name_start(c) {
let mut j = i + 1;
while j < chars.len() && is_name_continue(chars[j]) {
j += 1;
}
let text: String = chars[i..j].iter().collect();
let kind = if WORD_OPS.contains(&text.as_str()) {
Kind::Op
} else {
Kind::Name
};
toks.push(Tok {
kind,
text,
num: 0.0,
pos: i,
});
i = j;
continue;
}
if !c.is_ascii() {
return Err(ExpressionParseError::new(
format!("unicode operator syntax ({c:?}) — use the ascii text form"),
i,
));
}
return Err(ExpressionParseError::new(
format!("Unexpected character {c:?}"),
i,
));
}
toks.push(Tok::simple(Kind::Eof, chars.len()));
Ok(toks)
}
struct Parser {
toks: Vec<Tok>,
p: usize,
}
impl Parser {
fn new(toks: Vec<Tok>) -> Self {
Parser { toks, p: 0 }
}
fn peek(&self) -> &Tok {
&self.toks[(self.p).min(self.toks.len() - 1)]
}
fn peek_at(&self, k: usize) -> &Tok {
&self.toks[(self.p + k).min(self.toks.len() - 1)]
}
fn next(&mut self) -> Tok {
let t = self.toks[self.p.min(self.toks.len() - 1)].clone();
if self.p < self.toks.len() {
self.p += 1;
}
t
}
fn expect(&mut self, kind: Kind, what: &str) -> PResult<Tok> {
if self.peek().kind != kind {
return Err(self.fail_here(format!("Expected {what}")));
}
Ok(self.next())
}
fn expect_op(&mut self, v: &str, what: &str) -> PResult<()> {
if !self.peek().is(Kind::Op, v) {
return Err(self.fail_here(format!("Expected {what}")));
}
self.next();
Ok(())
}
fn fail_here(&self, msg: impl Into<String>) -> ExpressionParseError {
ExpressionParseError::new(msg, self.peek().pos)
}
fn at_word(&self, v: &str) -> bool {
self.peek().is(Kind::Name, v)
}
fn parse_entry(&mut self) -> PResult<Expr> {
let e = self.parse_expr(0)?;
if self.peek().kind != Kind::Eof {
return Err(self.fail_here("Unexpected trailing input"));
}
Ok(flatten(&e))
}
fn parse_expr(&mut self, min_prec: i32) -> PResult<Expr> {
let mut left = self.parse_prefix()?;
loop {
let (op, prec) = {
let t = self.peek();
if t.kind != Kind::Op || !INFIX.contains(&t.text.as_str()) {
break;
}
(t.text.clone(), op_precedence(&t.text))
};
if prec < min_prec {
break;
}
self.next();
let rhs = self.parse_expr(if op == "^" { prec } else { prec + 1 })?;
left = node(&op, vec![left, rhs]);
}
Ok(left)
}
fn parse_prefix(&mut self) -> PResult<Expr> {
if self.peek().is(Kind::Op, "-") && self.peek_at(1).kind == Kind::Num {
self.next();
let n = self.next();
return Ok(Expr::Number(-n.num));
}
if self.peek().kind == Kind::Op && (self.peek().text == "-" || self.peek().text == "not") {
let op = self.next().text;
let min = if op == "not" { NOT_MIN } else { UMINUS_MIN };
let operand = self.parse_expr(min)?;
return Ok(node(&op, vec![operand]));
}
self.parse_postfix()
}
fn parse_postfix(&mut self) -> PResult<Expr> {
let mut n = self.parse_atom()?;
while self.peek().kind == Kind::LBrack {
if self.peek_at(1).is(Kind::Name, "semiring") {
break;
}
if matches!(n, Expr::Variable(_))
&& self.peek_at(1).kind == Kind::Name
&& self.peek_at(2).kind == Kind::Eq
{
let Expr::Variable(table) = n else {
unreachable!("matched above")
};
n = self.parse_table_lookup(table)?;
continue;
}
self.next(); let mut idx = vec![self.parse_expr(0)?];
while self.peek().kind == Kind::Comma {
self.next();
idx.push(self.parse_expr(0)?);
}
self.expect(Kind::RBrack, "']'")?;
let mut args = vec![n];
args.extend(idx);
n = node("index", args);
}
if let Expr::Operator(d) = &n
&& d.op == "D"
&& self.peek().is(Kind::Op, "/")
{
let name_tok = self.peek_at(1).clone();
if name_tok.kind == Kind::Name
&& name_tok.text.chars().count() > 1
&& name_tok.text.starts_with('D')
{
self.next(); self.next(); let mut out = ExpressionNode {
op: "D".to_string(),
args: d.args.clone(),
..Default::default()
};
out.wrt = Some(name_tok.text.chars().skip(1).collect());
return Ok(Expr::operator(out));
}
}
Ok(n)
}
fn parse_atom(&mut self) -> PResult<Expr> {
let t = self.next();
match t.kind {
Kind::Num => Ok(Expr::Number(t.num)),
Kind::LParen => {
let e = self.parse_expr(0)?;
self.expect(Kind::RParen, "')'")?;
Ok(e)
}
Kind::LBrack => {
let value = self.parse_array_rest()?;
let mut n = ExpressionNode {
op: "const".to_string(),
args: Vec::new(),
..Default::default()
};
n.value = Some(Value::Array(value));
Ok(Expr::operator(n))
}
Kind::Name => {
let name = t.text;
if name == "true" || name == "false" {
return Ok(node(&name, Vec::new()));
}
if name == "makearray" && self.peek().kind == Kind::LParen {
return self.parse_makearray();
}
if self.peek().kind == Kind::LParen {
return self.parse_call(&name);
}
if self.peek().is(Kind::Op, "<")
&& (self.peek_at(1).is(Kind::Op, ">")
|| (self.peek_at(1).kind == Kind::Name && self.peek_at(2).kind == Kind::Eq))
{
return self.parse_template(&name);
}
if AGG_SYMS.contains(&name.as_str())
&& self.peek().kind == Kind::LBrack
&& self.aggregate_ahead()
{
return self.parse_aggregate(&name);
}
if ARGWITNESS_SYMS.contains(&name.as_str())
&& self.peek().kind == Kind::LBrack
&& self.aggregate_ahead()
{
return self.parse_arg_witness(&name);
}
Ok(Expr::Variable(name)) }
_ => Err(ExpressionParseError::new(
"Expected a number, name, '(', or '['",
t.pos,
)),
}
}
fn parse_array_rest(&mut self) -> PResult<Vec<Value>> {
let mut els: Vec<Value> = Vec::new();
if self.peek().kind != Kind::RBrack {
loop {
if self.peek().kind == Kind::LBrack {
self.next();
els.push(Value::Array(self.parse_array_rest()?)); } else {
let e = self.parse_expr(0)?;
els.push(expr_to_value(&e, self.peek().pos)?);
}
if self.peek().kind == Kind::Comma {
self.next();
continue;
}
break;
}
}
self.expect(Kind::RBrack, "']'")?;
Ok(els)
}
fn parse_call(&mut self, name: &str) -> PResult<Expr> {
self.next(); let mut args: Vec<Expr> = Vec::new();
let mut named: Vec<(String, Expr)> = Vec::new();
if self.peek().kind != Kind::RParen {
loop {
if self.peek().kind == Kind::Name && self.peek_at(1).kind == Kind::Eq {
let key = self.next().text;
self.next(); let v = self.parse_expr(0)?;
named.push((key, v));
} else {
args.push(self.parse_expr(0)?);
}
if self.peek().kind == Kind::Comma {
self.next();
continue;
}
break;
}
}
self.expect(Kind::RParen, &format!("',' or ')' in call to {name}(...)"))?;
make_call(name, args, named, self.peek().pos)
}
fn aggregate_ahead(&self) -> bool {
let mut depth = 0i32;
for i in self.p..self.toks.len() {
match self.toks[i].kind {
Kind::LBrack => depth += 1,
Kind::RBrack => {
depth -= 1;
if depth == 0 {
return self.toks.get(i + 1).is_some_and(|t| t.kind == Kind::LParen);
}
}
_ => {}
}
}
false
}
fn parse_aggregate(&mut self, sym: &str) -> PResult<Expr> {
self.next(); let mut output_idx: Vec<String> = Vec::new();
if self.peek().kind != Kind::RBrack {
loop {
let t = self.next();
if t.kind != Kind::Name {
return Err(ExpressionParseError::new(
"Expected an output index name",
t.pos,
));
}
output_idx.push(t.text);
if self.peek().kind == Kind::Comma {
self.next();
continue;
}
break;
}
}
self.expect(Kind::RBrack, "']' after aggregate output indices")?;
self.expect(Kind::LParen, "'(' before the aggregate body")?;
let expr = self.parse_expr(0)?;
self.expect(Kind::RParen, "')' after the aggregate body")?;
let mut ranges: HashMap<String, RangeSpec> = HashMap::new();
if self.at_word("where") {
self.next();
ranges = self.parse_ranges()?;
}
let mut join: Vec<JoinClause> = Vec::new();
if self.at_word("join") {
self.next();
join = self.parse_join()?;
}
let mut filter: Option<Expr> = None;
if self.at_word("if") {
self.next();
filter = Some(self.parse_expr(0)?);
}
let mut distinct = false;
if self.at_word("distinct") {
self.next();
distinct = true;
}
let mut key: Option<Expr> = None;
if self.at_word("key") && self.peek_at(1).kind == Kind::Eq {
self.next(); self.next(); key = Some(self.parse_expr(0)?);
}
let id = self.parse_id_clause()?;
let mut semiring: Option<String> = None;
if self.peek().kind == Kind::LBrack && self.peek_at(1).is(Kind::Name, "semiring") {
self.next(); self.next(); self.expect(Kind::Eq, "'=' in [semiring=…]")?;
let nm = self.next();
if nm.kind != Kind::Name {
return Err(ExpressionParseError::new(
"Expected a semiring name",
nm.pos,
));
}
semiring = Some(nm.text);
self.expect(Kind::RBrack, "']' after [semiring=…]")?;
}
if semiring.is_none() && !join.is_empty() {
semiring = Some("sum_product".to_string());
}
let mut n = ExpressionNode {
op: "faq".to_string(),
args: derive_aggregate_args(&expr, &join, filter.as_ref(), key.as_ref()),
..Default::default()
};
n.output_idx = Some(output_idx);
if semiring.is_some() {
n.semiring = semiring;
} else {
n.reduce = reduce_by_sym(sym).map(str::to_string);
}
n.ranges = Some(ranges);
if !join.is_empty() {
n.join = Some(join);
}
n.filter = filter.map(Box::new);
if distinct {
n.distinct = Some(true);
}
n.key = key.map(Box::new);
n.id = id;
n.expr = Some(Box::new(expr));
Ok(Expr::operator(n))
}
fn parse_arg_witness(&mut self, op: &str) -> PResult<Expr> {
self.next(); let at = self.next();
if at.kind != Kind::Name {
return Err(ExpressionParseError::new(
"Expected the arg-witness index name",
at.pos,
));
}
self.expect(Kind::RBrack, "']' after the arg-witness index")?;
self.expect(Kind::LParen, "'(' before the arg-witness body")?;
let expr = self.parse_expr(0)?;
self.expect(Kind::RParen, "')' after the arg-witness body")?;
let mut ranges: HashMap<String, RangeSpec> = HashMap::new();
if self.at_word("where") {
self.next();
ranges = self.parse_ranges()?;
}
let mut join: Vec<JoinClause> = Vec::new();
if self.at_word("join") {
self.next();
join = self.parse_join()?;
}
let mut filter: Option<Expr> = None;
if self.at_word("if") {
self.next();
filter = Some(self.parse_expr(0)?);
}
let id = self.parse_id_clause()?;
let mut n = ExpressionNode {
op: op.to_string(),
args: derive_aggregate_args(&expr, &join, filter.as_ref(), None),
..Default::default()
};
n.arg = Some(at.text);
n.ranges = Some(ranges);
if !join.is_empty() {
n.join = Some(join);
}
n.filter = filter.map(Box::new);
n.expr = Some(Box::new(expr));
n.id = id;
Ok(Expr::operator(n))
}
fn parse_id_clause(&mut self) -> PResult<Option<String>> {
if !(self.at_word("id") && self.peek_at(1).kind == Kind::Eq) {
return Ok(None);
}
self.next(); self.next(); let nm = self.next();
if nm.kind != Kind::Name {
return Err(ExpressionParseError::new(
"Expected a name after id=",
nm.pos,
));
}
Ok(Some(nm.text))
}
fn parse_table_lookup(&mut self, table: String) -> PResult<Expr> {
self.expect(Kind::LBrack, "'['")?;
let mut axes: HashMap<String, Expr> = HashMap::new();
loop {
let nm = self.next();
if nm.kind != Kind::Name {
return Err(ExpressionParseError::new(
"Expected an axis name in table[axis=…]",
nm.pos,
));
}
self.expect(Kind::Eq, "'=' in table[axis=…]")?;
let v = self.parse_expr(0)?;
axes.insert(nm.text, v);
if self.peek().kind == Kind::Comma {
self.next();
continue;
}
break;
}
self.expect(Kind::RBrack, "']' after table[axis=…]")?;
let mut n = ExpressionNode {
op: "table_lookup".to_string(),
args: Vec::new(),
..Default::default()
};
n.table = Some(table);
n.axes = Some(axes);
if self.peek().kind == Kind::Colon {
self.next();
let out = self.next();
if out.kind != Kind::Num || out.num.fract() != 0.0 || !out.num.is_finite() {
return Err(ExpressionParseError::new(
"Expected an integer output index after table[…]:",
out.pos,
));
}
n.output = Some(Value::from(out.num as i64));
}
Ok(Expr::operator(n))
}
fn parse_ranges(&mut self) -> PResult<HashMap<String, RangeSpec>> {
self.expect(Kind::LBrace, "'{' after where")?;
let mut ranges: HashMap<String, RangeSpec> = HashMap::new();
if self.peek().kind != Kind::RBrace {
loop {
let kt = self.next();
if kt.kind != Kind::Name {
return Err(ExpressionParseError::new(
"Expected a range index name",
kt.pos,
));
}
if !self.at_word("in") {
return Err(self.fail_here("Expected 'in' in a range clause"));
}
self.next(); let rhs = self.parse_range_rhs()?;
ranges.insert(kt.text, rhs);
if self.peek().kind == Kind::Comma {
self.next();
continue;
}
break;
}
}
self.expect(Kind::RBrace, "'}' to close the where clause")?;
Ok(ranges)
}
fn parse_range_rhs(&mut self) -> PResult<RangeSpec> {
let pos = self.peek().pos;
let bound = self.parse_expr(0)?;
if self.peek().kind == Kind::Colon {
self.next();
let hi_pos = self.peek().pos;
let hi = self.parse_expr(0)?;
let lo = expr_as_i64(&bound).ok_or_else(|| {
ExpressionParseError::new("a range bound must be an integer literal", pos)
})?;
let hi = expr_as_i64(&hi).ok_or_else(|| {
ExpressionParseError::new("a range bound must be an integer literal", hi_pos)
})?;
return Ok(RangeSpec::Interval([lo, hi]));
}
if let Expr::Variable(name) = &bound {
return Ok(RangeSpec::IndexSetRef {
from: name.clone(),
of: None,
});
}
if let Expr::Operator(n) = &bound
&& n.op.starts_with(|c: char| c == '_' || c.is_alphabetic())
{
let mut of = Vec::with_capacity(n.args.len());
for a in &n.args {
match a {
Expr::Variable(s) => of.push(s.clone()),
_ => {
return Err(self.fail_here("range set arguments must be names"));
}
}
}
return Ok(RangeSpec::IndexSetRef {
from: n.op.clone(),
of: Some(of),
});
}
Err(self.fail_here("malformed range (expected a set name, set(of…), or lo:hi)"))
}
fn parse_join(&mut self) -> PResult<Vec<JoinClause>> {
self.expect(Kind::LParen, "'(' after join")?;
let mut clauses: Vec<JoinClause> = Vec::new();
loop {
clauses.push(self.parse_join_clause()?);
if self.peek().kind == Kind::Semi {
self.next();
continue;
}
break;
}
self.expect(Kind::RParen, "')' to close join(…)")?;
Ok(clauses)
}
fn parse_join_clause(&mut self) -> PResult<JoinClause> {
if self.at_word("overlap") && self.peek_at(1).kind == Kind::LParen {
self.next(); return Ok(JoinClause {
overlap: Some(self.parse_overlap()?),
..Default::default()
});
}
let start = self.peek().pos;
let mut on: Vec<[String; 2]> = Vec::new();
let mut syms: Option<[String; 2]> = None;
loop {
let a = self.next();
if a.kind != Kind::Name {
return Err(ExpressionParseError::new("Expected a join key name", a.pos));
}
self.expect(Kind::Eq, "'=' in a join pair")?;
if a.text == "syms" && self.peek().kind == Kind::LBrack {
if syms.is_some() {
return Err(ExpressionParseError::new(
"duplicate syms=[…] in a join clause",
a.pos,
));
}
let names = self.parse_name_list("syms")?;
let Ok(pair) = <[String; 2]>::try_from(names) else {
return Err(ExpressionParseError::new(
"syms=[…] names exactly two range symbols",
a.pos,
));
};
syms = Some(pair);
} else {
let b = self.next();
if b.kind != Kind::Name {
return Err(ExpressionParseError::new("Expected a join key name", b.pos));
}
on.push([a.text, b.text]);
}
if self.peek().kind == Kind::Comma {
self.next();
continue;
}
break;
}
if on.is_empty() {
return Err(ExpressionParseError::new(
"a join clause needs at least one key pair l=r",
start,
));
}
Ok(JoinClause {
on,
syms,
..Default::default()
})
}
fn parse_overlap(&mut self) -> PResult<OverlapClause> {
self.expect(Kind::LParen, "'(' after overlap")?;
let mut src: Option<Vec<String>> = None;
let mut tgt: Option<Vec<String>> = None;
let mut eps: Option<f64> = None;
loop {
let k = self.next();
if k.kind != Kind::Name {
return Err(ExpressionParseError::new(
"Expected src=, tgt= or eps= in overlap(…)",
k.pos,
));
}
self.expect(Kind::Eq, &format!("'=' after {} in overlap(…)", k.text))?;
match k.text.as_str() {
"src" | "tgt" if (if k.text == "src" { &src } else { &tgt }).is_none() => {
let names = self.parse_name_list(&k.text)?;
if names.is_empty() {
return Err(ExpressionParseError::new(
format!("overlap {}=[…] must name at least one factor", k.text),
k.pos,
));
}
if k.text == "src" {
src = Some(names);
} else {
tgt = Some(names);
}
}
"eps" if eps.is_none() => match self.parse_expr(0)? {
Expr::Number(v) if v >= 0.0 => eps = Some(v),
Expr::Integer(v) if v >= 0 => eps = Some(v as f64),
_ => {
return Err(ExpressionParseError::new(
"overlap eps= must be a non-negative number",
k.pos,
));
}
},
_ => {
return Err(ExpressionParseError::new(
format!("unexpected or duplicate {}= in overlap(…)", k.text),
k.pos,
));
}
}
if self.peek().kind == Kind::Comma {
self.next();
continue;
}
break;
}
self.expect(Kind::RParen, "')' to close overlap(…)")?;
let (Some(src_env), Some(tgt_env)) = (src, tgt) else {
return Err(self.fail_here("overlap(…) requires both src=[…] and tgt=[…]"));
};
Ok(OverlapClause {
src_env,
tgt_env,
eps,
sym_src: None,
sym_tgt: None,
})
}
fn parse_name_list(&mut self, what: &str) -> PResult<Vec<String>> {
self.expect(Kind::LBrack, &format!("'[' after {what}="))?;
let mut names: Vec<String> = Vec::new();
if self.peek().kind != Kind::RBrack {
loop {
let t = self.next();
if t.kind != Kind::Name {
return Err(ExpressionParseError::new(
format!("Expected a name in {what}=[…]"),
t.pos,
));
}
names.push(t.text);
if self.peek().kind == Kind::Comma {
self.next();
continue;
}
break;
}
}
self.expect(Kind::RBrack, &format!("']' to close {what}=[…]"))?;
Ok(names)
}
fn parse_template(&mut self, name: &str) -> PResult<Expr> {
self.next(); let mut bindings: HashMap<String, Expr> = HashMap::new();
while !self.peek().is(Kind::Op, ">") {
let kt = self.next();
if kt.kind != Kind::Name {
return Err(ExpressionParseError::new(
"Expected a binding name in <…>",
kt.pos,
));
}
self.expect(Kind::Eq, "'=' in a template binding")?;
let v = self.parse_expr(TEMPLATE_ARG_MIN)?;
bindings.insert(kt.text, v);
if self.peek().kind == Kind::Comma {
self.next();
continue;
}
break;
}
self.expect_op(">", "'>' to close a template application")?;
let mut n = ExpressionNode {
op: "apply_expression_template".to_string(),
args: Vec::new(),
..Default::default()
};
n.name = Some(name.to_string());
n.bindings = Some(bindings);
Ok(Expr::operator(n))
}
fn parse_makearray(&mut self) -> PResult<Expr> {
self.next(); let mut regions: Vec<Vec<[RegionBound; 2]>> = Vec::new();
let mut values: Vec<Expr> = Vec::new();
if self.peek().kind != Kind::RParen {
loop {
self.expect(Kind::LBrack, "'[' to open a makearray region")?;
let mut region: Vec<[RegionBound; 2]> = Vec::new();
if self.peek().kind != Kind::RBrack {
loop {
let lo = self.parse_expr(0)?;
self.expect(Kind::Colon, "':' between a region's lo:hi bounds")?;
let hi = self.parse_expr(0)?;
region.push([region_bound(&lo), region_bound(&hi)]);
if self.peek().kind == Kind::Comma {
self.next();
continue;
}
break;
}
}
self.expect(Kind::RBrack, "']' to close a makearray region")?;
self.expect(Kind::Eq, "'=' after a makearray region")?;
regions.push(region);
let v = self.parse_expr(0)?;
values.push(flatten(&v));
if self.peek().kind == Kind::Comma {
self.next();
continue;
}
break;
}
}
self.expect(Kind::RParen, "')' to close makearray(...)")?;
let mut n = ExpressionNode {
op: "makearray".to_string(),
args: Vec::new(),
..Default::default()
};
n.regions = Some(regions);
n.values = Some(values);
Ok(Expr::operator(n))
}
}
fn node(op: &str, args: Vec<Expr>) -> Expr {
Expr::operator(ExpressionNode {
op: op.to_string(),
args,
..Default::default()
})
}
fn expr_as_i64(e: &Expr) -> Option<i64> {
match e {
Expr::Integer(i) => Some(*i),
Expr::Number(n) if n.fract() == 0.0 && n.abs() < 9_223_372_036_854_775_808.0 => {
Some(*n as i64)
}
_ => None,
}
}
fn region_bound(e: &Expr) -> RegionBound {
match expr_as_i64(e) {
Some(i) => RegionBound::Int(i),
None => RegionBound::Expr(flatten(e)),
}
}
fn expr_to_value(e: &Expr, pos: usize) -> PResult<Value> {
serde_json::to_value(e).map_err(|err| {
ExpressionParseError::new(format!("could not encode array element: {err}"), pos)
})
}
fn as_array_literal(e: &Expr, pos: usize) -> PResult<Vec<Value>> {
if let Expr::Operator(n) = e
&& n.op == "const"
&& let Some(Value::Array(v)) = &n.value
{
return Ok(v.clone());
}
Err(ExpressionParseError::new(
"expected an array literal [ ... ]",
pos,
))
}
fn as_int_array(e: &Expr, pos: usize) -> PResult<Vec<i64>> {
let items = as_array_literal(e, pos)?;
let mut out = Vec::with_capacity(items.len());
for it in items {
match it.as_i64() {
Some(i) => out.push(i),
None => {
return Err(ExpressionParseError::new(
"expected an array of integers",
pos,
));
}
}
}
Ok(out)
}
fn no_named(named: &[(String, Expr)], name: &str, pos: usize) -> PResult<()> {
match named.first() {
None => Ok(()),
Some((k, _)) => Err(ExpressionParseError::new(
format!("unexpected {k}=… in {name}(...)"),
pos,
)),
}
}
fn take_named(named: &[(String, Expr)], key: &str) -> Option<Expr> {
named.iter().find(|(k, _)| k == key).map(|(_, v)| v.clone())
}
fn make_call(name: &str, args: Vec<Expr>, named: Vec<(String, Expr)>, pos: usize) -> PResult<Expr> {
if name.contains('.') {
no_named(&named, name, pos)?;
let mut n = ExpressionNode {
op: "fn".to_string(),
args,
..Default::default()
};
n.name = Some(name.to_string());
return Ok(Expr::operator(n));
}
if name == "integral" && args.len() == 4 && matches!(args[1], Expr::Variable(_)) {
no_named(&named, name, pos)?;
let mut it = args.into_iter();
let a0 = it.next().expect("arity checked");
let var = match it.next().expect("arity checked") {
Expr::Variable(s) => s,
_ => unreachable!("matched above"),
};
let lower = it.next().expect("arity checked");
let upper = it.next().expect("arity checked");
let mut n = ExpressionNode {
op: "integral".to_string(),
args: vec![a0],
..Default::default()
};
n.int_var = Some(var);
n.lower = Some(Box::new(lower));
n.upper = Some(Box::new(upper));
return Ok(Expr::operator(n));
}
if name == "reshape" && args.len() == 2 {
no_named(&named, name, pos)?;
let shape = as_int_array(&args[1], pos)?;
let mut n = ExpressionNode {
op: "reshape".to_string(),
args: vec![args[0].clone()],
..Default::default()
};
n.shape = Some(shape);
return Ok(Expr::operator(n));
}
if name == "transpose" && (args.len() == 1 || args.len() == 2) {
no_named(&named, name, pos)?;
let perm = if args.len() == 2 {
Some(as_int_array(&args[1], pos)?)
} else {
None
};
let mut n = ExpressionNode {
op: "transpose".to_string(),
args: vec![args[0].clone()],
..Default::default()
};
n.perm = perm;
return Ok(Expr::operator(n));
}
if name == "concat" {
let Some(axis) = take_named(&named, "axis") else {
return Err(ExpressionParseError::new(
"concat(...) requires axis=<n>",
pos,
));
};
let Some(axis) = expr_as_i64(&axis) else {
return Err(ExpressionParseError::new(
"concat(...) requires an integer axis=<n>",
pos,
));
};
let mut n = ExpressionNode {
op: "concat".to_string(),
args,
..Default::default()
};
n.axis = Some(axis);
return Ok(Expr::operator(n));
}
if name == "polygon_intersection_area" || name == "intersect_polygon" {
let manifold = match take_named(&named, "manifold") {
Some(Expr::Variable(s)) => s,
_ => {
return Err(ExpressionParseError::new(
format!("{name}(...) requires manifold=<name>"),
pos,
));
}
};
if let Some((k, _)) = named.iter().find(|(k, _)| k != "manifold" && k != "id") {
return Err(ExpressionParseError::new(
format!("unexpected {k}=… in {name}(...)"),
pos,
));
}
let mut n = ExpressionNode {
op: name.to_string(),
args,
..Default::default()
};
n.manifold = Some(manifold);
if let Some(id) = take_named(&named, "id") {
let Expr::Variable(id) = id else {
return Err(ExpressionParseError::new(
format!("{name}(...) id=… must be a name"),
pos,
));
};
n.id = Some(id);
}
return Ok(Expr::operator(n));
}
if name == "skolem" && take_named(&named, "label").is_some() {
if let Some((k, _)) = named.iter().find(|(k, _)| k != "label") {
return Err(ExpressionParseError::new(
format!("unexpected {k}=… in skolem(...)"),
pos,
));
}
let Some(Expr::Variable(label)) = take_named(&named, "label") else {
return Err(ExpressionParseError::new(
"skolem(...) label=… must be a name",
pos,
));
};
let mut n = ExpressionNode {
op: "skolem".to_string(),
args,
..Default::default()
};
n.label = Some(label);
return Ok(Expr::operator(n));
}
no_named(&named, name, pos)?;
if STRUCTURAL_OPS.contains(&name) {
return Err(ExpressionParseError::new(
format!("'{name}' is not yet expressible in the text form"),
pos,
));
}
if name == "D" {
if args.len() == 2
&& let Expr::Variable(w) = &args[1]
{
let mut n = ExpressionNode {
op: "D".to_string(),
args: vec![args[0].clone()],
..Default::default()
};
n.wrt = Some(w.clone());
return Ok(Expr::operator(n));
}
if args.len() == 1 {
return Ok(node("D", args));
}
}
Ok(node(name, args))
}
fn derive_aggregate_args(
expr: &Expr,
join: &[JoinClause],
filter: Option<&Expr>,
key: Option<&Expr>,
) -> Vec<Expr> {
let mut out: Vec<String> = Vec::new();
collect_index_bases(expr, &mut out);
for c in join {
if let Some(ov) = &c.overlap {
for name in ov.src_env.iter().chain(&ov.tgt_env) {
push_unique(&mut out, name);
}
}
for pair in &c.on {
push_unique(&mut out, &pair[0]);
push_unique(&mut out, &pair[1]);
}
}
if let Some(f) = filter {
collect_index_bases(f, &mut out);
}
if let Some(k) = key {
collect_index_bases(k, &mut out);
}
out.into_iter().map(Expr::Variable).collect()
}
fn push_unique(out: &mut Vec<String>, name: &str) {
if !out.iter().any(|n| n == name) {
out.push(name.to_string());
}
}
fn collect_index_bases(e: &Expr, out: &mut Vec<String>) {
let Expr::Operator(n) = e else { return };
if n.op == "index"
&& let Some(Expr::Variable(base)) = n.args.first()
{
push_unique(out, base);
}
n.for_each_child(&mut |c| collect_index_bases(c, out));
}
fn flatten(e: &Expr) -> Expr {
let Expr::Operator(nd) = e else {
return e.clone();
};
let args: Vec<Expr> = nd.args.iter().map(flatten).collect();
let mut out = (**nd).clone();
for field in [&mut out.expr, &mut out.filter, &mut out.key] {
if let Some(v) = field.as_deref() {
*field = Some(Box::new(flatten(v)));
}
}
if matches!(nd.op.as_str(), "+" | "*" | "and" | "or") {
let mut merged: Vec<Expr> = Vec::with_capacity(args.len());
for a in args {
match &a {
Expr::Operator(an) if an.op == nd.op && an.wrt.is_none() => {
merged.extend(an.args.iter().cloned());
}
_ => merged.push(a),
}
}
out.args = merged;
} else {
out.args = args;
}
Expr::operator(out)
}
pub fn parse_expression(src: &str) -> Result<Expr, ExpressionParseError> {
Parser::new(tokenize(src)?).parse_entry()
}
pub fn parse_equation(src: &str) -> Result<Equation, ExpressionParseError> {
let toks = tokenize(src)?;
let mut depth = 0i32;
let mut angle = 0i32; let mut split: Option<usize> = None;
for i in 0..toks.len() {
if split.is_some() {
break;
}
let t = &toks[i];
match t.kind {
Kind::LParen | Kind::LBrack | Kind::LBrace => depth += 1,
Kind::RParen | Kind::RBrack | Kind::RBrace => depth -= 1,
Kind::Op
if t.text == "<"
&& toks.get(i + 1).is_some_and(|n| n.kind == Kind::Name)
&& toks.get(i + 2).is_some_and(|n| n.kind == Kind::Eq) =>
{
angle += 1;
}
Kind::Op if t.text == ">" && angle > 0 => angle -= 1,
Kind::Eq if depth == 0 && angle == 0 => split = Some(i),
_ => {}
}
}
let Some(split) = split else {
return Err(ExpressionParseError::new(
"Expected 'lhs = rhs'",
src.chars().count(),
));
};
let mut lhs_toks: Vec<Tok> = toks[..split].to_vec();
lhs_toks.push(Tok::simple(Kind::Eof, toks[split].pos));
let rhs_toks: Vec<Tok> = toks[split + 1..].to_vec();
Ok(Equation {
comment: None,
lhs: Parser::new(lhs_toks).parse_entry()?,
rhs: Parser::new(rhs_toks).parse_entry()?,
})
}