tuples
This commit is contained in:
113
specs.md
113
specs.md
@@ -45,7 +45,7 @@ GLSL;
|
||||
```
|
||||
|
||||
### Keywords
|
||||
`if`, `else`, `then`, `while`, `for`, `break`, `continue`, `true`, `false`, `enum`, `struct`, `union`, `case`, `return`, `defer`, `push`, `xx`, `and`, `or`
|
||||
`if`, `else`, `then`, `while`, `for`, `break`, `continue`, `true`, `false`, `enum`, `struct`, `union`, `case`, `return`, `defer`, `push`, `ufcs`, `in`, `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).
|
||||
|
||||
@@ -67,6 +67,7 @@ GLSL;
|
||||
| `\|` | bitwise OR |
|
||||
| `and` | logical AND (short-circuit) |
|
||||
| `or` | logical OR (short-circuit) |
|
||||
| `in` | membership test (tuples) |
|
||||
| `+=` | add-assign |
|
||||
| `-=` | sub-assign |
|
||||
| `*=` | mul-assign |
|
||||
@@ -267,6 +268,83 @@ 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}
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
#### Construction
|
||||
```sx
|
||||
pair := (40, 2); // positional tuple: (s64, s64)
|
||||
named := (x: 10, y: 20); // named tuple: (x: s64, y: s64)
|
||||
single := (42,); // 1-tuple (trailing comma in value position)
|
||||
zeroed : (s32, s32) = ---; // zero-initialized tuple
|
||||
```
|
||||
|
||||
Note: In value position, `(expr)` without a comma is a grouping expression, not a tuple. Use `(expr,)` for a 1-tuple value.
|
||||
|
||||
#### Type Syntax
|
||||
In type position, `(T)` is always a tuple type — no trailing comma needed. The `->` arrow disambiguates function types from tuple types:
|
||||
```sx
|
||||
(s64) // tuple type with one field
|
||||
(s64, s64) // tuple type with two fields
|
||||
(s64) -> s64 // function type: takes s64, returns s64
|
||||
(s64, s64) -> s64 // function type: takes two s64, returns s64
|
||||
```
|
||||
|
||||
#### Field Access
|
||||
```sx
|
||||
pair.0; // 40 — numeric index
|
||||
pair.1; // 2
|
||||
named.x; // 10 — named field
|
||||
named.0; // 10 — numeric index also works on named tuples
|
||||
```
|
||||
|
||||
#### As Return Type
|
||||
```sx
|
||||
swap :: (a: s64, b: s64) -> (s64, s64) { (b, a); }
|
||||
wrap :: (x: s64) -> (s64) { (x,); }
|
||||
|
||||
s := swap(1, 2); // s.0 = 2, s.1 = 1
|
||||
t := wrap(42); // t.0 = 42
|
||||
```
|
||||
|
||||
#### Representation
|
||||
Tuples are represented as anonymous LLVM struct types (same layout as named structs). A tuple `(s64, s64)` has LLVM type `{ i64, i64 }`.
|
||||
|
||||
#### Tuple Operators
|
||||
|
||||
**Equality and inequality** — element-wise comparison, both sides must have the same field count:
|
||||
```sx
|
||||
(1, 2) == (1, 2) // true
|
||||
(1, 2) != (1, 3) // true
|
||||
```
|
||||
|
||||
**Concatenation** (`+`) — creates a new tuple with fields from both sides:
|
||||
```sx
|
||||
c := (1, 2) + (3, 4); // c : (s64, s64, s64, s64)
|
||||
c.0; // 1
|
||||
c.3; // 4
|
||||
```
|
||||
|
||||
**Repetition** (`*`) — repeats a tuple N times (N must be a compile-time integer literal):
|
||||
```sx
|
||||
r := (1, 2) * 3; // r : (s64, s64, s64, s64, s64, s64)
|
||||
r.0; // 1
|
||||
r.5; // 2
|
||||
```
|
||||
|
||||
**Lexicographic comparison** (`<`, `<=`, `>`, `>=`) — compares element-by-element left to right:
|
||||
```sx
|
||||
(1, 2) < (1, 3) // true (first fields equal, 2 < 3)
|
||||
(2, 0) > (1, 9) // true (2 > 1, rest ignored)
|
||||
(1, 2) <= (1, 2) // true (all equal, <= allows tie)
|
||||
```
|
||||
|
||||
**Membership** (`in`) — checks if a value exists in a tuple:
|
||||
```sx
|
||||
3 in (1, 2, 3) // true
|
||||
5 in (1, 2, 3) // false
|
||||
```
|
||||
|
||||
### Array Types
|
||||
Fixed-size arrays with element type and length.
|
||||
```sx
|
||||
@@ -885,6 +963,39 @@ 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.
|
||||
|
||||
#### UFCS Aliases
|
||||
The `ufcs` keyword creates a name alias for a function, decoupling the method name from the function name:
|
||||
```sx
|
||||
arena_alloc :: (arena: *Arena, size: s64) -> *void { ... }
|
||||
alloc :: ufcs arena_alloc;
|
||||
|
||||
myArena.alloc(42); // calls arena_alloc(myArena, 42)
|
||||
alloc(myArena, 42); // also works as a direct call
|
||||
```
|
||||
|
||||
This avoids the naming redundancy of `myArena.arena_alloc(42)`.
|
||||
|
||||
#### Tuple UFCS Splatting
|
||||
When a tuple is used as the receiver of a UFCS call, its elements are unpacked as leading arguments:
|
||||
```sx
|
||||
num_add :: (a: s64, b: s64) -> s64 { a + b; }
|
||||
add :: ufcs num_add;
|
||||
|
||||
(40, 2).add(); // splats to num_add(40, 2) → 42
|
||||
(40,).add(2); // partial: num_add(40, 2) → 42
|
||||
40.add(2); // normal UFCS: num_add(40, 2) → 42
|
||||
```
|
||||
|
||||
With more arguments:
|
||||
```sx
|
||||
compute :: (a: s64, b: s64, c: s64, d: s64) -> s64 { a + b * c - d; }
|
||||
calc :: ufcs compute;
|
||||
|
||||
(1, 2, 3, 4).calc(); // full splat → compute(1, 2, 3, 4)
|
||||
(1, 2).calc(3, 4); // partial splat → compute(1, 2, 3, 4)
|
||||
1.calc(2, 3, 4); // normal UFCS → compute(1, 2, 3, 4)
|
||||
```
|
||||
|
||||
### Field Access
|
||||
```sx
|
||||
object.field
|
||||
|
||||
Reference in New Issue
Block a user