vtables, protocol
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
main :: () {
|
main :: () {
|
||||||
list : List(s32) = .{};
|
list : List(s32) = .{};
|
||||||
|
|
||||||
append(list, 1);
|
list.append(1);
|
||||||
|
|
||||||
list.append(3);
|
list.append(3);
|
||||||
list.append(4);
|
list.append(4);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,47 +1,34 @@
|
|||||||
#import "std.sx";
|
#import "std.sx";
|
||||||
|
|
||||||
// --- Allocator protocol ---
|
// --- Allocator protocol (inline: ctx + fn-ptrs, no vtable indirection) ---
|
||||||
|
|
||||||
Allocator :: struct {
|
Allocator :: protocol #inline {
|
||||||
ctx: *void;
|
alloc :: (size: s64) -> *void;
|
||||||
alloc_fn: (*void, s64) -> *void;
|
dealloc :: (ptr: *void);
|
||||||
free_fn: (*void, *void) -> void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allocator_alloc :: (a: Allocator, size: s64) -> *void {
|
|
||||||
a.alloc_fn(a.ctx, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
allocator_dealloc :: (a: Allocator, ptr: *void) {
|
|
||||||
a.free_fn(a.ctx, ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
alloc :: ufcs allocator_alloc;
|
|
||||||
dealloc :: ufcs allocator_dealloc;
|
|
||||||
|
|
||||||
// --- GPA: general purpose allocator (malloc/free wrapper) ---
|
// --- GPA: general purpose allocator (malloc/free wrapper) ---
|
||||||
|
|
||||||
GPA :: struct {
|
GPA :: struct {
|
||||||
alloc_count: s64;
|
alloc_count: s64;
|
||||||
|
|
||||||
|
create :: (gpa: *GPA) -> Allocator {
|
||||||
|
xx gpa;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gpa_alloc :: (ctx: *void, size: s64) -> *void {
|
impl Allocator for GPA {
|
||||||
gpa : *GPA = xx ctx;
|
alloc :: (self: *GPA, size: s64) -> *void {
|
||||||
gpa.alloc_count += 1;
|
self.alloc_count += 1;
|
||||||
malloc(size);
|
malloc(size);
|
||||||
|
}
|
||||||
|
dealloc :: (self: *GPA, ptr: *void) {
|
||||||
|
self.alloc_count -= 1;
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gpa_free :: (ctx: *void, ptr: *void) {
|
// --- Arena: multi-chunk bump allocator ---
|
||||||
gpa : *GPA = xx ctx;
|
|
||||||
gpa.alloc_count -= 1;
|
|
||||||
free(ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
gpa_create :: (gpa: *GPA) -> Allocator {
|
|
||||||
Allocator.{ ctx = gpa, alloc_fn = gpa_alloc, free_fn = gpa_free };
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Arena: multi-chunk bump allocator (Zig-inspired) ---
|
|
||||||
|
|
||||||
ArenaChunk :: struct {
|
ArenaChunk :: struct {
|
||||||
next: *ArenaChunk;
|
next: *ArenaChunk;
|
||||||
@@ -52,73 +39,71 @@ Arena :: struct {
|
|||||||
first: *ArenaChunk;
|
first: *ArenaChunk;
|
||||||
end_index: s64;
|
end_index: s64;
|
||||||
parent: Allocator;
|
parent: Allocator;
|
||||||
}
|
|
||||||
|
|
||||||
arena_add_chunk :: (a: *Arena, min_size: s64) {
|
add_chunk :: (a: *Arena, min_size: s64) {
|
||||||
prev_cap := if a.first != null then a.first.cap else 0;
|
prev_cap := if a.first != null then a.first.cap else 0;
|
||||||
needed := min_size + 16 + 16;
|
needed := min_size + 16 + 16;
|
||||||
len := (prev_cap + needed) * 3 / 2;
|
len := (prev_cap + needed) * 3 / 2;
|
||||||
raw := a.parent.alloc(len);
|
raw := a.parent.alloc(len);
|
||||||
chunk : *ArenaChunk = xx raw;
|
chunk : *ArenaChunk = xx raw;
|
||||||
chunk.next = a.first;
|
chunk.next = a.first;
|
||||||
chunk.cap = len;
|
chunk.cap = len;
|
||||||
a.first = chunk;
|
a.first = chunk;
|
||||||
a.end_index = 0;
|
a.end_index = 0;
|
||||||
}
|
|
||||||
|
|
||||||
arena_alloc :: (ctx: *void, size: s64) -> *void {
|
|
||||||
a : *Arena = xx ctx;
|
|
||||||
aligned := (size + 7) & (0 - 8);
|
|
||||||
if a.first != null {
|
|
||||||
usable := a.first.cap - 16;
|
|
||||||
if a.end_index + aligned <= usable {
|
|
||||||
buf : [*]u8 = xx a.first;
|
|
||||||
ptr := @buf[16 + a.end_index];
|
|
||||||
a.end_index = a.end_index + aligned;
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
arena_add_chunk(a, aligned);
|
|
||||||
buf : [*]u8 = xx a.first;
|
|
||||||
ptr := @buf[16 + a.end_index];
|
|
||||||
a.end_index = a.end_index + aligned;
|
|
||||||
ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
arena_free :: (ctx: *void, ptr: *void) {
|
create :: (a: *Arena, parent: Allocator, size: s64) -> Allocator {
|
||||||
}
|
a.first = null;
|
||||||
|
a.end_index = 0;
|
||||||
|
a.parent = parent;
|
||||||
|
a.add_chunk(size);
|
||||||
|
xx a;
|
||||||
|
}
|
||||||
|
|
||||||
arena_create :: (a: *Arena, parent: Allocator, size: s64) -> Allocator {
|
reset :: (a: *Arena) {
|
||||||
a.first = null;
|
if a.first != null {
|
||||||
a.end_index = 0;
|
it := a.first.next;
|
||||||
a.parent = parent;
|
while it != null {
|
||||||
arena_add_chunk(a, size);
|
next := it.next;
|
||||||
Allocator.{ ctx = a, alloc_fn = arena_alloc, free_fn = arena_free };
|
a.parent.dealloc(it);
|
||||||
}
|
it = next;
|
||||||
|
}
|
||||||
|
a.first.next = null;
|
||||||
|
}
|
||||||
|
a.end_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
arena_reset :: (a: *Arena) {
|
deinit :: (a: *Arena) {
|
||||||
// Keep first chunk (newest/largest), free the rest
|
it := a.first;
|
||||||
if a.first != null {
|
|
||||||
it := a.first.next;
|
|
||||||
while it != null {
|
while it != null {
|
||||||
next := it.next;
|
next := it.next;
|
||||||
a.parent.dealloc(it);
|
a.parent.dealloc(it);
|
||||||
it = next;
|
it = next;
|
||||||
}
|
}
|
||||||
a.first.next = null;
|
a.first = null;
|
||||||
|
a.end_index = 0;
|
||||||
}
|
}
|
||||||
a.end_index = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
arena_deinit :: (a: *Arena) {
|
impl Allocator for Arena {
|
||||||
it := a.first;
|
alloc :: (self: *Arena, size: s64) -> *void {
|
||||||
while it != null {
|
aligned := (size + 7) & (0 - 8);
|
||||||
next := it.next;
|
if self.first != null {
|
||||||
a.parent.dealloc(it);
|
usable := self.first.cap - 16;
|
||||||
it = next;
|
if self.end_index + aligned <= usable {
|
||||||
|
buf : [*]u8 = xx self.first;
|
||||||
|
ptr := @buf[16 + self.end_index];
|
||||||
|
self.end_index = self.end_index + aligned;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.add_chunk(aligned);
|
||||||
|
buf : [*]u8 = xx self.first;
|
||||||
|
ptr := @buf[16 + self.end_index];
|
||||||
|
self.end_index = self.end_index + aligned;
|
||||||
|
ptr;
|
||||||
}
|
}
|
||||||
a.first = null;
|
dealloc :: (self: *Arena, ptr: *void) {}
|
||||||
a.end_index = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- BufAlloc: bump allocator backed by a user-provided slice ---
|
// --- BufAlloc: bump allocator backed by a user-provided slice ---
|
||||||
@@ -127,29 +112,28 @@ BufAlloc :: struct {
|
|||||||
buf: [*]u8;
|
buf: [*]u8;
|
||||||
len: s64;
|
len: s64;
|
||||||
pos: s64;
|
pos: s64;
|
||||||
}
|
|
||||||
|
|
||||||
buf_alloc :: (ctx: *void, size: s64) -> *void {
|
create :: (b: *BufAlloc, buf: [*]u8, len: s64) -> Allocator {
|
||||||
b : *BufAlloc = xx ctx;
|
b.buf = buf;
|
||||||
aligned := (size + 7) & (0 - 8);
|
b.len = len;
|
||||||
if b.pos + aligned > b.len {
|
b.pos = 0;
|
||||||
return null;
|
xx b;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset :: (b: *BufAlloc) {
|
||||||
|
b.pos = 0;
|
||||||
}
|
}
|
||||||
ptr := @b.buf[b.pos];
|
|
||||||
b.pos = b.pos + aligned;
|
|
||||||
ptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buf_free :: (ctx: *void, ptr: *void) {
|
impl Allocator for BufAlloc {
|
||||||
}
|
alloc :: (self: *BufAlloc, size: s64) -> *void {
|
||||||
|
aligned := (size + 7) & (0 - 8);
|
||||||
buf_create :: (b: *BufAlloc, buf: [*]u8, len: s64) -> Allocator {
|
if self.pos + aligned > self.len {
|
||||||
b.buf = buf;
|
return null;
|
||||||
b.len = len;
|
}
|
||||||
b.pos = 0;
|
ptr := @self.buf[self.pos];
|
||||||
Allocator.{ ctx = b, alloc_fn = buf_alloc, free_fn = buf_free };
|
self.pos = self.pos + aligned;
|
||||||
}
|
ptr;
|
||||||
|
}
|
||||||
buf_reset :: (b: *BufAlloc) {
|
dealloc :: (self: *BufAlloc, ptr: *void) {}
|
||||||
b.pos = 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,12 +31,8 @@ context : Context = ---;
|
|||||||
|
|
||||||
// --- Slice & string allocation ---
|
// --- Slice & string allocation ---
|
||||||
|
|
||||||
context_alloc :: (size: s64) -> *void {
|
|
||||||
if context.allocator.ctx != null then context.allocator.alloc(size) else malloc(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
cstring :: (size: s64) -> string {
|
cstring :: (size: s64) -> string {
|
||||||
raw := context_alloc(size + 1);
|
raw := context.allocator.alloc(size + 1);
|
||||||
memset(raw, 0, size + 1);
|
memset(raw, 0, size + 1);
|
||||||
s : string = ---;
|
s : string = ---;
|
||||||
s.ptr = xx raw;
|
s.ptr = xx raw;
|
||||||
@@ -45,7 +41,7 @@ cstring :: (size: s64) -> string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
alloc_slice :: ($T: Type, count: s64) -> []T {
|
alloc_slice :: ($T: Type, count: s64) -> []T {
|
||||||
raw := context_alloc(count * size_of(T));
|
raw := context.allocator.alloc(count * size_of(T));
|
||||||
memset(raw, 0, count * size_of(T));
|
memset(raw, 0, count * size_of(T));
|
||||||
s : []T = ---;
|
s : []T = ---;
|
||||||
s.ptr = xx raw;
|
s.ptr = xx raw;
|
||||||
@@ -337,19 +333,19 @@ List :: struct ($T: Type) {
|
|||||||
items: [*]T = null;
|
items: [*]T = null;
|
||||||
len: s64 = 0;
|
len: s64 = 0;
|
||||||
cap: s64 = 0;
|
cap: s64 = 0;
|
||||||
}
|
|
||||||
|
|
||||||
append ::(list: *List($T), item: T) {
|
append :: (list: *List(T), item: T) {
|
||||||
if list.len >= list.cap {
|
if list.len >= list.cap {
|
||||||
new_cap := if list.cap == 0 then 4 else list.cap * 2;
|
new_cap := if list.cap == 0 then 4 else list.cap * 2;
|
||||||
new_items : [*]T = xx malloc(new_cap * size_of(T));
|
new_items : [*]T = xx context.allocator.alloc(new_cap * size_of(T));
|
||||||
if list.len > 0 {
|
if list.len > 0 {
|
||||||
memcpy(new_items, list.items, list.len * size_of(T));
|
memcpy(new_items, list.items, list.len * size_of(T));
|
||||||
free(list.items);
|
context.allocator.dealloc(list.items);
|
||||||
|
}
|
||||||
|
list.items = new_items;
|
||||||
|
list.cap = new_cap;
|
||||||
}
|
}
|
||||||
list.items = new_items;
|
list.items[list.len] = item;
|
||||||
list.cap = new_cap;
|
list.len += 1;
|
||||||
}
|
}
|
||||||
list.items[list.len] = item;
|
|
||||||
list.len += 1;
|
|
||||||
}
|
}
|
||||||
167
specs.md
167
specs.md
@@ -278,6 +278,165 @@ Struct values in string interpolation print as `TypeName{field:value, ...}`:
|
|||||||
print("{}", v1); // Vec4{x:1.0, y:2.0, z:3.0, w:0.0}
|
print("{}", v1); // Vec4{x:1.0, y:2.0, z:3.0, w:0.0}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Struct Methods
|
||||||
|
Functions declared inside a struct body become methods, registered as `StructName.method`:
|
||||||
|
```sx
|
||||||
|
Point :: struct {
|
||||||
|
x, y: s32;
|
||||||
|
|
||||||
|
sum :: (self: *Point) -> s32 { self.x + self.y; }
|
||||||
|
}
|
||||||
|
|
||||||
|
p := Point.{ x = 3, y = 4 };
|
||||||
|
print("{}\n", p.sum()); // 7
|
||||||
|
```
|
||||||
|
|
||||||
|
Methods receive the struct (typically as a pointer) as their first parameter. Dot-call syntax `obj.method(args)` resolves struct methods — it is **not** UFCS for arbitrary free functions. The pipe operator `|>` remains the universal UFCS mechanism.
|
||||||
|
|
||||||
|
### Protocol Types
|
||||||
|
|
||||||
|
Protocols define a set of method signatures that types can implement. They enable:
|
||||||
|
- **Static dispatch**: compile-time checked constraints on generic type parameters.
|
||||||
|
- **Dynamic dispatch**: type-erased protocol values with runtime method dispatch through function pointers.
|
||||||
|
|
||||||
|
#### Declaration
|
||||||
|
```sx
|
||||||
|
Allocator :: protocol #inline {
|
||||||
|
alloc :: (size: s64) -> *void;
|
||||||
|
dealloc :: (ptr: *void);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Protocol methods have an **implicit receiver** — no `self` in the protocol signature. The compiler adds `*Self` automatically. The `#inline` modifier embeds function pointers directly in the protocol value (no vtable indirection).
|
||||||
|
|
||||||
|
#### `#inline` vs default layout
|
||||||
|
|
||||||
|
| Layout | Declaration | Value layout | Dispatch cost |
|
||||||
|
|--------|-------------|--------------|---------------|
|
||||||
|
| `#inline` | `protocol #inline { ... }` | `{ ctx: *void, fn_ptr1, fn_ptr2, ... }` | Zero indirection |
|
||||||
|
| Default | `protocol { ... }` | `{ ctx: *void, __vtable: *Vtable }` | One pointer chase |
|
||||||
|
|
||||||
|
Use `#inline` for protocols with few methods where call overhead matters (e.g., allocators). Use the default layout for protocols with many methods to keep the value size small.
|
||||||
|
|
||||||
|
#### `impl` Blocks
|
||||||
|
```sx
|
||||||
|
impl Allocator for GPA {
|
||||||
|
alloc :: (self: *GPA, size: s64) -> *void {
|
||||||
|
self.alloc_count += 1;
|
||||||
|
malloc(size);
|
||||||
|
}
|
||||||
|
dealloc :: (self: *GPA, ptr: *void) {
|
||||||
|
self.alloc_count -= 1;
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Top-level declarations (not inside struct bodies)
|
||||||
|
- Enable retroactive conformance — implement a protocol for types you don't own
|
||||||
|
- Impl methods are also registered as struct methods (`GPA.alloc`) for direct calls
|
||||||
|
- Duplicate `{Protocol, Type}` pair in the same compilation unit is a compile error
|
||||||
|
|
||||||
|
#### Protocol Values and `xx` Conversion
|
||||||
|
Convert a concrete type to a protocol value with `xx`:
|
||||||
|
```sx
|
||||||
|
gpa := GPA.init();
|
||||||
|
a : Allocator = xx gpa; // concrete → protocol value
|
||||||
|
ptr := a.alloc(64); // dynamic dispatch through fn-ptr
|
||||||
|
a.dealloc(ptr);
|
||||||
|
```
|
||||||
|
|
||||||
|
`xx` works at assignment, call sites, and return positions:
|
||||||
|
```sx
|
||||||
|
use_allocator(xx gpa); // at call site
|
||||||
|
make_alloc :: () -> Allocator { xx gpa; } // in return position
|
||||||
|
```
|
||||||
|
|
||||||
|
Protocol values can be stored in struct fields, arrays, and passed through function calls:
|
||||||
|
```sx
|
||||||
|
Arena :: struct {
|
||||||
|
parent: Allocator; // protocol value as struct field
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
allocators : [2]Allocator = .[xx gpa, xx arena]; // protocol values in array
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Default Methods
|
||||||
|
Protocol methods can have bodies. `self` dispatches through the vtable (dynamic dispatch):
|
||||||
|
```sx
|
||||||
|
Writer :: protocol {
|
||||||
|
write :: (data: string) -> s64; // required
|
||||||
|
write_line :: (data: string) -> s64 { // default
|
||||||
|
n := self.write(data);
|
||||||
|
n + self.write("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Default methods are used unless overridden in the impl. Default methods calling `self.method()` dispatch through the vtable, so they work correctly with any concrete type.
|
||||||
|
|
||||||
|
#### `Self` Type
|
||||||
|
`Self` is a contextual keyword in protocol declarations — resolves to the concrete type in impls:
|
||||||
|
```sx
|
||||||
|
Eq :: protocol { eq :: (other: Self) -> bool; }
|
||||||
|
|
||||||
|
impl Eq for Point {
|
||||||
|
eq :: (self: *Point, other: Point) -> bool {
|
||||||
|
self.x == other.x and self.y == other.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static dispatch:
|
||||||
|
p1.eq(p2); // calls Point.eq directly
|
||||||
|
|
||||||
|
// Dynamic dispatch:
|
||||||
|
e : Eq = xx p1;
|
||||||
|
e.eq(p2); // dispatches through vtable, Self params erased to *void
|
||||||
|
```
|
||||||
|
|
||||||
|
For dynamic dispatch, `Self` parameters are erased to `*void` — the caller passes a pointer to the argument, and the thunk loads the concrete value.
|
||||||
|
|
||||||
|
#### Generic Constraints
|
||||||
|
`$T/Protocol` syntax validates that a type parameter implements the required protocol(s):
|
||||||
|
```sx
|
||||||
|
are_equal :: (a: $T/Eq, b: T) -> bool { a.eq(b); }
|
||||||
|
|
||||||
|
// Multiple constraints:
|
||||||
|
eq_and_hash :: (a: $T/Eq/Hashable, b: T) -> bool { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Constraints produce clear errors at monomorphization: `"s64 does not implement Hashable"`. Dispatch is static — same as unconstrained generics but with compile-time validation.
|
||||||
|
|
||||||
|
Constraints also work on struct type parameters:
|
||||||
|
```sx
|
||||||
|
SortedPair :: struct ($T: Type/Comparable) {
|
||||||
|
lo: T;
|
||||||
|
hi: T;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Generic Struct Impls
|
||||||
|
```sx
|
||||||
|
Pair :: struct ($T: Type) { a: T; b: T; }
|
||||||
|
|
||||||
|
impl Summable for Pair($T) {
|
||||||
|
sum :: (self: *Pair(T)) -> s32 { xx self.a + xx self.b; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The impl is instantiated per concrete type argument, like generic struct methods.
|
||||||
|
|
||||||
|
#### Dispatch Rules
|
||||||
|
|
||||||
|
| Usage | Dispatch | Cost |
|
||||||
|
|-------|----------|------|
|
||||||
|
| `gpa.alloc(64)` on `*GPA` | Static — direct call | Zero |
|
||||||
|
| `$T/Allocator` constraint | Static — monomorphized | Zero |
|
||||||
|
| `a : Allocator = xx gpa; a.alloc(64)` | Dynamic — fn-ptr / vtable | Indirect call |
|
||||||
|
|
||||||
|
Static dispatch is automatic when the concrete type is known. Dynamic dispatch only when explicitly type-erased via `xx` into a protocol value.
|
||||||
|
|
||||||
### Tuple Types
|
### Tuple Types
|
||||||
Anonymous product types with optional field names. Tuples are first-class values — they can be stored in variables, passed to functions, and returned.
|
Anonymous product types with optional field names. Tuples are first-class values — they can be stored in variables, passed to functions, and returned.
|
||||||
|
|
||||||
@@ -1155,7 +1314,11 @@ if handler := btn.on_click {
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Memory
|
#### Memory
|
||||||
Closure env is heap-allocated via `malloc`. The caller is responsible for freeing `closure.env` when the closure is no longer needed. Auto-promoted closures have a null env and require no freeing.
|
Closure env is allocated via `context.allocator`. The compiler auto-initializes `context` with a default GPA (malloc/free wrapper) at the start of `main()`. Use `push Context` to override with a custom allocator. Auto-promoted closures have a null env and require no allocation.
|
||||||
|
```sx
|
||||||
|
f := closure((x: s64) -> s64 => x + 10); // env allocated via default GPA
|
||||||
|
print("{}\n", f(5));
|
||||||
|
```
|
||||||
|
|
||||||
### Function Call
|
### Function Call
|
||||||
```sx
|
```sx
|
||||||
@@ -1283,7 +1446,7 @@ Context :: struct {
|
|||||||
context : Context = ---; // global mutable variable
|
context : Context = ---; // global mutable variable
|
||||||
```
|
```
|
||||||
|
|
||||||
Inside the pushed block, any code (including called functions) can read `context.allocator` and `context.data`. The standard library's `cstring()` and `alloc_slice()` functions use `context.allocator` for allocation when its `.ctx` is non-null, falling back to `malloc()` otherwise.
|
The compiler auto-initializes `context` with a default GPA (malloc/free wrapper) at the start of `main()`. Inside the pushed block, any code (including called functions) can read `context.allocator` and `context.data`. The standard library's `cstring()`, `alloc_slice()`, and `closure()` all allocate via `context.allocator`.
|
||||||
|
|
||||||
`push` requires a global mutable variable named `context` to be in scope (provided by `std.sx`).
|
`push` requires a global mutable variable named `context` to be in scope (provided by `std.sx`).
|
||||||
|
|
||||||
|
|||||||
28
src/ast.zig
28
src/ast.zig
@@ -64,6 +64,7 @@ pub const Node = struct {
|
|||||||
break_expr: void,
|
break_expr: void,
|
||||||
continue_expr: void,
|
continue_expr: void,
|
||||||
undef_literal: void,
|
undef_literal: void,
|
||||||
|
inferred_type: void,
|
||||||
builtin_expr: void,
|
builtin_expr: void,
|
||||||
foreign_expr: ForeignExpr,
|
foreign_expr: ForeignExpr,
|
||||||
library_decl: LibraryDecl,
|
library_decl: LibraryDecl,
|
||||||
@@ -73,6 +74,8 @@ pub const Node = struct {
|
|||||||
tuple_literal: TupleLiteral,
|
tuple_literal: TupleLiteral,
|
||||||
ufcs_alias: UfcsAlias,
|
ufcs_alias: UfcsAlias,
|
||||||
c_import_decl: CImportDecl,
|
c_import_decl: CImportDecl,
|
||||||
|
protocol_decl: ProtocolDecl,
|
||||||
|
impl_block: ImplBlock,
|
||||||
|
|
||||||
pub fn declName(self: Data) ?[]const u8 {
|
pub fn declName(self: Data) ?[]const u8 {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
@@ -85,6 +88,7 @@ pub const Node = struct {
|
|||||||
.namespace_decl => |d| d.name,
|
.namespace_decl => |d| d.name,
|
||||||
.ufcs_alias => |d| d.name,
|
.ufcs_alias => |d| d.name,
|
||||||
.c_import_decl => |d| d.name,
|
.c_import_decl => |d| d.name,
|
||||||
|
.protocol_decl => |d| d.name,
|
||||||
else => null,
|
else => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -274,6 +278,7 @@ pub const UnionDecl = struct {
|
|||||||
pub const StructTypeParam = struct {
|
pub const StructTypeParam = struct {
|
||||||
name: []const u8, // e.g. "N" or "T" (without $)
|
name: []const u8, // e.g. "N" or "T" (without $)
|
||||||
constraint: *Node, // type_expr: "u32" for value param, "Type" for type param
|
constraint: *Node, // type_expr: "u32" for value param, "Type" for type param
|
||||||
|
protocol_constraints: []const []const u8 = &.{}, // e.g. ["Eq", "Hashable"] for $T/Eq/Hashable
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const UsingEntry = struct {
|
pub const UsingEntry = struct {
|
||||||
@@ -288,6 +293,7 @@ pub const StructDecl = struct {
|
|||||||
field_defaults: []const ?*Node, // default value per field, null if none
|
field_defaults: []const ?*Node, // default value per field, null if none
|
||||||
type_params: []const StructTypeParam = &.{},
|
type_params: []const StructTypeParam = &.{},
|
||||||
using_entries: []const UsingEntry = &.{},
|
using_entries: []const UsingEntry = &.{},
|
||||||
|
methods: []const *Node = &.{}, // fn_decl nodes for struct methods
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const StructFieldInit = struct {
|
pub const StructFieldInit = struct {
|
||||||
@@ -311,6 +317,7 @@ pub const Lambda = struct {
|
|||||||
pub const TypeExpr = struct {
|
pub const TypeExpr = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
is_generic: bool = false,
|
is_generic: bool = false,
|
||||||
|
protocol_constraints: []const []const u8 = &.{}, // e.g. ["Eq", "Hashable"] for $T/Eq/Hashable
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const DeferStmt = struct {
|
pub const DeferStmt = struct {
|
||||||
@@ -465,3 +472,24 @@ pub const CImportDecl = struct {
|
|||||||
name: ?[]const u8 = null,
|
name: ?[]const u8 = null,
|
||||||
bitcode_paths: []const []const u8 = &.{}, // populated during import resolution
|
bitcode_paths: []const []const u8 = &.{}, // populated during import resolution
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const ProtocolMethodDecl = struct {
|
||||||
|
name: []const u8,
|
||||||
|
params: []const *Node, // type_expr nodes for parameter types (excluding implicit self)
|
||||||
|
param_names: []const []const u8, // parameter names (excluding implicit self)
|
||||||
|
return_type: ?*Node, // null = void return
|
||||||
|
default_body: ?*Node, // null = required method, non-null = default implementation
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ProtocolDecl = struct {
|
||||||
|
name: []const u8,
|
||||||
|
methods: []const ProtocolMethodDecl,
|
||||||
|
is_inline: bool = false, // #inline — embedded fn ptrs instead of vtable pointer
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ImplBlock = struct {
|
||||||
|
protocol_name: []const u8,
|
||||||
|
target_type: []const u8,
|
||||||
|
target_type_params: []const StructTypeParam = &.{}, // for `impl P for List($T)`
|
||||||
|
methods: []const *Node, // fn_decl nodes
|
||||||
|
};
|
||||||
|
|||||||
1307
src/codegen.zig
1307
src/codegen.zig
File diff suppressed because it is too large
Load Diff
@@ -289,7 +289,7 @@ pub const UnionFieldType = enum { int, float, bool_k, pointer, string };
|
|||||||
|
|
||||||
pub const ValueKind = enum { int, float, f32_k, bool_k, string };
|
pub const ValueKind = enum { int, float, f32_k, bool_k, string };
|
||||||
|
|
||||||
pub const BuiltinId = enum { print, out, sqrt, size_of, cast, malloc, free, memcpy, memset, type_of };
|
pub const BuiltinId = enum { print, out, sqrt, size_of, cast, malloc, free, memcpy, memset, type_of, alloc, dealloc };
|
||||||
|
|
||||||
/// A compiled function or expression — a flat sequence of instructions.
|
/// A compiled function or expression — a flat sequence of instructions.
|
||||||
pub const Chunk = struct {
|
pub const Chunk = struct {
|
||||||
@@ -1864,6 +1864,56 @@ pub const VM = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve UFCS aliases first (comptime builtins like allocator_alloc need priority)
|
||||||
|
for (self.root_decls) |decl| {
|
||||||
|
switch (decl.data) {
|
||||||
|
.ufcs_alias => |ua| {
|
||||||
|
if (std.mem.eql(u8, ua.name, name)) {
|
||||||
|
// Check if target is a builtin
|
||||||
|
if (std.meta.stringToEnum(BuiltinId, ua.target)) |id|
|
||||||
|
return self.callBuiltin(id, arg_count);
|
||||||
|
return self.callFunction(ua.target, arg_count);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.namespace_decl => |ns| {
|
||||||
|
for (ns.decls) |d| {
|
||||||
|
if (d.data == .ufcs_alias) {
|
||||||
|
const ua = d.data.ufcs_alias;
|
||||||
|
if (std.mem.eql(u8, ua.name, name)) {
|
||||||
|
if (std.meta.stringToEnum(BuiltinId, ua.target)) |id|
|
||||||
|
return self.callBuiltin(id, arg_count);
|
||||||
|
return self.callFunction(ua.target, arg_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search struct methods (after UFCS aliases, so builtins take priority)
|
||||||
|
for (self.root_decls) |decl| {
|
||||||
|
switch (decl.data) {
|
||||||
|
.struct_decl => |sd| {
|
||||||
|
for (sd.methods) |m| {
|
||||||
|
if (m.data == .fn_decl and std.mem.eql(u8, m.data.fn_decl.name, name))
|
||||||
|
return self.compileFunctionAndInvoke(name, m.data.fn_decl, arg_count);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.namespace_decl => |ns| {
|
||||||
|
for (ns.decls) |d| {
|
||||||
|
if (d.data == .struct_decl) {
|
||||||
|
for (d.data.struct_decl.methods) |m| {
|
||||||
|
if (m.data == .fn_decl and std.mem.eql(u8, m.data.fn_decl.name, name))
|
||||||
|
return self.compileFunctionAndInvoke(name, m.data.fn_decl, arg_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return error.UndefinedFunction;
|
return error.UndefinedFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2038,6 +2088,31 @@ pub const VM = struct {
|
|||||||
}
|
}
|
||||||
try self.push(.{ .void_val = {} });
|
try self.push(.{ .void_val = {} });
|
||||||
},
|
},
|
||||||
|
.alloc => {
|
||||||
|
// alloc(size) or alloc(allocator, size) — at comptime, equivalent to malloc
|
||||||
|
if (arg_count >= 2) {
|
||||||
|
const size_val = try self.pop();
|
||||||
|
_ = try self.pop(); // discard allocator struct
|
||||||
|
const size: usize = if (size_val.asInt()) |v| @intCast(@max(0, v)) else 0;
|
||||||
|
const buf = try self.allocator.alloc(u8, size);
|
||||||
|
@memset(buf, 0);
|
||||||
|
try self.push(.{ .byte_ptr_val = .{ .data = buf, .offset = 0 } });
|
||||||
|
} else if (arg_count == 1) {
|
||||||
|
const val = try self.pop();
|
||||||
|
const size: usize = if (val.asInt()) |v| @intCast(@max(0, v)) else 0;
|
||||||
|
const buf = try self.allocator.alloc(u8, size);
|
||||||
|
@memset(buf, 0);
|
||||||
|
try self.push(.{ .byte_ptr_val = .{ .data = buf, .offset = 0 } });
|
||||||
|
} else {
|
||||||
|
try self.push(.{ .byte_ptr_val = .{ .data = &.{}, .offset = 0 } });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.dealloc => {
|
||||||
|
// dealloc(ptr) — at comptime, no-op
|
||||||
|
var i: u8 = 0;
|
||||||
|
while (i < arg_count) : (i += 1) _ = try self.pop();
|
||||||
|
try self.push(.{ .void_val = {} });
|
||||||
|
},
|
||||||
.type_of => {
|
.type_of => {
|
||||||
// type_of(val) — return the type tag (matching ANY_TAG_* constants)
|
// type_of(val) — return the type tag (matching ANY_TAG_* constants)
|
||||||
if (arg_count >= 1) {
|
if (arg_count >= 1) {
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ pub const Lexer = struct {
|
|||||||
.{ "#source", Tag.hash_source },
|
.{ "#source", Tag.hash_source },
|
||||||
.{ "#define", Tag.hash_define },
|
.{ "#define", Tag.hash_define },
|
||||||
.{ "#flags", Tag.hash_flags },
|
.{ "#flags", Tag.hash_flags },
|
||||||
|
.{ "#inline", Tag.hash_inline },
|
||||||
};
|
};
|
||||||
inline for (directives) |d| {
|
inline for (directives) |d| {
|
||||||
const keyword = d[0];
|
const keyword = d[0];
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ pub const Server = struct {
|
|||||||
.constant => @intFromEnum(lsp.SymbolKindLsp.Constant),
|
.constant => @intFromEnum(lsp.SymbolKindLsp.Constant),
|
||||||
.enum_type => @intFromEnum(lsp.SymbolKindLsp.Enum),
|
.enum_type => @intFromEnum(lsp.SymbolKindLsp.Enum),
|
||||||
.struct_type => @intFromEnum(lsp.SymbolKindLsp.Struct),
|
.struct_type => @intFromEnum(lsp.SymbolKindLsp.Struct),
|
||||||
|
.protocol_type => @intFromEnum(lsp.SymbolKindLsp.Interface),
|
||||||
.type_alias => @intFromEnum(lsp.SymbolKindLsp.Class),
|
.type_alias => @intFromEnum(lsp.SymbolKindLsp.Class),
|
||||||
.param => @intFromEnum(lsp.SymbolKindLsp.Variable),
|
.param => @intFromEnum(lsp.SymbolKindLsp.Variable),
|
||||||
.namespace => @intFromEnum(lsp.SymbolKindLsp.Namespace),
|
.namespace => @intFromEnum(lsp.SymbolKindLsp.Namespace),
|
||||||
@@ -455,6 +456,7 @@ pub const Server = struct {
|
|||||||
.constant => @intFromEnum(lsp.CompletionItemKind.Constant),
|
.constant => @intFromEnum(lsp.CompletionItemKind.Constant),
|
||||||
.enum_type => @intFromEnum(lsp.CompletionItemKind.Enum),
|
.enum_type => @intFromEnum(lsp.CompletionItemKind.Enum),
|
||||||
.struct_type => @intFromEnum(lsp.CompletionItemKind.Struct),
|
.struct_type => @intFromEnum(lsp.CompletionItemKind.Struct),
|
||||||
|
.protocol_type => @intFromEnum(lsp.CompletionItemKind.Interface),
|
||||||
.type_alias => @intFromEnum(lsp.CompletionItemKind.Class),
|
.type_alias => @intFromEnum(lsp.CompletionItemKind.Class),
|
||||||
.param => @intFromEnum(lsp.CompletionItemKind.Variable),
|
.param => @intFromEnum(lsp.CompletionItemKind.Variable),
|
||||||
.namespace => @intFromEnum(lsp.CompletionItemKind.Module),
|
.namespace => @intFromEnum(lsp.CompletionItemKind.Module),
|
||||||
@@ -790,11 +792,13 @@ pub const Server = struct {
|
|||||||
for (fd.params, 0..) |param, pi| {
|
for (fd.params, 0..) |param, pi| {
|
||||||
if (pi > 0) try detail_buf.appendSlice(allocator, ", ");
|
if (pi > 0) try detail_buf.appendSlice(allocator, ", ");
|
||||||
try detail_buf.appendSlice(allocator, param.name);
|
try detail_buf.appendSlice(allocator, param.name);
|
||||||
try detail_buf.appendSlice(allocator, ": ");
|
if (param.type_expr.data != .inferred_type) {
|
||||||
if (param.type_expr.data == .type_expr) {
|
try detail_buf.appendSlice(allocator, ": ");
|
||||||
try detail_buf.appendSlice(allocator, param.type_expr.data.type_expr.name);
|
if (param.type_expr.data == .type_expr) {
|
||||||
} else {
|
try detail_buf.appendSlice(allocator, param.type_expr.data.type_expr.name);
|
||||||
try detail_buf.appendSlice(allocator, "?");
|
} else {
|
||||||
|
try detail_buf.appendSlice(allocator, "?");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try detail_buf.append(allocator, ')');
|
try detail_buf.append(allocator, ')');
|
||||||
@@ -838,6 +842,12 @@ pub const Server = struct {
|
|||||||
.kind = @intFromEnum(lsp.CompletionItemKind.Struct),
|
.kind = @intFromEnum(lsp.CompletionItemKind.Struct),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
.protocol_decl => |pd| {
|
||||||
|
try items.append(allocator, .{
|
||||||
|
.label = pd.name,
|
||||||
|
.kind = @intFromEnum(lsp.CompletionItemKind.Interface),
|
||||||
|
});
|
||||||
|
},
|
||||||
.var_decl => |vd| {
|
.var_decl => |vd| {
|
||||||
try items.append(allocator, .{
|
try items.append(allocator, .{
|
||||||
.label = vd.name,
|
.label = vd.name,
|
||||||
@@ -892,6 +902,43 @@ pub const Server = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (sym.kind == .protocol_type) {
|
||||||
|
const lookup_root = if (sym.origin) |origin_path|
|
||||||
|
if (self.documents.get(origin_path)) |od| od.root orelse root else root
|
||||||
|
else
|
||||||
|
root;
|
||||||
|
if (sx.sema.findNodeAtOffset(lookup_root, sym.def_span.start)) |node| {
|
||||||
|
if (node.data == .protocol_decl) {
|
||||||
|
const pd = node.data.protocol_decl;
|
||||||
|
for (pd.methods) |method| {
|
||||||
|
// Build detail string: (params) -> ret
|
||||||
|
var detail_buf = std.ArrayList(u8).empty;
|
||||||
|
try detail_buf.append(self.allocator, '(');
|
||||||
|
for (method.param_names, 0..) |pname, pi| {
|
||||||
|
if (pi > 0) try detail_buf.appendSlice(self.allocator, ", ");
|
||||||
|
try detail_buf.appendSlice(self.allocator, pname);
|
||||||
|
if (pi < method.params.len) {
|
||||||
|
try detail_buf.appendSlice(self.allocator, ": ");
|
||||||
|
if (method.params[pi].data == .type_expr) {
|
||||||
|
try detail_buf.appendSlice(self.allocator, method.params[pi].data.type_expr.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try detail_buf.append(self.allocator, ')');
|
||||||
|
if (method.return_type) |rt| {
|
||||||
|
try detail_buf.appendSlice(self.allocator, " -> ");
|
||||||
|
if (rt.data == .type_expr) {
|
||||||
|
try detail_buf.appendSlice(self.allocator, rt.data.type_expr.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try items.append(self.allocator, .{
|
||||||
|
.label = method.name,
|
||||||
|
.kind = @intFromEnum(lsp.CompletionItemKind.Method),
|
||||||
|
.detail = detail_buf.items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -956,11 +1003,13 @@ pub const Server = struct {
|
|||||||
if (pi > 0) try label_buf.appendSlice(self.allocator, ", ");
|
if (pi > 0) try label_buf.appendSlice(self.allocator, ", ");
|
||||||
const param_start = label_buf.items.len;
|
const param_start = label_buf.items.len;
|
||||||
try label_buf.appendSlice(self.allocator, param.name);
|
try label_buf.appendSlice(self.allocator, param.name);
|
||||||
try label_buf.appendSlice(self.allocator, ": ");
|
if (param.type_expr.data != .inferred_type) {
|
||||||
if (param.type_expr.data == .type_expr) {
|
try label_buf.appendSlice(self.allocator, ": ");
|
||||||
try label_buf.appendSlice(self.allocator, param.type_expr.data.type_expr.name);
|
if (param.type_expr.data == .type_expr) {
|
||||||
} else {
|
try label_buf.appendSlice(self.allocator, param.type_expr.data.type_expr.name);
|
||||||
try label_buf.appendSlice(self.allocator, "?");
|
} else {
|
||||||
|
try label_buf.appendSlice(self.allocator, "?");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const param_label = try self.allocator.dupe(u8, label_buf.items[param_start..]);
|
const param_label = try self.allocator.dupe(u8, label_buf.items[param_start..]);
|
||||||
try param_labels.append(self.allocator, param_label);
|
try param_labels.append(self.allocator, param_label);
|
||||||
@@ -1361,6 +1410,9 @@ pub const Server = struct {
|
|||||||
.kw_push,
|
.kw_push,
|
||||||
.kw_ufcs,
|
.kw_ufcs,
|
||||||
.kw_in,
|
.kw_in,
|
||||||
|
.kw_closure,
|
||||||
|
.kw_protocol,
|
||||||
|
.kw_impl,
|
||||||
.hash_run,
|
.hash_run,
|
||||||
.hash_import,
|
.hash_import,
|
||||||
.hash_insert,
|
.hash_insert,
|
||||||
@@ -1372,9 +1424,10 @@ pub const Server = struct {
|
|||||||
.hash_source,
|
.hash_source,
|
||||||
.hash_define,
|
.hash_define,
|
||||||
.hash_flags,
|
.hash_flags,
|
||||||
|
.hash_inline,
|
||||||
=> ST.keyword,
|
=> ST.keyword,
|
||||||
|
|
||||||
.kw_f32, .kw_f64, .kw_Type => ST.type_,
|
.kw_f32, .kw_f64, .kw_Type, .kw_Self => ST.type_,
|
||||||
|
|
||||||
.int_literal, .float_literal => ST.number,
|
.int_literal, .float_literal => ST.number,
|
||||||
.string_literal, .raw_string_literal => null,
|
.string_literal, .raw_string_literal => null,
|
||||||
@@ -1480,6 +1533,7 @@ pub const Server = struct {
|
|||||||
.param => ST.parameter,
|
.param => ST.parameter,
|
||||||
.enum_type => ST.enum_,
|
.enum_type => ST.enum_,
|
||||||
.struct_type => ST.struct_,
|
.struct_type => ST.struct_,
|
||||||
|
.protocol_type => ST.interface,
|
||||||
.type_alias => ST.type_,
|
.type_alias => ST.type_,
|
||||||
.namespace => ST.namespace,
|
.namespace => ST.namespace,
|
||||||
};
|
};
|
||||||
@@ -1817,8 +1871,10 @@ pub const Server = struct {
|
|||||||
for (params, 0..) |param, pi| {
|
for (params, 0..) |param, pi| {
|
||||||
if (pi > 0) try buf.appendSlice(self.allocator, ", ");
|
if (pi > 0) try buf.appendSlice(self.allocator, ", ");
|
||||||
try buf.appendSlice(self.allocator, param.name);
|
try buf.appendSlice(self.allocator, param.name);
|
||||||
try buf.appendSlice(self.allocator, ": ");
|
if (param.type_expr.data == .inferred_type) {
|
||||||
if (param.type_expr.data == .type_expr) {
|
// Inferred type — show name only
|
||||||
|
} else if (param.type_expr.data == .type_expr) {
|
||||||
|
try buf.appendSlice(self.allocator, ": ");
|
||||||
try buf.appendSlice(self.allocator, param.type_expr.data.type_expr.name);
|
try buf.appendSlice(self.allocator, param.type_expr.data.type_expr.name);
|
||||||
} else {
|
} else {
|
||||||
try buf.appendSlice(self.allocator, "?");
|
try buf.appendSlice(self.allocator, "?");
|
||||||
@@ -2305,11 +2361,13 @@ pub const Server = struct {
|
|||||||
for (fd.params, 0..) |param, pi| {
|
for (fd.params, 0..) |param, pi| {
|
||||||
if (pi > 0) try buf.appendSlice(allocator, ", ");
|
if (pi > 0) try buf.appendSlice(allocator, ", ");
|
||||||
try buf.appendSlice(allocator, param.name);
|
try buf.appendSlice(allocator, param.name);
|
||||||
try buf.appendSlice(allocator, ": ");
|
if (param.type_expr.data != .inferred_type) {
|
||||||
if (param.type_expr.data == .type_expr) {
|
try buf.appendSlice(allocator, ": ");
|
||||||
try buf.appendSlice(allocator, param.type_expr.data.type_expr.name);
|
if (param.type_expr.data == .type_expr) {
|
||||||
} else {
|
try buf.appendSlice(allocator, param.type_expr.data.type_expr.name);
|
||||||
try buf.appendSlice(allocator, "?");
|
} else {
|
||||||
|
try buf.appendSlice(allocator, "?");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try buf.append(allocator, ')');
|
try buf.append(allocator, ')');
|
||||||
@@ -2407,6 +2465,44 @@ pub const Server = struct {
|
|||||||
}
|
}
|
||||||
try buf.appendSlice(allocator, " }");
|
try buf.appendSlice(allocator, " }");
|
||||||
},
|
},
|
||||||
|
.protocol_decl => |pd| {
|
||||||
|
try buf.appendSlice(allocator, pd.name);
|
||||||
|
try buf.appendSlice(allocator, " :: protocol");
|
||||||
|
if (pd.is_inline) try buf.appendSlice(allocator, " #inline");
|
||||||
|
try buf.appendSlice(allocator, " { ");
|
||||||
|
for (pd.methods, 0..) |method, mi| {
|
||||||
|
if (mi > 0) try buf.appendSlice(allocator, " ");
|
||||||
|
try buf.appendSlice(allocator, method.name);
|
||||||
|
try buf.appendSlice(allocator, " :: (");
|
||||||
|
for (method.param_names, 0..) |pname, pi| {
|
||||||
|
if (pi > 0) try buf.appendSlice(allocator, ", ");
|
||||||
|
try buf.appendSlice(allocator, pname);
|
||||||
|
if (pi < method.params.len) {
|
||||||
|
try buf.appendSlice(allocator, ": ");
|
||||||
|
if (method.params[pi].data == .type_expr) {
|
||||||
|
try buf.appendSlice(allocator, method.params[pi].data.type_expr.name);
|
||||||
|
} else {
|
||||||
|
try buf.appendSlice(allocator, "?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try buf.append(allocator, ')');
|
||||||
|
if (method.return_type) |rt| {
|
||||||
|
try buf.appendSlice(allocator, " -> ");
|
||||||
|
if (rt.data == .type_expr) {
|
||||||
|
try buf.appendSlice(allocator, rt.data.type_expr.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try buf.appendSlice(allocator, ";");
|
||||||
|
}
|
||||||
|
try buf.appendSlice(allocator, " }");
|
||||||
|
},
|
||||||
|
.impl_block => |ib| {
|
||||||
|
try buf.appendSlice(allocator, "impl ");
|
||||||
|
try buf.appendSlice(allocator, ib.protocol_name);
|
||||||
|
try buf.appendSlice(allocator, " for ");
|
||||||
|
try buf.appendSlice(allocator, ib.target_type);
|
||||||
|
},
|
||||||
.const_decl => |cd| {
|
.const_decl => |cd| {
|
||||||
try buf.appendSlice(allocator, cd.name);
|
try buf.appendSlice(allocator, cd.name);
|
||||||
try buf.appendSlice(allocator, " :: ");
|
try buf.appendSlice(allocator, " :: ");
|
||||||
@@ -2507,6 +2603,10 @@ pub const Server = struct {
|
|||||||
try buf.appendSlice(allocator, sym.name);
|
try buf.appendSlice(allocator, sym.name);
|
||||||
try buf.appendSlice(allocator, " :: struct { ... }");
|
try buf.appendSlice(allocator, " :: struct { ... }");
|
||||||
},
|
},
|
||||||
|
.protocol_type => {
|
||||||
|
try buf.appendSlice(allocator, sym.name);
|
||||||
|
try buf.appendSlice(allocator, " :: protocol { ... }");
|
||||||
|
},
|
||||||
.type_alias => {
|
.type_alias => {
|
||||||
try buf.appendSlice(allocator, sym.name);
|
try buf.appendSlice(allocator, sym.name);
|
||||||
try buf.appendSlice(allocator, " :: (type)");
|
try buf.appendSlice(allocator, " :: (type)");
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ pub fn initializeResultJson(allocator: std.mem.Allocator) ![]const u8 {
|
|||||||
"\"completionProvider\":{{\"triggerCharacters\":[\".\"]}}," ++
|
"\"completionProvider\":{{\"triggerCharacters\":[\".\"]}}," ++
|
||||||
"\"signatureHelpProvider\":{{\"triggerCharacters\":[\"(\",\",\"]}}," ++
|
"\"signatureHelpProvider\":{{\"triggerCharacters\":[\"(\",\",\"]}}," ++
|
||||||
"\"semanticTokensProvider\":{{\"legend\":{{" ++
|
"\"semanticTokensProvider\":{{\"legend\":{{" ++
|
||||||
"\"tokenTypes\":[\"namespace\",\"type\",\"enum\",\"struct\",\"parameter\",\"variable\",\"enumMember\",\"function\",\"keyword\",\"number\",\"string\",\"operator\"]," ++
|
"\"tokenTypes\":[\"namespace\",\"type\",\"enum\",\"struct\",\"parameter\",\"variable\",\"enumMember\",\"function\",\"keyword\",\"number\",\"string\",\"operator\",\"interface\"]," ++
|
||||||
"\"tokenModifiers\":[\"declaration\",\"readonly\"]" ++
|
"\"tokenModifiers\":[\"declaration\",\"readonly\"]" ++
|
||||||
"}},\"full\":true}}," ++
|
"}},\"full\":true}}," ++
|
||||||
"\"inlayHintProvider\":true}}}}",
|
"\"inlayHintProvider\":true}}}}",
|
||||||
@@ -321,6 +321,7 @@ pub const SemanticTokenType = struct {
|
|||||||
pub const number: u32 = 9;
|
pub const number: u32 = 9;
|
||||||
pub const string_: u32 = 10;
|
pub const string_: u32 = 10;
|
||||||
pub const operator_: u32 = 11;
|
pub const operator_: u32 = 11;
|
||||||
|
pub const interface: u32 = 12;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Build a SemanticTokens JSON response.
|
/// Build a SemanticTokens JSON response.
|
||||||
|
|||||||
270
src/parser.zig
270
src/parser.zig
@@ -16,6 +16,8 @@ pub const Parser = struct {
|
|||||||
err_offset: ?u32 = null,
|
err_offset: ?u32 = null,
|
||||||
prev_end: u32 = 0,
|
prev_end: u32 = 0,
|
||||||
diagnostics: ?*errors.DiagnosticList = null,
|
diagnostics: ?*errors.DiagnosticList = null,
|
||||||
|
/// Type param names from enclosing generic struct (set while parsing methods)
|
||||||
|
struct_type_params: []const []const u8 = &.{},
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, source: [:0]const u8) Parser {
|
pub fn init(allocator: std.mem.Allocator, source: [:0]const u8) Parser {
|
||||||
var lexer = Lexer.init(source);
|
var lexer = Lexer.init(source);
|
||||||
@@ -75,8 +77,13 @@ pub const Parser = struct {
|
|||||||
return try self.createNode(start, .{ .comptime_expr = .{ .expr = expr } });
|
return try self.createNode(start, .{ .comptime_expr = .{ .expr = expr } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// impl Protocol for Type { methods }
|
||||||
|
if (self.current.tag == .kw_impl) {
|
||||||
|
return self.parseImplBlock(start);
|
||||||
|
}
|
||||||
|
|
||||||
// All top-level declarations start with an identifier
|
// All top-level declarations start with an identifier
|
||||||
if (self.current.tag != .identifier) {
|
if (self.current.tag != .identifier and self.current.tag != .kw_Self) {
|
||||||
return self.fail("expected identifier at top level");
|
return self.fail("expected identifier at top level");
|
||||||
}
|
}
|
||||||
const name = self.tokenSlice(self.current);
|
const name = self.tokenSlice(self.current);
|
||||||
@@ -170,6 +177,11 @@ pub const Parser = struct {
|
|||||||
return self.parseStructDecl(name, start_pos);
|
return self.parseStructDecl(name, start_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Protocol declaration
|
||||||
|
if (self.current.tag == .kw_protocol) {
|
||||||
|
return self.parseProtocolDecl(name, start_pos);
|
||||||
|
}
|
||||||
|
|
||||||
// C-style union declaration
|
// C-style union declaration
|
||||||
if (self.current.tag == .kw_union) {
|
if (self.current.tag == .kw_union) {
|
||||||
return self.parseUnionDecl(name, start_pos);
|
return self.parseUnionDecl(name, start_pos);
|
||||||
@@ -378,7 +390,7 @@ pub const Parser = struct {
|
|||||||
return try self.createNode(start, .{ .array_type_expr = .{ .length = len_node, .element_type = elem_type } });
|
return try self.createNode(start, .{ .array_type_expr = .{ .length = len_node, .element_type = elem_type } });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic type parameter introduction: $T
|
// Generic type parameter introduction: $T or $T/Protocol1/Protocol2
|
||||||
if (self.current.tag == .dollar) {
|
if (self.current.tag == .dollar) {
|
||||||
self.advance();
|
self.advance();
|
||||||
if (self.current.tag != .identifier) {
|
if (self.current.tag != .identifier) {
|
||||||
@@ -386,7 +398,18 @@ pub const Parser = struct {
|
|||||||
}
|
}
|
||||||
const name = self.tokenSlice(self.current);
|
const name = self.tokenSlice(self.current);
|
||||||
self.advance();
|
self.advance();
|
||||||
return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = true } });
|
// Parse optional protocol constraints: $T/Eq/Hashable
|
||||||
|
var constraints = std.ArrayList([]const u8).empty;
|
||||||
|
while (self.current.tag == .slash) {
|
||||||
|
self.advance(); // skip '/'
|
||||||
|
if (self.current.tag != .identifier) {
|
||||||
|
return self.fail("expected protocol name after '/'");
|
||||||
|
}
|
||||||
|
try constraints.append(self.allocator, self.tokenSlice(self.current));
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
|
const pc = try constraints.toOwnedSlice(self.allocator);
|
||||||
|
return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = true, .protocol_constraints = pc } });
|
||||||
}
|
}
|
||||||
// Function type: (ParamTypes) -> ReturnType
|
// Function type: (ParamTypes) -> ReturnType
|
||||||
// Tuple type: (T1, T2) or (T1) — no '->' after ')'
|
// Tuple type: (T1, T2) or (T1) — no '->' after ')'
|
||||||
@@ -525,7 +548,12 @@ pub const Parser = struct {
|
|||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
return try self.createNode(start, .{ .type_expr = .{ .name = name } });
|
// Mark as generic if name matches an enclosing struct's type param
|
||||||
|
var is_struct_generic = false;
|
||||||
|
for (self.struct_type_params) |tp| {
|
||||||
|
if (std.mem.eql(u8, tp, name)) { is_struct_generic = true; break; }
|
||||||
|
}
|
||||||
|
return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = is_struct_generic } });
|
||||||
}
|
}
|
||||||
// Inline struct type in type position: struct { ... }
|
// Inline struct type in type position: struct { ... }
|
||||||
if (self.current.tag == .kw_struct) {
|
if (self.current.tag == .kw_struct) {
|
||||||
@@ -682,17 +710,38 @@ pub const Parser = struct {
|
|||||||
self.advance();
|
self.advance();
|
||||||
try self.expect(.colon);
|
try self.expect(.colon);
|
||||||
const constraint = try self.parseTypeExpr();
|
const constraint = try self.parseTypeExpr();
|
||||||
try type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint });
|
// Parse optional protocol constraints: $T: Type/Eq/Hashable
|
||||||
|
var pc_list = std.ArrayList([]const u8).empty;
|
||||||
|
if (constraint.data == .type_expr and std.mem.eql(u8, constraint.data.type_expr.name, "Type")) {
|
||||||
|
while (self.current.tag == .slash) {
|
||||||
|
self.advance(); // skip '/'
|
||||||
|
if (self.current.tag != .identifier) {
|
||||||
|
return self.fail("expected protocol name after '/'");
|
||||||
|
}
|
||||||
|
try pc_list.append(self.allocator, self.tokenSlice(self.current));
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const pc = try pc_list.toOwnedSlice(self.allocator);
|
||||||
|
try type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint, .protocol_constraints = pc });
|
||||||
}
|
}
|
||||||
try self.expect(.r_paren);
|
try self.expect(.r_paren);
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.expect(.l_brace);
|
try self.expect(.l_brace);
|
||||||
|
|
||||||
|
// Set struct type params context so method params can reference T without $
|
||||||
|
var tp_names = std.ArrayList([]const u8).empty;
|
||||||
|
for (type_params.items) |tp| try tp_names.append(self.allocator, tp.name);
|
||||||
|
const saved_struct_type_params = self.struct_type_params;
|
||||||
|
self.struct_type_params = tp_names.items;
|
||||||
|
defer self.struct_type_params = saved_struct_type_params;
|
||||||
|
|
||||||
var field_names = std.ArrayList([]const u8).empty;
|
var field_names = std.ArrayList([]const u8).empty;
|
||||||
var field_types = std.ArrayList(*Node).empty;
|
var field_types = std.ArrayList(*Node).empty;
|
||||||
var field_defaults = std.ArrayList(?*Node).empty;
|
var field_defaults = std.ArrayList(?*Node).empty;
|
||||||
var using_entries = std.ArrayList(ast.UsingEntry).empty;
|
var using_entries = std.ArrayList(ast.UsingEntry).empty;
|
||||||
|
var methods = std.ArrayList(*Node).empty;
|
||||||
|
|
||||||
while (self.current.tag != .r_brace and self.current.tag != .eof) {
|
while (self.current.tag != .r_brace and self.current.tag != .eof) {
|
||||||
// Check for #using directive
|
// Check for #using directive
|
||||||
@@ -711,6 +760,20 @@ pub const Parser = struct {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method declaration: name :: (params) -> type { body }
|
||||||
|
if (self.current.tag == .identifier and self.peekNext() == .colon_colon) {
|
||||||
|
const method_start = self.current.loc.start;
|
||||||
|
const method_name = self.tokenSlice(self.current);
|
||||||
|
self.advance(); // skip name
|
||||||
|
self.advance(); // skip ::
|
||||||
|
if (self.current.tag == .l_paren and self.isFunctionDef()) {
|
||||||
|
try methods.append(self.allocator, try self.parseFnDecl(method_name, method_start));
|
||||||
|
} else {
|
||||||
|
return self.fail("only function declarations are allowed inside struct bodies");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Parse field group: name1, name2, ...: type (= default)?;
|
// Parse field group: name1, name2, ...: type (= default)?;
|
||||||
var group_names = std.ArrayList([]const u8).empty;
|
var group_names = std.ArrayList([]const u8).empty;
|
||||||
|
|
||||||
@@ -764,6 +827,166 @@ pub const Parser = struct {
|
|||||||
.field_defaults = try field_defaults.toOwnedSlice(self.allocator),
|
.field_defaults = try field_defaults.toOwnedSlice(self.allocator),
|
||||||
.type_params = try type_params.toOwnedSlice(self.allocator),
|
.type_params = try type_params.toOwnedSlice(self.allocator),
|
||||||
.using_entries = try using_entries.toOwnedSlice(self.allocator),
|
.using_entries = try using_entries.toOwnedSlice(self.allocator),
|
||||||
|
.methods = try methods.toOwnedSlice(self.allocator),
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseProtocolDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
|
||||||
|
self.advance(); // skip 'protocol'
|
||||||
|
|
||||||
|
// Check for #inline
|
||||||
|
var is_inline = false;
|
||||||
|
if (self.current.tag == .hash_inline) {
|
||||||
|
is_inline = true;
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.expect(.l_brace);
|
||||||
|
|
||||||
|
var methods = std.ArrayList(ast.ProtocolMethodDecl).empty;
|
||||||
|
|
||||||
|
while (self.current.tag != .r_brace and self.current.tag != .eof) {
|
||||||
|
// Method: name :: (params) -> type; or name :: (params) -> type { body }
|
||||||
|
if (self.current.tag != .identifier) {
|
||||||
|
return self.fail("expected method name in protocol body");
|
||||||
|
}
|
||||||
|
const method_name = self.tokenSlice(self.current);
|
||||||
|
self.advance();
|
||||||
|
try self.expect(.colon_colon);
|
||||||
|
try self.expect(.l_paren);
|
||||||
|
|
||||||
|
var param_types = std.ArrayList(*Node).empty;
|
||||||
|
var param_names = std.ArrayList([]const u8).empty;
|
||||||
|
|
||||||
|
while (self.current.tag != .r_paren and self.current.tag != .eof) {
|
||||||
|
if (param_types.items.len > 0) {
|
||||||
|
try self.expect(.comma);
|
||||||
|
}
|
||||||
|
// Parse: name: type
|
||||||
|
if (self.current.tag != .identifier and self.current.tag != .kw_Self) {
|
||||||
|
return self.fail("expected parameter name in protocol method");
|
||||||
|
}
|
||||||
|
const pname = self.tokenSlice(self.current);
|
||||||
|
self.advance();
|
||||||
|
try self.expect(.colon);
|
||||||
|
const ptype = try self.parseTypeExpr();
|
||||||
|
try param_names.append(self.allocator, pname);
|
||||||
|
try param_types.append(self.allocator, ptype);
|
||||||
|
}
|
||||||
|
try self.expect(.r_paren);
|
||||||
|
|
||||||
|
// Optional return type
|
||||||
|
var return_type: ?*Node = null;
|
||||||
|
if (self.current.tag == .arrow) {
|
||||||
|
self.advance();
|
||||||
|
return_type = try self.parseTypeExpr();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional body (default method) or semicolon
|
||||||
|
var default_body: ?*Node = null;
|
||||||
|
if (self.current.tag == .l_brace) {
|
||||||
|
default_body = try self.parseBlock();
|
||||||
|
} else {
|
||||||
|
if (self.current.tag == .semicolon) self.advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
try methods.append(self.allocator, .{
|
||||||
|
.name = method_name,
|
||||||
|
.params = try param_types.toOwnedSlice(self.allocator),
|
||||||
|
.param_names = try param_names.toOwnedSlice(self.allocator),
|
||||||
|
.return_type = return_type,
|
||||||
|
.default_body = default_body,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.expect(.r_brace);
|
||||||
|
|
||||||
|
return try self.createNode(start_pos, .{ .protocol_decl = .{
|
||||||
|
.name = name,
|
||||||
|
.methods = try methods.toOwnedSlice(self.allocator),
|
||||||
|
.is_inline = is_inline,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseImplBlock(self: *Parser, start_pos: u32) anyerror!*Node {
|
||||||
|
self.advance(); // skip 'impl'
|
||||||
|
|
||||||
|
// Protocol name
|
||||||
|
if (self.current.tag != .identifier) {
|
||||||
|
return self.fail("expected protocol name after 'impl'");
|
||||||
|
}
|
||||||
|
const protocol_name = self.tokenSlice(self.current);
|
||||||
|
self.advance();
|
||||||
|
|
||||||
|
// 'for' — note: 'for' is a keyword (kw_for), not an identifier
|
||||||
|
if (self.current.tag != .kw_for) {
|
||||||
|
return self.fail("expected 'for' after protocol name in impl block");
|
||||||
|
}
|
||||||
|
self.advance();
|
||||||
|
|
||||||
|
// Target type name
|
||||||
|
if (self.current.tag != .identifier) {
|
||||||
|
return self.fail("expected type name after 'for'");
|
||||||
|
}
|
||||||
|
const target_type = self.tokenSlice(self.current);
|
||||||
|
self.advance();
|
||||||
|
|
||||||
|
// Optional type params: impl Protocol for List($T)
|
||||||
|
var target_type_params = std.ArrayList(ast.StructTypeParam).empty;
|
||||||
|
if (self.current.tag == .l_paren) {
|
||||||
|
self.advance(); // skip '('
|
||||||
|
while (self.current.tag != .r_paren and self.current.tag != .eof) {
|
||||||
|
if (target_type_params.items.len > 0) {
|
||||||
|
try self.expect(.comma);
|
||||||
|
}
|
||||||
|
try self.expect(.dollar);
|
||||||
|
if (self.current.tag != .identifier) {
|
||||||
|
return self.fail("expected type parameter name after '$'");
|
||||||
|
}
|
||||||
|
const param_name = self.tokenSlice(self.current);
|
||||||
|
self.advance();
|
||||||
|
// Optional constraint — for now just use Type
|
||||||
|
const constraint = try self.createNode(self.current.loc.start, .{ .type_expr = .{ .name = "Type" } });
|
||||||
|
try target_type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint });
|
||||||
|
}
|
||||||
|
try self.expect(.r_paren);
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.expect(.l_brace);
|
||||||
|
|
||||||
|
// Set struct type params context so method params can reference T without $
|
||||||
|
var tp_names = std.ArrayList([]const u8).empty;
|
||||||
|
for (target_type_params.items) |tp| try tp_names.append(self.allocator, tp.name);
|
||||||
|
const saved_struct_type_params = self.struct_type_params;
|
||||||
|
self.struct_type_params = tp_names.items;
|
||||||
|
defer self.struct_type_params = saved_struct_type_params;
|
||||||
|
|
||||||
|
var methods = std.ArrayList(*Node).empty;
|
||||||
|
|
||||||
|
while (self.current.tag != .r_brace and self.current.tag != .eof) {
|
||||||
|
// Method: name :: (params) -> type { body }
|
||||||
|
if (self.current.tag != .identifier) {
|
||||||
|
return self.fail("expected method name in impl block");
|
||||||
|
}
|
||||||
|
const method_start = self.current.loc.start;
|
||||||
|
const method_name = self.tokenSlice(self.current);
|
||||||
|
self.advance();
|
||||||
|
try self.expect(.colon_colon);
|
||||||
|
|
||||||
|
if (self.current.tag == .l_paren and self.isFunctionDef()) {
|
||||||
|
try methods.append(self.allocator, try self.parseFnDecl(method_name, method_start));
|
||||||
|
} else {
|
||||||
|
return self.fail("expected function declaration in impl block");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.expect(.r_brace);
|
||||||
|
|
||||||
|
return try self.createNode(start_pos, .{ .impl_block = .{
|
||||||
|
.protocol_name = protocol_name,
|
||||||
|
.target_type = target_type,
|
||||||
|
.target_type_params = try target_type_params.toOwnedSlice(self.allocator),
|
||||||
|
.methods = try methods.toOwnedSlice(self.allocator),
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -848,7 +1071,13 @@ pub const Parser = struct {
|
|||||||
const param_name = self.tokenSlice(self.current);
|
const param_name = self.tokenSlice(self.current);
|
||||||
const param_name_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
|
const param_name_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||||
self.advance();
|
self.advance();
|
||||||
try self.expect(.colon);
|
// Optional type annotation: if no ':', infer type from context
|
||||||
|
if (self.current.tag != .colon) {
|
||||||
|
const inferred_node = try self.createNode(param_name_span.start, .{ .inferred_type = {} });
|
||||||
|
try params.append(self.allocator, .{ .name = param_name, .name_span = param_name_span, .type_expr = inferred_node });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.advance(); // consume ':'
|
||||||
const is_variadic = self.current.tag == .dot_dot;
|
const is_variadic = self.current.tag == .dot_dot;
|
||||||
if (is_variadic) self.advance();
|
if (is_variadic) self.advance();
|
||||||
const param_type = try self.parseTypeExpr();
|
const param_type = try self.parseTypeExpr();
|
||||||
@@ -856,7 +1085,18 @@ pub const Parser = struct {
|
|||||||
if (is_ct_param and param_type.data == .type_expr) {
|
if (is_ct_param and param_type.data == .type_expr) {
|
||||||
const constraint_name = param_type.data.type_expr.name;
|
const constraint_name = param_type.data.type_expr.name;
|
||||||
if (std.mem.eql(u8, constraint_name, "Type")) {
|
if (std.mem.eql(u8, constraint_name, "Type")) {
|
||||||
param_type.data = .{ .type_expr = .{ .name = param_name, .is_generic = true } };
|
// Parse optional protocol constraints: $T: Type/Eq/Hashable
|
||||||
|
var constraints = std.ArrayList([]const u8).empty;
|
||||||
|
while (self.current.tag == .slash) {
|
||||||
|
self.advance(); // skip '/'
|
||||||
|
if (self.current.tag != .identifier) {
|
||||||
|
return self.fail("expected protocol name after '/'");
|
||||||
|
}
|
||||||
|
try constraints.append(self.allocator, self.tokenSlice(self.current));
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
|
const pc = try constraints.toOwnedSlice(self.allocator);
|
||||||
|
param_type.data = .{ .type_expr = .{ .name = param_name, .is_generic = true, .protocol_constraints = pc } };
|
||||||
} else {
|
} else {
|
||||||
is_comptime_param = true;
|
is_comptime_param = true;
|
||||||
}
|
}
|
||||||
@@ -906,8 +1146,13 @@ pub const Parser = struct {
|
|||||||
for (generic_names.items) |gen_name| {
|
for (generic_names.items) |gen_name| {
|
||||||
if (!seen.contains(gen_name)) {
|
if (!seen.contains(gen_name)) {
|
||||||
try seen.put(gen_name, {});
|
try seen.put(gen_name, {});
|
||||||
|
// Propagate protocol constraints from the TypeExpr if present
|
||||||
|
const pc = if (param.type_expr.data == .type_expr)
|
||||||
|
param.type_expr.data.type_expr.protocol_constraints
|
||||||
|
else
|
||||||
|
&[_][]const u8{};
|
||||||
const type_constraint = self.createNode(param.type_expr.span.start, .{ .type_expr = .{ .name = "Type" } }) catch continue;
|
const type_constraint = self.createNode(param.type_expr.span.start, .{ .type_expr = .{ .name = "Type" } }) catch continue;
|
||||||
type_params.append(self.allocator, .{ .name = gen_name, .constraint = type_constraint }) catch {};
|
type_params.append(self.allocator, .{ .name = gen_name, .constraint = type_constraint, .protocol_constraints = pc }) catch {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1476,6 +1721,11 @@ pub const Parser = struct {
|
|||||||
self.advance();
|
self.advance();
|
||||||
return try self.createNode(start, .{ .identifier = .{ .name = name } });
|
return try self.createNode(start, .{ .identifier = .{ .name = name } });
|
||||||
},
|
},
|
||||||
|
.kw_closure => {
|
||||||
|
// `closure` keyword used as identifier in expressions (closure intrinsic call)
|
||||||
|
self.advance();
|
||||||
|
return try self.createNode(start, .{ .identifier = .{ .name = "closure" } });
|
||||||
|
},
|
||||||
.dot => {
|
.dot => {
|
||||||
self.advance();
|
self.advance();
|
||||||
// Anonymous struct literal: .{ ... }
|
// Anonymous struct literal: .{ ... }
|
||||||
@@ -1946,7 +2196,9 @@ pub const Parser = struct {
|
|||||||
if (self.current.tag == .r_paren) break :blk true; // empty parens
|
if (self.current.tag == .r_paren) break :blk true; // empty parens
|
||||||
if (self.current.tag != .identifier) break :blk false;
|
if (self.current.tag != .identifier) break :blk false;
|
||||||
self.advance();
|
self.advance();
|
||||||
break :blk self.current.tag == .colon;
|
break :blk self.current.tag == .colon or
|
||||||
|
self.current.tag == .comma or
|
||||||
|
self.current.tag == .r_paren;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Restore to '(' and scan past parens inline (not via peekPastParens which restores state)
|
// Restore to '(' and scan past parens inline (not via peekPastParens which restores state)
|
||||||
|
|||||||
49
src/sema.zig
49
src/sema.zig
@@ -16,6 +16,7 @@ pub const SymbolKind = enum {
|
|||||||
function,
|
function,
|
||||||
enum_type,
|
enum_type,
|
||||||
struct_type,
|
struct_type,
|
||||||
|
protocol_type,
|
||||||
type_alias,
|
type_alias,
|
||||||
param,
|
param,
|
||||||
namespace,
|
namespace,
|
||||||
@@ -650,9 +651,10 @@ pub const Analyzer = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Built-in names that aren't declared in source
|
// Built-in names that aren't declared in source
|
||||||
if (std.mem.eql(u8, name, "io")) return;
|
const builtins = [_][]const u8{ "io", "true", "false", "cast", "closure", "out", "size_of", "malloc", "free", "memcpy", "memset" };
|
||||||
if (std.mem.eql(u8, name, "true") or std.mem.eql(u8, name, "false")) return;
|
for (builtins) |b| {
|
||||||
if (std.mem.eql(u8, name, "cast")) return;
|
if (std.mem.eql(u8, name, b)) return;
|
||||||
|
}
|
||||||
|
|
||||||
try self.diagnostics.append(self.allocator, .{
|
try self.diagnostics.append(self.allocator, .{
|
||||||
.level = .warn,
|
.level = .warn,
|
||||||
@@ -844,6 +846,7 @@ pub const Analyzer = struct {
|
|||||||
.param,
|
.param,
|
||||||
.match_arm,
|
.match_arm,
|
||||||
.undef_literal,
|
.undef_literal,
|
||||||
|
.inferred_type,
|
||||||
.builtin_expr,
|
.builtin_expr,
|
||||||
.foreign_expr,
|
.foreign_expr,
|
||||||
.library_decl,
|
.library_decl,
|
||||||
@@ -863,6 +866,30 @@ pub const Analyzer = struct {
|
|||||||
.slice_expr,
|
.slice_expr,
|
||||||
.tuple_type_expr,
|
.tuple_type_expr,
|
||||||
=> {},
|
=> {},
|
||||||
|
.protocol_decl => |pd| {
|
||||||
|
try self.addSymbol(pd.name, .protocol_type, null, node.span);
|
||||||
|
// Recurse into default method bodies
|
||||||
|
for (pd.methods) |method| {
|
||||||
|
if (method.default_body) |body| {
|
||||||
|
try self.pushScope();
|
||||||
|
// `self` is implicit in protocol default methods
|
||||||
|
try self.addSymbol("self", .param, null, node.span);
|
||||||
|
for (method.param_names) |pname| {
|
||||||
|
try self.addSymbol(pname, .param, null, node.span);
|
||||||
|
}
|
||||||
|
try self.analyzeNode(body);
|
||||||
|
self.popScope();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.impl_block => |ib| {
|
||||||
|
// Each impl block gets its own scope so methods don't conflict across impls
|
||||||
|
try self.pushScope();
|
||||||
|
for (ib.methods) |method_node| {
|
||||||
|
try self.analyzeNode(method_node);
|
||||||
|
}
|
||||||
|
self.popScope();
|
||||||
|
},
|
||||||
.ufcs_alias => |ua| {
|
.ufcs_alias => |ua| {
|
||||||
// Register the alias name as a function and resolve the target
|
// Register the alias name as a function and resolve the target
|
||||||
try self.addSymbol(ua.name, .function, null, node.span);
|
try self.addSymbol(ua.name, .function, null, node.span);
|
||||||
@@ -1205,6 +1232,7 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
|
|||||||
.param,
|
.param,
|
||||||
.match_arm,
|
.match_arm,
|
||||||
.undef_literal,
|
.undef_literal,
|
||||||
|
.inferred_type,
|
||||||
.builtin_expr,
|
.builtin_expr,
|
||||||
.foreign_expr,
|
.foreign_expr,
|
||||||
.library_decl,
|
.library_decl,
|
||||||
@@ -1228,6 +1256,21 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
|
|||||||
.ufcs_alias,
|
.ufcs_alias,
|
||||||
.closure_type_expr,
|
.closure_type_expr,
|
||||||
=> {},
|
=> {},
|
||||||
|
.protocol_decl => |pd| {
|
||||||
|
for (pd.methods) |method| {
|
||||||
|
if (method.default_body) |body| {
|
||||||
|
if (findNodeAtOffset(body, offset)) |found| return found;
|
||||||
|
}
|
||||||
|
for (method.params) |param| {
|
||||||
|
if (findNodeAtOffset(param, offset)) |found| return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.impl_block => |ib| {
|
||||||
|
for (ib.methods) |method_node| {
|
||||||
|
if (findNodeAtOffset(method_node, offset)) |found| return found;
|
||||||
|
}
|
||||||
|
},
|
||||||
.tuple_literal => |tl| {
|
.tuple_literal => |tl| {
|
||||||
for (tl.elements) |elem| {
|
for (tl.elements) |elem| {
|
||||||
if (findNodeAtOffset(elem.value, offset)) |found| return found;
|
if (findNodeAtOffset(elem.value, offset)) |found| return found;
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ pub const Tag = enum {
|
|||||||
kw_push, // push
|
kw_push, // push
|
||||||
kw_ufcs, // ufcs
|
kw_ufcs, // ufcs
|
||||||
kw_in, // in
|
kw_in, // in
|
||||||
|
kw_closure, // closure
|
||||||
|
kw_protocol, // protocol
|
||||||
|
kw_impl, // impl
|
||||||
|
kw_Self, // Self (in protocol declarations)
|
||||||
|
|
||||||
// Symbols
|
// Symbols
|
||||||
colon, // :
|
colon, // :
|
||||||
@@ -103,6 +107,7 @@ pub const Tag = enum {
|
|||||||
hash_source, // #source (inside #import c { ... })
|
hash_source, // #source (inside #import c { ... })
|
||||||
hash_define, // #define (inside #import c { ... })
|
hash_define, // #define (inside #import c { ... })
|
||||||
hash_flags, // #flags (inside #import c { ... })
|
hash_flags, // #flags (inside #import c { ... })
|
||||||
|
hash_inline, // #inline (protocol layout modifier)
|
||||||
triple_minus, // ---
|
triple_minus, // ---
|
||||||
|
|
||||||
// Special
|
// Special
|
||||||
@@ -169,7 +174,7 @@ pub const Tag = enum {
|
|||||||
|
|
||||||
pub fn isTypeKeyword(tag: Tag) bool {
|
pub fn isTypeKeyword(tag: Tag) bool {
|
||||||
return switch (tag) {
|
return switch (tag) {
|
||||||
.kw_f32, .kw_f64, .kw_Type => true,
|
.kw_f32, .kw_f64, .kw_Type, .kw_Self => true,
|
||||||
else => false,
|
else => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -215,6 +220,10 @@ pub const keywords = std.StaticStringMap(Tag).initComptime(.{
|
|||||||
.{ "push", .kw_push },
|
.{ "push", .kw_push },
|
||||||
.{ "ufcs", .kw_ufcs },
|
.{ "ufcs", .kw_ufcs },
|
||||||
.{ "in", .kw_in },
|
.{ "in", .kw_in },
|
||||||
|
.{ "closure", .kw_closure },
|
||||||
|
.{ "protocol", .kw_protocol },
|
||||||
|
.{ "impl", .kw_impl },
|
||||||
|
.{ "Self", .kw_Self },
|
||||||
});
|
});
|
||||||
|
|
||||||
pub fn getKeyword(bytes: []const u8) ?Tag {
|
pub fn getKeyword(bytes: []const u8) ?Tag {
|
||||||
|
|||||||
@@ -200,6 +200,36 @@ pub const Type = union(enum) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the canonical type name for this type, or null for complex types.
|
||||||
|
/// Used for looking up impl methods on non-struct types (e.g., s32.eq).
|
||||||
|
pub fn toName(self: Type) ?[]const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.signed => |w| switch (w) {
|
||||||
|
8 => "s8",
|
||||||
|
16 => "s16",
|
||||||
|
32 => "s32",
|
||||||
|
64 => "s64",
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
.unsigned => |w| switch (w) {
|
||||||
|
8 => "u8",
|
||||||
|
16 => "u16",
|
||||||
|
32 => "u32",
|
||||||
|
64 => "u64",
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
.f32 => "f32",
|
||||||
|
.f64 => "f64",
|
||||||
|
.boolean => "bool",
|
||||||
|
.string_type => "string",
|
||||||
|
.void_type => "void",
|
||||||
|
.struct_type => |n| n,
|
||||||
|
.enum_type => |n| n,
|
||||||
|
.union_type => |n| n,
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fromTypeExpr(node: *Node) ?Type {
|
pub fn fromTypeExpr(node: *Node) ?Type {
|
||||||
if (node.data != .type_expr) return null;
|
if (node.data != .type_expr) return null;
|
||||||
return fromName(node.data.type_expr.name);
|
return fromName(node.data.type_expr.name);
|
||||||
|
|||||||
@@ -462,4 +462,102 @@ opt-closure: none
|
|||||||
opt-closure: 15
|
opt-closure: 15
|
||||||
opt-closure-btn: 1 99
|
opt-closure-btn: 1 99
|
||||||
opt-closure-btn: null
|
opt-closure-btn: null
|
||||||
|
closure-ptr: 3
|
||||||
|
closure-enum: 2
|
||||||
|
closure-rstr: [INFO] ok
|
||||||
|
closure-rstruct: 11 22
|
||||||
|
closure-linear: 37
|
||||||
|
closure-clamp: 0 100 255
|
||||||
|
closure-compose2: 12
|
||||||
|
closure-chain: 22
|
||||||
|
closure-map: 3 6 9 12 15
|
||||||
|
closure-filter: 3 [3 4 5]
|
||||||
|
closure-sort: 5 4 3 2 1
|
||||||
|
closure-fe: item 0=10
|
||||||
|
closure-fe: item 1=20
|
||||||
|
closure-fe: item 2=30
|
||||||
|
closure-find: 2
|
||||||
|
closure-any: false true
|
||||||
|
closure-struct-field: -5
|
||||||
|
closure-btn: 1 99
|
||||||
|
closure-counter: 1 2 3
|
||||||
|
closure-acc: 105 115
|
||||||
|
closure-loop: 115
|
||||||
|
closure-reassign: 11
|
||||||
|
closure-reassign: 20
|
||||||
|
closure-snapstruct: 15
|
||||||
|
closure-cap-promoted: 11
|
||||||
|
closure-iife: 15
|
||||||
|
closure-toggle: none
|
||||||
|
closure-toggle: true
|
||||||
|
closure-panel: main 800x600
|
||||||
|
closure-chain-call: true
|
||||||
|
closure-loop-0: 1
|
||||||
|
closure-loop-1: 11
|
||||||
|
closure-loop-4: 41
|
||||||
|
closure-cond: 10
|
||||||
|
closure-form: submitted
|
||||||
|
closure-form: no cancel
|
||||||
|
closure-null-env: true
|
||||||
|
closure-slice: 10 20 30
|
||||||
|
closure-arena: 15
|
||||||
|
closure-gpa: 17 allocs=0
|
||||||
|
closure-opt: 42
|
||||||
|
closure-ropt: 50
|
||||||
|
closure-ropt: none
|
||||||
|
closure-mixed: 10
|
||||||
|
closure-mixed: 15
|
||||||
|
closure-mixed: 25
|
||||||
|
closure-factory-indep: 20 30 40
|
||||||
|
closure-deep-chain: 122
|
||||||
|
closure-8cap: 36
|
||||||
|
closure-4param: 10
|
||||||
|
closure-shared-ptr: 7
|
||||||
|
closure-f64: true
|
||||||
|
closure-zerocap: 49 true
|
||||||
|
closure-struct-method: 7
|
||||||
|
closure-multi-factory: 10
|
||||||
|
closure-multi-factory: 20
|
||||||
|
closure-multi-factory: 30
|
||||||
|
closure-bool-cap: true false
|
||||||
|
closure-as-arg: 142
|
||||||
|
closure-strfmt: hello world
|
||||||
|
closure-ptr-before: 10
|
||||||
|
closure-ptr-after: 42
|
||||||
|
closure-neg: -70
|
||||||
|
closure-proto-cap: true
|
||||||
|
closure-chain-factory: 37
|
||||||
|
closure-while-cond: 3
|
||||||
|
closure-infer: 7
|
||||||
|
closure-infer-arg: 15
|
||||||
|
closure-infer-block: 12
|
||||||
|
closure-infer-cap: 105
|
||||||
|
closure-infer-factory: 35
|
||||||
|
closure-infer-compose: 11
|
||||||
|
closure-infer-void: 42
|
||||||
|
=== Protocols ===
|
||||||
|
P1.1: 3
|
||||||
|
P1.2: 30
|
||||||
|
P2.1: 42
|
||||||
|
P2.2: 150
|
||||||
|
P2.3: 5 10
|
||||||
|
P3.1: 5
|
||||||
|
P3.2: 12
|
||||||
|
hi hi
|
||||||
|
P4.1: 2
|
||||||
|
yo yo
|
||||||
|
P4.2: 2
|
||||||
|
P4.3: 6 2
|
||||||
|
P5.1: true false
|
||||||
|
P5.2: 10 20
|
||||||
|
P5.5: true false
|
||||||
|
P5.3: true false
|
||||||
|
P6.1: true false
|
||||||
|
P6.2: true false
|
||||||
|
P6.3: true false
|
||||||
|
P6.4: 40
|
||||||
|
P6.5: 20
|
||||||
|
P7.1: 30
|
||||||
|
P7.2: 10 300
|
||||||
|
P2.6: 5 10
|
||||||
=== DONE ===
|
=== DONE ===
|
||||||
|
|||||||
Reference in New Issue
Block a user