From 3a4cd495e748e0d62925efc1d01561ffb5e80191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20F=C3=A4rber?= <01mf02@gmail.com> Date: Fri, 3 Apr 2026 14:10:20 +0200 Subject: [PATCH] Implement `yield $x`. --- jaq-core/src/compile.rs | 32 +++++++++++++++++++++++++------- jaq-core/src/exn.rs | 1 + jaq-core/src/filter.rs | 29 +++++++++++++++++++++++++---- jaq-core/src/load/parse.rs | 3 +++ 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/jaq-core/src/compile.rs b/jaq-core/src/compile.rs index fa282419..0e9e636b 100644 --- a/jaq-core/src/compile.rs +++ b/jaq-core/src/compile.rs @@ -101,8 +101,10 @@ pub(crate) enum Term { /// Singleton object (`{f: g}`) ObjSingle(T, T), - /// Bound variable (`$x`), label (`label $x`), or filter argument (`a`) + /// Bound variable (`$x`) or filter argument (`a`) Var(VarId), + Break(VarId), + Yield(VarId), /// Call to a filter (`filter`, `filter(…)`) CallDef(TermId, Box<[Arg]>, VarSkip, CallType), @@ -324,6 +326,16 @@ pub(crate) enum Bind { Fun(F), } +impl Bind { + pub(crate) fn get_label(&self) -> Option<&L> { + if let Self::Label(l) = self { + Some(l) + } else { + None + } + } +} + struct Locals { // usize = number of vars funs: MapVec<(S, Arity), (Fun, usize)>, @@ -636,7 +648,12 @@ impl<'s, F> Compiler<&'s str, F> { Arr(t) => Term::Arr(self.iterm(t.map_or_else(|| Call("!empty", Vec::new()), |t| *t))), Neg(t) => Term::Neg(self.iterm(*t)), Label(x, t) => Term::Label(self.with_label(x, |c| c.iterm(*t))), - Break(x) => self.break_(x), + Break(x) => self + .label(x) + .map_or_else(|| self.fail(x, Undefined::Label), Term::Break), + Yield(x) => self + .label(x) + .map_or_else(|| self.fail(x, Undefined::Label), Term::Yield), IfThenElse(if_thens, else_) => { let f = |(if_, then_)| (self.iterm(if_), self.iterm_tr(then_, tr)); let if_thens: Vec<_> = if_thens.into_iter().map(f).collect(); @@ -860,11 +877,12 @@ impl<'s, F> Compiler<&'s str, F> { self.fail(x, Undefined::Var) } - fn break_(&mut self, x: &'s str) -> Term { - if let Some(l) = self.locals.vars.bound.get_last(&Bind::Label(x)) { - return Term::Var(self.locals.vars.total - l); - } - self.fail(x, Undefined::Label) + fn label(&mut self, x: &'s str) -> Option { + self.locals + .vars + .bound + .get_last(&Bind::Label(x)) + .map(|l| self.locals.vars.total - l) } fn obj_entry(&mut self, k: parse::Term<&'s str>, v: Option>) -> Term { diff --git a/jaq-core/src/exn.rs b/jaq-core/src/exn.rs index 5e2a07ad..62180ffc 100644 --- a/jaq-core/src/exn.rs +++ b/jaq-core/src/exn.rs @@ -22,6 +22,7 @@ pub(crate) enum Inner<'a, V> { /// If this can be observed by users, then this is a bug. TailCall(Box<(&'a TermId, Vars, CallInput)>), Break(usize), + Yield(Box<(usize, V)>), } #[derive(Clone, Debug)] diff --git a/jaq-core/src/filter.rs b/jaq-core/src/filter.rs index f888affb..0a4464a2 100644 --- a/jaq-core/src/filter.rs +++ b/jaq-core/src/filter.rs @@ -239,12 +239,14 @@ fn bind_run<'a, D: DataT, T: Clone + 'a>( fn label_run<'a, D: DataT, T: 'a>( cv: Cv<'a, D, T>, + from: fn(D::V<'a>) -> ValX<'a, T, D::V<'a>>, run: impl Fn(Cv<'a, D, T>) -> ValXs<'a, T, D::V<'a>>, ) -> ValXs<'a, T, D::V<'a>> { let ctx = cv.0.cons_label(); let labels = ctx.labels; Box::new(run((ctx, cv.1)).map_while(move |y| match y { Err(Exn(exn::Inner::Break(b))) if b == labels => None, + Err(Exn(exn::Inner::Yield(y))) if y.0 == labels => Some(from(y.1)), y => Some(y), })) } @@ -549,8 +551,14 @@ impl Id { Ast::Var(v) => match cv.0.vars.get(*v).unwrap() { Bind::Var(v) => box_once(Ok(v.clone())), Bind::Fun((id, vars)) => id.run((cv.0.with_vars(vars.clone()), cv.1)), - Bind::Label(l) => box_once(Err(Exn(exn::Inner::Break(*l)))), + Bind::Label(_) => panic!(), }, + Ast::Break(v) => box_once(Err(Exn(exn::Inner::Break( + *cv.0.vars.get(*v).unwrap().get_label().unwrap(), + )))), + Ast::Yield(v) => box_once(Err(Exn(exn::Inner::Yield( + (*cv.0.vars.get(*v).unwrap().get_label().unwrap(), cv.1).into(), + )))), Ast::CallDef(id, args, skip, call_typ) => { let data = cv.0.data.clone(); let with_vars = move |vars| Ctx { @@ -566,7 +574,7 @@ impl Id { let cvs = bind_vars(args, cv.0.with_vars(Vars::new([])), cv, Clone::clone); flat_map_then(cvs, |cv| (cv.0.lut().funs[*id].run)(cv)) } - Ast::Label(id) => label_run(cv, |cv| id.run(cv)), + Ast::Label(id) => label_run(cv, Ok, |cv| id.run(cv)), } } @@ -576,7 +584,8 @@ impl Id { /// In particular, `v | path(f)` in context `c` yields the same paths as /// `f.paths((c, (v, Default::default())))`. pub fn paths<'a, D: DataT>(&self, cv: Cvp<'a, D>) -> ValPathXs<'a, D::V<'a>> { - let err = |v| box_once(Err(Exn::from(Error::path_expr(v)))); + let err1 = |v| Err(Exn::from(Error::path_expr(v))); + let err = |v| box_once(err1(v)); let proj_cv = |cv: &Cvp<'a, D>| (cv.0.clone(), cv.1 .0.clone()); let proj_val = |(val, _path): &(D::V<'a>, _)| val.clone(); match &cv.0.lut().terms[self.0] { @@ -633,6 +642,12 @@ impl Id { Bind::Fun(l) => l.0.paths((cv.0.with_vars(l.1.clone()), cv.1)), Bind::Label(l) => box_once(Err(Exn(exn::Inner::Break(*l)))), }, + Ast::Break(v) => box_once(Err(Exn(exn::Inner::Break( + *cv.0.vars.get(*v).unwrap().get_label().unwrap(), + )))), + Ast::Yield(v) => box_once(Err(Exn(exn::Inner::Yield( + (*cv.0.vars.get(*v).unwrap().get_label().unwrap(), cv.1 .0).into(), + )))), Ast::Fold(xs, pat, init, update, fold_type) => { let xs = rc_lazy_list::List::from_iter(run_and_bind(xs, proj_cv(&cv), pat)); fold_run(xs, cv, init, update, fold_type, |f, cv| f.paths(cv)) @@ -648,7 +663,7 @@ impl Id { let (into, from) = (exn::CallInput::Paths, exn::CallInput::unwrap_paths); def_run(id, call_typ, cvs, Id::paths, with_vars, into, from) } - Ast::Label(id) => label_run(cv, |cv| id.paths(cv)), + Ast::Label(id) => label_run(cv, err1, |cv| id.paths(cv)), Ast::Native(id, args) => { let cvs = bind_vars(args, cv.0.with_vars(Vars::new([])), cv, proj_val); flat_map_then(cvs, |cv| (cv.0.lut().funs[*id].paths)(cv)) @@ -718,6 +733,12 @@ impl Id { Bind::Fun(l) => l.0.update((cv.0.with_vars(l.1.clone()), cv.1), f), Bind::Label(l) => box_once(Err(Exn(exn::Inner::Break(*l)))), }, + Ast::Break(v) => box_once(Err(Exn(exn::Inner::Break( + *cv.0.vars.get(*v).unwrap().get_label().unwrap(), + )))), + Ast::Yield(v) => box_once(Err(Exn(exn::Inner::Yield( + (*cv.0.vars.get(*v).unwrap().get_label().unwrap(), cv.1).into(), + )))), Ast::CallDef(id, args, skip, _call_typ) => { let init = cv.1.clone(); let cvs = bind_vars(args, cv.0.clone().skip_vars(*skip), cv, Clone::clone); diff --git a/jaq-core/src/load/parse.rs b/jaq-core/src/load/parse.rs index 84559dfe..dd9b751b 100644 --- a/jaq-core/src/load/parse.rs +++ b/jaq-core/src/load/parse.rs @@ -107,6 +107,8 @@ pub enum Term { Label(S, Box), /// Break out from control flow to location variable, e.g. `break $x` Break(S), + /// TODO!!! + Yield(S), /// `reduce` and `foreach`, e.g. `reduce .[] as $x (0; .+$x)` Fold(S, Box, Pattern, Vec), @@ -515,6 +517,7 @@ impl<'s, 't> Parser<'s, 't> { Term::Label(x, Box::new(tm)) } Some(Token("break", _)) => Term::Break(self.var()?), + Some(Token("yield", _)) => Term::Yield(self.var()?), Some(Token(fold @ ("reduce" | "foreach"), _)) => { let xs = self.atom()?; self.just("as")?;