diff options
Diffstat (limited to 'examples')
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 @@ +.{} |
