Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ permissions:
contents: write

env:
ZIG_VERSION: "0.15.2"
ZIG_VERSION: "0.16.0"

jobs:
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -402,7 +402,7 @@ jobs:
end
end

depends_on "zig" => "0.15.2"
depends_on "zig" => "0.16.0"

def install
bin.install "bin/run"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/runtime-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
with:
version: 0.15.2
version: 0.16.0
- name: Build runtime
run: zig build
- name: Runtime tests
Expand All @@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
with:
version: 0.15.2
version: 0.16.0
- name: Run benchmarks
run: zig build bench-runtime
- name: Upload results
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/zig-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Set up Zig
uses: mlugg/setup-zig@v2
with:
version: 0.15.2
version: 0.16.0

- name: Build
run: zig build
Expand All @@ -45,7 +45,7 @@ jobs:
- name: Set up Zig
uses: mlugg/setup-zig@v2
with:
version: 0.15.2
version: 0.16.0

- name: Build compiler
run: zig build
Expand All @@ -68,7 +68,7 @@ jobs:
- name: Set up Zig
uses: mlugg/setup-zig@v2
with:
version: 0.15.2
version: 0.16.0

- name: Zig test
run: zig test src/root.zig
Expand All @@ -85,7 +85,7 @@ jobs:
- name: Set up Zig
uses: mlugg/setup-zig@v2
with:
version: 0.15.2
version: 0.16.0

- name: Build with sanitizers
run: zig build -Dsanitize=true
Expand Down Expand Up @@ -146,7 +146,7 @@ jobs:
if: steps.changes.outputs.should_run == 'true'
uses: mlugg/setup-zig@v2
with:
version: 0.15.2
version: 0.16.0

- name: Build compiler
if: steps.changes.outputs.should_run == 'true'
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ main.o
/test
.agents/
.claude/
/root
/runner
/tmp-*-XXXXXX
/dap-*-XXXXXX
/lsp-*-XXXXXX
/skills-lock.json
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Repository Guidelines

## Canonical Docs
Start with `CLAUDE.md` for architecture, Zig 0.15 conventions, and current workflow rules. Keep shared instructions in `AGENTS.md` and `CLAUDE.md` synchronized in the same PR whenever commands, branch targets, or contributor process changes.
Start with `CLAUDE.md` for architecture, Zig 0.16 conventions, and current workflow rules. Keep shared instructions in `AGENTS.md` and `CLAUDE.md` synchronized in the same PR whenever commands, branch targets, or contributor process changes.

## Project Structure & Module Organization
`src/` holds compiler passes and CLI entrypoints such as `main.zig`, `driver.zig`, `formatter.zig`, and `lsp.zig`. `src/runtime/` contains the C runtime, with runtime tests in `src/runtime/tests/`. `stdlib/` stores standard library `.run` packages, `examples/` has sample programs, and `tests/` contains Zig debug/e2e runners plus fuzz corpus inputs. `website/` is the Astro/Starlight docs site, `editors/` contains VS Code and tree-sitter tooling, and `blog/helm/` deploys Strapi.
Expand All @@ -10,7 +10,7 @@ Start with `CLAUDE.md` for architecture, Zig 0.15 conventions, and current workf
`zig build` builds the compiler. `zig build test` runs Zig unit tests, `zig build test-e2e` runs end-to-end compiler cases, and `make test-runtime` runs the runtime C suite. Use `zig build run -- <command> <file.run>` for compiler commands such as `check`, `run`, `fmt`, `tokens`, or `ast`. Website work stays in `website/`: `bun run dev` for local docs work and `bun run build` for a production build. Editor examples: `cd editors/tree-sitter-run && bun run test` and `cd editors/vscode && bun run compile`.

## Coding Style & Naming Conventions
Format Zig with `zig fmt`. Follow the Zig 0.15 patterns already used here: prefer `ArrayList.empty`, pass allocators per method call, and match existing module naming. Token tags use the `kw_` prefix; AST tags use `_stmt`, `_decl`, `_literal`, and `type_` for type-related nodes. Runtime C in `src/runtime/` should stay `clang-format` clean. Website code uses `oxfmt` and `oxlint`.
Format Zig with `zig fmt`. Follow the Zig 0.16 patterns already used here: prefer `ArrayList.empty`, pass allocators per method call, and match existing module naming. Token tags use the `kw_` prefix; AST tags use `_stmt`, `_decl`, `_literal`, and `type_` for type-related nodes. Runtime C in `src/runtime/` should stay `clang-format` clean. Website code uses `oxfmt` and `oxlint`.

## Testing Guidelines
Keep tests close to the code they validate: inline Zig `test` blocks, runtime files named `src/runtime/tests/test_*.c`, e2e cases in `tests/e2e/cases/*.run`, and stdlib tests as `*_test.run`. Add or update the nearest matching test for every behavior change; there is no published coverage target. For Linux runtime work, `zig build test -Dsanitize=true` matches CI sanitizer coverage.
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Compiler commands: `run`, `build`, `check`, `tokens` (dump lexer output), `ast`

## Architecture

Run is a systems programming language compiler written in Zig 0.15. The full compilation pipeline:
Run is a systems programming language compiler written in Zig 0.16. The full compilation pipeline:

```
Source (.run) → Lexer → Parser → Naming → Resolve → TypeCheck → Lower (IR) → CodegenC → zig cc
Expand Down Expand Up @@ -60,7 +60,7 @@ Source (.run) → Lexer → Parser → Naming → Resolve → TypeCheck → Lowe

Tests are embedded `test` blocks in source files, discovered through root.zig.

## Zig 0.15 Conventions
## Zig 0.16 Conventions

These differ from older Zig versions and are critical to get right:

Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Thanks for your interest in contributing to Run! This document covers how to get

## Development Setup

1. Install [Zig 0.15](https://ziglang.org/download/) or later
1. Install [Zig 0.16](https://ziglang.org/download/) or later
2. Clone the repository:
```bash
git clone https://github.com/marsolab/runlang.git
Expand Down Expand Up @@ -33,7 +33,7 @@ Thanks for your interest in contributing to Run! This document covers how to get
- Use `kw_` prefix for keyword tokens
- Use `_stmt`, `_decl`, `_literal` suffixes for AST node tags
- Use `type_` prefix for type-related AST nodes
- Use `ArrayList.empty` (not `.init(allocator)`) — Zig 0.15 convention
- Use `ArrayList.empty` (not `.init(allocator)`) — Zig 0.16 convention
- Pass the allocator to each method call rather than storing it in containers

## Submitting a Pull Request
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub fun main() {

### Prerequisites

Run's compiler is written in [Zig](https://ziglang.org/). You need **Zig 0.15** or later.
Run's compiler is written in [Zig](https://ziglang.org/). You need **Zig 0.16** or later.

### Build from Source

Expand Down
123 changes: 60 additions & 63 deletions benchmarks/bench.zig
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
const std = @import("std");
const compiler = @import("compiler");

const Lexer = compiler.Lexer;
const Parser = compiler.Parser;
const Lexer = @import("compiler").Lexer;
const Parser = @import("compiler").Parser;
const Dir = std.Io.Dir;
const File = std.Io.File;

const WARMUP = 3;
const ITERS = 10;

pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();

const stdout = std.fs.File.stdout().deprecatedWriter();
pub fn main(init: std.process.Init) !void {
const allocator = init.gpa;
const io = init.io;
var stdout_file = File.stdout().writer(io, &.{});
const stdout = &stdout_file.interface;

try stdout.writeAll("Run Compiler Benchmarks\n");
try stdout.writeAll("=======================\n\n");
Expand Down Expand Up @@ -47,13 +46,13 @@ pub fn main() !void {

// Measured
for (0..ITERS) |iter| {
var timer = try std.time.Timer.start();
const start_ns = std.Io.Clock.awake.now(io).nanoseconds;
var lexer = Lexer.init(source);
while (true) {
const tok = lexer.next();
if (tok.tag == .eof) break;
}
timings[iter] = timer.read();
timings[iter] = @intCast(std.Io.Clock.awake.now(io).nanoseconds - start_ns);
}

const stats = computeStats(&timings);
Expand All @@ -79,14 +78,14 @@ pub fn main() !void {

// Measured
for (0..ITERS) |iter| {
var timer = try std.time.Timer.start();
const start_ns = std.Io.Clock.awake.now(io).nanoseconds;
var lexer = Lexer.init(source);
var tokens = try lexer.tokenize(allocator);
defer tokens.deinit(allocator);
var parser = Parser.init(allocator, tokens.items, source);
defer parser.deinit();
_ = try parser.parseFile();
timings[iter] = timer.read();
timings[iter] = @intCast(std.Io.Clock.awake.now(io).nanoseconds - start_ns);
}

const stats = computeStats(&timings);
Expand All @@ -102,13 +101,13 @@ pub fn main() !void {
defer allocator.free(tmp_path);

{
const f = try std.fs.cwd().createFile(tmp_path, .{});
defer f.close();
try f.writeAll(source);
const f = try Dir.cwd().createFile(io, tmp_path, .{});
defer f.close(io);
try f.writeStreamingAll(io, source);
}
defer std.fs.cwd().deleteFile(tmp_path) catch {};
defer Dir.cwd().deleteFile(io, tmp_path) catch {};

const compiler_path = findCompiler(allocator) catch {
const compiler_path = findCompiler(io, allocator) catch {
try stdout.writeAll(" (skipped — compiler binary not found, run 'zig build' first)\n");
break;
};
Expand All @@ -118,17 +117,17 @@ pub fn main() !void {

// Warmup
for (0..WARMUP) |_| {
_ = runCompilerCheck(allocator, compiler_path, tmp_path) catch continue;
_ = runCompilerCheck(io, allocator, compiler_path, tmp_path) catch continue;
}

// Measured
for (0..ITERS) |iter| {
var timer = try std.time.Timer.start();
_ = runCompilerCheck(allocator, compiler_path, tmp_path) catch {
const start_ns = std.Io.Clock.awake.now(io).nanoseconds;
_ = runCompilerCheck(io, allocator, compiler_path, tmp_path) catch {
timings[iter] = 0;
continue;
};
timings[iter] = timer.read();
timings[iter] = @intCast(std.Io.Clock.awake.now(io).nanoseconds - start_ns);
}

const stats = computeStats(&timings);
Expand Down Expand Up @@ -189,64 +188,62 @@ fn formatDuration(ns: u64) [12]u8 {

fn generateSource(allocator: std.mem.Allocator, num_fns: usize) ![]const u8 {
var buf: std.ArrayList(u8) = .empty;
const writer = buf.writer(allocator);
try writer.writeAll("package main\n\nuse \"fmt\"\n\n");
try buf.appendSlice(allocator, "package main\n\nuse \"fmt\"\n\n");

for (0..num_fns) |i| {
try writer.print("fun func_{d}() {{\n", .{i});
try writer.writeAll(" let x = 42\n");
try writer.writeAll(" var y = 10\n");
try writer.writeAll(" y = y + x\n");
try writer.writeAll(" if y > 50 {\n");
try writer.writeAll(" fmt.println(\"big\")\n");
try writer.writeAll(" } else {\n");
try writer.writeAll(" fmt.println(\"small\")\n");
try writer.writeAll(" }\n");
try writer.writeAll(" var i = 0\n");
try writer.writeAll(" for i < 3 {\n");
try writer.writeAll(" i = i + 1\n");
try writer.writeAll(" }\n");
try writer.writeAll(" fmt.println(y)\n");
try writer.writeAll("}\n\n");
try buf.print(allocator, "fun func_{d}() {{\n", .{i});
try buf.appendSlice(allocator, " let x = 42\n");
try buf.appendSlice(allocator, " var y = 10\n");
try buf.appendSlice(allocator, " y = y + x\n");
try buf.appendSlice(allocator, " if y > 50 {\n");
try buf.appendSlice(allocator, " fmt.println(\"big\")\n");
try buf.appendSlice(allocator, " } else {\n");
try buf.appendSlice(allocator, " fmt.println(\"small\")\n");
try buf.appendSlice(allocator, " }\n");
try buf.appendSlice(allocator, " var i = 0\n");
try buf.appendSlice(allocator, " for i < 3 {\n");
try buf.appendSlice(allocator, " i = i + 1\n");
try buf.appendSlice(allocator, " }\n");
try buf.appendSlice(allocator, " fmt.println(y)\n");
try buf.appendSlice(allocator, "}\n\n");
}

try writer.writeAll("pub fun main() {\n");
try writer.writeAll(" func_0()\n");
try writer.writeAll("}\n");
try buf.appendSlice(allocator, "pub fun main() {\n");
try buf.appendSlice(allocator, " func_0()\n");
try buf.appendSlice(allocator, "}\n");

return buf.toOwnedSlice(allocator);
}

fn findCompiler(allocator: std.mem.Allocator) ![]const u8 {
fn findCompiler(io: std.Io, allocator: std.mem.Allocator) ![]const u8 {
const local_path = "zig-out/bin/run";
if (std.fs.cwd().access(local_path, .{})) |_| {
if (Dir.cwd().access(io, local_path, .{})) |_| {
return try allocator.dupe(u8, local_path);
} else |_| {}
return error.CompilerNotFound;
}

fn runCompilerCheck(
io: std.Io,
allocator: std.mem.Allocator,
compiler_path: []const u8,
source_path: []const u8,
) !void {
var child = std.process.Child.init(&.{ compiler_path, "check", "--no-color", source_path }, allocator);
child.stdout_behavior = .Pipe;
child.stderr_behavior = .Pipe;
try child.spawn();

// Drain output
while (true) {
var buf: [4096]u8 = undefined;
const n = child.stdout.?.read(&buf) catch break;
if (n == 0) break;
}
while (true) {
var buf: [4096]u8 = undefined;
const n = child.stderr.?.read(&buf) catch break;
if (n == 0) break;
}
const result = try std.process.run(allocator, io, .{
.argv = &.{ compiler_path, "check", "--no-color", source_path },
.stdout_limit = .limited(1024 * 1024),
.stderr_limit = .limited(1024 * 1024),
});
defer allocator.free(result.stdout);
defer allocator.free(result.stderr);

if (childExitCode(result.term) != 0) return error.CheckFailed;
}

const result = try child.wait();
if (result.Exited != 0) return error.CheckFailed;
fn childExitCode(term: std.process.Child.Term) u8 {
return switch (term) {
.exited => |code| code,
.signal => |sig| if (@intFromEnum(sig) + 128 <= std.math.maxInt(u8)) @as(u8, @intCast(@intFromEnum(sig) + 128)) else std.math.maxInt(u8),
.stopped, .unknown => 1,
};
}
Loading
Loading