C+
Language

Build a construction DSL with @ctx

C+ has one piece of DSL syntax, the builder block @ctx { ... }, and it is not special-cased: any package can become a construction DSL by shipping a small protocol. This example does both halves — defines a tiny DSL package, then uses it — and the whole thing compiles and runs.

The DSL here builds a weighted-sum tree (the same shape a UI package like @view { text(...) } uses; the mechanism is identical, only the item type differs).

Create the DSL

A context package needs three things: an item type, constructor functions that return it, and a Builder with new / add / finish. That is the entire contract.

src/group.cplus:

pub struct Item { pub value: i32, pub weight: i32 }

// A constructor function: the bare `leaf(...)` you write inside @group.
pub fn leaf(v: i32) -> Item { return Item { value: v, weight: 1 }; }

impl Item {
    // A method modifier: `.boost(1)` on an item line.
    pub fn boost(mut self, by: i32) { self.weight = self.weight + by; return; }
}

pub struct Builder { sum: i32 }

impl Builder {
    pub fn new() -> Builder { return Builder { sum: 0 }; }
    pub fn add(mut self, item: Item) { self.sum = self.sum + item.value * item.weight; return; }
    pub fn finish(move self) -> Item { return Item { value: self.sum, weight: 1 }; }
}

// A container element: takes a *filled* Builder and folds its children
// into one Item. `nest { ... }` inside @group calls this.
pub fn nest(b: Builder) -> Item { return Item { value: b.sum, weight: 1 }; }

That is a complete DSL. No macros, no compiler hooks — just a struct, some functions, and the three Builder methods.

Use it

src/main.cplus:

import "./group" as group;

fn main() -> i32 {
    let zero = @group { };          // empty block: new() then finish()

    let base = 4;
    let tree = @group {
        let doubled = base * 2;     // a setup `let`, spliced through

        group::leaf(doubled)
            .weight = 2             // field-assignment modifier on the item above

        group::leaf(3)
            .boost(1)               // method modifier

        nest {                      // a same-context container element
            group::leaf(5)
        }
    };

    return tree.value + zero.value;
}

How it lowers

The block is rewritten to ordinary calls — this is exactly what the compiler emits, and it is what you could have written by hand:

let tree = {
    let mut __b = group::Builder::new();
    let doubled = base * 2;

    let mut __i0 = group::leaf(doubled);
    __i0.weight = 2;
    __b.add(__i0);

    let mut __i1 = group::leaf(3);
    __i1.boost(1);
    __b.add(__i1);

    // nest { ... } builds its own filled Builder, then group::nest(that)
    let mut __c = group::Builder::new();
    __c.add(group::leaf(5));
    __b.add(group::nest(__c));

    __b.finish()
};

Because the output never names a collection type (no Vec), DSL packages work even on embedded targets where the heap is gated.

Results

cpc build
./target/debug/bb ; echo $?

It exits 27. The arithmetic, value * weight summed per item:

16 + 6 + 5 + 0 = 27.

Reproduce

A two-file project:

# Cplus.toml
[package]
name = "bb"

[[bin]]
name = "bb"
path = "src/main.cplus"

Put group.cplus and main.cplus from above in src/, then:

cpc build
./target/debug/bb ; echo $?     # 27

For the full feature — contextual name lookup, if/for collection control flow, and the parse-time rules — see Builder blocks.


‹ Back to all examples