Skip to main content

ReduceCallback

Trait ReduceCallback 

pub trait ReduceCallback {
    // Required method
    fn reduce(
        &mut self,
        cx: &mut ReduceCx<'_>,
        rule_display_id: DisplayId,
        children: Vec<NodeValue>,
    ) -> NodeValue;

    // Provided methods
    fn reduce_expand1(
        &mut self,
        cx: &mut ReduceCx<'_>,
        rule_display_id: DisplayId,
        children: Vec<NodeValue>,
    ) -> NodeValue { ... }
    fn failed(&self) -> bool { ... }
    fn routes(&self) -> Option<Arc<RouteSet>> { ... }
    fn token_shifted(&mut self, _token: &Token) -> Option<Handle> { ... }
    fn stamp_passthrough(&mut self, _slot: u32, _plan: PendingSpan) { ... }
    fn flush(&mut self) { ... }
}
Expand description

The public reduce-time transform hook — Lark’s embedded transformer= (create_callback), the one core seam every binding (Python Transformer, WASM JS fn, C fn-ptr) boxes as Box<dyn ReduceCallback + Send>. Object-safe (no generics, no Self return) so it is dyn-compatible; Send at the box site (an owned PyObject/JS handle is Send). One trait serves both engines: LALR drives it from apply_shape, Earley from the per-rule chain’s node builder.

Fires once per reduction, for every rule — named, _-splice, and the __/___*_star/_plus synthetics alike (a _addop splice rule’s method fires; the ___addop_star_0 synthetic fires __default__). Lark has no “default vs. override” split at this seam: the binding’s impl does that dispatch (named method, else __default__, else the tree builder) and returns one value. The return becomes the reduced node’s value on the parse stack; a _-splice parent then drains it (see the Foreign doc + ReduceCx::build_tree).

ExpandSingleChild (?rule, no alias) short-circuits a lone shaped child before the hook (Lark wraps the method innermost) — the hook is not called for a collapsed single child. v_args-style call adaptation is binding-specific and lives above this seam.

Required Methods§

fn reduce( &mut self, cx: &mut ReduceCx<'_>, rule_display_id: DisplayId, children: Vec<NodeValue>, ) -> NodeValue

Transform one reduction. cx resolves token leaves and builds arena-backed trees (the drainable default a _-splice parent expects); rule_display_id indexes ParseTable::display_names (Lark’s user_callback_name); children are the shaped children (filter_out dropped, placeholders inserted, _-child splices already drained).

Provided Methods§

fn reduce_expand1( &mut self, cx: &mut ReduceCx<'_>, rule_display_id: DisplayId, children: Vec<NodeValue>, ) -> NodeValue

Self::reduce for an ExpandSingleChild (?rule) reduction whose final child count the engine cannot determine: a batching hook deferred a _-splice child (a NodeValue::Handle with Handle::with_splice_flag), so Lark’s collapse-if-single-child decision (ExpandSingleChild wraps the callback innermost) must happen at the hook’s drain, after the splice expansion: exactly one final child → that child is the value, callback skipped; otherwise the callback fires. Only a batching hook can receive this call — a synchronous hook never returns handles, so the engine resolves the count itself and calls plain Self::reduce. The default delegates to reduce.

fn failed(&self) -> bool

true once a host-side callback failure has been latched: the engine polls this after every Self::reduce and aborts the parse with crate::ParseError::Callback (the binding re-raises its latched exception in place of that error), bounding run-ahead past a failing callback — a batching hook discovers failures only at a drain, so without this poll the engine would consume the rest of the input. Default false: a hook without an internal error latch never aborts.

fn routes(&self) -> Option<Arc<RouteSet>>

Per-display reduce routing for a hybrid transform parse: None (the default) = every reduce fires the hook, today’s behavior for every existing hook; Some routes per RouteSet (un-hooked displays build natively, no hook call). Fetched once at parse start. LALR-only — Earley extraction ignores routes (an embedded transformer is LALR-only in every binding).

fn token_shifted(&mut self, _token: &Token) -> Option<Handle>

The shift anchor — lark’s callbacks[token.type](token) on the SHIFT branch (lalr_parser_state.py:88), the terminal-callback half of the one callback dict lark keys by both rule and token type. The engine calls this from [ParserState::shift_token] for every shifted token whose terminal the fetched RouteSet::hooked_tokens marks — once per shifted token, in stream order, before the token can be consumed, filtered, or collapsed — so it reaches the three shapes reduce-time application cannot (a ?rule that collapses to its single child, a collapsing root, a filter_out token that is shifted then dropped).

Returns the value’s stand-in on the parse stack:

  • Some(handle) — a batching hook enqueues the callback (materialize the token, call the user fn, store the result in handle’s slot at the drain) and returns a plain Handle (never Handle::with_splice_flag; the collapse logic distinguishes token Handles from deferred _-splice children by that flag). The engine pushes NodeValue::Handle in place of the raw NodeValue::Token; the result flows through every existing Handle arm (native build/splice, ?rule collapse, span Slot source, the finish_ok Handle→Foreign flush).
  • None — apply nothing; the engine pushes the raw token. The default. A hook that has no slot table cannot express an inline transformed value through this interface, so synchronous token callbacks are unsupported here; the only implementor is the batching binding, and hooked_tokens is all-false for hooks (WASM/C) that don’t provide one.

After it, the engine polls Self::failed (mirroring the reduce poll) so a raised callback aborts within batch_size shifts.

fn stamp_passthrough(&mut self, _slot: u32, _plan: PendingSpan)

A native ?rule collapse (transform + propagate_positions) returned a bare batching Handle: lark’s PropagatePositions still wraps that reduce, so if the slot’s eventual value is a Tree its meta must be updated from this reduce’s raw span (plan) — plain fields kept, container widened. A batching hook queues a stamp-only step (ordered after the slot’s own drain); the default no-op serves synchronous hooks, whose values the engine already stamped via the returned plan.

fn flush(&mut self)

Control is about to leave the engine for caller code mid-parse (the on_error callback): a batching hook runs its queued work now, so that code observes every reduction before it. The engine polls Self::failed right after and does not call out if it is set. Default no-op: a synchronous hook has nothing queued.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§