use pest::iterators::Pair;
use pest::Parser as PestParser;
use pest_derive::Parser;
use std::{fmt, sync::Arc};
use super::ast::*;
use crate::data::value::Val;
use super::write_terminal::{
build_patch_op, is_chain_write_terminal, steps_to_path as write_steps_to_path,
};
const INVALID_HAS_ARRAY_RHS: &str = "__jetro_invalid_has_array_rhs";
#[derive(Parser)]
#[grammar = "grammar.pest"]
pub struct V2Parser;
#[derive(Debug)]
pub struct ParseError(pub String);
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "parse error: {}", self.0)
}
}
impl std::error::Error for ParseError {}
impl From<pest::error::Error<Rule>> for ParseError {
fn from(e: pest::error::Error<Rule>) -> Self {
ParseError(e.to_string())
}
}
pub fn parse(input: &str) -> Result<Expr, ParseError> {
let mut pairs = V2Parser::parse(Rule::program, input)?;
let program = pairs.next().unwrap();
let expr_pair = program.into_inner().next().unwrap();
let expr = parse_expr(expr_pair);
validate_has_array_rhs(&expr)?;
validate_pattern_linearity(&expr)?;
if strict_match_lint_enabled() {
validate_match_exhaustiveness(&expr)?;
}
let expr = crate::compile::lambda_lower::inline_let_bound_lambdas(expr);
Ok(expr)
}
fn validate_has_array_rhs(expr: &Expr) -> Result<(), ParseError> {
fn check(expr: &Expr) -> Result<(), ParseError> {
match expr {
Expr::Chain(base, steps) => {
check(base)?;
for step in steps {
match step {
Step::Method(name, _) if name == INVALID_HAS_ARRAY_RHS => {
return Err(ParseError(
"has [...] requires scalar literal elements".to_string(),
));
}
Step::DynIndex(expr) | Step::InlineFilter(expr) => check(expr)?,
Step::Method(_, args) | Step::OptMethod(_, args) => {
for arg in args {
match arg {
Arg::Pos(expr) | Arg::Named(_, expr) => check(expr)?,
}
}
}
Step::DeepMatch { arms, .. } => {
for arm in arms {
if let Some(guard) = &arm.guard {
check(guard)?;
}
check(&arm.body)?;
}
}
_ => {}
}
}
}
Expr::BinOp(lhs, _, rhs) | Expr::Coalesce(lhs, rhs) => {
check(lhs)?;
check(rhs)?;
}
Expr::UnaryNeg(inner) | Expr::Not(inner) => check(inner)?,
Expr::Kind { expr, .. } => check(expr)?,
Expr::Array(elems) => {
for elem in elems {
match elem {
ArrayElem::Expr(expr) | ArrayElem::Spread(expr) => check(expr)?,
}
}
}
Expr::Object(fields) => {
for field in fields {
match field {
ObjField::Kv { val, cond, .. } => {
check(val)?;
if let Some(cond) = cond {
check(cond)?;
}
}
ObjField::Dynamic { key, val } => {
check(key)?;
check(val)?;
}
ObjField::Short(_) => {}
ObjField::Spread(expr) | ObjField::SpreadDeep(expr) => check(expr)?,
}
}
}
Expr::Pipeline { base, steps } => {
check(base)?;
for step in steps {
match step {
PipeStep::Forward(expr) => check(expr)?,
PipeStep::Bind(_) => {}
}
}
}
Expr::ListComp { expr, iter, cond, .. }
| Expr::GenComp { expr, iter, cond, .. }
| Expr::SetComp { expr, iter, cond, .. } => {
check(expr)?;
check(iter)?;
if let Some(cond) = cond {
check(cond)?;
}
}
Expr::DictComp {
key,
val,
iter,
cond,
..
} => {
check(key)?;
check(val)?;
check(iter)?;
if let Some(cond) = cond {
check(cond)?;
}
}
Expr::Lambda { body, .. } => check(body)?,
Expr::Let { init, body, .. } => {
check(init)?;
check(body)?;
}
Expr::IfElse { cond, then_, else_ } => {
check(cond)?;
check(then_)?;
check(else_)?;
}
Expr::Try { body, default } => {
check(body)?;
check(default)?;
}
Expr::GlobalCall { args, .. } => {
for arg in args {
match arg {
Arg::Pos(expr) | Arg::Named(_, expr) => check(expr)?,
}
}
}
Expr::Match { scrutinee, arms } => {
check(scrutinee)?;
for arm in arms {
if let Some(guard) = &arm.guard {
check(guard)?;
}
check(&arm.body)?;
}
}
Expr::Cast { expr, .. } => check(expr)?,
Expr::Patch { root, ops } => {
check(root)?;
for op in ops {
check_patch_op(op)?;
}
}
Expr::UpdateBatch { root, ops, .. } => {
check(root)?;
for op in ops {
check_patch_op(op)?;
}
}
Expr::DeleteMark => {}
Expr::FString(parts) => {
for part in parts {
if let FStringPart::Interp { expr, .. } = part {
check(expr)?;
}
}
}
Expr::Null
| Expr::Bool(_)
| Expr::Int(_)
| Expr::Float(_)
| Expr::Str(_)
| Expr::Root
| Expr::Current
| Expr::Ident(_) => {}
}
Ok(())
}
fn check_patch_op(op: &PatchOp) -> Result<(), ParseError> {
check(&op.val)?;
if let Some(cond) = &op.cond {
check(cond)?;
}
Ok(())
}
check(expr)
}
fn strict_match_lint_enabled() -> bool {
std::env::var_os("JETRO_STRICT_MATCH").is_some()
}
fn pat_is_irrefutable(pat: &Pat) -> bool {
match pat {
Pat::Wild | Pat::Bind(_) => true,
Pat::Or(alts) => alts.iter().any(pat_is_irrefutable),
_ => false,
}
}
fn validate_match_exhaustiveness(expr: &Expr) -> Result<(), ParseError> {
fn check(e: &Expr) -> Result<(), ParseError> {
match e {
Expr::Match { scrutinee, arms } => {
check(scrutinee)?;
let exhaustive = arms
.iter()
.any(|a| a.guard.is_none() && pat_is_irrefutable(&a.pat));
if !exhaustive {
return Err(ParseError(
"non-exhaustive match: no unguarded catch-all arm \
(`_`, a bare bind, or an or-pattern containing one)"
.to_string(),
));
}
for arm in arms {
if let Some(g) = arm.guard.as_ref() {
check(g)?;
}
check(&arm.body)?;
}
Ok(())
}
Expr::Chain(base, _) => check(base),
Expr::BinOp(l, _, r) | Expr::Coalesce(l, r) => {
check(l)?;
check(r)
}
Expr::UnaryNeg(e) | Expr::Not(e) | Expr::Cast { expr: e, .. } => check(e),
Expr::Kind { expr, .. } => check(expr),
_ => Ok(()),
}
}
check(expr)
}
fn validate_pattern_linearity(expr: &Expr) -> Result<(), ParseError> {
use std::collections::BTreeSet;
fn collect_binds(pat: &Pat, out: &mut BTreeSet<String>) {
match pat {
Pat::Wild | Pat::Lit(_) | Pat::Range { .. } => {}
Pat::Bind(name) => {
out.insert(name.clone());
}
Pat::Kind { name, .. } => {
if let Some(n) = name.as_deref() {
out.insert(n.to_string());
}
}
Pat::Or(alts) => {
for alt in alts {
collect_binds(alt, out);
}
}
Pat::Obj { fields, rest } => {
for (_, sub) in fields {
collect_binds(sub, out);
}
if let Some(Some(name)) = rest {
out.insert(name.clone());
}
}
Pat::Arr { elems, rest } => {
for sub in elems {
collect_binds(sub, out);
}
if let Some(Some(name)) = rest {
out.insert(name.clone());
}
}
}
}
fn walk_pat(pat: &Pat) -> Result<(), ParseError> {
if let Pat::Or(alts) = pat {
let mut first: Option<BTreeSet<String>> = None;
for alt in alts {
let mut names = BTreeSet::new();
collect_binds(alt, &mut names);
match &first {
None => first = Some(names),
Some(existing) if existing == &names => {}
Some(existing) => {
return Err(ParseError(format!(
"or-pattern arms must bind the same variables; \
got {{{}}} vs {{{}}}",
existing.iter().cloned().collect::<Vec<_>>().join(", "),
names.iter().cloned().collect::<Vec<_>>().join(", "),
)));
}
}
}
}
match pat {
Pat::Wild | Pat::Lit(_) | Pat::Bind(_) | Pat::Kind { .. } | Pat::Range { .. } => {}
Pat::Or(alts) => {
for alt in alts {
walk_pat(alt)?;
}
}
Pat::Obj { fields, .. } => {
for (_, sub) in fields {
walk_pat(sub)?;
}
}
Pat::Arr { elems, .. } => {
for sub in elems {
walk_pat(sub)?;
}
}
}
Ok(())
}
fn walk(e: &Expr) -> Result<(), ParseError> {
match e {
Expr::Match { scrutinee, arms } => {
walk(scrutinee)?;
for arm in arms {
walk_pat(&arm.pat)?;
if let Some(g) = arm.guard.as_ref() {
walk(g)?;
}
walk(&arm.body)?;
}
}
Expr::Chain(base, steps) => {
walk(base)?;
for step in steps {
if let Step::DeepMatch { arms, .. } = step {
for arm in arms {
walk_pat(&arm.pat)?;
if let Some(g) = arm.guard.as_ref() {
walk(g)?;
}
walk(&arm.body)?;
}
}
}
}
Expr::BinOp(l, _, r) | Expr::Coalesce(l, r) => {
walk(l)?;
walk(r)?;
}
Expr::UnaryNeg(e) | Expr::Not(e) | Expr::Cast { expr: e, .. } => walk(e)?,
Expr::Kind { expr, .. } => walk(expr)?,
Expr::Object(_)
| Expr::Array(_)
| Expr::Pipeline { .. }
| Expr::ListComp { .. }
| Expr::DictComp { .. }
| Expr::SetComp { .. }
| Expr::GenComp { .. }
| Expr::Lambda { .. }
| Expr::Let { .. }
| Expr::IfElse { .. }
| Expr::Try { .. }
| Expr::GlobalCall { .. }
| Expr::Patch { .. }
| Expr::UpdateBatch { .. }
| Expr::FString(_)
| Expr::Null
| Expr::Bool(_)
| Expr::Int(_)
| Expr::Float(_)
| Expr::Str(_)
| Expr::Root
| Expr::Current
| Expr::Ident(_)
| Expr::DeleteMark => {}
}
Ok(())
}
walk(expr)
}
fn is_kw(rule: Rule) -> bool {
matches!(
rule,
Rule::kw_and
| Rule::kw_or
| Rule::kw_not
| Rule::kw_for
| Rule::kw_in
| Rule::kw_if
| Rule::kw_else
| Rule::kw_let
| Rule::kw_lambda
| Rule::kw_kind
| Rule::kw_is
| Rule::kw_as
| Rule::kw_try
| Rule::kw_when
| Rule::kw_match
| Rule::kw_with
)
}
fn parse_expr(pair: Pair<Rule>) -> Expr {
match pair.as_rule() {
Rule::expr => parse_expr(pair.into_inner().next().unwrap()),
Rule::cond_expr => parse_cond(pair),
Rule::pipe_expr => parse_pipeline(pair),
Rule::coalesce_expr => parse_coalesce(pair),
Rule::or_expr => parse_or(pair),
Rule::and_expr => parse_and(pair),
Rule::not_expr => parse_not(pair),
Rule::kind_expr => parse_kind(pair),
Rule::contains_expr => parse_contains(pair),
Rule::cmp_expr => parse_cmp(pair),
Rule::add_expr => parse_add(pair),
Rule::mul_expr => parse_mul(pair),
Rule::cast_expr => parse_cast(pair),
Rule::unary_expr => parse_unary(pair),
Rule::postfix_expr => parse_postfix_expr(pair),
Rule::primary => parse_primary(pair),
r => panic!("unexpected rule in parse_expr: {:?}", r),
}
}
fn parse_cond(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner().filter(|p| !is_kw(p.as_rule()));
let head = inner.next().unwrap();
if head.as_rule() == Rule::try_expr {
return parse_try(head);
}
let then_ = parse_expr(head);
let cond = match inner.next() {
Some(p) => parse_expr(p),
None => return then_,
};
let else_ = parse_expr(inner.next().unwrap());
Expr::IfElse {
cond: Box::new(cond),
then_: Box::new(then_),
else_: Box::new(else_),
}
}
fn parse_try(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner().filter(|p| !is_kw(p.as_rule()));
let body_pair = inner.next().unwrap();
let body = {
let mut bi = body_pair.into_inner();
parse_expr(bi.next().unwrap())
};
let default = parse_expr(inner.next().unwrap());
Expr::Try {
body: Box::new(body),
default: Box::new(default),
}
}
fn parse_pipeline(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner();
let base = parse_expr(inner.next().unwrap()); let mut steps: Vec<PipeStep> = Vec::new();
for step_pair in inner {
let inner_step = step_pair.into_inner().next().unwrap();
match inner_step.as_rule() {
Rule::pipe_forward => {
let inner_pair = inner_step.into_inner().next().unwrap();
let expr = if inner_pair.as_rule() == Rule::pipe_method_call {
let mut mi = inner_pair.into_inner();
let name = mi.next().unwrap().as_str().to_string();
let args = mi.next().map(parse_arg_list).unwrap_or_default();
Expr::Chain(Box::new(Expr::Current), vec![Step::Method(name, args)])
} else {
parse_expr(inner_pair)
};
steps.push(PipeStep::Forward(expr));
}
Rule::pipe_bind => {
let target = parse_bind_target(inner_step.into_inner().next().unwrap());
steps.push(PipeStep::Bind(target));
}
r => panic!("unexpected pipe_step inner: {:?}", r),
}
}
if steps.is_empty() {
base
} else {
Expr::Pipeline {
base: Box::new(base),
steps,
}
}
}
fn parse_bind_target(pair: Pair<Rule>) -> BindTarget {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::ident => BindTarget::Name(inner.as_str().to_string()),
Rule::bind_obj => {
let mut fields = Vec::new();
let mut rest = None;
for p in inner.into_inner() {
match p.as_rule() {
Rule::ident => fields.push(p.as_str().to_string()),
Rule::bind_rest => {
rest = Some(
p.into_inner()
.find(|x| x.as_rule() == Rule::ident)
.unwrap()
.as_str()
.to_string(),
);
}
_ => {}
}
}
BindTarget::Obj { fields, rest }
}
Rule::bind_arr => {
let fields: Vec<String> = inner
.into_inner()
.filter(|p| p.as_rule() == Rule::ident)
.map(|p| p.as_str().to_string())
.collect();
BindTarget::Arr(fields)
}
r => panic!("unexpected bind_target inner: {:?}", r),
}
}
fn parse_coalesce(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner();
let first = parse_expr(inner.next().unwrap());
inner.fold(first, |acc, rhs| {
Expr::Coalesce(Box::new(acc), Box::new(parse_expr(rhs)))
})
}
fn parse_or(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner().filter(|p| !is_kw(p.as_rule()));
let first = parse_expr(inner.next().unwrap());
inner.fold(first, |acc, rhs| {
Expr::BinOp(Box::new(acc), BinOp::Or, Box::new(parse_expr(rhs)))
})
}
fn parse_and(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner().filter(|p| !is_kw(p.as_rule()));
let first = parse_expr(inner.next().unwrap());
inner.fold(first, |acc, rhs| {
Expr::BinOp(Box::new(acc), BinOp::And, Box::new(parse_expr(rhs)))
})
}
fn parse_not(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner();
let first = inner.next().unwrap();
if first.as_rule() == Rule::kw_not {
let operand = inner.next().unwrap();
Expr::Not(Box::new(parse_expr(operand)))
} else {
parse_expr(first)
}
}
fn parse_kind(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner();
let cmp = parse_expr(inner.next().unwrap());
match inner.next() {
None => cmp,
Some(p) if matches!(p.as_rule(), Rule::kw_kind | Rule::kw_is) => {
let next = inner.next().unwrap();
let (negate, kind_type_str) = if next.as_rule() == Rule::kw_not {
(true, inner.next().unwrap().as_str())
} else {
(false, next.as_str())
};
let ty = match kind_type_str {
"null" => KindType::Null,
"bool" => KindType::Bool,
"number" => KindType::Number,
"string" => KindType::Str,
"array" => KindType::Array,
"object" => KindType::Object,
other => panic!("unknown kind type: {}", other),
};
Expr::Kind {
expr: Box::new(cmp),
ty,
negate,
}
}
_ => cmp,
}
}
fn parse_contains(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner();
let lhs = parse_expr(inner.next().unwrap());
match inner.next() {
None => lhs,
Some(_op_pair) => {
let rhs = parse_expr(inner.next().unwrap());
let method = match has_array_literal_arg(&rhs) {
Some(arg) => Step::Method("has_all".to_string(), vec![Arg::Pos(arg)]),
None if matches!(rhs, Expr::Array(_)) => {
Step::Method(INVALID_HAS_ARRAY_RHS.to_string(), Vec::new())
}
None => Step::Method("includes".to_string(), vec![Arg::Pos(rhs)]),
};
Expr::Chain(
Box::new(lhs),
vec![method],
)
}
}
}
fn has_array_literal_arg(expr: &Expr) -> Option<Expr> {
let Expr::Array(elems) = expr else {
return None;
};
let mut out = Vec::with_capacity(elems.len());
for elem in elems {
let ArrayElem::Expr(expr) = elem else {
return None;
};
out.push(scalar_literal_to_val(expr)?);
}
Some(val_to_expr(Val::Arr(Arc::new(out))))
}
fn scalar_literal_to_val(expr: &Expr) -> Option<Val> {
match expr {
Expr::Null => Some(Val::Null),
Expr::Bool(b) => Some(Val::Bool(*b)),
Expr::Int(n) => Some(Val::Int(*n)),
Expr::Float(f) => Some(Val::Float(*f)),
Expr::Str(s) => Some(Val::Str(Arc::from(s.as_str()))),
_ => None,
}
}
fn val_to_expr(value: Val) -> Expr {
match value {
Val::Null => Expr::Null,
Val::Bool(b) => Expr::Bool(b),
Val::Int(n) => Expr::Int(n),
Val::Float(f) => Expr::Float(f),
Val::Str(s) => Expr::Str(s.to_string()),
Val::Arr(items) => Expr::Array(
items
.iter()
.cloned()
.map(|item| ArrayElem::Expr(val_to_expr(item)))
.collect(),
),
other => panic!("unexpected has literal value: {other:?}"),
}
}
fn parse_cmp(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner();
let lhs = parse_expr(inner.next().unwrap());
if let Some(op_pair) = inner.next() {
let op = match op_pair.as_str() {
"==" => BinOp::Eq,
"!=" => BinOp::Neq,
"<" => BinOp::Lt,
"<=" => BinOp::Lte,
">" => BinOp::Gt,
">=" => BinOp::Gte,
"~=" => BinOp::Fuzzy,
o => panic!("unknown cmp op: {}", o),
};
let rhs = parse_expr(inner.next().unwrap());
Expr::BinOp(Box::new(lhs), op, Box::new(rhs))
} else {
lhs
}
}
fn parse_add(pair: Pair<Rule>) -> Expr {
parse_left_assoc(pair, |s| match s {
"+" => Some(BinOp::Add),
"-" => Some(BinOp::Sub),
_ => None,
})
}
fn parse_mul(pair: Pair<Rule>) -> Expr {
parse_left_assoc(pair, |s| match s {
"*" => Some(BinOp::Mul),
"/" => Some(BinOp::Div),
"%" => Some(BinOp::Mod),
_ => None,
})
}
fn parse_left_assoc<F>(pair: Pair<Rule>, op_fn: F) -> Expr
where
F: Fn(&str) -> Option<BinOp>,
{
let mut inner = pair.into_inner().peekable();
let first = parse_expr(inner.next().unwrap());
let mut acc = first;
while inner.peek().is_some() {
let op_pair = inner.next().unwrap();
let op = op_fn(op_pair.as_str()).unwrap();
let rhs = parse_expr(inner.next().unwrap());
acc = Expr::BinOp(Box::new(acc), op, Box::new(rhs));
}
acc
}
fn parse_cast(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner().peekable();
let mut acc = parse_expr(inner.next().unwrap());
while inner.peek().is_some() {
let kw = inner.next().unwrap();
debug_assert_eq!(kw.as_rule(), Rule::kw_as);
let ty_pair = inner.next().unwrap();
let ty = match ty_pair.as_str() {
"int" => CastType::Int,
"float" => CastType::Float,
"number" => CastType::Number,
"string" => CastType::Str,
"bool" => CastType::Bool,
"array" => CastType::Array,
"object" => CastType::Object,
"null" => CastType::Null,
o => panic!("unknown cast type: {}", o),
};
acc = Expr::Cast {
expr: Box::new(acc),
ty,
};
}
acc
}
fn parse_unary(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner();
let first = inner.next().unwrap();
match first.as_rule() {
Rule::unary_neg => {
let operand = parse_expr(inner.next().unwrap());
Expr::UnaryNeg(Box::new(operand))
}
_ => parse_expr(first),
}
}
fn parse_postfix_expr(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner();
let base = parse_primary(inner.next().unwrap());
let raw_steps: Vec<Step> = inner.flat_map(parse_postfix_step).collect();
let mut steps: Vec<Step> = Vec::with_capacity(raw_steps.len());
for s in raw_steps {
match s {
Step::Quantifier(QuantifierKind::First) => {
match steps.last() {
Some(Step::Field(_)) => {
if let Some(Step::Field(k)) = steps.pop() {
steps.push(Step::OptField(k));
}
}
Some(Step::Method(_, _)) => {
if let Some(Step::Method(n, a)) = steps.pop() {
steps.push(Step::OptMethod(n, a));
}
}
_ => {
}
}
}
other => steps.push(other),
}
}
if let Some(rewritten) = classify_chain_write(&base, &steps) {
return rewritten;
}
let steps = expand_wildcards(steps);
base.maybe_chain(steps)
}
fn expand_wildcards(steps: Vec<Step>) -> Vec<Step> {
if !steps.iter().any(|s| matches!(s, Step::Wildcard)) {
return steps;
}
let mut out: Vec<Step> = Vec::with_capacity(steps.len());
let mut iter = steps.into_iter().peekable();
while let Some(step) = iter.next() {
if matches!(step, Step::Wildcard) {
let rest: Vec<Step> = iter.by_ref().collect();
if rest.is_empty() {
break;
}
let rest = expand_wildcards(rest);
let body = Expr::Chain(Box::new(Expr::Current), rest);
let lam = Expr::Lambda {
params: vec!["@".to_string()],
body: Box::new(body),
};
out.push(Step::Method("map".to_string(), vec![Arg::Pos(lam)]));
return out;
}
out.push(step);
}
out
}
fn classify_chain_write(base: &Expr, steps: &[Step]) -> Option<Expr> {
if !matches!(base, Expr::Root) {
return None;
}
let last = steps.last()?;
let (name, args) = match last {
Step::Method(n, a) => (n.as_str(), a),
_ => return None,
};
if !is_chain_write_terminal(name) {
return None;
}
let prefix = &steps[..steps.len() - 1];
let path = write_steps_to_path(prefix, true)?;
if name == "update" {
let ops = build_update_ops(args)?;
return Some(Expr::UpdateBatch {
root: Box::new(Expr::Root),
selector: path,
ops,
});
}
let op = build_patch_op(name, args, Vec::new())?;
Some(Expr::UpdateBatch {
root: Box::new(Expr::Root),
selector: path,
ops: vec![op],
})
}
fn build_update_ops(args: &[Arg]) -> Option<Vec<PatchOp>> {
if args.len() != 1 {
return None;
}
let Expr::Object(fields) = arg_expr(args.first()?) else {
return None;
};
let focus = crate::plan::update::UPDATE_FOCUS_BINDING.to_string();
let mut ops = Vec::new();
for field in fields {
let ObjField::Kv {
key,
val,
optional: false,
cond,
} = field
else {
return None;
};
ops.push(PatchOp {
path: parse_update_path_key(key)?,
val: qualify_update_expr(val.clone(), &focus, &[]),
cond: cond
.as_ref()
.map(|expr| qualify_update_expr(expr.clone(), &focus, &[])),
});
}
Some(ops)
}
fn parse_update_path_key(key: &str) -> Option<Vec<PathStep>> {
let mut idx = 0usize;
let bytes = key.as_bytes();
let first = parse_update_ident(key, &mut idx)?;
let mut out = vec![PathStep::Field(first)];
while idx < bytes.len() {
match bytes[idx] {
b'.' => {
idx += 1;
out.push(PathStep::Field(parse_update_ident(key, &mut idx)?));
}
b'[' => {
let close = find_matching_bracket(key, idx)?;
let inner = key[idx + 1..close].trim();
if inner == "*" {
out.push(PathStep::Wildcard);
} else if let Some(rest) = inner.strip_prefix("* if") {
let expr = parse_embedded_expr(rest.trim())?;
out.push(PathStep::WildcardFilter(Box::new(expr)));
} else if let Ok(n) = inner.parse::<i64>() {
out.push(PathStep::Index(n));
} else {
let expr = parse_embedded_expr(inner)?;
out.push(PathStep::DynIndex(expr));
}
idx = close + 1;
}
_ => return None,
}
}
Some(out)
}
fn parse_update_ident(src: &str, idx: &mut usize) -> Option<String> {
let start = *idx;
for (off, ch) in src[start..].char_indices() {
if !(ch.is_ascii_alphanumeric() || ch == '_' || ch == '-') {
if off == 0 {
return None;
}
*idx = start + off;
return Some(src[start..start + off].to_string());
}
}
if start == src.len() {
return None;
}
*idx = src.len();
Some(src[start..].to_string())
}
fn find_matching_bracket(src: &str, open: usize) -> Option<usize> {
let mut depth = 0usize;
for (idx, ch) in src[open..].char_indices() {
match ch {
'[' => depth += 1,
']' => {
depth = depth.checked_sub(1)?;
if depth == 0 {
return Some(open + idx);
}
}
_ => {}
}
}
None
}
fn parse_embedded_expr(src: &str) -> Option<Expr> {
let mut pairs = V2Parser::parse(Rule::expr, src).ok()?;
Some(parse_expr(pairs.next()?))
}
fn qualify_update_expr(expr: Expr, focus: &str, bound: &[String]) -> Expr {
match expr {
Expr::Ident(name) if !bound.iter().any(|b| b == &name) => Expr::Chain(
Box::new(Expr::Ident(focus.to_string())),
vec![Step::Field(name)],
),
Expr::Chain(base, steps) => {
Expr::Chain(Box::new(qualify_update_expr(*base, focus, bound)), steps)
}
Expr::BinOp(lhs, op, rhs) => Expr::BinOp(
Box::new(qualify_update_expr(*lhs, focus, bound)),
op,
Box::new(qualify_update_expr(*rhs, focus, bound)),
),
Expr::UnaryNeg(inner) => {
Expr::UnaryNeg(Box::new(qualify_update_expr(*inner, focus, bound)))
}
Expr::Not(inner) => Expr::Not(Box::new(qualify_update_expr(*inner, focus, bound))),
Expr::Kind { expr, ty, negate } => Expr::Kind {
expr: Box::new(qualify_update_expr(*expr, focus, bound)),
ty,
negate,
},
Expr::Coalesce(lhs, rhs) => Expr::Coalesce(
Box::new(qualify_update_expr(*lhs, focus, bound)),
Box::new(qualify_update_expr(*rhs, focus, bound)),
),
Expr::Object(fields) => Expr::Object(
fields
.into_iter()
.map(|field| qualify_update_obj_field(field, focus, bound))
.collect(),
),
Expr::Array(items) => Expr::Array(
items
.into_iter()
.map(|item| match item {
ArrayElem::Expr(expr) => {
ArrayElem::Expr(qualify_update_expr(expr, focus, bound))
}
ArrayElem::Spread(expr) => {
ArrayElem::Spread(qualify_update_expr(expr, focus, bound))
}
})
.collect(),
),
Expr::IfElse { cond, then_, else_ } => Expr::IfElse {
cond: Box::new(qualify_update_expr(*cond, focus, bound)),
then_: Box::new(qualify_update_expr(*then_, focus, bound)),
else_: Box::new(qualify_update_expr(*else_, focus, bound)),
},
Expr::Try { body, default } => Expr::Try {
body: Box::new(qualify_update_expr(*body, focus, bound)),
default: Box::new(qualify_update_expr(*default, focus, bound)),
},
Expr::Cast { expr, ty } => Expr::Cast {
expr: Box::new(qualify_update_expr(*expr, focus, bound)),
ty,
},
Expr::FString(parts) => Expr::FString(
parts
.into_iter()
.map(|part| match part {
FStringPart::Lit(s) => FStringPart::Lit(s),
FStringPart::Interp { expr, fmt } => FStringPart::Interp {
expr: qualify_update_expr(expr, focus, bound),
fmt,
},
})
.collect(),
),
Expr::GlobalCall { name, args } => Expr::GlobalCall {
name,
args: args
.into_iter()
.map(|arg| qualify_update_arg(arg, focus, bound))
.collect(),
},
Expr::Pipeline { base, steps } => Expr::Pipeline {
base: Box::new(qualify_update_expr(*base, focus, bound)),
steps: qualify_update_pipe_steps(steps, focus, bound),
},
Expr::ListComp {
expr,
vars,
iter,
cond,
} => {
let iter = qualify_update_expr(*iter, focus, bound);
let nested = extend_bound(bound, &vars);
Expr::ListComp {
expr: Box::new(qualify_update_expr(*expr, focus, &nested)),
vars,
iter: Box::new(iter),
cond: cond.map(|expr| Box::new(qualify_update_expr(*expr, focus, &nested))),
}
}
Expr::DictComp {
key,
val,
vars,
iter,
cond,
} => {
let iter = qualify_update_expr(*iter, focus, bound);
let nested = extend_bound(bound, &vars);
Expr::DictComp {
key: Box::new(qualify_update_expr(*key, focus, &nested)),
val: Box::new(qualify_update_expr(*val, focus, &nested)),
vars,
iter: Box::new(iter),
cond: cond.map(|expr| Box::new(qualify_update_expr(*expr, focus, &nested))),
}
}
Expr::SetComp {
expr,
vars,
iter,
cond,
} => {
let iter = qualify_update_expr(*iter, focus, bound);
let nested = extend_bound(bound, &vars);
Expr::SetComp {
expr: Box::new(qualify_update_expr(*expr, focus, &nested)),
vars,
iter: Box::new(iter),
cond: cond.map(|expr| Box::new(qualify_update_expr(*expr, focus, &nested))),
}
}
Expr::GenComp {
expr,
vars,
iter,
cond,
} => {
let iter = qualify_update_expr(*iter, focus, bound);
let nested = extend_bound(bound, &vars);
Expr::GenComp {
expr: Box::new(qualify_update_expr(*expr, focus, &nested)),
vars,
iter: Box::new(iter),
cond: cond.map(|expr| Box::new(qualify_update_expr(*expr, focus, &nested))),
}
}
Expr::Lambda { params, body } => {
let nested = extend_bound(bound, ¶ms);
Expr::Lambda {
params,
body: Box::new(qualify_update_expr(*body, focus, &nested)),
}
}
Expr::Let { name, init, body } => {
let init = qualify_update_expr(*init, focus, bound);
let mut nested = bound.to_vec();
nested.push(name.clone());
Expr::Let {
name,
init: Box::new(init),
body: Box::new(qualify_update_expr(*body, focus, &nested)),
}
}
Expr::Patch { root, ops } => Expr::Patch {
root: Box::new(qualify_update_expr(*root, focus, bound)),
ops: ops
.into_iter()
.map(|op| qualify_update_patch_op(op, focus, bound))
.collect(),
},
Expr::Match { scrutinee, arms } => Expr::Match {
scrutinee: Box::new(qualify_update_expr(*scrutinee, focus, bound)),
arms: arms
.into_iter()
.map(|arm| qualify_update_match_arm(arm, focus, bound))
.collect(),
},
other => other,
}
}
fn qualify_update_obj_field(field: ObjField, focus: &str, bound: &[String]) -> ObjField {
match field {
ObjField::Kv {
key,
val,
optional,
cond,
} => ObjField::Kv {
key,
val: qualify_update_expr(val, focus, bound),
optional,
cond: cond.map(|expr| qualify_update_expr(expr, focus, bound)),
},
ObjField::Dynamic { key, val } => ObjField::Dynamic {
key: qualify_update_expr(key, focus, bound),
val: qualify_update_expr(val, focus, bound),
},
ObjField::Spread(expr) => ObjField::Spread(qualify_update_expr(expr, focus, bound)),
ObjField::SpreadDeep(expr) => ObjField::SpreadDeep(qualify_update_expr(expr, focus, bound)),
other => other,
}
}
fn qualify_update_arg(arg: Arg, focus: &str, bound: &[String]) -> Arg {
match arg {
Arg::Pos(expr) => Arg::Pos(qualify_update_expr(expr, focus, bound)),
Arg::Named(name, expr) => Arg::Named(name, qualify_update_expr(expr, focus, bound)),
}
}
fn qualify_update_pipe_steps(steps: Vec<PipeStep>, focus: &str, bound: &[String]) -> Vec<PipeStep> {
let mut scoped = bound.to_vec();
steps
.into_iter()
.map(|step| match step {
PipeStep::Forward(expr) => PipeStep::Forward(qualify_update_expr(expr, focus, &scoped)),
PipeStep::Bind(target) => {
collect_bind_target_names(&target, &mut scoped);
PipeStep::Bind(target)
}
})
.collect()
}
fn qualify_update_patch_op(op: PatchOp, focus: &str, bound: &[String]) -> PatchOp {
PatchOp {
path: op
.path
.into_iter()
.map(|step| qualify_update_path_step(step, focus, bound))
.collect(),
val: qualify_update_expr(op.val, focus, bound),
cond: op.cond.map(|expr| qualify_update_expr(expr, focus, bound)),
}
}
fn qualify_update_path_step(step: PathStep, focus: &str, bound: &[String]) -> PathStep {
match step {
PathStep::DynIndex(expr) => PathStep::DynIndex(qualify_update_expr(expr, focus, bound)),
PathStep::WildcardFilter(expr) => {
PathStep::WildcardFilter(Box::new(qualify_update_expr(*expr, focus, bound)))
}
other => other,
}
}
fn qualify_update_match_arm(arm: MatchArm, focus: &str, bound: &[String]) -> MatchArm {
let mut nested = bound.to_vec();
collect_pat_names(&arm.pat, &mut nested);
MatchArm {
pat: arm.pat,
guard: arm
.guard
.map(|expr| qualify_update_expr(expr, focus, &nested)),
body: qualify_update_expr(arm.body, focus, &nested),
}
}
fn extend_bound(bound: &[String], names: &[String]) -> Vec<String> {
let mut nested = bound.to_vec();
nested.extend(names.iter().cloned());
nested
}
fn collect_bind_target_names(target: &BindTarget, out: &mut Vec<String>) {
match target {
BindTarget::Name(name) => out.push(name.clone()),
BindTarget::Obj { fields, rest } => {
out.extend(fields.iter().cloned());
if let Some(name) = rest {
out.push(name.clone());
}
}
BindTarget::Arr(names) => out.extend(names.iter().cloned()),
}
}
fn collect_pat_names(pat: &Pat, out: &mut Vec<String>) {
match pat {
Pat::Bind(name) => out.push(name.clone()),
Pat::Or(alts) => {
for alt in alts {
collect_pat_names(alt, out);
}
}
Pat::Obj { fields, rest } => {
for (_, field_pat) in fields {
collect_pat_names(field_pat, out);
}
if let Some(Some(name)) = rest {
out.push(name.clone());
}
}
Pat::Arr { elems, rest } => {
for elem in elems {
collect_pat_names(elem, out);
}
if let Some(Some(name)) = rest {
out.push(name.clone());
}
}
Pat::Kind {
name: Some(name), ..
} => out.push(name.clone()),
Pat::Wild | Pat::Lit(_) | Pat::Kind { name: None, .. } | Pat::Range { .. } => {}
}
}
fn arg_expr(a: &Arg) -> &Expr {
match a {
Arg::Pos(e) | Arg::Named(_, e) => e,
}
}
fn parse_postfix_step(pair: Pair<Rule>) -> Vec<Step> {
let inner_pair = pair.into_inner().next().unwrap();
match inner_pair.as_rule() {
Rule::field_access => {
let name = inner_pair.into_inner().next().unwrap().as_str().to_string();
vec![Step::Field(name)]
}
Rule::descendant => {
let mut di = inner_pair.into_inner();
match di.next() {
Some(p) => vec![Step::Descendant(p.as_str().to_string())],
None => vec![Step::DescendAll],
}
}
Rule::deep_method => {
let mut mi = inner_pair.into_inner();
let name = mi.next().unwrap().as_str().to_string();
let args = mi.next().map(parse_arg_list).unwrap_or_default();
let mapped = match name.as_str() {
"find" | "find_all" | "findAll" => "deep_find".to_string(),
"shape" => "deep_shape".to_string(),
"like" => "deep_like".to_string(),
other => format!("deep_{}", other),
};
vec![Step::Method(mapped, args)]
}
Rule::deep_match => {
let mut arms: Vec<MatchArm> = Vec::new();
let mut early_stop = false;
for child in inner_pair.into_inner().filter(|p| !is_kw(p.as_rule())) {
match child.as_rule() {
Rule::deep_match_op => {
early_stop = child.as_str().ends_with('!');
}
Rule::match_arm => {
let mut ai = child.into_inner().filter(|p| !is_kw(p.as_rule()));
let pat = parse_pat(ai.next().expect("match arm pattern"));
let rest: Vec<_> = ai.collect();
let (guard, body) = match rest.len() {
1 => (None, parse_expr(rest.into_iter().next().unwrap())),
2 => {
let mut it = rest.into_iter();
let g = parse_expr(it.next().unwrap());
let b = parse_expr(it.next().unwrap());
(Some(g), b)
}
_ => panic!(
"deep_match arm: expected 1 or 2 trailing exprs (guard?, body)"
),
};
arms.push(MatchArm { pat, guard, body });
}
_ => {}
}
}
vec![Step::DeepMatch { arms, early_stop }]
}
Rule::inline_filter => {
let expr = parse_expr(inner_pair.into_inner().next().unwrap());
vec![Step::InlineFilter(Box::new(expr))]
}
Rule::quantifier => {
let s = inner_pair.as_str();
if s.starts_with('!') {
vec![Step::Quantifier(QuantifierKind::One)]
} else {
vec![Step::Quantifier(QuantifierKind::First)]
}
}
Rule::method_call => {
let mut mi = inner_pair.into_inner();
let name = mi.next().unwrap().as_str().to_string();
let args = mi.next().map(parse_arg_list).unwrap_or_default();
vec![Step::Method(name, args)]
}
Rule::index_access => {
let bi = inner_pair.into_inner().next().unwrap();
vec![parse_bracket(bi)]
}
Rule::dyn_field => {
let expr = parse_expr(inner_pair.into_inner().next().unwrap());
vec![Step::DynIndex(Box::new(expr))]
}
Rule::map_into_shape => {
let mut guard: Option<Expr> = None;
let mut body: Option<Expr> = None;
let mut saw_if = false;
for p in inner_pair.into_inner() {
match p.as_rule() {
Rule::kw_if => saw_if = true,
Rule::expr => {
if saw_if && guard.is_none() {
guard = Some(parse_expr(p));
} else {
body = Some(parse_expr(p));
}
}
_ => {}
}
}
let body = body.expect("map_into_shape requires body");
let mut steps = Vec::new();
if let Some(g) = guard {
steps.push(Step::Method("filter".into(), vec![Arg::Pos(g)]));
}
steps.push(Step::Method("map".into(), vec![Arg::Pos(body)]));
steps
}
r => panic!("unexpected postfix rule: {:?}", r),
}
}
fn parse_bracket(pair: Pair<Rule>) -> Step {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::idx_only => Step::Index(inner.as_str().parse().unwrap()),
Rule::wildcard => Step::Wildcard,
Rule::wildcard_filter => {
let expr = inner
.into_inner()
.find(|p| p.as_rule() == Rule::expr)
.map(parse_expr)
.expect("wildcard filter requires predicate");
Step::InlineFilter(Box::new(expr))
}
Rule::slice_full => {
let mut i = inner.into_inner();
let a = i.next().unwrap().as_str().parse().ok();
let b = i.next().unwrap().as_str().parse().ok();
Step::Slice(a, b, None)
}
Rule::slice_from => {
let a = inner.into_inner().next().unwrap().as_str().parse().ok();
Step::Slice(a, None, None)
}
Rule::slice_to => {
let b = inner.into_inner().next().unwrap().as_str().parse().ok();
Step::Slice(None, b, None)
}
Rule::slice_step => {
let s = inner.as_str();
let mut parts = s.split(':');
let a = parts.next().and_then(|p| p.parse().ok());
let b = parts.next().and_then(|p| p.parse().ok());
let st = parts.next().and_then(|p| p.parse().ok());
Step::Slice(a, b, st)
}
Rule::expr => Step::DynIndex(Box::new(parse_expr(inner))),
r => panic!("unexpected bracket rule: {:?}", r),
}
}
fn parse_primary(pair: Pair<Rule>) -> Expr {
let inner = if pair.as_rule() == Rule::primary {
pair.into_inner().next().unwrap()
} else {
pair
};
match inner.as_rule() {
Rule::literal => parse_literal(inner),
Rule::root => Expr::Root,
Rule::current => Expr::Current,
Rule::bare_leading_field => {
let name = inner
.into_inner()
.next()
.expect("bare_leading_field has a field_name child")
.as_str()
.to_string();
Expr::Chain(
Box::new(Expr::Current),
vec![Step::Field(name)],
)
}
Rule::ident => Expr::Ident(inner.as_str().to_string()),
Rule::let_expr => parse_let(inner),
Rule::lambda_expr => parse_lambda(inner),
Rule::arrow_lambda => parse_arrow_lambda(inner),
Rule::list_comp => parse_list_comp(inner),
Rule::dict_comp => parse_dict_comp(inner),
Rule::set_comp => parse_set_comp(inner),
Rule::gen_comp => parse_gen_comp(inner),
Rule::obj_construct => parse_obj(inner),
Rule::arr_construct => parse_arr(inner),
Rule::global_call => parse_global_call(inner),
Rule::expr => parse_expr(inner),
Rule::patch_block => parse_patch(inner),
Rule::match_expr => parse_match_expr(inner),
Rule::kw_delete => Expr::DeleteMark,
r => panic!("unexpected primary rule: {:?}", r),
}
}
fn parse_match_expr(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner().filter(|p| !is_kw(p.as_rule()));
let scrutinee = parse_expr(inner.next().expect("match scrutinee"));
let mut arms: Vec<MatchArm> = Vec::new();
for arm_pair in inner {
if arm_pair.as_rule() != Rule::match_arm {
continue;
}
let mut ai = arm_pair.into_inner().filter(|p| !is_kw(p.as_rule()));
let pat_pair = ai.next().expect("match arm pattern");
let pat = parse_pat(pat_pair);
let rest: Vec<_> = ai.collect();
let (guard, body) = match rest.len() {
1 => (None, parse_expr(rest.into_iter().next().unwrap())),
2 => {
let mut it = rest.into_iter();
let g = parse_expr(it.next().unwrap());
let b = parse_expr(it.next().unwrap());
(Some(g), b)
}
_ => panic!("match arm: expected 1 or 2 trailing exprs (guard?, body)"),
};
arms.push(MatchArm { pat, guard, body });
}
Expr::Match {
scrutinee: Box::new(scrutinee),
arms,
}
}
fn parse_pat(pair: Pair<Rule>) -> Pat {
match pair.as_rule() {
Rule::pat_or => {
let mut alts: Vec<Pat> = pair.into_inner().map(parse_pat).collect();
if alts.len() == 1 {
alts.pop().unwrap()
} else {
Pat::Or(alts)
}
}
Rule::pat_atom => {
let inner = pair.into_inner().next().expect("pat_atom inner");
parse_pat(inner)
}
Rule::pat_range => {
let mut it = pair.into_inner();
let lo_pair = it.next().expect("pat_range: lo");
let op_pair = it.next().expect("pat_range: op");
let hi_pair = it.next().expect("pat_range: hi");
let lo: f64 = lo_pair.as_str().parse().expect("range lo as f64");
let hi: f64 = hi_pair.as_str().parse().expect("range hi as f64");
let inclusive = op_pair.as_str() == "..=";
Pat::Range { lo, hi, inclusive }
}
Rule::pat_wild => Pat::Wild,
Rule::pat_literal => Pat::Lit(parse_pat_literal(pair)),
Rule::pat_kind_bind => {
let mut it = pair.into_inner();
let name = it.next().unwrap().as_str().to_string();
let kind = parse_kind_type(it.next().unwrap().as_str());
Pat::Kind {
name: Some(name),
kind,
}
}
Rule::pat_kind_only => {
let kt = pair.into_inner().next().unwrap().as_str();
Pat::Kind {
name: None,
kind: parse_kind_type(kt),
}
}
Rule::pat_bind => Pat::Bind(pair.as_str().to_string()),
Rule::pat_obj => {
let mut fields: Vec<(String, Pat)> = Vec::new();
let mut rest: Option<Option<String>> = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::pat_obj_field => {
let inner = p.into_inner().next().unwrap();
match inner.as_rule() {
Rule::pat_obj_field_kv => {
let mut fi = inner.into_inner();
let k = fi.next().unwrap().as_str().to_string();
let v = parse_pat(fi.next().unwrap());
fields.push((k, v));
}
Rule::pat_obj_field_short => {
let k = inner.as_str().to_string();
fields.push((k.clone(), Pat::Bind(k)));
}
_ => {}
}
}
Rule::pat_obj_rest => {
let inner = p.into_inner().next().unwrap();
match inner.as_rule() {
Rule::pat_obj_rest_named => {
let nm = inner.into_inner().next().unwrap().as_str().to_string();
rest = Some(Some(nm));
}
Rule::pat_obj_rest_anon => rest = Some(None),
_ => {}
}
}
_ => {}
}
}
Pat::Obj { fields, rest }
}
Rule::pat_arr => {
let mut elems: Vec<Pat> = Vec::new();
let mut rest: Option<Option<String>> = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::pat_or | Rule::pat_atom => elems.push(parse_pat(p)),
Rule::pat_arr_rest => {
let inner = p.into_inner().next().unwrap();
match inner.as_rule() {
Rule::pat_arr_rest_named => {
let nm = inner.into_inner().next().unwrap().as_str().to_string();
rest = Some(Some(nm));
}
Rule::pat_arr_rest_anon => rest = Some(None),
_ => {}
}
}
_ => {}
}
}
Pat::Arr { elems, rest }
}
r => panic!("unexpected pattern rule: {:?}", r),
}
}
fn parse_pat_literal(pair: Pair<Rule>) -> PatLit {
let inner = pair.into_inner().next().expect("pat_literal inner");
match inner.as_rule() {
Rule::pat_lit_null => PatLit::Null,
Rule::pat_lit_true => PatLit::Bool(true),
Rule::pat_lit_false => PatLit::Bool(false),
Rule::pat_lit_int => PatLit::Int(inner.as_str().parse().expect("pat int")),
Rule::pat_lit_float => PatLit::Float(inner.as_str().parse().expect("pat float")),
Rule::pat_lit_str => {
let raw = inner.as_str();
let stripped = &raw[1..raw.len() - 1];
PatLit::Str(stripped.to_string())
}
r => panic!("unexpected pat literal rule: {:?}", r),
}
}
fn parse_kind_type(name: &str) -> KindType {
match name {
"number" => KindType::Number,
"string" => KindType::Str,
"array" => KindType::Array,
"object" => KindType::Object,
"bool" => KindType::Bool,
"null" => KindType::Null,
other => panic!("unknown kind type: {other}"),
}
}
fn parse_literal(pair: Pair<Rule>) -> Expr {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::lit_null => Expr::Null,
Rule::lit_true => Expr::Bool(true),
Rule::lit_false => Expr::Bool(false),
Rule::lit_int => Expr::Int(inner.as_str().parse().unwrap()),
Rule::lit_float => Expr::Float(inner.as_str().parse().unwrap()),
Rule::lit_fstring => {
let raw = inner.as_str();
let content = &raw[2..raw.len() - 1]; let parts = parse_fstring_content(content);
Expr::FString(parts)
}
Rule::lit_str => {
let s = inner.into_inner().next().unwrap();
let raw = s.as_str();
Expr::Str(unescape_str_lit(&raw[1..raw.len() - 1]))
}
r => panic!("unexpected literal rule: {:?}", r),
}
}
fn parse_fstring_content(raw: &str) -> Vec<FStringPart> {
let mut parts = Vec::new();
let mut lit = String::new();
let mut chars = raw.chars().peekable();
while let Some(c) = chars.next() {
match c {
'{' => {
if chars.peek() == Some(&'{') {
chars.next();
lit.push('{');
} else {
if !lit.is_empty() {
parts.push(FStringPart::Lit(std::mem::take(&mut lit)));
}
let mut inner = String::new();
let mut depth = 1usize;
for c2 in chars.by_ref() {
match c2 {
'{' => {
depth += 1;
inner.push(c2);
}
'}' => {
depth -= 1;
if depth == 0 {
break;
}
inner.push(c2);
}
_ => inner.push(c2),
}
}
let (expr_str, fmt) = split_fstring_interp(&inner);
let expr = parse(expr_str.trim())
.unwrap_or_else(|e| panic!("f-string parse error in {{{}}}: {}", inner, e));
parts.push(FStringPart::Interp { expr, fmt });
}
}
'}' if chars.peek() == Some(&'}') => {
chars.next();
lit.push('}');
}
_ => lit.push(c),
}
}
if !lit.is_empty() {
parts.push(FStringPart::Lit(lit));
}
parts
}
fn split_fstring_interp(inner: &str) -> (&str, Option<FmtSpec>) {
let mut depth = 0usize;
let mut pipe_pos: Option<usize> = None;
let mut colon_pos: Option<usize> = None;
for (i, c) in inner.char_indices() {
match c {
'(' | '[' | '{' => depth += 1,
')' | ']' | '}' => {
if depth > 0 {
depth -= 1;
}
}
'|' if depth == 0 && pipe_pos.is_none() => pipe_pos = Some(i),
':' if depth == 0 && colon_pos.is_none() => colon_pos = Some(i),
_ => {}
}
}
if let Some(p) = pipe_pos {
return (
&inner[..p],
Some(FmtSpec::Pipe(inner[p + 1..].trim().to_string())),
);
}
if let Some(c) = colon_pos {
return (&inner[..c], Some(FmtSpec::Spec(inner[c + 1..].to_string())));
}
(inner, None)
}
fn parse_let(pair: Pair<Rule>) -> Expr {
let inner: Vec<Pair<Rule>> = pair
.into_inner()
.filter(|p| !matches!(p.as_rule(), Rule::kw_let | Rule::kw_in))
.collect();
let (bindings, body_pair) = inner.split_at(inner.len() - 1);
let body = parse_expr(body_pair[0].clone());
let mut tuple_counter = 0u32;
bindings.iter().rev().fold(body, |acc, b| {
let mut bi = b.clone().into_inner();
let target = bi.next().unwrap();
let init = parse_expr(bi.next().unwrap());
match target.as_rule() {
Rule::ident => Expr::Let {
name: target.as_str().to_string(),
init: Box::new(init),
body: Box::new(acc),
},
Rule::let_target => {
let inner = target.into_inner().next().unwrap();
match inner.as_rule() {
Rule::ident => Expr::Let {
name: inner.as_str().to_string(),
init: Box::new(init),
body: Box::new(acc),
},
Rule::let_tuple_target => lower_tuple_let_binding(
init,
parse_let_tuple_names(inner),
acc,
&mut tuple_counter,
),
_ => unreachable!("unexpected let target inner: {:?}", inner.as_rule()),
}
}
Rule::let_tuple_target => lower_tuple_let_binding(
init,
parse_let_tuple_names(target),
acc,
&mut tuple_counter,
),
_ => unreachable!("unexpected let binding target: {:?}", target.as_rule()),
}
})
}
fn parse_let_tuple_names(pair: Pair<Rule>) -> Vec<String> {
pair.into_inner()
.filter(|p| matches!(p.as_rule(), Rule::ident))
.map(|p| p.as_str().to_string())
.collect()
}
fn lower_tuple_let_binding(init: Expr, names: Vec<String>, body: Expr, counter: &mut u32) -> Expr {
let tmp = loop {
let candidate = format!("__lettuple_{}", *counter);
*counter += 1;
if !names.iter().any(|name| name == &candidate) {
break candidate;
}
};
let destructured = names
.into_iter()
.enumerate()
.rev()
.fold(body, |acc, (i, name)| {
let indexed = Expr::Chain(
Box::new(Expr::Ident(tmp.clone())),
vec![Step::Index(i as i64)],
);
Expr::Let {
name,
init: Box::new(indexed),
body: Box::new(acc),
}
});
Expr::Let {
name: tmp,
init: Box::new(init),
body: Box::new(destructured),
}
}
enum LambdaPatParam {
Ident(String),
Array {
names: Vec<String>,
rest: Option<String>,
},
}
fn collect_lambda_params(params_pair: Pair<Rule>) -> Vec<LambdaPatParam> {
let mut out = Vec::new();
for p in params_pair.into_inner() {
match p.as_rule() {
Rule::ident => out.push(LambdaPatParam::Ident(p.as_str().to_string())),
Rule::lambda_param => {
let inner = p.into_inner().next().unwrap();
match inner.as_rule() {
Rule::ident => out.push(LambdaPatParam::Ident(inner.as_str().to_string())),
Rule::lambda_array_pat => {
out.push(parse_lambda_array_pat(inner));
}
_ => {}
}
}
Rule::lambda_array_pat => {
out.push(parse_lambda_array_pat(p));
}
_ => {}
}
}
out
}
fn parse_lambda_array_pat(pair: Pair<Rule>) -> LambdaPatParam {
let mut names: Vec<String> = Vec::new();
let mut rest: Option<String> = None;
for child in pair.into_inner() {
match child.as_rule() {
Rule::ident => names.push(child.as_str().to_string()),
Rule::lambda_array_rest => {
let inner = child.into_inner().next().unwrap();
rest = Some(inner.as_str().to_string());
}
_ => {}
}
}
LambdaPatParam::Array { names, rest }
}
fn synth_destructure(
counter: &mut u32,
pat: Vec<String>,
rest: Option<String>,
) -> (String, Box<dyn FnOnce(Expr) -> Expr>) {
let id = format!("__lampat_{}", *counter);
*counter += 1;
let synth = id.clone();
let wrap: Box<dyn FnOnce(Expr) -> Expr> = Box::new(move |body| {
let mut acc = body;
if let Some(rest_name) = rest {
let prefix_len = pat.len() as i64;
let slice = Expr::Chain(
Box::new(Expr::Ident(synth.clone())),
vec![Step::Slice(Some(prefix_len), None, None)],
);
acc = Expr::Let {
name: rest_name,
init: Box::new(slice),
body: Box::new(acc),
};
}
pat.into_iter()
.enumerate()
.rev()
.fold(acc, |acc, (i, name)| {
let index = Expr::Chain(
Box::new(Expr::Ident(synth.clone())),
vec![Step::Index(i as i64)],
);
Expr::Let {
name,
init: Box::new(index),
body: Box::new(acc),
}
})
});
(id, wrap)
}
fn lower_lambda_params(
params: Vec<LambdaPatParam>,
) -> (Vec<String>, Box<dyn FnOnce(Expr) -> Expr>) {
let mut names = Vec::with_capacity(params.len());
let mut wrappers: Vec<Box<dyn FnOnce(Expr) -> Expr>> = Vec::new();
let mut counter: u32 = 0;
for p in params {
match p {
LambdaPatParam::Ident(n) => names.push(n),
LambdaPatParam::Array { names: pat, rest } => {
let (synth, wrap) = synth_destructure(&mut counter, pat, rest);
names.push(synth);
wrappers.push(wrap);
}
}
}
let body_wrap: Box<dyn FnOnce(Expr) -> Expr> = Box::new(move |body| {
wrappers.into_iter().rev().fold(body, |acc, w| w(acc))
});
(names, body_wrap)
}
fn parse_lambda(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner().filter(|p| p.as_rule() != Rule::kw_lambda);
let params_pair = inner.next().unwrap();
let raw = collect_lambda_params(params_pair);
let (params, wrap) = lower_lambda_params(raw);
let body = parse_expr(inner.next().unwrap());
Expr::Lambda {
params,
body: Box::new(wrap(body)),
}
}
fn parse_arrow_lambda(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner();
let params_pair = inner.next().unwrap();
let raw = collect_lambda_params(params_pair);
let (params, wrap) = lower_lambda_params(raw);
let body = parse_expr(inner.next().unwrap());
Expr::Lambda {
params,
body: Box::new(wrap(body)),
}
}
fn comp_inner_filter(pair: Pair<Rule>) -> impl Iterator<Item = Pair<Rule>> {
pair.into_inner()
.filter(|p| !matches!(p.as_rule(), Rule::kw_for | Rule::kw_in | Rule::kw_if))
}
fn parse_comp_vars(pair: Pair<Rule>) -> Vec<String> {
pair.into_inner()
.filter(|p| p.as_rule() == Rule::ident)
.map(|p| p.as_str().to_string())
.collect()
}
fn unescape_str_lit(s: &str) -> String {
let bytes = s.as_bytes();
let mut out = String::with_capacity(s.len());
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b != b'\\' || i + 1 >= bytes.len() {
out.push(b as char);
i += 1;
continue;
}
let next = bytes[i + 1];
match next {
b'n' => {
out.push('\n');
i += 2;
}
b'r' => {
out.push('\r');
i += 2;
}
b't' => {
out.push('\t');
i += 2;
}
b'0' => {
out.push('\0');
i += 2;
}
b'\\' => {
out.push('\\');
i += 2;
}
b'"' => {
out.push('"');
i += 2;
}
b'\'' => {
out.push('\'');
i += 2;
}
b'x' if i + 3 < bytes.len() => {
let hex = &s[i + 2..i + 4];
if let Ok(n) = u8::from_str_radix(hex, 16) {
out.push(n as char);
i += 4;
} else {
out.push('\\');
i += 1;
}
}
b'u' if i + 5 < bytes.len() => {
let hex = &s[i + 2..i + 6];
if let Ok(n) = u32::from_str_radix(hex, 16) {
if let Some(c) = char::from_u32(n) {
out.push(c);
i += 6;
continue;
}
}
out.push('\\');
i += 1;
}
_ => {
out.push('\\');
i += 1;
}
}
}
out
}
fn collect_comp_conds<'a, I: Iterator<Item = Pair<'a, Rule>>>(rest: I) -> Option<Box<Expr>> {
let exprs: Vec<Expr> = rest.map(parse_expr).collect();
let mut it = exprs.into_iter();
let first = it.next()?;
let combined = it.fold(first, |acc, e| {
Expr::BinOp(Box::new(acc), BinOp::And, Box::new(e))
});
Some(Box::new(combined))
}
fn parse_list_comp(pair: Pair<Rule>) -> Expr {
let mut inner = comp_inner_filter(pair);
let expr = parse_expr(inner.next().unwrap());
let vars = parse_comp_vars(inner.next().unwrap());
let iter = parse_expr(inner.next().unwrap());
let cond = collect_comp_conds(inner);
Expr::ListComp {
expr: Box::new(expr),
vars,
iter: Box::new(iter),
cond,
}
}
fn parse_dict_comp(pair: Pair<Rule>) -> Expr {
let mut inner = comp_inner_filter(pair);
let key = parse_expr(inner.next().unwrap());
let val = parse_expr(inner.next().unwrap());
let vars = parse_comp_vars(inner.next().unwrap());
let iter = parse_expr(inner.next().unwrap());
let cond = collect_comp_conds(inner);
Expr::DictComp {
key: Box::new(key),
val: Box::new(val),
vars,
iter: Box::new(iter),
cond,
}
}
fn parse_set_comp(pair: Pair<Rule>) -> Expr {
let mut inner = comp_inner_filter(pair);
let expr = parse_expr(inner.next().unwrap());
let vars = parse_comp_vars(inner.next().unwrap());
let iter = parse_expr(inner.next().unwrap());
let cond = collect_comp_conds(inner);
Expr::SetComp {
expr: Box::new(expr),
vars,
iter: Box::new(iter),
cond,
}
}
fn parse_gen_comp(pair: Pair<Rule>) -> Expr {
let mut inner = comp_inner_filter(pair);
let expr = parse_expr(inner.next().unwrap());
let vars = parse_comp_vars(inner.next().unwrap());
let iter = parse_expr(inner.next().unwrap());
let cond = collect_comp_conds(inner);
Expr::GenComp {
expr: Box::new(expr),
vars,
iter: Box::new(iter),
cond,
}
}
fn parse_obj(pair: Pair<Rule>) -> Expr {
let fields = pair
.into_inner()
.filter(|p| p.as_rule() == Rule::obj_field)
.map(parse_obj_field)
.collect();
Expr::Object(fields)
}
fn parse_obj_field(pair: Pair<Rule>) -> ObjField {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::obj_field_dyn => {
let mut i = inner.into_inner();
let key = parse_expr(i.next().unwrap());
let val = parse_expr(i.next().unwrap());
ObjField::Dynamic { key, val }
}
Rule::obj_field_opt_v => {
let mut i = inner.into_inner();
let key = obj_key_str(i.next().unwrap());
let val = parse_expr(i.next().unwrap());
ObjField::Kv {
key,
val,
optional: true,
cond: None,
}
}
Rule::obj_field_opt => {
let key = obj_key_str(inner.into_inner().next().unwrap());
ObjField::Kv {
key: key.clone(),
val: Expr::Ident(key),
optional: true,
cond: None,
}
}
Rule::obj_field_spread | Rule::obj_field_spread_star => {
let expr = parse_expr(inner.into_inner().next().unwrap());
ObjField::Spread(expr)
}
Rule::obj_field_spread_deep => {
let expr = parse_expr(inner.into_inner().next().unwrap());
ObjField::SpreadDeep(expr)
}
Rule::obj_field_kv => {
let mut cond: Option<Expr> = None;
let mut key: Option<String> = None;
let mut val: Option<Expr> = None;
let mut saw_when = false;
for p in inner.into_inner() {
match p.as_rule() {
Rule::kw_when => saw_when = true,
Rule::obj_key_expr => key = Some(obj_key_str(p)),
Rule::expr => {
if saw_when {
cond = Some(parse_expr(p));
} else {
val = Some(parse_expr(p));
}
}
_ => {}
}
}
ObjField::Kv {
key: key.expect("obj_field_kv missing key"),
val: val.expect("obj_field_kv missing val"),
optional: false,
cond,
}
}
Rule::obj_field_short => {
let name = inner.into_inner().next().unwrap().as_str().to_string();
ObjField::Short(name)
}
r => panic!("unexpected obj_field rule: {:?}", r),
}
}
fn obj_key_str(pair: Pair<Rule>) -> String {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::ident | Rule::loose_ident => inner.as_str().to_string(),
Rule::lit_str => {
let s = inner.into_inner().next().unwrap();
let raw = s.as_str();
raw[1..raw.len() - 1].to_string()
}
r => panic!("unexpected obj_key_expr rule: {:?}", r),
}
}
fn parse_arr(pair: Pair<Rule>) -> Expr {
let elems = pair
.into_inner()
.filter(|p| p.as_rule() == Rule::arr_elem)
.map(|elem| {
let inner = elem.into_inner().next().unwrap();
match inner.as_rule() {
Rule::arr_spread => {
let expr = parse_expr(inner.into_inner().next().unwrap());
ArrayElem::Spread(expr)
}
_ => ArrayElem::Expr(parse_expr(inner)),
}
})
.collect();
Expr::Array(elems)
}
fn parse_global_call(pair: Pair<Rule>) -> Expr {
let mut inner = pair.into_inner();
let name = inner.next().unwrap().as_str().to_string();
let args = inner.next().map(parse_arg_list).unwrap_or_default();
Expr::GlobalCall { name, args }
}
fn parse_patch(pair: Pair<Rule>) -> Expr {
let mut root: Option<Expr> = None;
let mut ops: Vec<PatchOp> = Vec::new();
for p in pair.into_inner() {
match p.as_rule() {
Rule::kw_patch => {}
Rule::patch_field => ops.push(parse_patch_field(p)),
_ => {
if root.is_none() {
root = Some(parse_expr(p));
}
}
}
}
Expr::Patch {
root: Box::new(root.expect("patch requires root expression")),
ops,
}
}
fn parse_patch_field(pair: Pair<Rule>) -> PatchOp {
let mut path: Vec<PathStep> = Vec::new();
let mut val: Option<Expr> = None;
let mut cond: Option<Expr> = None;
let mut saw_when = false;
for p in pair.into_inner() {
match p.as_rule() {
Rule::patch_key => path = parse_patch_key(p),
Rule::kw_when => saw_when = true,
Rule::expr => {
if saw_when {
cond = Some(parse_expr(p));
} else {
val = Some(parse_expr(p));
}
}
_ => {}
}
}
PatchOp {
path,
val: val.expect("patch_field missing val"),
cond,
}
}
fn parse_patch_key(pair: Pair<Rule>) -> Vec<PathStep> {
let mut steps: Vec<PathStep> = Vec::new();
let mut first = true;
for p in pair.into_inner() {
match p.as_rule() {
Rule::ident if first => {
steps.push(PathStep::Field(p.as_str().to_string()));
first = false;
}
Rule::patch_step => steps.push(parse_patch_step(p)),
_ => {}
}
}
steps
}
fn parse_patch_step(pair: Pair<Rule>) -> PathStep {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::pp_dot_field => {
let name = inner.into_inner().next().unwrap().as_str().to_string();
PathStep::Field(name)
}
Rule::pp_index => {
let idx: i64 = inner.into_inner().next().unwrap().as_str().parse().unwrap();
PathStep::Index(idx)
}
Rule::pp_wild => PathStep::Wildcard,
Rule::pp_wild_filter => {
let mut e: Option<Expr> = None;
for p in inner.into_inner() {
if p.as_rule() == Rule::expr {
e = Some(parse_expr(p));
}
}
PathStep::WildcardFilter(Box::new(e.expect("pp_wild_filter missing expr")))
}
Rule::pp_descendant => {
let name = inner.into_inner().next().unwrap().as_str().to_string();
PathStep::Descendant(name)
}
r => panic!("unexpected patch_step rule: {:?}", r),
}
}
fn parse_arg_list(pair: Pair<Rule>) -> Vec<Arg> {
pair.into_inner()
.filter(|p| p.as_rule() == Rule::arg)
.map(parse_arg)
.collect()
}
fn parse_arg(pair: Pair<Rule>) -> Arg {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::named_arg => {
let mut i = inner.into_inner();
let name = i.next().unwrap().as_str().to_string();
let val = parse_expr(i.next().unwrap());
Arg::Named(name, val)
}
Rule::pos_arg => Arg::Pos(parse_expr(inner.into_inner().next().unwrap())),
r => panic!("unexpected arg rule: {:?}", r),
}
}