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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ Only Zig files need formatting checks for now.
- Zig variables use snake_case.
- Zig types and functions use camelCase.
- Comments are encouraged for compiler/runtime logic, but keep them concise and useful.
- Any non-trivial code added must be properly commented in the code. Comments should explain intent, invariants, or tricky control flow, not restate obvious assignments.
- Any new Zig file under `src/` must start with a file docblock (`//! ...`) describing the general role of the file.
- Any new functions, structs, objects, properties, and enums introduced in Zig or Buzz code must have a docblock.

## Runtime And GC Rules

Expand Down
739 changes: 515 additions & 224 deletions src/Ast.zig

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Codegen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1959,7 +1959,7 @@ fn generateFunction(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?

if (function_type != .ScriptEntryPoint and function_type != .Repl) {
// `extern` functions don't have upvalues
if (function_type == .Extern) {
if (function_type == .Extern and self.flavor.resolveDynLib()) {
try self.OP_CONSTANT(
locations[node],
components.native.?.toValue(),
Expand Down
407 changes: 207 additions & 200 deletions src/Parser.zig

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Reporter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ pub const Error = enum(u8) {
default_value_type = 104,
unexhaustive_match = 105,
match_condition_type = 106,
match_duplicate_condition = 107,
};

// Inspired by https://github.com/zesterer/ariadne
pub const ReportKind = enum {
@"error",
warning,
Expand Down
111 changes: 111 additions & 0 deletions src/TypeChecker.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const o = @import("obj.zig");
const Reporter = @import("Reporter.zig");
const Ast = @import("Ast.zig");
const GC = @import("GC.zig");
const Value = @import("value.zig").Value;
const BuildOptions = @import("build_options");
const io = @import("io.zig");

Expand Down Expand Up @@ -1811,12 +1812,122 @@ fn checkMatch(ast: Ast.Slice, reporter: *Reporter, gc: *GC, _: ?Ast.Node.Index,
condition_type_def.?,
"Bad `match` condition type",
);
had_error = true;
}
}
},
}
}

if (!had_error) {
const numeric_match_value = !value_type_def.optional and
(value_type_def.def_type == .Integer or value_type_def.def_type == .Double);
var seen_values = std.ArrayList(Value).empty;
defer seen_values.deinit(gc.allocator);
var seen_conditions = std.ArrayList(Ast.Node.Index).empty;
defer seen_conditions.deinit(gc.allocator);

for (node_components.branches) |branch| {
for (branch.conditions) |condition| {
const is_constant = ast.isConstant(gc.allocator, condition) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => false,
};
if (!is_constant) {
continue;
}

const condition_value = ast.toValue(condition, gc) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => continue,
};

for (seen_values.items, seen_conditions.items) |seen_value, seen_condition| {
const duplicate_condition = condition_value.eql(seen_value);
var overlapping_condition = false;

// Numeric range matches use rg.contains semantics: normalized half-open intervals.
if (!duplicate_condition and numeric_match_value) {
const condition_type_def = type_defs[condition] orelse continue;
const seen_condition_type_def = type_defs[seen_condition] orelse continue;
const condition_is_range = !condition_type_def.optional and
condition_type_def.def_type == .Range;
const seen_condition_is_range = !seen_condition_type_def.optional and
seen_condition_type_def.def_type == .Range;

if (condition_is_range and seen_condition_is_range) {
const condition_range = o.ObjRange.cast(condition_value.obj()).?;
const seen_range = o.ObjRange.cast(seen_value.obj()).?;
const condition_low = @min(condition_range.low, condition_range.high);
const condition_high = @max(condition_range.low, condition_range.high);
const seen_low = @min(seen_range.low, seen_range.high);
const seen_high = @max(seen_range.low, seen_range.high);

overlapping_condition = @max(condition_low, seen_low) < @min(condition_high, seen_high);
} else if (condition_is_range and seen_value.isNumber()) {
const condition_range = o.ObjRange.cast(condition_value.obj()).?;
const condition_low: f64 = @floatFromInt(@min(condition_range.low, condition_range.high));
const condition_high: f64 = @floatFromInt(@max(condition_range.low, condition_range.high));
const seen_number = if (seen_value.isInteger())
@as(f64, @floatFromInt(seen_value.integer()))
else
seen_value.double();

overlapping_condition = (value_type_def.def_type == .Double or
seen_value.isInteger() or
(std.math.isFinite(seen_number) and seen_number == @trunc(seen_number))) and
seen_number >= condition_low and seen_number < condition_high;
} else if (seen_condition_is_range and condition_value.isNumber()) {
const seen_range = o.ObjRange.cast(seen_value.obj()).?;
const seen_low: f64 = @floatFromInt(@min(seen_range.low, seen_range.high));
const seen_high: f64 = @floatFromInt(@max(seen_range.low, seen_range.high));
const condition_number = if (condition_value.isInteger())
@as(f64, @floatFromInt(condition_value.integer()))
else
condition_value.double();

overlapping_condition = (value_type_def.def_type == .Double or
condition_value.isInteger() or
(std.math.isFinite(condition_number) and condition_number == @trunc(condition_number))) and
condition_number >= seen_low and condition_number < seen_high;
}
}

if (duplicate_condition) {
reporter.reportWithOrigin(
.match_duplicate_condition,
ast.tokens.get(locations[condition]),
ast.tokens.get(end_locations[condition]),
ast.tokens.get(locations[seen_condition]),
ast.tokens.get(end_locations[seen_condition]),
"Duplicate `match` condition",
.{},
"first used here",
);
return true;
}

if (overlapping_condition) {
reporter.reportWithOrigin(
.match_duplicate_condition,
ast.tokens.get(locations[condition]),
ast.tokens.get(end_locations[condition]),
ast.tokens.get(locations[seen_condition]),
ast.tokens.get(end_locations[seen_condition]),
"Overlapping `match` condition",
.{},
"overlaps with this condition",
);
return true;
}
}

try seen_values.append(gc.allocator, condition_value);
try seen_conditions.append(gc.allocator, condition);
}
}
}

return had_error;
}

Expand Down
113 changes: 113 additions & 0 deletions src/lib/static_libraries.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//! Shared registry for statically bundled Buzz headers and native libraries.

const std = @import("std");
const buzz_api = @import("buzz_api.zig");

/// Describes a statically bundled Buzz library.
pub const Library = struct {
/// Name used by Buzz imports.
name: []const u8,
/// Header file installed under `lib/buzz` and embedded from `src/lib`.
header_path: []const u8,
/// Native Zig implementation beside this file, when the library exposes externs.
zig_path: ?[]const u8,
/// Whether the native Zig implementation is available in wasm builds.
wasm_native: bool,
};

/// Libraries bundled with the compiler and runtime.
pub const all = [_]Library{
.{ .name = "buffer", .header_path = "buffer.buzz", .zig_path = "buzz_buffer.zig", .wasm_native = true },
.{ .name = "crypto", .header_path = "crypto.buzz", .zig_path = "buzz_crypto.zig", .wasm_native = true },
.{ .name = "debug", .header_path = "debug.buzz", .zig_path = "buzz_debug.zig", .wasm_native = true },
.{ .name = "errors", .header_path = "errors.buzz", .zig_path = null, .wasm_native = false },
.{ .name = "ffi", .header_path = "ffi.buzz", .zig_path = "buzz_ffi.zig", .wasm_native = false },
.{ .name = "fs", .header_path = "fs.buzz", .zig_path = "buzz_fs.zig", .wasm_native = false },
.{ .name = "gc", .header_path = "gc.buzz", .zig_path = "buzz_gc.zig", .wasm_native = true },
.{ .name = "http", .header_path = "http.buzz", .zig_path = "buzz_http.zig", .wasm_native = false },
.{ .name = "io", .header_path = "io.buzz", .zig_path = "buzz_io.zig", .wasm_native = false },
.{ .name = "math", .header_path = "math.buzz", .zig_path = "buzz_math.zig", .wasm_native = true },
.{ .name = "os", .header_path = "os.buzz", .zig_path = "buzz_os.zig", .wasm_native = false },
.{ .name = "serialize", .header_path = "serialize.buzz", .zig_path = "buzz_serialize.zig", .wasm_native = true },
.{ .name = "std", .header_path = "std.buzz", .zig_path = "buzz_std.zig", .wasm_native = true },
.{ .name = "test", .header_path = "testing.buzz", .zig_path = null, .wasm_native = false },
.{ .name = "toml", .header_path = "toml.buzz", .zig_path = null, .wasm_native = false },
};

/// Returns the library registered for a Buzz import name.
pub fn byName(name: []const u8) ?Library {
inline for (all) |library| {
if (std.mem.eql(u8, name, library.name)) {
return library;
}
}

return null;
}

/// Returns the native method map for statically linked libraries on the target.
pub fn nativeLibraries(comptime is_wasm: bool) std.StaticStringMap(std.StaticStringMap(buzz_api.NativeFn)) {
comptime {
const count = nativeLibraryCount(is_wasm);
var entries: [count]struct { []const u8, std.StaticStringMap(buzz_api.NativeFn) } = undefined;
var index = 0;

for (all) |library| {
if (hasNativeLibrary(library, is_wasm)) {
entries[index] = .{ library.name, nativeMethods(library) };
index += 1;
}
}

return std.StaticStringMap(std.StaticStringMap(buzz_api.NativeFn)).initComptime(entries);
}
}

/// Counts native libraries available on the selected target.
fn nativeLibraryCount(comptime is_wasm: bool) comptime_int {
comptime {
var count = 0;

for (all) |library| {
if (hasNativeLibrary(library, is_wasm)) {
count += 1;
}
}

return count;
}
}

/// Returns whether a library has a native implementation for the selected target.
fn hasNativeLibrary(comptime library: Library, comptime is_wasm: bool) bool {
return library.zig_path != null and (!is_wasm or library.wasm_native);
}

/// Returns native methods for a registry entry with a native Zig implementation.
fn nativeMethods(comptime library: Library) std.StaticStringMap(buzz_api.NativeFn) {
const zig_path = library.zig_path.?;

if (std.mem.eql(u8, zig_path, "buzz_buffer.zig")) return checkedNativeMethods(library, @import("buzz_buffer.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_crypto.zig")) return checkedNativeMethods(library, @import("buzz_crypto.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_debug.zig")) return checkedNativeMethods(library, @import("buzz_debug.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_ffi.zig")) return checkedNativeMethods(library, @import("buzz_ffi.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_fs.zig")) return checkedNativeMethods(library, @import("buzz_fs.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_gc.zig")) return checkedNativeMethods(library, @import("buzz_gc.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_http.zig")) return checkedNativeMethods(library, @import("buzz_http.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_io.zig")) return checkedNativeMethods(library, @import("buzz_io.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_math.zig")) return checkedNativeMethods(library, @import("buzz_math.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_os.zig")) return checkedNativeMethods(library, @import("buzz_os.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_serialize.zig")) return checkedNativeMethods(library, @import("buzz_serialize.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_std.zig")) return checkedNativeMethods(library, @import("buzz_std.zig").library);

@compileError("unknown native library path: " ++ zig_path);
}

/// Verifies a native library against its registry entry and returns its methods.
fn checkedNativeMethods(comptime library: Library, comptime native_library: anytype) std.StaticStringMap(buzz_api.NativeFn) {
if (!std.mem.eql(u8, native_library.name, library.name)) {
@compileError("native library name mismatch for " ++ library.zig_path.?);
}

return native_library.methods;
}
Loading
Loading