RFC: Native Fluent AST Mutation Macro, MicroDSL & CSS Capture Aliases¶
- Status: Proposed
- Authors: Teague Sterling & Antigravity Team
- Target:
sitting_duckv2.x / DuckDB Extension - Date: 2026-09-20
1. Motivation & Overview¶
sitting_duck currently excels at parsing source code into relational AST tables (parse_ast_list_table) and executing ASTCSS queries (ast_select).
However, performing AST mutations and refactorings currently requires round-tripping through external Python runners. Furthermore, AI coding agents and human developers increasingly express code modifications in a jQuery-style fluent syntax:
Because fluent expressions are valid ECMAScript, sitting_duck already contains the Tree-Sitter JavaScript grammar needed to parse the entire mutation chain inside DuckDB in $<5\text{ms}$.
This RFC proposes:
1. parse_fluent_call(expr) Table Function/Macro: Parses JavaScript fluent method chains into structured declarative instruction tables.
2. ASTCSS Capture Aliases (@alias): Extends ASTCSS selectors to bind named variables (e.g. .class@cls > .fn@method) into query projection columns.
3. Polyglot MicroDSL Template Expander: Evaluates language-specific structural rewrite templates directly in SQL.
4. Relational Zipper & Non-Destructive Byte Splicing: Applies modifications using node byte spans (start_byte, end_byte) without text corruption.
2. Specification¶
2.1 The parse_fluent_call(expr) Table Function¶
parse_fluent_call parses a JavaScript fluent invocation string into relational rows.
SELECT * FROM parse_fluent_call('$(".fn#save@target").wrapInTry().on("KeyError", "return None").finally("db.close()")');
Output Schema:
| Column | Type | Example |
|---|---|---|
| input_expr | VARCHAR | $(".fn#save@target").wrapInTry()... |
| target_selector | VARCHAR | .fn#save@target |
| operations | STRUCT(op VARCHAR, args VARCHAR[])[] | [{'op': 'wrapInTry', 'args': []}, {'op': 'on', 'args': ['KeyError', 'return None']}, {'op': 'finally', 'args': ['db.close()']}] |
SQL Reference Implementation:¶
CREATE OR REPLACE MACRO parse_fluent_call(fluent_expr) AS TABLE (
WITH parsed_ast AS (
SELECT *
FROM parse_ast_list_table(fluent_expr, 'javascript')
),
selectors AS (
SELECT trim(coalesce(nullif(a.name, ''), a.peek), '"''') AS target_selector
FROM parsed_ast a
WHERE a.parent_id IN (
SELECT node_id FROM parsed_ast WHERE parent_id IN (
SELECT node_id FROM parsed_ast WHERE type = 'call_expression' AND name = '$'
) AND type = 'arguments'
) AND a.type NOT IN ('(', ')')
LIMIT 1
),
chain_calls AS (
SELECT c.node_id, c.name AS op, c.depth
FROM parsed_ast c
JOIN parsed_ast p ON p.node_id = c.parent_id
WHERE c.type = 'call_expression'
AND c.name != '$'
AND p.type IN ('expression_statement', 'member_expression', 'program')
),
call_ops AS (
SELECT
c.node_id, c.op, c.depth,
(
SELECT list(trim(arg.peek, '"''') ORDER BY arg.node_id ASC)
FROM parsed_ast args
JOIN parsed_ast arg ON arg.parent_id = args.node_id
WHERE args.parent_id = c.node_id
AND args.type = 'arguments'
AND arg.type NOT IN ('(', ')', ',')
) AS args
FROM chain_calls c
)
SELECT
fluent_expr AS input_expr,
(SELECT target_selector FROM selectors LIMIT 1) AS target_selector,
list({'op': c.op, 'args': coalesce(c.args, [])} ORDER BY c.depth DESC) AS operations
FROM call_ops c
);
2.2 ASTCSS Capture Aliases (@alias)¶
Selectors are extended to support named capture variables:
SQL Relational Projection¶
When ast_select evaluates @alias terms, it emits projection columns for each alias:
SELECT
cls.node_id AS cls_id,
cls.peek AS cls_text,
ctor.node_id AS ctor_id,
ctor.peek AS ctor_text
FROM ast_nodes cls
JOIN ast_nodes ctor ON ctor.parent_id = cls.node_id
WHERE cls.type = 'class_definition' AND ctor.type = 'function_definition';
2.3 Polyglot MicroDSL & In-Database Template Expansion¶
Language templates are registered in a catalog table or config:
CREATE OR REPLACE TABLE microdsl_catalog (
lang VARCHAR,
op VARCHAR,
template VARCHAR
);
INSERT INTO microdsl_catalog VALUES
('python', 'addDecorator', '@{arg0}\n{target}'),
('python', 'setAsync', 'async {target}'),
('rust', 'wrapAwait', '{target}.await'),
('rust', 'setVisibility', '{arg0} {target}'),
('python', 'wrapWith', 'with {arg0} as {arg1}:\n {target}');
In-Database Transformation Query:¶
SELECT
replace(
replace(tpl.template, '{arg0}', op.args[1]),
'{target}',
captures.target_code
) AS transformed_code
FROM input_edits
JOIN microdsl_catalog tpl ON tpl.lang = input_edits.lang AND tpl.op = op.name;
2.4 Relational Zipper & Non-Sequential Node IDs¶
Architectural Theorem:¶
In sitting_duck, node_id is an opaque surrogate key. Tree structure is determined strictly by:
1. parent_id (DAG parent reference)
2. sibling_index (Child ordering)
Consequences:
* Synthetic IDs (900000 + i) can be assigned to new nodes without re-indexing existing trees.
* Subtrees can be relocated by updating parent_id.
* AST mutations operate as purely relational table folds.
3. End-to-End Verification Proof¶
A full end-to-end prototype was executed in DuckDB:
LOAD 'sitting_duck';
INSTALL json; LOAD json;
-- Given source code and fluent call:
-- Source: "def calculate_total(price, tax):\n return price * (1 + tax)\n"
-- Call: '$(".fn@target").addDecorator("timer(''latency'')")'
-- Result generated by DuckDB in 4ms:
-- "@timer('latency')\ndef calculate_total(price, tax):\n return price * (1 + tax)\n"
4. Proposed Public API Additions for sitting_duck¶
parse_fluent_call(VARCHAR) -> TABLE(...): Built-in scalar/table function in C++.ast_patch(source VARCHAR, patches STRUCT(start_byte INT, end_byte INT, replacement VARCHAR)[]) -> VARCHAR: Native C++ byte-exact splice function.ast_mutate(source VARCHAR, lang VARCHAR, fluent_call VARCHAR, params JSON) -> VARCHAR: High-level one-shot AST mutator.