feat: macros (multi-rep, hygiene), Drop field-move, lean multi-OS CI
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
Sessions 56–69: declarative macro! with rep/zip/literal/block and unhygienic var $name binders; partial field-move skip Drop; @[Release] polish; LSP type hierarchy; CI Nim cache + lean macOS + Windows smoke.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
// C precedence safety: Bux AST must survive C codegen without rewrite.
|
||||
// Mul(Add(a,b), c) must stay (a+b)*c, not a+b*c.
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::String::{String_FromInt};
|
||||
|
||||
func MulSum(a: int, b: int, c: int) -> int {
|
||||
// AST: (a+b)*c → 9 for (1,2,3); wrong C emit gives 1+2*3=7
|
||||
return (a + b) * c;
|
||||
}
|
||||
|
||||
func SubDiv(a: int, b: int, c: int) -> int {
|
||||
// AST: (a-b)/c → 3 for (10,4,2); wrong C emit gives 10-4/2=8
|
||||
return (a - b) / c;
|
||||
}
|
||||
|
||||
func ShiftSum(a: int, b: int) -> int {
|
||||
// (a+b)<<1 → 6 for (1,2)
|
||||
return (a + b) << 1;
|
||||
}
|
||||
|
||||
func Mix(a: int, b: int, c: int, d: int) -> int {
|
||||
// ((a+b)*c)-d → 7 for (1,2,3,2)
|
||||
return (a + b) * c - d;
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
let m: int = MulSum(1, 2, 3);
|
||||
let s: int = SubDiv(10, 4, 2);
|
||||
let sh: int = ShiftSum(1, 2);
|
||||
let x: int = Mix(1, 2, 3, 2);
|
||||
PrintLine(String_FromInt(m));
|
||||
PrintLine(String_FromInt(s));
|
||||
PrintLine(String_FromInt(sh));
|
||||
PrintLine(String_FromInt(x));
|
||||
if m != 9 {
|
||||
PrintLine("FAIL MulSum");
|
||||
return 1;
|
||||
}
|
||||
if s != 3 {
|
||||
PrintLine("FAIL SubDiv");
|
||||
return 1;
|
||||
}
|
||||
if sh != 6 {
|
||||
PrintLine("FAIL ShiftSum");
|
||||
return 1;
|
||||
}
|
||||
if x != 7 {
|
||||
PrintLine("FAIL Mix");
|
||||
return 1;
|
||||
}
|
||||
PrintLine("PASS c_precedence");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Session 66 — gensym hygiene + fragment kinds literal / block
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::String::{String_FromInt};
|
||||
|
||||
// Template locals are gensym'd per expansion — two uses of `n` in one Main
|
||||
// must not collide under the C backend's function-scoped locals.
|
||||
macro! with_acc {
|
||||
( $start:literal ) => {
|
||||
var n: int = $start;
|
||||
n = n + 1;
|
||||
n
|
||||
}
|
||||
}
|
||||
|
||||
// literal: only int/float/string/char/bool literals (not 1+2)
|
||||
macro! only_lit {
|
||||
( $x:literal ) => {
|
||||
{
|
||||
$x
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// block: only `{ … }` block expressions
|
||||
macro! wrap_block {
|
||||
( $b:block ) => {
|
||||
$b
|
||||
}
|
||||
}
|
||||
|
||||
// lit alias for literal
|
||||
macro! double_lit {
|
||||
( $n:lit ) => {
|
||||
($n) + ($n)
|
||||
}
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
// gensym: two expansions both introduce `n`
|
||||
let a: int = with_acc!(10);
|
||||
let b: int = with_acc!(20);
|
||||
// 11, 21
|
||||
let c: int = only_lit!(7);
|
||||
let d: int = wrap_block!({ 1 + 2 });
|
||||
let e: int = double_lit!(21);
|
||||
PrintLine(String_FromInt(a));
|
||||
PrintLine(String_FromInt(b));
|
||||
PrintLine(String_FromInt(c));
|
||||
PrintLine(String_FromInt(d));
|
||||
PrintLine(String_FromInt(e));
|
||||
if a != 11 {
|
||||
PrintLine("FAIL with_acc gensym a");
|
||||
return 1;
|
||||
}
|
||||
if b != 21 {
|
||||
PrintLine("FAIL with_acc gensym b");
|
||||
return 1;
|
||||
}
|
||||
if c != 7 {
|
||||
PrintLine("FAIL only_lit");
|
||||
return 1;
|
||||
}
|
||||
if d != 3 {
|
||||
PrintLine("FAIL wrap_block");
|
||||
return 1;
|
||||
}
|
||||
if e != 42 {
|
||||
PrintLine("FAIL double_lit");
|
||||
return 1;
|
||||
}
|
||||
PrintLine("PASS macro_hygiene");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Session 63 — multi-rep patterns, compound/zipped rep, nested template $(…)*
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::String::{String_FromInt};
|
||||
|
||||
// Compound rep: $( $a:expr , $b:expr ),* → parallel lists, zip in template
|
||||
macro! add_pairs {
|
||||
( $($a:expr, $b:expr),* ) => {
|
||||
var __acc: int = 0;
|
||||
$( __acc = __acc + ($a + $b); )*
|
||||
__acc
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-rep groups separated by `;` in the call
|
||||
// sum_n!(1,2; 10,20,30) → sum first group + sum second group
|
||||
macro! sum_groups {
|
||||
( $($x:expr),* ; $($y:expr),* ) => {
|
||||
var __s: int = 0;
|
||||
$( __s = __s + $x; )*
|
||||
$( __s = __s + $y; )*
|
||||
__s
|
||||
}
|
||||
}
|
||||
|
||||
// Nested template: outer over $x, inner body uses current $x once
|
||||
// (same-list nested MacroRep expands once when bound as single)
|
||||
macro! double_each_sum {
|
||||
( $($x:expr),* ) => {
|
||||
var __t: int = 0;
|
||||
$(
|
||||
$( __t = __t + $x; )*
|
||||
$( __t = __t + $x; )*
|
||||
)*
|
||||
__t
|
||||
}
|
||||
}
|
||||
|
||||
// Prefix fixed + trailing rep still works
|
||||
macro! named_sum {
|
||||
( $label:ident, $($n:expr),* ) => {
|
||||
var __u: int = 0;
|
||||
$( __u = __u + $n; )*
|
||||
__u
|
||||
}
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
let p: int = add_pairs!(1, 10, 2, 20);
|
||||
// (1+10)+(2+20) = 33
|
||||
let g: int = sum_groups!(1, 2; 10, 20, 30);
|
||||
// 1+2+10+20+30 = 63
|
||||
let d: int = double_each_sum!(3, 4);
|
||||
// (3+3)+(4+4) = 14
|
||||
let n: int = named_sum!(ignored, 5, 6, 7);
|
||||
// 18
|
||||
PrintLine(String_FromInt(p));
|
||||
PrintLine(String_FromInt(g));
|
||||
PrintLine(String_FromInt(d));
|
||||
PrintLine(String_FromInt(n));
|
||||
if p != 33 {
|
||||
PrintLine("FAIL add_pairs");
|
||||
return 1;
|
||||
}
|
||||
if g != 63 {
|
||||
PrintLine("FAIL sum_groups");
|
||||
return 1;
|
||||
}
|
||||
if d != 14 {
|
||||
PrintLine("FAIL double_each_sum");
|
||||
return 1;
|
||||
}
|
||||
if n != 18 {
|
||||
PrintLine("FAIL named_sum");
|
||||
return 1;
|
||||
}
|
||||
PrintLine("PASS macro_nested");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Macro repetition $(…)* + fragment kinds expr/ident/tt (session 61)
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::String::{String_FromInt};
|
||||
|
||||
// Sum any number of int exprs
|
||||
// Template body is a single block (lets + $(…)* + result expr).
|
||||
macro! sum_n {
|
||||
( $($x:expr),* ) => {
|
||||
var __sum_acc: int = 0;
|
||||
$( __sum_acc = __sum_acc + $x; )*
|
||||
__sum_acc
|
||||
}
|
||||
}
|
||||
|
||||
// Bind an identifier name and call it as a zero-arg func via expr wrap
|
||||
// (ident fragment must be a bare identifier at the call site)
|
||||
macro! call0 {
|
||||
( $f:ident ) => {
|
||||
{
|
||||
$f()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tt is accepted like expr (token-tree MVP)
|
||||
macro! id_tt {
|
||||
( $t:tt ) => {
|
||||
{
|
||||
$t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FortyTwo() -> int {
|
||||
return 42;
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
let a: int = sum_n!(1, 2, 3);
|
||||
let b: int = sum_n!();
|
||||
let c: int = call0!(FortyTwo);
|
||||
let d: int = id_tt!(7);
|
||||
PrintLine(String_FromInt(a));
|
||||
PrintLine(String_FromInt(b));
|
||||
PrintLine(String_FromInt(c));
|
||||
PrintLine(String_FromInt(d));
|
||||
if a != 6 {
|
||||
PrintLine("FAIL sum_n 1+2+3");
|
||||
return 1;
|
||||
}
|
||||
if b != 0 {
|
||||
PrintLine("FAIL sum_n empty");
|
||||
return 1;
|
||||
}
|
||||
if c != 42 {
|
||||
PrintLine("FAIL call0 ident");
|
||||
return 1;
|
||||
}
|
||||
if d != 7 {
|
||||
PrintLine("FAIL id_tt");
|
||||
return 1;
|
||||
}
|
||||
PrintLine("PASS macro_repeat");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Declarative macro! + built-in quote! (session 59)
|
||||
// Expands before type-check; $frags splice with call-site hygiene.
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::String::{String_FromInt};
|
||||
|
||||
macro! twice {
|
||||
($x:expr) => {
|
||||
($x) + ($x)
|
||||
}
|
||||
}
|
||||
|
||||
macro! add2 {
|
||||
($a:expr, $b:expr) => {
|
||||
($a) + ($b)
|
||||
}
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
let a: int = twice!(21);
|
||||
let b: int = add2!(10, 32);
|
||||
// quote! is a built-in identity expand (call-site graft)
|
||||
let c: int = quote!(a + 1);
|
||||
PrintLine(String_FromInt(a));
|
||||
PrintLine(String_FromInt(b));
|
||||
PrintLine(String_FromInt(c));
|
||||
if a != 42 {
|
||||
PrintLine("FAIL twice");
|
||||
return 1;
|
||||
}
|
||||
if b != 42 {
|
||||
PrintLine("FAIL add2");
|
||||
return 1;
|
||||
}
|
||||
if c != 43 {
|
||||
PrintLine("FAIL quote");
|
||||
return 1;
|
||||
}
|
||||
PrintLine("PASS macro_twice");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Session 69 — unhygienic binders: `var $name` keeps the call-site identifier
|
||||
// (not gensym'd). Hygienic template locals (`acc`) still get unique names.
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::String::{String_FromInt};
|
||||
|
||||
// Introduce a binder named by the call-site ident; keep that name (unhygienic)
|
||||
macro! let_mut {
|
||||
( $name:ident, $init:literal ) => {
|
||||
var $name: int = $init;
|
||||
$name = $name + 1;
|
||||
$name
|
||||
}
|
||||
}
|
||||
|
||||
// Hygienic local `acc` must not collide across two expansions
|
||||
macro! double_acc {
|
||||
( $start:literal ) => {
|
||||
var acc: int = $start;
|
||||
acc = acc + acc;
|
||||
acc
|
||||
}
|
||||
}
|
||||
|
||||
// Mixed: unhygienic $name + hygienic scratch
|
||||
macro! bump_named {
|
||||
( $name:ident ) => {
|
||||
var $name: int = 0;
|
||||
var scratch: int = 1;
|
||||
$name = $name + scratch;
|
||||
$name
|
||||
}
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
// Unhygienic: binder becomes `counter` inside the expansion block
|
||||
let a: int = let_mut!(counter, 10);
|
||||
// 11
|
||||
let b: int = let_mut!(other, 20);
|
||||
// 21
|
||||
// Hygienic: two expansions with local `acc`
|
||||
let c: int = double_acc!(3);
|
||||
let d: int = double_acc!(5);
|
||||
// 6, 10
|
||||
let e: int = bump_named!(n);
|
||||
// 1
|
||||
PrintLine(String_FromInt(a));
|
||||
PrintLine(String_FromInt(b));
|
||||
PrintLine(String_FromInt(c));
|
||||
PrintLine(String_FromInt(d));
|
||||
PrintLine(String_FromInt(e));
|
||||
if a != 11 {
|
||||
PrintLine("FAIL let_mut counter");
|
||||
return 1;
|
||||
}
|
||||
if b != 21 {
|
||||
PrintLine("FAIL let_mut other");
|
||||
return 1;
|
||||
}
|
||||
if c != 6 {
|
||||
PrintLine("FAIL double_acc 3");
|
||||
return 1;
|
||||
}
|
||||
if d != 10 {
|
||||
PrintLine("FAIL double_acc 5");
|
||||
return 1;
|
||||
}
|
||||
if e != 1 {
|
||||
PrintLine("FAIL bump_named");
|
||||
return 1;
|
||||
}
|
||||
PrintLine("PASS macro_unhygienic");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Session 68 — partial field moves out of @[Drop] parents
|
||||
// `return bag.items` / `let x = bag.items` must skip Bag_Drop (no double-free).
|
||||
// Non-droppable fields (`bag.tag`) do not mark the parent moved.
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::Array::{Array, Array_New, Array_Push, Array_Len, Array_Get};
|
||||
import Std::String::{String_FromInt, String_Concat};
|
||||
import Std::Test::{Test_AssertTrue, Test_Pass};
|
||||
|
||||
@[Drop]
|
||||
struct Bag {
|
||||
items: Array<int>,
|
||||
tag: int
|
||||
}
|
||||
|
||||
func Bag_Drop(self: *Bag) {
|
||||
Array_Drop<int>(&self.items);
|
||||
}
|
||||
|
||||
// Move droppable field out via return
|
||||
func TakeItems() -> Array<int> {
|
||||
var items: Array<int> = Array_New<int>(4);
|
||||
Array_Push<int>(&items, 42);
|
||||
let bag: Bag = Bag { items: items, tag: 7 };
|
||||
return bag.items;
|
||||
}
|
||||
|
||||
// Move droppable field via let; read non-droppable tag after
|
||||
func PeekTagAndTake() -> int {
|
||||
var items: Array<int> = Array_New<int>(2);
|
||||
Array_Push<int>(&items, 1);
|
||||
let bag: Bag = Bag { items: items, tag: 99 };
|
||||
let moved: Array<int> = bag.items;
|
||||
let t: int = bag.tag;
|
||||
discard Array_Len<int>(&moved);
|
||||
return t;
|
||||
}
|
||||
|
||||
// Whole-struct return still moves bag (existing path)
|
||||
func MakeBag() -> Bag {
|
||||
var items: Array<int> = Array_New<int>(2);
|
||||
Array_Push<int>(&items, 10);
|
||||
Array_Push<int>(&items, 20);
|
||||
let bag: Bag = Bag { items: items, tag: 3 };
|
||||
return bag;
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
let taken: Array<int> = TakeItems();
|
||||
Test_AssertTrue(Array_Len<int>(&taken) == 1);
|
||||
Test_AssertTrue(Array_Get<int>(&taken, 0) == 42);
|
||||
|
||||
let tag: int = PeekTagAndTake();
|
||||
Test_AssertTrue(tag == 99);
|
||||
|
||||
let b: Bag = MakeBag();
|
||||
Test_AssertTrue(Array_Len<int>(&b.items) == 2);
|
||||
Test_AssertTrue(Array_Get<int>(&b.items, 0) == 10);
|
||||
Test_AssertTrue(b.tag == 3);
|
||||
|
||||
PrintLine(String_Concat("ok=", String_FromInt(tag as int64)));
|
||||
Test_Pass("move_field_partial");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// C.4 — @[Release] zero-cost path vs @[Checked]
|
||||
// Default / Release: free; Checked: catches double-mut and dangling returns.
|
||||
import Std::Io::{PrintLine, PrintInt};
|
||||
import Std::Test::{Test_AssertEqInt, Test_Pass};
|
||||
|
||||
// Tier 1 — unchecked (default)
|
||||
func UncheckedInc(p: *int) {
|
||||
*p = *p + 1;
|
||||
}
|
||||
|
||||
// Tier 2 — borrow checked API surface
|
||||
@[Checked]
|
||||
func SafeInc(p: &mut int) {
|
||||
*p = *p + 1;
|
||||
}
|
||||
|
||||
@[Checked]
|
||||
func SafeGet(p: &int) -> int {
|
||||
return *p;
|
||||
}
|
||||
|
||||
// Tier 3 — explicit zero-cost hot path (Release wins over Checked)
|
||||
@[Checked]
|
||||
@[Release]
|
||||
func HotInc(p: &mut int) {
|
||||
// Would be fine either way; attribute documents "no checker cost here"
|
||||
*p = *p + 1;
|
||||
}
|
||||
|
||||
@[Release]
|
||||
func HotDangle() -> &int {
|
||||
// Allowed: Release disables dangling-return checks
|
||||
var x: int = 99;
|
||||
return &x;
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
var n: int = 10;
|
||||
UncheckedInc(&n);
|
||||
Test_AssertEqInt(n, 11);
|
||||
|
||||
SafeInc(&n);
|
||||
Test_AssertEqInt(n, 12);
|
||||
|
||||
HotInc(&n);
|
||||
Test_AssertEqInt(n, 13);
|
||||
|
||||
let v: int = SafeGet(&n);
|
||||
Test_AssertEqInt(v, 13);
|
||||
|
||||
// HotDangle is only used to prove Release compiles; do not dereference
|
||||
discard HotDangle();
|
||||
|
||||
PrintInt(n);
|
||||
PrintLine("");
|
||||
Test_Pass("ownership_release");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user