...
This commit is contained in:
361
readme.md
361
readme.md
@@ -1,14 +1,322 @@
|
||||
# sx
|
||||
|
||||
*** HIGHLY EXPERIMENTAL *** DON'T USE ***
|
||||
An experimental systems programming language with Jai-inspired syntax, compile-time execution, generics, closures, protocols, and an LLVM backend.
|
||||
|
||||
This experiment is trying to answer a few questions:
|
||||
> **Status**: Highly experimental. The language and compiler are under active development.
|
||||
|
||||
Q: Can we have an system language to build declarative ui ?
|
||||
## At a Glance
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
NOTE:
|
||||
> i hope you have memory... currently it doesn't free anything :D
|
||||
Point :: struct {
|
||||
x, y: s32;
|
||||
magnitude :: (self: *Point) -> f32 { sqrt(self.x * self.x + self.y * self.y); }
|
||||
}
|
||||
|
||||
main :: () {
|
||||
p := Point.{ x = 3, y = 4 };
|
||||
print("point: {}, magnitude: {}\n", p, p.magnitude());
|
||||
}
|
||||
```
|
||||
|
||||
**Key characteristics:**
|
||||
|
||||
- Jai-inspired declaration syntax: `name :: value` for constants, `name := value` for variables
|
||||
- Compiles to native code via LLVM 19
|
||||
- Compile-time execution with `#run`
|
||||
- Generics via monomorphization
|
||||
- First-class closures with value capture
|
||||
- Protocol-based polymorphism (traits)
|
||||
- Pattern matching on enums, optionals, and type categories
|
||||
- C interop via `#foreign` and `#import c`
|
||||
- Targets: macOS (ARM64, x86_64), Linux (x86_64, ARM64), Windows (x86_64), WebAssembly
|
||||
|
||||
## Building
|
||||
|
||||
Requires **Zig 0.16+** and **LLVM 19+**.
|
||||
|
||||
```sh
|
||||
zig build
|
||||
```
|
||||
|
||||
On macOS with Homebrew LLVM:
|
||||
```sh
|
||||
# default path: /opt/homebrew/opt/llvm@19
|
||||
zig build
|
||||
```
|
||||
|
||||
Custom LLVM path:
|
||||
```sh
|
||||
zig build -Dllvm-prefix=/path/to/llvm
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
sx run file.sx # compile and run
|
||||
sx build file.sx # compile to binary
|
||||
sx build file.sx -o out # compile with output path
|
||||
sx ir file.sx # emit LLVM IR
|
||||
sx lsp # start language server
|
||||
```
|
||||
|
||||
Options:
|
||||
```
|
||||
--target <triple> target platform (shortcuts: macos, linux, windows, wasm)
|
||||
--opt <level> optimization: none, less, default, aggressive
|
||||
--cpu <name> target CPU
|
||||
-o <path> output path
|
||||
```
|
||||
|
||||
## Language Overview
|
||||
|
||||
### Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `s8`..`s64`, `u8`..`u64` | Signed/unsigned integers (default: `s64`) |
|
||||
| `f32`, `f64` | Floating point (default: `f32`) |
|
||||
| `bool` | `true` / `false` |
|
||||
| `string` | UTF-8 fat pointer `{ptr, len}` |
|
||||
| `[N]T` | Fixed-size array |
|
||||
| `[]T` | Slice (fat pointer) |
|
||||
| `*T`, `[*]T` | Single / many pointer |
|
||||
| `?T` | Optional |
|
||||
| `struct`, `enum`, `union` | Composite types |
|
||||
| `Closure(args) -> ret` | Closure type |
|
||||
|
||||
### Declarations
|
||||
|
||||
```sx
|
||||
// Constants (compile-time when possible)
|
||||
PI :: 3.14159;
|
||||
MAX : s32 : 100;
|
||||
|
||||
// Variables (mutable)
|
||||
x := 42; // inferred type
|
||||
y : s32 = 0; // explicit type
|
||||
z : s32 = ---; // uninitialized
|
||||
```
|
||||
|
||||
### Structs
|
||||
|
||||
```sx
|
||||
Vec3 :: struct {
|
||||
x, y, z: f32;
|
||||
|
||||
length :: (self: *Vec3) -> f32 {
|
||||
sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
|
||||
}
|
||||
}
|
||||
|
||||
v := Vec3.{ x = 1, y = 2, z = 3 };
|
||||
v2 := Vec3.{ 1, 2, 3 }; // positional
|
||||
print("{}\n", v.length());
|
||||
```
|
||||
|
||||
Structs support field defaults, `#using` for composition, and methods defined in the body.
|
||||
|
||||
### Enums (Tagged Unions)
|
||||
|
||||
```sx
|
||||
Shape :: enum {
|
||||
circle: f32;
|
||||
rect: struct { w, h: f32; };
|
||||
none;
|
||||
}
|
||||
|
||||
area :: (s: Shape) -> f32 {
|
||||
if s == {
|
||||
case .circle: (r) => 3.14159 * r * r;
|
||||
case .rect: (r) => r.w * r.h;
|
||||
case .none: 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Flag enums with power-of-2 values:
|
||||
```sx
|
||||
Perms :: enum flags { read; write; execute; }
|
||||
rw := Perms.read | Perms.write;
|
||||
```
|
||||
|
||||
### Optionals
|
||||
|
||||
```sx
|
||||
x: ?s32 = 42;
|
||||
y: ?s32 = null;
|
||||
|
||||
val := x ?? 0; // null coalescing
|
||||
forced := x!; // force unwrap (traps on null)
|
||||
|
||||
if v := x { // safe unwrap
|
||||
print("{}\n", v);
|
||||
}
|
||||
|
||||
// Optional chaining
|
||||
node: ?Node = get_node();
|
||||
name := node?.name ?? "unknown";
|
||||
```
|
||||
|
||||
### Generics
|
||||
|
||||
```sx
|
||||
max :: (a: $T, b: T) -> T {
|
||||
if a > b then a else b;
|
||||
}
|
||||
|
||||
List :: struct ($T: Type) {
|
||||
items: [*]T;
|
||||
len: s64;
|
||||
|
||||
append :: (self: *List(T), item: T) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
Generic constraints via protocols:
|
||||
```sx
|
||||
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);
|
||||
}
|
||||
|
||||
add5 := make_adder(5);
|
||||
print("{}\n", add5(100)); // 105
|
||||
```
|
||||
|
||||
Closures capture by value. Bare functions auto-promote to closures when needed.
|
||||
|
||||
### Protocols
|
||||
|
||||
```sx
|
||||
Drawable :: protocol {
|
||||
draw :: (x: s32, y: s32);
|
||||
}
|
||||
|
||||
impl Drawable for Circle {
|
||||
draw :: (self: *Circle, x: s32, y: s32) { ... }
|
||||
}
|
||||
|
||||
shape : Drawable = xx my_circle; // type erasure via xx
|
||||
shape.draw(10, 20); // dynamic dispatch
|
||||
```
|
||||
|
||||
`#inline` protocols store function pointers directly (no vtable indirection):
|
||||
```sx
|
||||
Allocator :: protocol #inline {
|
||||
alloc :: (size: s64) -> *void;
|
||||
dealloc :: (ptr: *void);
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern Matching
|
||||
|
||||
```sx
|
||||
// On enums
|
||||
if shape == {
|
||||
case .circle: (r) => print("radius: {}\n", r);
|
||||
case .rect: (r) => print("{}x{}\n", r.w, r.h);
|
||||
case .none: print("nothing\n");
|
||||
}
|
||||
|
||||
// On optionals
|
||||
if opt == {
|
||||
case .some: (val) => use(val);
|
||||
case .none: fallback();
|
||||
}
|
||||
|
||||
// On type categories (via Any)
|
||||
if type_of(val) == {
|
||||
case int: print("integer\n");
|
||||
case string: print("string\n");
|
||||
case struct: print("struct\n");
|
||||
}
|
||||
```
|
||||
|
||||
### Control Flow
|
||||
|
||||
```sx
|
||||
// Chained comparisons
|
||||
if 0 <= x <= 100 { ... }
|
||||
|
||||
// While
|
||||
while i < 10 { i += 1; }
|
||||
|
||||
// For (arrays and slices)
|
||||
for items: (val) { print("{}\n", val); }
|
||||
for items: (val, idx) { print("[{}] = {}\n", idx, val); }
|
||||
|
||||
// Defer
|
||||
f := open("file.txt");
|
||||
defer close(f);
|
||||
|
||||
// Multi-target assignment (atomic swap)
|
||||
a, b = b, a;
|
||||
```
|
||||
|
||||
### Pipe Operator
|
||||
|
||||
```sx
|
||||
result := data |> parse() |> transform() |> serialize();
|
||||
// equivalent to: serialize(transform(parse(data)))
|
||||
```
|
||||
|
||||
### Compile-Time Execution
|
||||
|
||||
```sx
|
||||
// Evaluate at compile time
|
||||
FIBONACCI_10 :: #run fib(10);
|
||||
|
||||
// Generate code at compile time
|
||||
#insert #run generate_lookup_table();
|
||||
```
|
||||
|
||||
### C Interop
|
||||
|
||||
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";
|
||||
```
|
||||
|
||||
Direct C header import:
|
||||
```sx
|
||||
#import c {
|
||||
#include "vendors/mylib/api.h";
|
||||
#source "vendors/mylib/impl.c";
|
||||
};
|
||||
```
|
||||
|
||||
### Modules
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx"; // flat import
|
||||
math :: #import "modules/math.sx"; // namespaced import
|
||||
```
|
||||
|
||||
### Implicit Context
|
||||
|
||||
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.append(42);
|
||||
}
|
||||
|
||||
// Override allocator for a scope
|
||||
push Context.{ allocator = my_arena } {
|
||||
do_work(); // all allocations use my_arena
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Sort Example
|
||||
|
||||
@@ -47,34 +355,35 @@ main :: () {
|
||||
arr : []s64 = .[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]
|
||||
}
|
||||
```
|
||||
|
||||
## Building
|
||||
## Standard Library
|
||||
|
||||
Requires **Zig 0.16+** and **LLVM 19**.
|
||||
The standard library (`modules/std.sx`) provides:
|
||||
|
||||
- **I/O**: `print(fmt, args...)`, `out(str)`
|
||||
- **Collections**: `List($T)` (dynamic array)
|
||||
- **Strings**: `concat`, `substr`, `int_to_string`, `float_to_string`, `cstring`
|
||||
- **Memory**: `Allocator` protocol, `GPA` (general purpose), `Arena` (bump allocator)
|
||||
- **Math**: `sqrt`, `sin`, `cos`
|
||||
- **Introspection**: `type_of`, `type_name`, `field_count`, `field_name`, `field_value`, `size_of`
|
||||
|
||||
## Cross-Compilation
|
||||
|
||||
```sh
|
||||
zig build
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# compile to binary
|
||||
sx build examples/06-generic.sx
|
||||
|
||||
# compile and run
|
||||
sx run examples/06-generic.sx
|
||||
|
||||
# emit LLVM IR
|
||||
sx ir examples/06-generic.sx
|
||||
|
||||
# start the language server
|
||||
sx lsp
|
||||
sx build app.sx --target linux # Linux x86_64
|
||||
sx build app.sx --target macos-arm # macOS ARM64
|
||||
sx build app.sx --target windows # Windows x86_64
|
||||
sx build app.sx --target wasm # WebAssembly
|
||||
```
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- [Jonathan Blow](https://en.wikipedia.org/wiki/Jonathan_Blow) — for Jai, the language that inspired this one
|
||||
- [Andrew Kelley](https://andrewkelley.me) — for Zig, which made this compiler a joy to write
|
||||
- [Jonathan Blow](https://en.wikipedia.org/wiki/Jonathan_Blow) for Jai, the language that inspired this one
|
||||
- [Andrew Kelley](https://andrewkelley.me) for Zig, which made this compiler a joy to write
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
Reference in New Issue
Block a user