polydat_core/dsl/cursor_sugar.rs
1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Cursor-constructor sugar registry.
5//!
6//! A *cursor sugar* lets a node module (vectordata, future
7//! tabular sources, etc.) recognize a non-standard `cursor x =
8//! foo(...)` form and rewrite it into:
9//!
10//! 1. A synthetic constructor expression (typically `range(...)`)
11//! that the standard extent-resolution path can drive, and
12//! 2. Zero or more auxiliary bindings to emit after the cursor's
13//! input ports are wired — typical examples are an
14//! init-time prebuffer call and per-field projection bindings
15//! (`<cursor>__vector := vector_at("ds:profile",
16//! <cursor>__ordinal)`).
17//!
18//! The core compiler stays agnostic: it walks the inventory of
19//! registered handlers, picks the first match, and applies the
20//! rewrite. Adding a new sugar form is a single
21//! `inventory::submit!` plus a handler function — no compile.rs
22//! change required.
23
24use crate::ast::PortType;
25use crate::dsl::ast::Expr;
26
27/// A handler that recognizes one or more cursor-constructor
28/// shapes and produces a [`CursorSugar`] rewrite. Return:
29/// - `Ok(Some(s))` when the handler matched and the rewrite
30/// should be applied.
31/// - `Ok(None)` when this handler isn't responsible for the
32/// given constructor — the dispatch loop continues on to the
33/// next handler.
34/// - `Err(msg)` when the handler matched the *name* but the
35/// arguments don't validate. The caller surfaces the error
36/// directly (with the cursor name prepended).
37pub type CursorSugarFn =
38 fn(source_name: &str, constructor: &Expr) -> Result<Option<CursorSugar>, String>;
39
40/// One inventory entry. Handlers self-name for diagnostic
41/// listings (`describe wiring cursor-sugar`, future) and so the
42/// dispatcher can attribute errors to the right module.
43pub struct CursorSugarRegistration {
44 /// The function that recognises and lowers the sugar.
45 pub handler: CursorSugarFn,
46 /// Short identifier of the sugar family for diagnostics
47 /// (e.g. `"vectordata"`).
48 pub name: &'static str,
49}
50
51inventory::collect!(CursorSugarRegistration);
52
53/// The rewrite returned by a sugar handler.
54pub struct CursorSugar {
55 /// Replacement for the user's constructor. The standard
56 /// extent-resolution path runs against this — typically a
57 /// `range(0, <count_function>(...))` call.
58 pub effective_constructor: Expr,
59 /// Auxiliary bindings the compiler should emit after the
60 /// cursor's input ports are wired. Order is preserved.
61 pub aux_bindings: Vec<AuxBinding>,
62}
63
64/// One binding emitted by sugar after cursor input wiring.
65///
66/// If `projection` is `Some`, the binding's output wire is
67/// promoted to a cursor projection — both registered on the
68/// `SourceSchema.projections` list and added as a kernel output
69/// the runtime can read.
70pub struct AuxBinding {
71 /// The binding's name.
72 pub name: String,
73 /// Its expression.
74 pub value: Expr,
75 /// The cursor projection it becomes, as `(name, type)`, if any.
76 pub projection: Option<(String, PortType)>,
77}
78
79/// Walk the inventory and dispatch to the first handler that
80/// matches `constructor`. Returns the rewrite to apply, `None`
81/// if no handler matches, or the handler's error.
82pub fn dispatch(source_name: &str, constructor: &Expr) -> Result<Option<CursorSugar>, String> {
83 for reg in inventory::iter::<CursorSugarRegistration> {
84 match (reg.handler)(source_name, constructor) {
85 Ok(Some(s)) => return Ok(Some(s)),
86 Ok(None) => continue,
87 Err(e) => return Err(e),
88 }
89 }
90 Ok(None)
91}