Skip to main content

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}