lang: rename signed integer types sN -> iN
Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.
Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).
Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.
zig build test: 426/426; examples suite: 595/595.
This commit is contained in:
104
readme.md
104
readme.md
@@ -10,7 +10,7 @@ An experimental systems programming language with Jai-inspired syntax, compile-t
|
||||
#import "modules/std.sx";
|
||||
|
||||
Point :: struct {
|
||||
x, y: s32;
|
||||
x, y: i32;
|
||||
magnitude :: (self: *Point) -> f32 { sqrt(self.x * self.x + self.y * self.y); }
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ Options:
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `s8`..`s64`, `u8`..`u64` | Signed/unsigned integers (default: `s64`) |
|
||||
| `i8`..`i64`, `u8`..`u64` | Signed/unsigned integers (default: `i64`) |
|
||||
| `f32`, `f64` | Floating point (default: `f32`) |
|
||||
| `bool` | `true` / `false` |
|
||||
| `string` | UTF-8 fat pointer `{ptr, len}` |
|
||||
@@ -87,15 +87,15 @@ Options:
|
||||
| `Closure(args) -> ret` | Closure type |
|
||||
|
||||
**Numeric limits.** A field-like access on a builtin integer type name folds to
|
||||
a compile-time constant of that type: `s64.max` → `9223372036854775807`,
|
||||
`u8.min` → `0`, `s3.max` → `3`. It works for every width `s1`..`s64` / `u1`..`u64`
|
||||
a compile-time constant of that type: `i64.max` → `9223372036854775807`,
|
||||
`u8.min` → `0`, `i3.max` → `3`. It works for every width `i1`..`i64` / `u1`..`u64`
|
||||
plus `usize`/`isize`, and is usable anywhere a constant of that type is — including
|
||||
array dimensions (`[u8.max]T` is a 255-element array). The float types `f32`/`f64`
|
||||
expose `.min` / `.max` too (with `.min` = most-negative finite = `-max`, **not**
|
||||
C's `DBL_MIN`) plus the float-only `.epsilon` (ULP of 1.0, not C#'s denormal
|
||||
`Epsilon`), `.min_positive` (smallest normal = C `DBL_MIN`), `.true_min` (smallest
|
||||
subnormal — beware flush-to-zero CPU modes), `.inf`, and `.nan`. A float-only
|
||||
accessor on an integer (`s32.epsilon`), or any accessor on a non-numeric type, is
|
||||
accessor on an integer (`i32.epsilon`), or any accessor on a non-numeric type, is
|
||||
a clean compile error. The fold applies only to a bare type-name receiver: a raw
|
||||
identifier that binds a value shadowing a type name (`` `f64 := … `` then
|
||||
`` `f64.epsilon ``) reads the value's field, not the limit — for a local, global,
|
||||
@@ -109,12 +109,12 @@ even when it flows into an integer binding or an array dimension, so it truncate
|
||||
```sx
|
||||
// Constants (compile-time when possible)
|
||||
PI :: 3.14159;
|
||||
MAX : s32 : 100;
|
||||
MAX : i32 : 100;
|
||||
|
||||
// Variables (mutable)
|
||||
x := 42; // inferred type
|
||||
y : s32 = 0; // explicit type
|
||||
z : s32 = ---; // uninitialized
|
||||
y : i32 = 0; // explicit type
|
||||
z : i32 = ---; // uninitialized
|
||||
```
|
||||
|
||||
A typed constant's initializer must be compatible with its annotation — an
|
||||
@@ -123,7 +123,7 @@ integer fits any integer or float, a float a float type, a string `string`,
|
||||
constant expression alike: both `N : string : 4` and `N : string : M + 2` are a
|
||||
compile-time `type mismatch` error, not a silently-accepted constant. Mixed
|
||||
int+float arithmetic promotes to the float in either operand order (`n + 0.5` and
|
||||
`0.5 + n` are both `f64`), so `C : s64 : M + 0.5` is rejected regardless of order
|
||||
`0.5 + n` are both `f64`), so `C : i64 : M + 0.5` is rejected regardless of order
|
||||
while `F : f64 : M + 0.5` folds to `2.5`.
|
||||
|
||||
**Aggregate constants.** Array- and struct-typed `::` constants are immutable
|
||||
@@ -132,8 +132,8 @@ value, and unused tables are dropped from the binary. `::` is the one and only
|
||||
const spelling (`const` is not a keyword):
|
||||
|
||||
```sx
|
||||
K : [4]s64 : .[11, 22, 33, 44]; // typed array const
|
||||
A :: .[1, 2, 3]; // untyped — infers [3]s64
|
||||
K : [4]i64 : .[11, 22, 33, 44]; // typed array const
|
||||
A :: .[1, 2, 3]; // untyped — infers [3]i64
|
||||
M :: .[1, 2.2, 3]; // numeric mix promotes — [3]f64
|
||||
LIT :: Color.{ r = 255, g = 0, b = 0 }; // struct const — also one global
|
||||
|
||||
@@ -152,7 +152,7 @@ integer-typed binding *without* a cast follows the same integral-fold rule an
|
||||
array dimension uses: an **integral** compile-time float folds to its integer, a
|
||||
**non-integral** one is a compile error. It holds whether the value is a literal
|
||||
or *any* compile-time-constant float expression — including one that references a
|
||||
float-typed const (`F : f64 : 2.5; y : s64 = F + 1.5` → `4`), a builtin float
|
||||
float-typed const (`F : f64 : 2.5; y : i64 = F + 1.5` → `4`), a builtin float
|
||||
numeric-limit accessor (`f64.max - f64.max` → `0`, while `f64.true_min + 0.5`
|
||||
errors), a float `%` (`6.0 % 4.0` → `2`, while `5.5 % 2.0` = `1.5` errors), or a
|
||||
float `/` (`6.0 / 2.0` → `3`, while `5.0 / 2.0` = `2.5` errors — a float `/` is
|
||||
@@ -161,30 +161,30 @@ the compile-time float evaluator recognises every leaf shape the integer one doe
|
||||
no constant float form escapes the rule at one site while folding at another — and
|
||||
is uniform
|
||||
across a typed local, a parameter default, a struct field default, a call
|
||||
argument, a typed constant, **and an array dimension / count** — `y : s64 = 4.0`,
|
||||
`K : s64 : 4.0`, `y : s64 = M + 2.0`, and `[F + 1.5]s64` (≡ `[4]s64`, whether
|
||||
argument, a typed constant, **and an array dimension / count** — `y : i64 = 4.0`,
|
||||
`K : i64 : 4.0`, `y : i64 = M + 2.0`, and `[F + 1.5]i64` (≡ `[4]i64`, whether
|
||||
written directly, through a const, or via a type alias) all give `4`, while
|
||||
`y : s64 = 1.5`, `N : s64 : 1.5`, `y : s64 = M + 0.5`, `y : s64 = F + 0.25`
|
||||
(= `2.75`), and `[F + 0.25]s64` all error (one wording at the binding sites:
|
||||
`y : i64 = 1.5`, `N : i64 : 1.5`, `y : i64 = M + 0.5`, `y : i64 = F + 0.25`
|
||||
(= `2.75`), and `[F + 0.25]i64` all error (one wording at the binding sites:
|
||||
`cannot implicitly narrow non-integral float …`; a dimension instead reports
|
||||
`array dimension must be an integer, but '…' is a non-integral float`, since the
|
||||
cast escape does not apply in a count position). An explicit `xx` / `cast(s64)`
|
||||
is the escape hatch and always truncates (`y : s64 = xx 1.5` → `1`,
|
||||
`y : s64 = xx (M + 0.5)` → `2`); a genuine runtime float is likewise unaffected.
|
||||
cast escape does not apply in a count position). An explicit `xx` / `cast(i64)`
|
||||
is the escape hatch and always truncates (`y : i64 = xx 1.5` → `1`,
|
||||
`y : i64 = xx (M + 0.5)` → `2`); a genuine runtime float is likewise unaffected.
|
||||
|
||||
Builtin type names (`s2`, `u8`, `bool`, `string`, …) are reserved and a *bare*
|
||||
Builtin type names (`i2`, `u8`, `bool`, `string`, …) are reserved and a *bare*
|
||||
spelling can't be used as an identifier at a **value-binding or declaration-name**
|
||||
site — a value binding (`:=` / typed local / parameter), a `::` constant or
|
||||
function declaration, an `impl` method definition, or a `::` type declaration
|
||||
(`struct` / `enum` / `union` / alias / `protocol` / …) — each is an error
|
||||
(`s2 :: 5` and `s2 :: (n) { … }` are rejected just like `s2 := 5`). **Member-name
|
||||
(`i2 :: 5` and `i2 :: (n) { … }` are rejected just like `i2 := 5`). **Member-name
|
||||
positions are exempt**: a struct *field*, a union *tag*, and a protocol
|
||||
*method-signature* may be a bare reserved spelling (`struct { s2: s64 }`,
|
||||
`union { u8: … }`, `protocol { s2 :: () -> s64 }`) — they are reached via `obj.name`,
|
||||
*method-signature* may be a bare reserved spelling (`struct { i2: i64 }`,
|
||||
`union { u8: … }`, `protocol { i2 :: () -> i64 }`) — they are reached via `obj.name`,
|
||||
so they never mis-lower. The bare exemption covers only the identifier-classified
|
||||
reserved names (`s1`..`s64`, `u1`..`u64`, `bool`, `string`, `void`, `usize`,
|
||||
reserved names (`i1`..`i64`, `u1`..`u64`, `bool`, `string`, `void`, `usize`,
|
||||
`isize`, `Any`); `f32` and `f64` are lexer keywords, so even in a member slot they
|
||||
need the backtick (`` struct { `f32: s64 } ``). A leading backtick escapes one into
|
||||
need the backtick (`` struct { `f32: i64 } ``). A leading backtick escapes one into
|
||||
a **raw identifier**:
|
||||
`` `name `` is the literal identifier `name` (the backtick drops out of the text),
|
||||
usable in **every** position — value, declaration, and type, and optional in the
|
||||
@@ -192,24 +192,24 @@ exempt member positions. It is the only way handwritten sx can spell a reserved
|
||||
name in a binding or declaration site.
|
||||
|
||||
```sx
|
||||
`s2 := 2.5; // identifier "s2", distinct from the s2 type
|
||||
print("{}\n", `s2); // 2.5 (or bare `s2` in value position)
|
||||
`i2 := 2.5; // identifier "i2", distinct from the i2 type
|
||||
print("{}\n", `i2); // 2.5 (or bare `i2` in value position)
|
||||
|
||||
`s2 :: struct { x: s64; } // declare a type named with a reserved spelling
|
||||
v : `s2 = ---; // and reference it as a type — resolves to the struct
|
||||
x : s2 = 3; // bare `s2` in type position is still the int type
|
||||
`i2 :: struct { x: i64; } // declare a type named with a reserved spelling
|
||||
v : `i2 = ---; // and reference it as a type — resolves to the struct
|
||||
x : i2 = 3; // bare `i2` in type position is still the int type
|
||||
```
|
||||
|
||||
It works in every identifier position — local, global, parameter, struct field,
|
||||
union tag, function name, type/alias/import name, a top-level or struct-body
|
||||
constant, and the control-flow / capture / binding forms (destructure, `if`/`while`
|
||||
binding, `for` capture, match capture, `catch`/`onfail` tag) — and a reserved-spelled
|
||||
function is bare-callable (`s2(10)`). A backtick name used as a type resolves to a
|
||||
`` `name ``-declared type — including a parameterized template (`` `s2(s64) ``) and
|
||||
function is bare-callable (`i2(10)`). A backtick name used as a type resolves to a
|
||||
`` `name ``-declared type — including a parameterized template (`` `i2(i64) ``) and
|
||||
under pointer/optional wrappers — else a normal `unknown type` error.
|
||||
|
||||
Foreign declarations from `#import c { … }` are exempt automatically: C names that
|
||||
collide with reserved type names (e.g. `s1`, `s2`) import unedited, and a foreign
|
||||
collide with reserved type names (e.g. `i1`, `i2`) import unedited, and a foreign
|
||||
reserved-name function is bare-callable by its C name.
|
||||
|
||||
### Structs
|
||||
@@ -257,8 +257,8 @@ rw := Perms.read | Perms.write;
|
||||
### Optionals
|
||||
|
||||
```sx
|
||||
x: ?s32 = 42;
|
||||
y: ?s32 = null;
|
||||
x: ?i32 = 42;
|
||||
y: ?i32 = null;
|
||||
|
||||
val := x ?? 0; // null coalescing
|
||||
forced := x!; // force unwrap (traps on null)
|
||||
@@ -281,7 +281,7 @@ max :: (a: $T, b: T) -> T {
|
||||
|
||||
List :: struct ($T: Type) {
|
||||
items: [*]T;
|
||||
len: s64;
|
||||
len: i64;
|
||||
|
||||
append :: (self: *List(T), item: T) { ... }
|
||||
}
|
||||
@@ -295,8 +295,8 @@ are_equal :: ($T: Type/Eq, a: T, b: T) -> bool { a.eq(b); }
|
||||
### Closures
|
||||
|
||||
```sx
|
||||
make_adder :: (n: s64) -> Closure(s64) -> s64 {
|
||||
closure((x: s64) -> s64 => x + n);
|
||||
make_adder :: (n: i64) -> Closure(i64) -> i64 {
|
||||
closure((x: i64) -> i64 => x + n);
|
||||
}
|
||||
|
||||
add5 := make_adder(5);
|
||||
@@ -309,11 +309,11 @@ Closures capture by value. Bare functions auto-promote to closures when needed.
|
||||
|
||||
```sx
|
||||
Drawable :: protocol {
|
||||
draw :: (x: s32, y: s32);
|
||||
draw :: (x: i32, y: i32);
|
||||
}
|
||||
|
||||
impl Drawable for Circle {
|
||||
draw :: (self: *Circle, x: s32, y: s32) { ... }
|
||||
draw :: (self: *Circle, x: i32, y: i32) { ... }
|
||||
}
|
||||
|
||||
shape : Drawable = xx my_circle; // type erasure via xx
|
||||
@@ -323,7 +323,7 @@ shape.draw(10, 20); // dynamic dispatch
|
||||
`#inline` protocols store function pointers directly (no vtable indirection):
|
||||
```sx
|
||||
Allocator :: protocol #inline {
|
||||
alloc :: (size: s64) -> *void;
|
||||
alloc :: (size: i64) -> *void;
|
||||
dealloc :: (ptr: *void);
|
||||
}
|
||||
```
|
||||
@@ -400,8 +400,8 @@ FIBONACCI_10 :: #run fib(10);
|
||||
Foreign functions:
|
||||
```sx
|
||||
libc :: #library "c";
|
||||
printf :: (fmt: [:0]u8, args: ..Any) -> s32 #foreign libc;
|
||||
write_fd :: (fd: s32, buf: [*]u8, count: u64) -> s64 #foreign libc "write";
|
||||
printf :: (fmt: [:0]u8, args: ..Any) -> i32 #foreign libc;
|
||||
write_fd :: (fd: i32, buf: [*]u8, count: u64) -> i64 #foreign libc "write";
|
||||
```
|
||||
|
||||
Direct C header import:
|
||||
@@ -436,7 +436,7 @@ functions, constants, AND types alike**: a flat import of a flat import is NOT
|
||||
bare-visible (when `A` imports `B` and `B` imports `C`, `A` does not see `C`'s
|
||||
top-level names — including its types — so qualify them, or `#import "C"` directly
|
||||
if you reference them). This holds for a *parameterized* type head too: a generic
|
||||
struct / parameterized protocol / type-returning function used as `Box(s64)` is
|
||||
struct / parameterized protocol / type-returning function used as `Box(i64)` is
|
||||
gated exactly like a bare leaf type — the constructor head must be reachable over
|
||||
your own or a direct flat import, not two hops away. A bare reference to a
|
||||
namespaced-only import's member — function, module constant, or **type** (leaf or
|
||||
@@ -444,7 +444,7 @@ generic head) — is likewise not visible and is rejected (`type 'X' is not visi
|
||||
#import the module that declares it`); qualify it as `m.name`. The type gate holds
|
||||
wherever a bare type name is named — a value/field annotation, a reflection /
|
||||
type-arg slot (`size_of(T)`, `size_of(*T)`), a typed array-literal head (`T.[…]`),
|
||||
a parameterized head (`Box(s64)`), or a type-as-value / type-match arm — not just
|
||||
a parameterized head (`Box(i64)`), or a type-as-value / type-match arm — not just
|
||||
plain annotations. **Own-wins** holds at every one of those sites too, exactly like
|
||||
a bare call: when the querying module declares its OWN same-name type, that bare
|
||||
reference resolves to ITS author — never a same-name flat import. Ambiguity is
|
||||
@@ -455,8 +455,8 @@ multiple flat-imported modules; qualify the reference or remove the duplicate
|
||||
import`) — never a silent pick of one author. Qualifying the reference is a real
|
||||
escape hatch for a **generic head** too: `ns.Box(args)` selects the template
|
||||
AUTHORED by `ns`'s module, so two namespaces each declaring a same-name
|
||||
`Box($T)` with different layouts stay distinct types (`a.Box(s64)` and
|
||||
`b.Box(s64)` instantiate their own author's fields), never the global last-wins
|
||||
`Box($T)` with different layouts stay distinct types (`a.Box(i64)` and
|
||||
`b.Box(i64)` instantiate their own author's fields), never the global last-wins
|
||||
template. (A library's own *internal* type references still resolve: a generic
|
||||
struct / pack fn / protocol body is instantiated in the module that defines it, so
|
||||
e.g. `List(T).append`'s `alloc: Allocator` is visible there regardless of the call
|
||||
@@ -503,7 +503,7 @@ Box :: r.Box; // generic head re-export — same template
|
||||
|
||||
// consumer.sx
|
||||
#import "facade.sx";
|
||||
b := Box(s64).{ item = 3 }; // rich.sx's Box, via the facade
|
||||
b := Box(i64).{ item = 3 }; // rich.sx's Box, via the facade
|
||||
```
|
||||
|
||||
### Implicit Context
|
||||
@@ -513,7 +513,7 @@ Every program gets an implicit `context` with a default allocator:
|
||||
```sx
|
||||
// No boilerplate needed — context is auto-initialized
|
||||
main :: () {
|
||||
list := List(s64).create(); // uses context.allocator
|
||||
list := List(i64).create(); // uses context.allocator
|
||||
list.append(42);
|
||||
}
|
||||
|
||||
@@ -529,7 +529,7 @@ push Context.{ allocator = my_arena } {
|
||||
#import "modules/std.sx";
|
||||
|
||||
quick_sort :: (items: []$T) {
|
||||
partition :: (items: []T, lo: s64, hi: s64) -> s64 {
|
||||
partition :: (items: []T, lo: i64, hi: i64) -> i64 {
|
||||
pivot := items[hi];
|
||||
i := lo - 1;
|
||||
j := lo;
|
||||
@@ -545,7 +545,7 @@ quick_sort :: (items: []$T) {
|
||||
i;
|
||||
}
|
||||
|
||||
sort :: (items: []T, lo: s64, hi: s64) {
|
||||
sort :: (items: []T, lo: i64, hi: i64) {
|
||||
if lo < hi {
|
||||
pi := partition(items, lo, hi);
|
||||
sort(items, lo, pi - 1);
|
||||
@@ -557,7 +557,7 @@ quick_sort :: (items: []$T) {
|
||||
}
|
||||
|
||||
main :: () {
|
||||
arr : []s64 = .[333, 2, 3, 5, 2, 2, 3, 4, 5, 6, 6, 1];
|
||||
arr : []i64 = .[333, 2, 3, 5, 2, 2, 3, 4, 5, 6, 6, 1];
|
||||
quick_sort(arr);
|
||||
print("{}\n", arr);
|
||||
// [1, 2, 2, 2, 3, 3, 4, 5, 5, 6, 6, 333]
|
||||
|
||||
Reference in New Issue
Block a user