agra 292fd937c6 lsp: project-wide find-references + revive the LSP test suite
find-references only searched documents the editor had open, so asking
for references to a field from a file whose users were all closed
returned just the definition. Load every .sx under the workspace root
before matching so uses in unopened files are found too.

The LSP server's own tests were dormant: nested under the `lsp` struct in
root.zig, refAllDecls never reached them, and they had bit-rotted (stale
DocumentStore.init arity, an unaligned dummy io, fake /test/ paths that
no longer resolve). Reference the lsp files directly so their tests run,
give the doc-store tests a real Threaded io with bare paths, and fix the
stale extractIdentAtOffset expectation.

Extract referencesPayload from the transport so it is unit-testable, and
add tests covering cross-document field references, includeDeclaration,
the for-loop capture inlay hint, and workspace file loading.
2026-05-31 13:36:20 +03:00
...
2026-02-12 10:13:36 +02:00
...
2026-02-18 16:18:31 +02:00
2026-02-17 16:57:12 +02:00
2026-05-17 13:19:08 +03:00
2026-02-09 18:07:41 +02:00
sm
2026-03-02 21:00:55 +02:00
2026-02-22 17:24:04 +02:00
2026-02-09 18:07:41 +02:00
2026-02-09 18:07:41 +02:00
...
2026-03-02 21:08:09 +02:00
2026-05-18 17:40:10 +03:00
2026-05-18 17:40:10 +03:00
2026-05-18 17:40:10 +03:00
2026-05-18 17:40:10 +03:00

sx

An experimental systems programming language with Jai-inspired syntax, compile-time execution, generics, closures, protocols, and an LLVM backend.

Status: Highly experimental. The language and compiler are under active development.

At a Glance

#import "modules/std.sx";

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+.

zig build

On macOS with Homebrew LLVM:

# default path: /opt/homebrew/opt/llvm@19
zig build

Custom LLVM path:

zig build -Dllvm-prefix=/path/to/llvm

Usage

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

// 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

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)

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:

Perms :: enum flags { read; write; execute; }
rw := Perms.read | Perms.write;

Optionals

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

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:

are_equal :: ($T: Type/Eq, a: T, b: T) -> bool { a.eq(b); }

Closures

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

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):

Allocator :: protocol #inline {
    alloc :: (size: s64) -> *void;
    dealloc :: (ptr: *void);
}

Pattern Matching

// 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

// 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

result := data |> parse() |> transform() |> serialize();
// equivalent to: serialize(transform(parse(data)))

Compile-Time Execution

// Evaluate at compile time
FIBONACCI_10 :: #run fib(10);

// Generate code at compile time
#insert #run generate_lookup_table();

C Interop

Foreign functions:

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:

#import c {
    #include "vendors/mylib/api.h";
    #source "vendors/mylib/impl.c";
};

Modules

#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:

// 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

#import "modules/std.sx";

quick_sort :: (items: []$T) {
    partition :: (items: []T, lo: s64, hi: s64) -> s64 {
        pivot := items[hi];
        i := lo - 1;
        j := lo;
        while j < hi {
            if items[j] < pivot {
                i += 1;
                items[i], items[j] = items[j], items[i];
            }
            j += 1;
        }
        i += 1;
        items[i], items[hi] = items[hi], items[i];
        i;
    }

    sort :: (items: []T, lo: s64, hi: s64) {
        if lo < hi {
            pi := partition(items, lo, hi);
            sort(items, lo, pi - 1);
            sort(items, pi + 1, hi);
        }
    }

    sort(items, 0, items.len - 1);
}

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]
}

Standard Library

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

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

License

MIT

Description
No description provided
Readme MIT 65 MiB
Languages
Zig 98.9%
Shell 0.6%
C++ 0.4%