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) { ... }
}
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.

Dyn Compatibility§

This trait is dyn compatible.

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

Implementors§