comptime format
This commit is contained in:
92
specs.md
92
specs.md
@@ -45,7 +45,7 @@ GLSL;
|
||||
```
|
||||
|
||||
### Keywords
|
||||
`if`, `else`, `then`, `while`, `break`, `continue`, `true`, `false`, `enum`, `struct`, `union`, `case`, `return`, `defer`, `xx`, `and`, `or`
|
||||
`if`, `else`, `then`, `while`, `for`, `break`, `continue`, `true`, `false`, `enum`, `struct`, `union`, `case`, `return`, `defer`, `push`, `xx`, `and`, `or`
|
||||
|
||||
> Note: `enum` is used for both payload-less and payload-bearing sum types (tagged unions). `union` is reserved for C-style untagged unions (memory overlays).
|
||||
|
||||
@@ -241,6 +241,26 @@ v1.x // read field x of struct v1
|
||||
v1.x = 3.0; // assign to field x of struct v1
|
||||
```
|
||||
|
||||
#### `#using` — Struct Composition
|
||||
`#using StructName;` inside a struct declaration embeds all fields from `StructName` at that position. The embedded fields are accessed directly, as if declared inline.
|
||||
|
||||
```sx
|
||||
UBase :: struct { x: s32; y: s32; }
|
||||
UExt :: struct { #using UBase; z: s32; }
|
||||
e := UExt.{ x = 1, y = 2, z = 3 };
|
||||
print("{}\n", e.x); // 1
|
||||
```
|
||||
|
||||
`#using` may appear at any field position (beginning, middle, end) and multiple `#using` entries are allowed:
|
||||
```sx
|
||||
UPos :: struct { px: s32; py: s32; }
|
||||
UCol :: struct { r: s32; g: s32; }
|
||||
USprite :: struct { #using UPos; #using UCol; scale: s32; }
|
||||
s := USprite.{ px = 10, py = 20, r = 255, g = 128, scale = 1 };
|
||||
```
|
||||
|
||||
The referenced struct must be declared before use. This is purely a compile-time field expansion — no runtime overhead.
|
||||
|
||||
#### Struct Interpolation
|
||||
Struct values in string interpolation print as `TypeName{field:value, ...}`:
|
||||
```sx
|
||||
@@ -849,6 +869,22 @@ compute(6)
|
||||
print("hello")
|
||||
```
|
||||
|
||||
### UFCS (Uniform Function Call Syntax)
|
||||
```sx
|
||||
object.func(args) // equivalent to func(object, args)
|
||||
```
|
||||
When `object.func(args)` is encountered and `func` is not a field of `object`'s type, the compiler rewrites the call to `func(object, args)`. This enables method-like syntax without dedicated method declarations.
|
||||
|
||||
```sx
|
||||
Point :: struct { x: s32; y: s32; }
|
||||
point_sum :: (p: Point) -> s32 { p.x + p.y; }
|
||||
|
||||
p := Point.{3, 4};
|
||||
print("{}\n", p.point_sum()); // calls point_sum(p) → 7
|
||||
```
|
||||
|
||||
UFCS works with pointer receivers (auto-deref applies) and generic functions. If the field name exists as both a struct field and a free function, the struct field takes priority.
|
||||
|
||||
### Field Access
|
||||
```sx
|
||||
object.field
|
||||
@@ -875,6 +911,31 @@ Statements are terminated by `;`.
|
||||
- **Break**: `break;` — exits a match arm or while loop
|
||||
- **Continue**: `continue;` — skips to the next iteration of a while loop
|
||||
- **Defer**: `defer expr;` — defers execution of `expr` until the enclosing block exits (LIFO order)
|
||||
- **Push**: `push expr { body }` — scoped context override (see below)
|
||||
|
||||
### `push` Statement and Implicit `context`
|
||||
|
||||
The `push` statement temporarily overrides a global `context` variable for the duration of a block. The previous context is saved before the block and restored after it exits.
|
||||
|
||||
```sx
|
||||
push Context.{ arena = @arena, data = xx @logger } {
|
||||
handle(client); // inside here, `context` has the new value
|
||||
}
|
||||
// context is restored to its previous value here
|
||||
```
|
||||
|
||||
**`Context` struct** — defined in `std.sx`:
|
||||
```sx
|
||||
Context :: struct {
|
||||
arena: *Arena; // pointer to active arena allocator (or null)
|
||||
data: *void; // opaque pointer for application-specific data
|
||||
}
|
||||
context : Context = ---; // global mutable variable
|
||||
```
|
||||
|
||||
Inside the pushed block, any code (including called functions) can read `context.arena` and `context.data`. The standard library's `cstring()` function checks `context.arena` and uses it for allocation when available, falling back to `malloc()` otherwise.
|
||||
|
||||
`push` requires a global mutable variable named `context` to be in scope (provided by `std.sx`).
|
||||
|
||||
---
|
||||
|
||||
@@ -994,7 +1055,19 @@ main :: () {
|
||||
}
|
||||
```
|
||||
|
||||
The inserted string must contain valid `sx` statements (including semicolons). The statements are parsed and compiled in the same scope as the `#insert` site.
|
||||
The inserted string must contain valid `sx` statements (including semicolons). The statements are parsed and compiled in the same scope as the `#insert` site. Variables created by one `#insert` are visible to subsequent `#insert` directives in the same function.
|
||||
|
||||
### Comptime Call Evaluation
|
||||
|
||||
When a `::` constant binding is initialized with a function call and all arguments are comptime-known (literals or other `::` constants), the compiler attempts to evaluate the entire call at compile time using the bytecode VM. If evaluation succeeds, the result is baked into the binary as a static constant with zero runtime overhead.
|
||||
|
||||
```sx
|
||||
body :: "<html><body><h1>Hello</h1></body></html>";
|
||||
response :: format("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}", body.len, body);
|
||||
// response is a static string constant — no runtime allocation
|
||||
```
|
||||
|
||||
This works for any function, not just `format`. The mechanism is general: the VM compiles the function body (including `#insert` directives, variadic `..Any` args, and calls to other functions) and executes it entirely at compile time. If the VM encounters something it cannot evaluate (e.g., foreign function calls, unsupported operations), it silently falls through to runtime codegen.
|
||||
|
||||
---
|
||||
|
||||
@@ -1082,18 +1155,20 @@ var_decl = IDENT ':=' expr ';'
|
||||
fn_decl = IDENT '::' '(' params? ')' ('->' type)? block
|
||||
| IDENT '::' block
|
||||
enum_decl = IDENT '::' 'enum' '{' (IDENT ';')* '}'
|
||||
struct_decl = IDENT '::' 'struct' '{' field_group* '}'
|
||||
struct_decl = IDENT '::' 'struct' '{' struct_member* '}'
|
||||
struct_member = field_group | '#using' IDENT ';'
|
||||
field_group = IDENT (',' IDENT)* ':' type ('=' expr)? ';'
|
||||
params = param (',' param)*
|
||||
param = IDENT ':' type
|
||||
block = '{' stmt* '}'
|
||||
stmt = decl | assignment ';' | multi_assign ';' | return_stmt | defer_stmt | insert_stmt
|
||||
| break_stmt | continue_stmt | expr ';'
|
||||
| push_stmt | break_stmt | continue_stmt | expr ';'
|
||||
return_stmt = 'return' expr? ';'
|
||||
break_stmt = 'break' ';'
|
||||
continue_stmt = 'continue' ';'
|
||||
defer_stmt = 'defer' expr ';'
|
||||
insert_stmt = '#insert' expr ';'
|
||||
push_stmt = 'push' expr block
|
||||
assignment = lvalue ('=' | '+=' | '-=' | '*=' | '/=') expr
|
||||
multi_assign = lvalue (',' lvalue)+ '=' expr (',' expr)+
|
||||
lvalue = IDENT | postfix '.' IDENT
|
||||
@@ -1125,15 +1200,6 @@ type = '$' IDENT | 's32' | 'f32' | 'f64' | 'bool' | 'string'
|
||||
|
||||
## 12. Open Questions
|
||||
|
||||
These are inferred gaps — things not shown in the readme that need decisions:
|
||||
|
||||
- **`return`**: Both `return expr;` and implicit return (last expression) are supported.
|
||||
- **Else in match**: Is there a default/else arm in pattern matching?
|
||||
- **Nested functions**: Can functions be defined inside other functions?
|
||||
- **Mutability of params**: Are function parameters immutable by default?
|
||||
- **Array/list types**: Not shown — deferred.
|
||||
- **Struct types**: Implemented — named struct types with positional/named/shorthand literals.
|
||||
- **Imports/modules**: `#import` directive supports flat and namespaced imports (see Section 8).
|
||||
- **Operator overloading**: Not shown — presumably no.
|
||||
- **Semicolons**: Required on all statements? What about the last expression in a block?
|
||||
- **Top-level expressions**: Are bare expressions allowed at the top level or only declarations?
|
||||
|
||||
Reference in New Issue
Block a user