aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/build-options/.gitignore (renamed from examples/hello-zig/.gitignore)0
-rw-r--r--examples/build-options/.nova/Configuration.json4
-rw-r--r--examples/build-options/.nova/Tasks/Zig Package.json16
-rw-r--r--examples/build-options/build.zig43
-rw-r--r--examples/build-options/build.zig.zon12
-rw-r--r--examples/build-options/src/main.zig15
-rw-r--r--examples/hello-zig/.editorconfig6
-rw-r--r--examples/hello-zig/.nova/Configuration.json8
-rw-r--r--examples/hello-zig/.nova/Tasks/Zig Debug.json12
-rw-r--r--examples/hello-zig/.nova/Tasks/Zig Package (macOS Terminal).json7
-rw-r--r--examples/hello-zig/.nova/Tasks/Zig Package.json8
-rw-r--r--examples/hello-zig/build.zig47
-rw-r--r--examples/hello-zig/build.zig.zon12
-rw-r--r--examples/hello-zig/src/main.zig32
-rw-r--r--examples/hello-zig/src/root.zig83
-rw-r--r--examples/monorepo/.gitignore7
-rw-r--r--examples/monorepo/.nova/Configuration.json4
-rw-r--r--examples/monorepo/build.zig30
-rw-r--r--examples/monorepo/build.zig.zon13
-rw-r--r--examples/monorepo/packages/alpha/build.zig17
-rw-r--r--examples/monorepo/packages/alpha/build.zig.zon8
-rw-r--r--examples/monorepo/packages/alpha/src/lib.zig23
-rw-r--r--examples/monorepo/packages/beta/build.zig17
-rw-r--r--examples/monorepo/packages/beta/build.zig.zon8
-rw-r--r--examples/monorepo/packages/beta/src/lib.zig44
-rw-r--r--examples/monorepo/src/main.zig9
-rw-r--r--examples/multi-step/.gitignore2
-rw-r--r--examples/multi-step/.nova/Configuration.json4
-rw-r--r--examples/multi-step/.nova/Tasks/Zig Package.json10
-rw-r--r--examples/multi-step/.nova/Tasks/Zig Test.json10
-rw-r--r--examples/multi-step/.nova/Tasks/Zig Watch.json11
-rw-r--r--examples/multi-step/build.zig96
-rw-r--r--examples/multi-step/build.zig.zon12
-rw-r--r--examples/multi-step/src/lib.zig67
-rw-r--r--examples/multi-step/src/main.zig35
-rw-r--r--examples/snippets/.nova/Configuration.json4
-rw-r--r--examples/snippets/error-example.zig19
-rw-r--r--examples/snippets/fibonacci.zig41
-rw-r--r--examples/snippets/syntax-tour.zig283
-rw-r--r--examples/test-suite/.gitignore2
-rw-r--r--examples/test-suite/.nova/Configuration.json4
-rw-r--r--examples/test-suite/.nova/Tasks/Zig Test (all).json10
-rw-r--r--examples/test-suite/.nova/Tasks/Zig Test (math).json11
-rw-r--r--examples/test-suite/build.zig41
-rw-r--r--examples/test-suite/build.zig.zon12
-rw-r--r--examples/test-suite/src/math.zig70
-rw-r--r--examples/test-suite/src/strings.zig86
-rw-r--r--examples/zon-examples/full-types.zon74
-rw-r--r--examples/zon-examples/invalid.zon12
-rw-r--r--examples/zon-examples/minimal.zon1
50 files changed, 1187 insertions, 215 deletions
diff --git a/examples/hello-zig/.gitignore b/examples/build-options/.gitignore
index 3389c86..3389c86 100644
--- a/examples/hello-zig/.gitignore
+++ b/examples/build-options/.gitignore
diff --git a/examples/build-options/.nova/Configuration.json b/examples/build-options/.nova/Configuration.json
new file mode 100644
index 0000000..44cadf8
--- /dev/null
+++ b/examples/build-options/.nova/Configuration.json
@@ -0,0 +1,4 @@
+{
+ "zls.enable_build_on_save" : false,
+ "zls.zig_exe_path" : "\/opt\/homebrew\/bin\/zig"
+}
diff --git a/examples/build-options/.nova/Tasks/Zig Package.json b/examples/build-options/.nova/Tasks/Zig Package.json
new file mode 100644
index 0000000..216d413
--- /dev/null
+++ b/examples/build-options/.nova/Tasks/Zig Package.json
@@ -0,0 +1,16 @@
+{
+ "extension" : {
+ "identifier" : "at.dcz.nova-zig",
+ "name" : "Zig"
+ },
+ "extensionTemplate" : "zigBuildRun",
+ "extensionValues" : {
+ "runStep" : "run",
+ "userOptions" : [
+ "verbose",
+ "port=9090",
+ "backend=sqlite",
+ "max-conn=256"
+ ]
+ }
+}
diff --git a/examples/build-options/build.zig b/examples/build-options/build.zig
new file mode 100644
index 0000000..5412e8d
--- /dev/null
+++ b/examples/build-options/build.zig
@@ -0,0 +1,43 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ // Boolean flag: -Dverbose
+ const verbose = b.option(bool, "verbose", "Enable verbose logging") orelse false;
+
+ // Integer option: -Dport=<n>
+ const port = b.option(u16, "port", "TCP port to listen on") orelse 8080;
+
+ // Enum option: -Dbackend=<name>
+ const Backend = enum { memory, sqlite, postgres };
+ const backend = b.option(Backend, "backend", "Storage backend") orelse .memory;
+
+ // Hyphenated key option: -Dmax-conn=<n> (tests USER_OPTION_REGEX with hyphens)
+ const max_conn = b.option(u32, "max-conn", "Maximum simultaneous connections") orelse 128;
+
+ const options = b.addOptions();
+ options.addOption(bool, "verbose", verbose);
+ options.addOption(u16, "port", port);
+ options.addOption(Backend, "backend", backend);
+ options.addOption(u32, "max_conn", max_conn);
+
+ const exe = b.addExecutable(.{
+ .name = "build-options-demo",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "build_options", .module = options.createModule() },
+ },
+ }),
+ });
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ if (b.args) |args| run_cmd.addArgs(args);
+ const run_step = b.step("run", "Run the build-options demo");
+ run_step.dependOn(&run_cmd.step);
+}
diff --git a/examples/build-options/build.zig.zon b/examples/build-options/build.zig.zon
new file mode 100644
index 0000000..7c3e5c0
--- /dev/null
+++ b/examples/build-options/build.zig.zon
@@ -0,0 +1,12 @@
+.{
+ .name = .build_options_demo,
+ .version = "0.1.0",
+ .fingerprint = 0xf453d2b8ee5c2b92,
+ .minimum_zig_version = "0.16.0",
+ .dependencies = .{},
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/build-options/src/main.zig b/examples/build-options/src/main.zig
new file mode 100644
index 0000000..5bab011
--- /dev/null
+++ b/examples/build-options/src/main.zig
@@ -0,0 +1,15 @@
+const std = @import("std");
+const opts = @import("build_options");
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+ var buf: [1024]u8 = undefined;
+ var fw: std.Io.File.Writer = .init(.stdout(), io, &buf);
+ defer fw.interface.flush() catch {};
+ const w = &fw.interface;
+
+ try w.print("verbose = {}\n", .{opts.verbose});
+ try w.print("port = {d}\n", .{opts.port});
+ try w.print("backend = {s}\n", .{@tagName(opts.backend)});
+ try w.print("max_conn = {d}\n", .{opts.max_conn});
+}
diff --git a/examples/hello-zig/.editorconfig b/examples/hello-zig/.editorconfig
deleted file mode 100644
index d5f8c65..0000000
--- a/examples/hello-zig/.editorconfig
+++ /dev/null
@@ -1,6 +0,0 @@
-root = true
-
-[*]
-indent_style = space
-indent_size = 4
-insert_final_newline = true
diff --git a/examples/hello-zig/.nova/Configuration.json b/examples/hello-zig/.nova/Configuration.json
deleted file mode 100644
index 47e341a..0000000
--- a/examples/hello-zig/.nova/Configuration.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "at.dcz.nova-zig.toolchain.lldb-dap-path" : "\/Library\/Developer\/CommandLineTools\/usr\/bin\/lldb-dap",
- "at.dcz.nova-zig.toolchain.zig-path" : "\/opt\/homebrew\/bin\/zig",
- "at.dcz.nova-zig.zls.build-on-save" : true,
- "at.dcz.nova-zig.zls.enabled" : true,
- "zls.enable_build_on_save" : true,
- "zls.zig_exe_path" : "\/opt\/homebrew\/bin\/zig"
-}
diff --git a/examples/hello-zig/.nova/Tasks/Zig Debug.json b/examples/hello-zig/.nova/Tasks/Zig Debug.json
deleted file mode 100644
index 53b7f13..0000000
--- a/examples/hello-zig/.nova/Tasks/Zig Debug.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "extension" : {
- "identifier" : "at.dcz.nova-zig",
- "name" : "Zig"
- },
- "extensionTemplate" : "zigDebug",
- "extensionValues" : {
- "console" : "internalConsole",
- "programPath" : "zig-out\/bin\/hello-zig"
- },
- "openLogOnRun" : "start"
-}
diff --git a/examples/hello-zig/.nova/Tasks/Zig Package (macOS Terminal).json b/examples/hello-zig/.nova/Tasks/Zig Package (macOS Terminal).json
deleted file mode 100644
index d1fa04d..0000000
--- a/examples/hello-zig/.nova/Tasks/Zig Package (macOS Terminal).json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "extension" : {
- "identifier" : "at.dcz.nova-zig",
- "name" : "Zig"
- },
- "extensionTemplate" : "zigBuildRunTerminal"
-}
diff --git a/examples/hello-zig/.nova/Tasks/Zig Package.json b/examples/hello-zig/.nova/Tasks/Zig Package.json
deleted file mode 100644
index 10830e6..0000000
--- a/examples/hello-zig/.nova/Tasks/Zig Package.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "buildBeforeRunning" : true,
- "extension" : {
- "identifier" : "at.dcz.nova-zig",
- "name" : "Zig"
- },
- "extensionTemplate" : "zigBuildRun"
-}
diff --git a/examples/hello-zig/build.zig b/examples/hello-zig/build.zig
deleted file mode 100644
index 5c56fb0..0000000
--- a/examples/hello-zig/build.zig
+++ /dev/null
@@ -1,47 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- const lib_mod = b.addModule("hello_zig", .{
- .root_source_file = b.path("src/root.zig"),
- .target = target,
- });
-
- const exe = b.addExecutable(.{
- .name = "hello-zig",
- .root_module = b.createModule(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
- .imports = &.{
- .{ .name = "hello_zig", .module = lib_mod },
- },
- }),
- });
-
- b.installArtifact(exe);
-
- const run_step = b.step("run", "Run the sample app");
- const run_cmd = b.addRunArtifact(exe);
- run_step.dependOn(&run_cmd.step);
-
- if (b.args) |args| {
- run_cmd.addArgs(args);
- }
-
- const lib_tests = b.addTest(.{
- .root_module = lib_mod,
- });
- const exe_tests = b.addTest(.{
- .root_module = exe.root_module,
- });
-
- const run_lib_tests = b.addRunArtifact(lib_tests);
- const run_exe_tests = b.addRunArtifact(exe_tests);
-
- const test_step = b.step("test", "Run the sample tests");
- test_step.dependOn(&run_lib_tests.step);
- test_step.dependOn(&run_exe_tests.step);
-}
diff --git a/examples/hello-zig/build.zig.zon b/examples/hello-zig/build.zig.zon
deleted file mode 100644
index 09f7da2..0000000
--- a/examples/hello-zig/build.zig.zon
+++ /dev/null
@@ -1,12 +0,0 @@
-.{
- .name = .hello_zig,
- .version = "0.1.0",
- .fingerprint = 0x1c1a468675f426fe, // Changing this has security and trust implications.
- .minimum_zig_version = "0.15.2",
- .dependencies = .{},
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/hello-zig/src/main.zig b/examples/hello-zig/src/main.zig
deleted file mode 100644
index 74eb590..0000000
--- a/examples/hello-zig/src/main.zig
+++ /dev/null
@@ -1,32 +0,0 @@
-const std = @import("std");
-const hello_zig = @import("hello_zig");
-
-pub fn main() !void {
- var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
- defer arena.deinit();
-
- const allocator = arena.allocator();
- const args = try std.process.argsAlloc(allocator);
- const name = if (args.len > 1) args[1] else "Nova";
-
- var greeter = hello_zig.Greeter{
- .name = name,
- .punctuation = '!',
- };
-
- const stdout = std.fs.File.stdout().deprecatedWriter();
- try greeter.write(stdout, .verbose);
-
- const sample_values = [_]i32{ -3, -2, 0, 1, 2, 3, 4 };
- var squares = try hello_zig.collectEvenSquares(allocator, &sample_values);
- defer squares.deinit(allocator);
-
- std.debug.print("even squares: {any}\n", .{squares.items});
-}
-
-test "main module can compute a summary" {
- const greeting = try hello_zig.describeNumber(std.testing.allocator, 42);
- defer std.testing.allocator.free(greeting);
-
- try std.testing.expect(std.mem.startsWith(u8, greeting, "positive"));
-}
diff --git a/examples/hello-zig/src/root.zig b/examples/hello-zig/src/root.zig
deleted file mode 100644
index f6600ac..0000000
--- a/examples/hello-zig/src/root.zig
+++ /dev/null
@@ -1,83 +0,0 @@
-const std = @import("std");
-
-pub const OutputStyle = enum {
- compact,
- verbose,
-};
-
-pub const NumberKind = union(enum) {
- negative: i64,
- zero,
- positive: u64,
-};
-
-pub const Greeter = struct {
- name: []const u8,
- punctuation: u8 = '!',
-
- pub fn write(self: Greeter, writer: anytype, style: OutputStyle) !void {
- switch (style) {
- .compact => try writer.print("hello, {s}{c}\n", .{ self.name, self.punctuation }),
- .verbose => {
- try writer.print(
- \\hello, {s}{c}
- \\this sample is here to exercise Zig syntax support in Nova.
- \\
- , .{ self.name, self.punctuation });
- },
- }
- }
-};
-
-pub fn classifyNumber(value: i64) NumberKind {
- if (value == 0) return .zero;
- if (value < 0) return .{ .negative = value };
- return .{ .positive = @intCast(value) };
-}
-
-pub fn describeNumber(allocator: std.mem.Allocator, value: i64) ![]u8 {
- return switch (classifyNumber(value)) {
- .zero => std.fmt.allocPrint(allocator, "zero", .{}),
- .negative => |negative| std.fmt.allocPrint(allocator, "negative({d})", .{negative}),
- .positive => |positive| std.fmt.allocPrint(allocator, "positive({d})", .{positive}),
- };
-}
-
-pub fn collectEvenSquares(
- allocator: std.mem.Allocator,
- values: []const i32,
-) !std.ArrayList(i32) {
- var result: std.ArrayList(i32) = .empty;
- errdefer result.deinit(allocator);
-
- for (values) |value| {
- if (@mod(value, 2) != 0) continue;
- try result.append(allocator, value * value);
- }
-
- return result;
-}
-
-test "classifyNumber covers the main branches" {
- try std.testing.expectEqual(NumberKind{ .negative = -5 }, classifyNumber(-5));
- try std.testing.expectEqual(NumberKind.zero, classifyNumber(0));
- try std.testing.expectEqual(NumberKind{ .positive = 9 }, classifyNumber(9));
-}
-
-test "collectEvenSquares keeps only even numbers" {
- const values = [_]i32{ -4, -3, 0, 1, 2, 7 };
- var result = try collectEvenSquares(std.testing.allocator, &values);
- defer result.deinit(std.testing.allocator);
-
- try std.testing.expectEqualSlices(i32, &.{ 16, 0, 4 }, result.items);
-}
-
-test "Greeter.write renders verbose output" {
- var list: std.ArrayList(u8) = .empty;
- defer list.deinit(std.testing.allocator);
-
- const writer = list.writer(std.testing.allocator);
- try (Greeter{ .name = "Zig" }).write(writer, .verbose);
-
- try std.testing.expect(std.mem.indexOf(u8, list.items, "exercise Zig syntax support") != null);
-}
diff --git a/examples/monorepo/.gitignore b/examples/monorepo/.gitignore
new file mode 100644
index 0000000..575c3ca
--- /dev/null
+++ b/examples/monorepo/.gitignore
@@ -0,0 +1,7 @@
+.zig-cache/
+zig-cache/
+zig-out/
+packages/alpha/.zig-cache/
+packages/alpha/zig-out/
+packages/beta/.zig-cache/
+packages/beta/zig-out/
diff --git a/examples/monorepo/.nova/Configuration.json b/examples/monorepo/.nova/Configuration.json
new file mode 100644
index 0000000..44cadf8
--- /dev/null
+++ b/examples/monorepo/.nova/Configuration.json
@@ -0,0 +1,4 @@
+{
+ "zls.enable_build_on_save" : false,
+ "zls.zig_exe_path" : "\/opt\/homebrew\/bin\/zig"
+}
diff --git a/examples/monorepo/build.zig b/examples/monorepo/build.zig
new file mode 100644
index 0000000..f565154
--- /dev/null
+++ b/examples/monorepo/build.zig
@@ -0,0 +1,30 @@
+const std = @import("std");
+
+// Root build.zig for a two-package monorepo.
+// Open the workspace root in Nova → discovers "test-all" and "gen-docs" steps.
+// Open packages/beta/ separately → discovers that package's own "test" step.
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const exe = b.addExecutable(.{
+ .name = "monorepo-root",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
+ });
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ const run_step = b.step("run", "Run the root executable");
+ run_step.dependOn(&run_cmd.step);
+
+ // Placeholder step that would orchestrate sub-package tests in a real monorepo.
+ const test_all_step = b.step("test-all", "Run tests for all packages");
+ _ = test_all_step;
+
+ const docs_step = b.step("gen-docs", "Generate docs for all packages");
+ _ = docs_step;
+}
diff --git a/examples/monorepo/build.zig.zon b/examples/monorepo/build.zig.zon
new file mode 100644
index 0000000..ed71224
--- /dev/null
+++ b/examples/monorepo/build.zig.zon
@@ -0,0 +1,13 @@
+.{
+ .name = .monorepo_root,
+ .version = "0.1.0",
+ .fingerprint = 0xe044bb6d81fdc07a,
+ .minimum_zig_version = "0.16.0",
+ .dependencies = .{},
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ "packages",
+ },
+}
diff --git a/examples/monorepo/packages/alpha/build.zig b/examples/monorepo/packages/alpha/build.zig
new file mode 100644
index 0000000..9ec9553
--- /dev/null
+++ b/examples/monorepo/packages/alpha/build.zig
@@ -0,0 +1,17 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lib_tests = b.addTest(.{
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/lib.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
+ });
+ const run_tests = b.addRunArtifact(lib_tests);
+ const test_step = b.step("test", "Run alpha tests");
+ test_step.dependOn(&run_tests.step);
+}
diff --git a/examples/monorepo/packages/alpha/build.zig.zon b/examples/monorepo/packages/alpha/build.zig.zon
new file mode 100644
index 0000000..b5b32fc
--- /dev/null
+++ b/examples/monorepo/packages/alpha/build.zig.zon
@@ -0,0 +1,8 @@
+.{
+ .name = .alpha,
+ .version = "0.1.0",
+ .fingerprint = 0xd0e0396aca382c40,
+ .minimum_zig_version = "0.16.0",
+ .dependencies = .{},
+ .paths = .{ "build.zig", "build.zig.zon", "src" },
+}
diff --git a/examples/monorepo/packages/alpha/src/lib.zig b/examples/monorepo/packages/alpha/src/lib.zig
new file mode 100644
index 0000000..2aa7716
--- /dev/null
+++ b/examples/monorepo/packages/alpha/src/lib.zig
@@ -0,0 +1,23 @@
+const std = @import("std");
+
+/// Double a value.
+pub fn double(x: i32) i32 {
+ return x * 2;
+}
+
+/// Square a value.
+pub fn square(x: i32) i32 {
+ return x * x;
+}
+
+test "alpha: double" {
+ try std.testing.expectEqual(@as(i32, 8), double(4));
+ try std.testing.expectEqual(@as(i32, -6), double(-3));
+ try std.testing.expectEqual(@as(i32, 0), double(0));
+}
+
+test "alpha: square" {
+ try std.testing.expectEqual(@as(i32, 9), square(3));
+ try std.testing.expectEqual(@as(i32, 9), square(-3));
+ try std.testing.expectEqual(@as(i32, 0), square(0));
+}
diff --git a/examples/monorepo/packages/beta/build.zig b/examples/monorepo/packages/beta/build.zig
new file mode 100644
index 0000000..fa4c1cd
--- /dev/null
+++ b/examples/monorepo/packages/beta/build.zig
@@ -0,0 +1,17 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lib_tests = b.addTest(.{
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/lib.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
+ });
+ const run_tests = b.addRunArtifact(lib_tests);
+ const test_step = b.step("test", "Run beta tests");
+ test_step.dependOn(&run_tests.step);
+}
diff --git a/examples/monorepo/packages/beta/build.zig.zon b/examples/monorepo/packages/beta/build.zig.zon
new file mode 100644
index 0000000..94cccf7
--- /dev/null
+++ b/examples/monorepo/packages/beta/build.zig.zon
@@ -0,0 +1,8 @@
+.{
+ .name = .beta,
+ .version = "0.1.0",
+ .fingerprint = 0x8f910463cee85531,
+ .minimum_zig_version = "0.16.0",
+ .dependencies = .{},
+ .paths = .{ "build.zig", "build.zig.zon", "src" },
+}
diff --git a/examples/monorepo/packages/beta/src/lib.zig b/examples/monorepo/packages/beta/src/lib.zig
new file mode 100644
index 0000000..690b38c
--- /dev/null
+++ b/examples/monorepo/packages/beta/src/lib.zig
@@ -0,0 +1,44 @@
+// findNearestZigBuildDir walk-up test:
+//
+// When this file is the active editor in Nova, the "Current Zig File" clean
+// action calls findNearestZigBuildDir starting at packages/beta/src/.
+// It finds packages/beta/build.zig after one walk step and uses that
+// directory as the working directory — NOT the monorepo root build.zig.
+//
+// To verify: open this file, trigger clean, observe the task console shows
+// the beta package directory, not the monorepo root.
+const std = @import("std");
+
+/// Halve a value (integer division, truncated toward zero).
+pub fn halve(x: i32) i32 {
+ return @divTrunc(x, 2);
+}
+
+/// Negate a value.
+pub fn negate(x: i32) i32 {
+ return -x;
+}
+
+/// Absolute value.
+pub fn abs(x: i32) i32 {
+ return if (x < 0) -x else x;
+}
+
+test "beta: halve" {
+ try std.testing.expectEqual(@as(i32, 3), halve(6));
+ try std.testing.expectEqual(@as(i32, 3), halve(7)); // truncated
+ try std.testing.expectEqual(@as(i32, -2), halve(-5));
+ try std.testing.expectEqual(@as(i32, 0), halve(0));
+}
+
+test "beta: negate" {
+ try std.testing.expectEqual(@as(i32, -7), negate(7));
+ try std.testing.expectEqual(@as(i32, 7), negate(-7));
+ try std.testing.expectEqual(@as(i32, 0), negate(0));
+}
+
+test "beta: abs" {
+ try std.testing.expectEqual(@as(i32, 5), abs(5));
+ try std.testing.expectEqual(@as(i32, 5), abs(-5));
+ try std.testing.expectEqual(@as(i32, 0), abs(0));
+}
diff --git a/examples/monorepo/src/main.zig b/examples/monorepo/src/main.zig
new file mode 100644
index 0000000..667259e
--- /dev/null
+++ b/examples/monorepo/src/main.zig
@@ -0,0 +1,9 @@
+const std = @import("std");
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+ var buf: [256]u8 = undefined;
+ var fw: std.Io.File.Writer = .init(.stdout(), io, &buf);
+ defer fw.interface.flush() catch {};
+ try fw.interface.print("monorepo root\n", .{});
+}
diff --git a/examples/multi-step/.gitignore b/examples/multi-step/.gitignore
new file mode 100644
index 0000000..3389c86
--- /dev/null
+++ b/examples/multi-step/.gitignore
@@ -0,0 +1,2 @@
+.zig-cache/
+zig-out/
diff --git a/examples/multi-step/.nova/Configuration.json b/examples/multi-step/.nova/Configuration.json
new file mode 100644
index 0000000..44cadf8
--- /dev/null
+++ b/examples/multi-step/.nova/Configuration.json
@@ -0,0 +1,4 @@
+{
+ "zls.enable_build_on_save" : false,
+ "zls.zig_exe_path" : "\/opt\/homebrew\/bin\/zig"
+}
diff --git a/examples/multi-step/.nova/Tasks/Zig Package.json b/examples/multi-step/.nova/Tasks/Zig Package.json
new file mode 100644
index 0000000..db6905c
--- /dev/null
+++ b/examples/multi-step/.nova/Tasks/Zig Package.json
@@ -0,0 +1,10 @@
+{
+ "extension" : {
+ "identifier" : "at.dcz.nova-zig",
+ "name" : "Zig"
+ },
+ "extensionTemplate" : "zigBuildRun",
+ "extensionValues" : {
+ "runStep" : "run"
+ }
+}
diff --git a/examples/multi-step/.nova/Tasks/Zig Test.json b/examples/multi-step/.nova/Tasks/Zig Test.json
new file mode 100644
index 0000000..7517ee7
--- /dev/null
+++ b/examples/multi-step/.nova/Tasks/Zig Test.json
@@ -0,0 +1,10 @@
+{
+ "extension" : {
+ "identifier" : "at.dcz.nova-zig",
+ "name" : "Zig"
+ },
+ "extensionTemplate" : "zigTest",
+ "extensionValues" : {
+ "summary" : "failures"
+ }
+}
diff --git a/examples/multi-step/.nova/Tasks/Zig Watch.json b/examples/multi-step/.nova/Tasks/Zig Watch.json
new file mode 100644
index 0000000..aed951a
--- /dev/null
+++ b/examples/multi-step/.nova/Tasks/Zig Watch.json
@@ -0,0 +1,11 @@
+{
+ "extension" : {
+ "identifier" : "at.dcz.nova-zig",
+ "name" : "Zig"
+ },
+ "extensionTemplate" : "zigWatch",
+ "extensionValues" : {
+ "incremental" : "on",
+ "step" : "run"
+ }
+}
diff --git a/examples/multi-step/build.zig b/examples/multi-step/build.zig
new file mode 100644
index 0000000..f9a2d3d
--- /dev/null
+++ b/examples/multi-step/build.zig
@@ -0,0 +1,96 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lib_mod = b.addModule("multi_step", .{
+ .root_source_file = b.path("src/lib.zig"),
+ .target = target,
+ });
+
+ const exe = b.addExecutable(.{
+ .name = "multi-step",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "multi_step", .module = lib_mod },
+ },
+ }),
+ });
+ b.installArtifact(exe);
+
+ // ── Step: run ──────────────────────────────────────────────────────────────
+ const run_cmd = b.addRunArtifact(exe);
+ if (b.args) |args| run_cmd.addArgs(args);
+ const run_step = b.step("run", "Build and run the multi-step demo");
+ run_step.dependOn(&run_cmd.step);
+
+ // ── Step: test ─────────────────────────────────────────────────────────────
+ const lib_tests = b.addTest(.{ .root_module = lib_mod });
+ const run_tests = b.addRunArtifact(lib_tests);
+ const test_step = b.step("test", "Run all unit tests");
+ test_step.dependOn(&run_tests.step);
+
+ // ── Step: check-fmt (hyphenated) ───────────────────────────────────────────
+ const fmt_check = b.addFmt(.{
+ .paths = &.{ "src", "build.zig" },
+ .check = true,
+ });
+ const fmt_step = b.step("check-fmt", "Verify source formatting without modifying files");
+ fmt_step.dependOn(&fmt_check.step);
+
+ // ── Step: gen-docs (hyphenated) ────────────────────────────────────────────
+ const docs = b.addInstallDirectory(.{
+ .source_dir = exe.getEmittedDocs(),
+ .install_dir = .prefix,
+ .install_subdir = "docs",
+ });
+ const docs_step = b.step("gen-docs", "Generate and install HTML documentation");
+ docs_step.dependOn(&docs.step);
+
+ // ── Step: run_server (underscore) ──────────────────────────────────────────
+ const server_cmd = b.addRunArtifact(exe);
+ server_cmd.addArg("--server");
+ const server_step = b.step("run_server", "Run the executable in server mode");
+ server_step.dependOn(&server_cmd.step);
+
+ // ── Step: bench (short name, ReleaseFast) ──────────────────────────────────
+ const bench_exe = b.addExecutable(.{
+ .name = "bench",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = .ReleaseFast,
+ .imports = &.{
+ .{ .name = "multi_step", .module = lib_mod },
+ },
+ }),
+ });
+ const bench_cmd = b.addRunArtifact(bench_exe);
+ bench_cmd.addArg("--bench");
+ const bench_step = b.step("bench", "Build and run benchmarks (ReleaseFast)");
+ bench_step.dependOn(&bench_cmd.step);
+
+ // ── Step: release-macos (compound hyphenated, cross-compile) ───────────────
+ const macos_target = b.resolveTargetQuery(.{
+ .cpu_arch = .aarch64,
+ .os_tag = .macos,
+ });
+ const release_exe = b.addExecutable(.{
+ .name = "multi-step",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = macos_target,
+ .optimize = .ReleaseSmall,
+ .imports = &.{
+ .{ .name = "multi_step", .module = lib_mod },
+ },
+ }),
+ });
+ const release_install = b.addInstallArtifact(release_exe, .{});
+ const release_step = b.step("release-macos", "Build a ReleaseSmall binary for aarch64-macos");
+ release_step.dependOn(&release_install.step);
+}
diff --git a/examples/multi-step/build.zig.zon b/examples/multi-step/build.zig.zon
new file mode 100644
index 0000000..16f0449
--- /dev/null
+++ b/examples/multi-step/build.zig.zon
@@ -0,0 +1,12 @@
+.{
+ .name = .multi_step,
+ .version = "0.1.0",
+ .fingerprint = 0x15810748d919d76b,
+ .minimum_zig_version = "0.16.0",
+ .dependencies = .{},
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/multi-step/src/lib.zig b/examples/multi-step/src/lib.zig
new file mode 100644
index 0000000..ea244c1
--- /dev/null
+++ b/examples/multi-step/src/lib.zig
@@ -0,0 +1,67 @@
+const std = @import("std");
+
+/// Returns the sum of a slice of i32 values.
+pub fn sum(values: []const i32) i64 {
+ var total: i64 = 0;
+ for (values) |v| total += v;
+ return total;
+}
+
+/// Returns the product of a slice of i32 values.
+/// Returns 1 for an empty slice (identity element).
+pub fn product(values: []const i32) i64 {
+ var result: i64 = 1;
+ for (values) |v| result *= v;
+ return result;
+}
+
+/// Returns the minimum value, or null for an empty slice.
+pub fn min(values: []const i32) ?i32 {
+ if (values.len == 0) return null;
+ var m = values[0];
+ for (values[1..]) |v| {
+ if (v < m) m = v;
+ }
+ return m;
+}
+
+/// Returns the maximum value, or null for an empty slice.
+pub fn max(values: []const i32) ?i32 {
+ if (values.len == 0) return null;
+ var m = values[0];
+ for (values[1..]) |v| {
+ if (v > m) m = v;
+ }
+ return m;
+}
+
+test "sum empty" {
+ try std.testing.expectEqual(@as(i64, 0), sum(&.{}));
+}
+
+test "sum positive values" {
+ try std.testing.expectEqual(@as(i64, 15), sum(&.{ 1, 2, 3, 4, 5 }));
+}
+
+test "sum with negatives" {
+ try std.testing.expectEqual(@as(i64, 0), sum(&.{ -3, -2, -1, 0, 1, 2, 3 }));
+}
+
+test "product empty" {
+ try std.testing.expectEqual(@as(i64, 1), product(&.{}));
+}
+
+test "product small" {
+ try std.testing.expectEqual(@as(i64, 120), product(&.{ 1, 2, 3, 4, 5 }));
+}
+
+test "min and max" {
+ const data = [_]i32{ 3, 1, 4, 1, 5, 9, 2, 6 };
+ try std.testing.expectEqual(@as(?i32, 1), min(&data));
+ try std.testing.expectEqual(@as(?i32, 9), max(&data));
+}
+
+test "min max empty" {
+ try std.testing.expectEqual(@as(?i32, null), min(&.{}));
+ try std.testing.expectEqual(@as(?i32, null), max(&.{}));
+}
diff --git a/examples/multi-step/src/main.zig b/examples/multi-step/src/main.zig
new file mode 100644
index 0000000..a984b18
--- /dev/null
+++ b/examples/multi-step/src/main.zig
@@ -0,0 +1,35 @@
+const std = @import("std");
+const lib = @import("multi_step");
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+ var buf: [4096]u8 = undefined;
+ var fw: std.Io.File.Writer = .init(.stdout(), io, &buf);
+ defer fw.interface.flush() catch {};
+ const w = &fw.interface;
+
+ const args = try init.minimal.args.toSlice(init.arena.allocator());
+ const mode = if (args.len > 1) args[1] else "";
+
+ if (std.mem.eql(u8, mode, "--server")) {
+ try w.print("Server mode (simulated)\n", .{});
+ return;
+ }
+
+ if (std.mem.eql(u8, mode, "--bench")) {
+ const data = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ var i: u32 = 0;
+ while (i < 1_000_000) : (i += 1) {
+ _ = lib.sum(&data);
+ }
+ try w.print("bench: 1M sum() calls completed\n", .{});
+ return;
+ }
+
+ const data = [_]i32{ 10, 20, 30, 40, 50 };
+ try w.print("data: {any}\n", .{data});
+ try w.print("sum = {d}\n", .{lib.sum(&data)});
+ try w.print("product = {d}\n", .{lib.product(&data)});
+ try w.print("min = {?d}\n", .{lib.min(&data)});
+ try w.print("max = {?d}\n", .{lib.max(&data)});
+}
diff --git a/examples/snippets/.nova/Configuration.json b/examples/snippets/.nova/Configuration.json
new file mode 100644
index 0000000..44cadf8
--- /dev/null
+++ b/examples/snippets/.nova/Configuration.json
@@ -0,0 +1,4 @@
+{
+ "zls.enable_build_on_save" : false,
+ "zls.zig_exe_path" : "\/opt\/homebrew\/bin\/zig"
+}
diff --git a/examples/snippets/error-example.zig b/examples/snippets/error-example.zig
new file mode 100644
index 0000000..f35c684
--- /dev/null
+++ b/examples/snippets/error-example.zig
@@ -0,0 +1,19 @@
+// INVALID FILE — used to test the Nova issue matcher.
+//
+// Open this file and run it with the "Current Zig File" task (zig run).
+// The compiler will report an error that the issue matcher captures:
+//
+// error-example.zig:16:23: error: type 'u8' cannot represent integer value '300'
+//
+// This verifies that the regexp ^(.+?):(\d+):(\d+):\s*(error|warning):\s*(.+)$
+// correctly extracts file, line, column, severity, and message.
+
+const std = @import("std");
+
+pub fn main() void {
+ // The literal 300 does not fit in a u8 (max 255). Zig catches
+ // this at compile time and reports an error at this line.
+ const value: u8 = 300;
+ _ = value;
+ _ = std.io.getStdOut();
+}
diff --git a/examples/snippets/fibonacci.zig b/examples/snippets/fibonacci.zig
new file mode 100644
index 0000000..327bbd3
--- /dev/null
+++ b/examples/snippets/fibonacci.zig
@@ -0,0 +1,41 @@
+const std = @import("std");
+
+/// Compute the nth Fibonacci number iteratively.
+pub fn fibonacci(n: u64) u64 {
+ if (n <= 1) return n;
+ var a: u64 = 0;
+ var b: u64 = 1;
+ var i: u64 = 2;
+ while (i <= n) : (i += 1) {
+ const next = a + b;
+ a = b;
+ b = next;
+ }
+ return b;
+}
+
+pub fn main(init: std.process.Init) !void {
+ const io = init.io;
+ var buf: [4096]u8 = undefined;
+ var fw: std.Io.File.Writer = .init(.stdout(), io, &buf);
+ defer fw.interface.flush() catch {};
+ const w = &fw.interface;
+
+ try w.print("Fibonacci sequence (first 12 terms):\n", .{});
+ for (0..12) |n| {
+ try w.print(" fib({d}) = {d}\n", .{ n, fibonacci(n) });
+ }
+}
+
+test "fibonacci base cases" {
+ try std.testing.expectEqual(@as(u64, 0), fibonacci(0));
+ try std.testing.expectEqual(@as(u64, 1), fibonacci(1));
+ try std.testing.expectEqual(@as(u64, 1), fibonacci(2));
+}
+
+test "fibonacci sequence" {
+ const expected = [_]u64{ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };
+ for (expected, 0..) |v, n| {
+ try std.testing.expectEqual(v, fibonacci(n));
+ }
+}
diff --git a/examples/snippets/syntax-tour.zig b/examples/snippets/syntax-tour.zig
new file mode 100644
index 0000000..4c29588
--- /dev/null
+++ b/examples/snippets/syntax-tour.zig
@@ -0,0 +1,283 @@
+//! Root-level doc comment (module doc).
+//! This file exercises every syntax category for highlighting verification.
+//! Open in Nova and check that all constructs colour correctly.
+
+const std = @import("std");
+
+// ── Keywords: pub, const, var, comptime, extern, export, inline, noinline ──────
+
+/// Doc comment on a public constant.
+pub const VERSION: []const u8 = "0.15.0";
+
+pub var mutable_counter: u32 = 0;
+
+// ── Enums ──────────────────────────────────────────────────────────────────────
+
+pub const Color = enum(u8) {
+ red = 0,
+ green = 1,
+ blue = 2,
+
+ pub fn toHex(self: Color) u24 {
+ return switch (self) {
+ .red => 0xFF0000,
+ .green => 0x00FF00,
+ .blue => 0x0000FF,
+ };
+ }
+};
+
+// ── Structs with default field values ─────────────────────────────────────────
+
+pub const Point = struct {
+ x: f64 = 0.0,
+ y: f64 = 0.0,
+
+ pub fn distanceTo(self: Point, other: Point) f64 {
+ const dx = self.x - other.x;
+ const dy = self.y - other.y;
+ return std.math.sqrt(dx * dx + dy * dy);
+ }
+
+ /// Generic format method — tests `anytype` keyword.
+ pub fn format(self: Point, comptime fmt: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
+ _ = fmt;
+ try writer.print("({d:.2}, {d:.2})", .{ self.x, self.y });
+ }
+};
+
+// ── Tagged union ───────────────────────────────────────────────────────────────
+
+pub const Number = union(enum) {
+ integer: i64,
+ float: f64,
+
+ pub fn isZero(self: Number) bool {
+ return switch (self) {
+ .integer => |v| v == 0,
+ .float => |v| v == 0.0,
+ };
+ }
+};
+
+// ── Opaque type ───────────────────────────────────────────────────────────────
+
+pub const Handle = opaque {};
+
+// ── Packed struct ─────────────────────────────────────────────────────────────
+
+pub const Flags = packed struct {
+ enabled: bool,
+ verbose: bool,
+ debug: bool,
+ _pad: u5 = 0,
+};
+
+// ── Comptime ──────────────────────────────────────────────────────────────────
+
+pub fn typeName(comptime T: type) []const u8 {
+ return @typeName(T);
+}
+
+/// Comptime block with labeled break.
+pub const BITS_IN_BYTE: comptime_int = blk: {
+ var n: u8 = 255;
+ var count: usize = 0;
+ while (n > 0) : (n >>= 1) count += 1;
+ break :blk count;
+};
+
+// ── Generics ──────────────────────────────────────────────────────────────────
+
+pub fn Stack(comptime T: type) type {
+ return struct {
+ items: std.ArrayList(T) = .empty,
+
+ pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
+ self.items.deinit(allocator);
+ }
+
+ pub fn push(self: *@This(), allocator: std.mem.Allocator, value: T) !void {
+ try self.items.append(allocator, value);
+ }
+
+ pub fn pop(self: *@This()) ?T {
+ return self.items.pop();
+ }
+ };
+}
+
+// ── Builtin functions ─────────────────────────────────────────────────────────
+
+pub fn builtinDemo(value: i32) u32 {
+ const as_u32: u32 = @intCast(value);
+ const as_f32: f32 = @floatFromInt(value);
+ _ = as_f32;
+ _ = @sizeOf(u64);
+ _ = @alignOf(*u8);
+ _ = @typeInfo(Color);
+ return as_u32;
+}
+
+// ── String literal variants ───────────────────────────────────────────────────
+
+pub const PLAIN: []const u8 = "hello, Nova!";
+
+pub const ESCAPE: []const u8 = "tab:\there\nnewline\x00null";
+
+/// Multiline string — each line starts with `\\`.
+pub const MULTILINE: []const u8 =
+ \\first line
+ \\second line
+ \\third line
+;
+
+pub const CHAR_LITERAL: u8 = 'Z';
+pub const UNICODE_ESCAPE: []const u8 = "\u{2744}"; // ❄
+pub const HEX_ESCAPE: u8 = '\xff';
+
+// ── Error sets and error union ─────────────────────────────────────────────────
+
+pub const ParseError = error{
+ UnexpectedToken,
+ EndOfStream,
+ Overflow,
+};
+
+pub fn parseU8(s: []const u8) ParseError!u8 {
+ if (s.len == 0) return error.EndOfStream;
+ return std.fmt.parseInt(u8, s, 10) catch error.UnexpectedToken;
+}
+
+// ── Optional / orelse / if capture ───────────────────────────────────────────
+
+pub fn firstByte(s: []const u8) ?u8 {
+ return if (s.len == 0) null else s[0];
+}
+
+pub fn firstByteOr(s: []const u8, fallback: u8) u8 {
+ return firstByte(s) orelse fallback;
+}
+
+// ── Switch with integer ranges ────────────────────────────────────────────────
+
+pub fn classify(n: i32) []const u8 {
+ return switch (n) {
+ std.math.minInt(i32)...-1 => "negative",
+ 0 => "zero",
+ 1...100 => "small positive",
+ else => "large positive",
+ };
+}
+
+// ── For with index, multi-sequence ────────────────────────────────────────────
+
+pub fn sumSlice(items: []const i32) i64 {
+ var total: i64 = 0;
+ for (items, 0..) |item, idx| {
+ _ = idx;
+ total += item;
+ }
+ return total;
+}
+
+// ── While with continue expression ───────────────────────────────────────────
+
+pub fn countDown(start: u32) u32 {
+ var i = start;
+ var steps: u32 = 0;
+ while (i > 0) : (i -= 1) steps += 1;
+ return steps;
+}
+
+// ── Defer / errdefer ─────────────────────────────────────────────────────────
+
+pub fn withDefer(allocator: std.mem.Allocator) ![]u8 {
+ const buf = try allocator.alloc(u8, 16);
+ errdefer allocator.free(buf);
+ @memset(buf, 0);
+ return buf;
+}
+
+// ── Pointer types, alignment ──────────────────────────────────────────────────
+
+pub fn ptrDemo(p: *align(8) u64) u64 {
+ return p.*;
+}
+
+// ── inline / noinline ─────────────────────────────────────────────────────────
+
+pub inline fn inlineAdd(a: u32, b: u32) u32 {
+ return a + b;
+}
+
+pub noinline fn noinlineAdd(a: u32, b: u32) u32 {
+ return a + b;
+}
+
+// ── threadlocal ───────────────────────────────────────────────────────────────
+
+threadlocal var tl_counter: u32 = 0;
+
+pub fn bumpCounter() void {
+ tl_counter += 1;
+}
+
+// ── extern ────────────────────────────────────────────────────────────────────
+
+extern fn c_strlen(ptr: [*:0]const u8) usize;
+
+// ── Inline assembly ───────────────────────────────────────────────────────────
+
+pub fn nop() void {
+ asm volatile ("nop");
+}
+
+// ── Tests ─────────────────────────────────────────────────────────────────────
+
+test "syntax-tour: classify" {
+ try std.testing.expectEqualStrings("zero", classify(0));
+ try std.testing.expectEqualStrings("negative", classify(-5));
+ try std.testing.expectEqualStrings("small positive", classify(42));
+ try std.testing.expectEqualStrings("large positive", classify(200));
+}
+
+test "syntax-tour: sumSlice" {
+ try std.testing.expectEqual(@as(i64, 15), sumSlice(&.{ 1, 2, 3, 4, 5 }));
+ try std.testing.expectEqual(@as(i64, 0), sumSlice(&.{}));
+}
+
+test "syntax-tour: parseU8 errors" {
+ try std.testing.expectError(error.EndOfStream, parseU8(""));
+ try std.testing.expectError(error.UnexpectedToken, parseU8("abc"));
+}
+
+test "syntax-tour: firstByte" {
+ try std.testing.expectEqual(@as(?u8, 'h'), firstByte("hello"));
+ try std.testing.expectEqual(@as(?u8, null), firstByte(""));
+}
+
+test "syntax-tour: Color.toHex" {
+ try std.testing.expectEqual(@as(u24, 0xFF0000), Color.red.toHex());
+ try std.testing.expectEqual(@as(u24, 0x0000FF), Color.blue.toHex());
+}
+
+test "syntax-tour: Number.isZero" {
+ try std.testing.expect((Number{ .integer = 0 }).isZero());
+ try std.testing.expect(!(Number{ .float = 3.14 }).isZero());
+}
+
+test "syntax-tour: Stack(i32)" {
+ const gpa = std.testing.allocator;
+ var s = Stack(i32){};
+ defer s.deinit(gpa);
+ try s.push(gpa, 10);
+ try s.push(gpa, 20);
+ try std.testing.expectEqual(@as(?i32, 20), s.pop());
+ try std.testing.expectEqual(@as(?i32, 10), s.pop());
+ try std.testing.expectEqual(@as(?i32, null), s.pop());
+}
+
+test "syntax-tour: BITS_IN_BYTE" {
+ try std.testing.expectEqual(@as(comptime_int, 8), BITS_IN_BYTE);
+}
diff --git a/examples/test-suite/.gitignore b/examples/test-suite/.gitignore
new file mode 100644
index 0000000..3389c86
--- /dev/null
+++ b/examples/test-suite/.gitignore
@@ -0,0 +1,2 @@
+.zig-cache/
+zig-out/
diff --git a/examples/test-suite/.nova/Configuration.json b/examples/test-suite/.nova/Configuration.json
new file mode 100644
index 0000000..44cadf8
--- /dev/null
+++ b/examples/test-suite/.nova/Configuration.json
@@ -0,0 +1,4 @@
+{
+ "zls.enable_build_on_save" : false,
+ "zls.zig_exe_path" : "\/opt\/homebrew\/bin\/zig"
+}
diff --git a/examples/test-suite/.nova/Tasks/Zig Test (all).json b/examples/test-suite/.nova/Tasks/Zig Test (all).json
new file mode 100644
index 0000000..44edb03
--- /dev/null
+++ b/examples/test-suite/.nova/Tasks/Zig Test (all).json
@@ -0,0 +1,10 @@
+{
+ "extension" : {
+ "identifier" : "at.dcz.nova-zig",
+ "name" : "Zig"
+ },
+ "extensionTemplate" : "zigTest",
+ "extensionValues" : {
+ "summary" : "all"
+ }
+}
diff --git a/examples/test-suite/.nova/Tasks/Zig Test (math).json b/examples/test-suite/.nova/Tasks/Zig Test (math).json
new file mode 100644
index 0000000..a4d1580
--- /dev/null
+++ b/examples/test-suite/.nova/Tasks/Zig Test (math).json
@@ -0,0 +1,11 @@
+{
+ "extension" : {
+ "identifier" : "at.dcz.nova-zig",
+ "name" : "Zig"
+ },
+ "extensionTemplate" : "zigTest",
+ "extensionValues" : {
+ "summary" : "all",
+ "testFilter" : "math:"
+ }
+}
diff --git a/examples/test-suite/build.zig b/examples/test-suite/build.zig
new file mode 100644
index 0000000..3b723e1
--- /dev/null
+++ b/examples/test-suite/build.zig
@@ -0,0 +1,41 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const math_mod = b.addModule("math", .{
+ .root_source_file = b.path("src/math.zig"),
+ .target = target,
+ });
+
+ const strings_mod = b.addModule("strings", .{
+ .root_source_file = b.path("src/strings.zig"),
+ .target = target,
+ });
+
+ const math_tests = b.addTest(.{
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/math.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
+ });
+ const strings_tests = b.addTest(.{
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/strings.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
+ });
+
+ _ = math_mod;
+ _ = strings_mod;
+
+ const run_math = b.addRunArtifact(math_tests);
+ const run_strings = b.addRunArtifact(strings_tests);
+
+ const test_step = b.step("test", "Run all tests");
+ test_step.dependOn(&run_math.step);
+ test_step.dependOn(&run_strings.step);
+}
diff --git a/examples/test-suite/build.zig.zon b/examples/test-suite/build.zig.zon
new file mode 100644
index 0000000..3d11b66
--- /dev/null
+++ b/examples/test-suite/build.zig.zon
@@ -0,0 +1,12 @@
+.{
+ .name = .test_suite,
+ .version = "0.1.0",
+ .fingerprint = 0x1ee2422d1c238201,
+ .minimum_zig_version = "0.16.0",
+ .dependencies = .{},
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/test-suite/src/math.zig b/examples/test-suite/src/math.zig
new file mode 100644
index 0000000..ef870ce
--- /dev/null
+++ b/examples/test-suite/src/math.zig
@@ -0,0 +1,70 @@
+const std = @import("std");
+
+pub fn factorial(n: u64) u64 {
+ if (n == 0) return 1;
+ return n * factorial(n - 1);
+}
+
+pub fn isPrime(n: u64) bool {
+ if (n < 2) return false;
+ if (n == 2) return true;
+ if (n % 2 == 0) return false;
+ var i: u64 = 3;
+ while (i * i <= n) : (i += 2) {
+ if (n % i == 0) return false;
+ }
+ return true;
+}
+
+pub fn gcd(a: u64, b: u64) u64 {
+ var x = a;
+ var y = b;
+ while (y != 0) {
+ const t = y;
+ y = x % y;
+ x = t;
+ }
+ return x;
+}
+
+// ── Tests with "math: " prefix ────────────────────────────────────────────────
+// Run all: zig build test
+// Run math only: zig build test -- --test-filter "math:"
+
+test "math: factorial base cases" {
+ try std.testing.expectEqual(@as(u64, 1), factorial(0));
+ try std.testing.expectEqual(@as(u64, 1), factorial(1));
+ try std.testing.expectEqual(@as(u64, 2), factorial(2));
+}
+
+test "math: factorial larger values" {
+ try std.testing.expectEqual(@as(u64, 120), factorial(5));
+ try std.testing.expectEqual(@as(u64, 5040), factorial(7));
+ try std.testing.expectEqual(@as(u64, 3628800), factorial(10));
+}
+
+test "math: isPrime small primes" {
+ try std.testing.expect(isPrime(2));
+ try std.testing.expect(isPrime(3));
+ try std.testing.expect(isPrime(5));
+ try std.testing.expect(isPrime(97));
+}
+
+test "math: isPrime composites and edge cases" {
+ try std.testing.expect(!isPrime(0));
+ try std.testing.expect(!isPrime(1));
+ try std.testing.expect(!isPrime(4));
+ try std.testing.expect(!isPrime(100));
+}
+
+test "math: gcd basics" {
+ try std.testing.expectEqual(@as(u64, 6), gcd(12, 18));
+ try std.testing.expectEqual(@as(u64, 1), gcd(7, 13));
+ try std.testing.expectEqual(@as(u64, 5), gcd(5, 0));
+ try std.testing.expectEqual(@as(u64, 5), gcd(0, 5));
+}
+
+// Deliberately named differently — filtered OUT by "--test-filter math:"
+test "number theory: Euclid handles equal inputs" {
+ try std.testing.expectEqual(@as(u64, 42), gcd(42, 42));
+}
diff --git a/examples/test-suite/src/strings.zig b/examples/test-suite/src/strings.zig
new file mode 100644
index 0000000..ba53f8c
--- /dev/null
+++ b/examples/test-suite/src/strings.zig
@@ -0,0 +1,86 @@
+const std = @import("std");
+
+/// Trim leading and trailing ASCII whitespace.
+pub fn trim(s: []const u8) []const u8 {
+ return std.mem.trim(u8, s, " \t\r\n");
+}
+
+/// Count non-overlapping occurrences of `needle` in `haystack`.
+pub fn countOccurrences(haystack: []const u8, needle: []const u8) usize {
+ if (needle.len == 0) return 0;
+ var count: usize = 0;
+ var i: usize = 0;
+ while (i + needle.len <= haystack.len) {
+ if (std.mem.eql(u8, haystack[i .. i + needle.len], needle)) {
+ count += 1;
+ i += needle.len;
+ } else {
+ i += 1;
+ }
+ }
+ return count;
+}
+
+/// Reverse a string byte-by-byte (ASCII safe; not grapheme-safe).
+pub fn reverseAlloc(allocator: std.mem.Allocator, s: []const u8) ![]u8 {
+ const result = try allocator.alloc(u8, s.len);
+ for (s, 0..) |byte, i| result[s.len - 1 - i] = byte;
+ return result;
+}
+
+/// Returns true if `s` starts with `prefix`.
+pub fn startsWith(s: []const u8, prefix: []const u8) bool {
+ return std.mem.startsWith(u8, s, prefix);
+}
+
+// ── Tests with "strings: " prefix ────────────────────────────────────────────
+// Run all: zig build test
+// Run strings only: zig build test -- --test-filter "strings:"
+
+test "strings: trim removes spaces" {
+ try std.testing.expectEqualStrings("hello", trim(" hello "));
+}
+
+test "strings: trim handles empty and whitespace-only" {
+ try std.testing.expectEqualStrings("", trim(""));
+ try std.testing.expectEqualStrings("", trim(" \t\n"));
+}
+
+test "strings: trim preserves inner whitespace" {
+ try std.testing.expectEqualStrings("hello world", trim(" hello world "));
+}
+
+test "strings: countOccurrences basic" {
+ try std.testing.expectEqual(@as(usize, 3), countOccurrences("abcabcabc", "abc"));
+}
+
+test "strings: countOccurrences empty needle" {
+ try std.testing.expectEqual(@as(usize, 0), countOccurrences("hello", ""));
+}
+
+test "strings: countOccurrences no match" {
+ try std.testing.expectEqual(@as(usize, 0), countOccurrences("hello", "xyz"));
+}
+
+test "strings: countOccurrences overlapping boundary" {
+ // Non-overlapping: "aa" in "aaaa" = 2
+ try std.testing.expectEqual(@as(usize, 2), countOccurrences("aaaa", "aa"));
+}
+
+test "strings: reverseAlloc" {
+ const reversed = try reverseAlloc(std.testing.allocator, "hello");
+ defer std.testing.allocator.free(reversed);
+ try std.testing.expectEqualStrings("olleh", reversed);
+}
+
+test "strings: reverseAlloc empty" {
+ const reversed = try reverseAlloc(std.testing.allocator, "");
+ defer std.testing.allocator.free(reversed);
+ try std.testing.expectEqualStrings("", reversed);
+}
+
+test "strings: startsWith" {
+ try std.testing.expect(startsWith("hello world", "hello"));
+ try std.testing.expect(!startsWith("hello world", "world"));
+ try std.testing.expect(startsWith("abc", ""));
+}
diff --git a/examples/zon-examples/full-types.zon b/examples/zon-examples/full-types.zon
new file mode 100644
index 0000000..5a06044
--- /dev/null
+++ b/examples/zon-examples/full-types.zon
@@ -0,0 +1,74 @@
+// ZON full-type showcase — exercises all value forms the ZON grammar supports.
+// Open in Nova to verify syntax highlighting across every literal kind.
+.{
+ // String (double-quoted)
+ .name = "nova-zig-full",
+
+ // Integers — decimal, hex, octal, binary, with digit separators
+ .decimal = 42,
+ .hex = 0xFF,
+ .octal = 0o77,
+ .binary = 0b1010_1010,
+ .large = 1_000_000,
+
+ // Floats
+ .pi = 3.14159,
+ .scientific = 1.5e10,
+ .negative_float = -0.001,
+
+ // Booleans
+ .flag_on = true,
+ .flag_off = false,
+
+ // Null
+ .optional = null,
+
+ // Enum tag / identifier literal (dot-prefixed)
+ .level = .info,
+ .theme = .solarized,
+
+ // Negative integer
+ .offset = -7,
+
+ // Nested struct
+ .metadata = .{
+ .author = "David",
+ .year = 2025,
+ .prerelease = false,
+ },
+
+ // Array of strings
+ .targets = .{
+ "x86_64-linux",
+ "aarch64-macos",
+ "wasm32-wasi",
+ },
+
+ // Array of nested structs
+ .rules = .{
+ .{ .name = "lint", .enabled = true },
+ .{ .name = "format", .enabled = false },
+ .{ .name = "test", .enabled = true },
+ },
+
+ // Deeply nested
+ .config = .{
+ .server = .{
+ .host = "localhost",
+ .port = 8080,
+ .tls = .{
+ .enabled = false,
+ .cert = null,
+ },
+ },
+ },
+
+ // Unicode string
+ .label = "Zig \u{2744} Nova",
+
+ // Empty struct
+ .empty = .{},
+
+ // Empty array
+ .no_items = .{},
+}
diff --git a/examples/zon-examples/invalid.zon b/examples/zon-examples/invalid.zon
new file mode 100644
index 0000000..e588d73
--- /dev/null
+++ b/examples/zon-examples/invalid.zon
@@ -0,0 +1,12 @@
+// INVALID ZON — used to test error reporting on .zon files.
+//
+// ZON struct fields must begin with a dot followed by an identifier.
+// The bare key `name` on line 8 (no leading dot) is invalid syntax.
+// Any tool that parses this file (ZLS, zig build, zig fmt) should
+// report an error around that line, exercising the issue matcher on
+// non-.zig source files.
+.{
+ name = "missing-dot",
+ .value = 1,
+ .ok = true,
+}
diff --git a/examples/zon-examples/minimal.zon b/examples/zon-examples/minimal.zon
new file mode 100644
index 0000000..47c47bc
--- /dev/null
+++ b/examples/zon-examples/minimal.zon
@@ -0,0 +1 @@
+.{}