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 so listings can show them
41/// and so the dispatcher can attribute errors to the right module.
42pub struct CursorSugarRegistration {
43    /// The function that recognises and lowers the sugar.
44    pub handler: CursorSugarFn,
45    /// Short identifier of the sugar family for diagnostics
46    /// (e.g. `"vectordata"`).
47    pub name: &'static str,
48}
49
50inventory::collect!(CursorSugarRegistration);
51
52/// The rewrite returned by a sugar handler.
53pub struct CursorSugar {
54    /// Replacement for the user's constructor. The standard
55    /// extent-resolution path runs against this — typically a
56    /// `range(0, <count_function>(...))` call.
57    pub effective_constructor: Expr,
58    /// Auxiliary bindings the compiler should emit after the
59    /// cursor's input ports are wired. Order is preserved.
60    pub aux_bindings: Vec<AuxBinding>,
61}
62
63/// One binding emitted by sugar after cursor input wiring.
64///
65/// If `projection` is `Some`, the binding's output wire is
66/// promoted to a cursor projection — both registered on the
67/// `SourceSchema.projections` list and added as a kernel output
68/// the runtime can read.
69pub struct AuxBinding {
70    /// The binding's name.
71    pub name: String,
72    /// Its expression.
73    pub value: Expr,
74    /// The cursor projection it becomes, as `(name, type)`, if any.
75    pub projection: Option<(String, PortType)>,
76}
77
78/// Walk the inventory and dispatch to the first handler that
79/// matches `constructor`. Returns the rewrite to apply, `None`
80/// if no handler matches, or the handler's error.
81pub fn dispatch(source_name: &str, constructor: &Expr) -> Result<Option<CursorSugar>, String> {
82    for reg in inventory::iter::<CursorSugarRegistration> {
83        match (reg.handler)(source_name, constructor) {
84            Ok(Some(s)) => return Ok(Some(s)),
85            Ok(None) => continue,
86            Err(e) => return Err(e),
87        }
88    }
89    Ok(None)
90}