diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4d92933b3ffc2773110675cbe61c502e2359734..4fd3a46010f6e891a8b671348cce1db15e4084c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,8 @@
- feat: add array index syntax via `x[1]` and `x.1`
- feat: add `len` property to arrays
- feat: rename `noreturn` to `never`
-- feat: allow instantiating `@extern`
+- feat: allow instantiating `@extern` at compile time
+- feat: implement tuples
- fix: crash when printing unused struct declaration error
- fix: provide array result type to sema
- fix: crash when analyzing an empty array without a result type
diff --git a/src/cli.zig b/src/cli.zig
index fd981159f46ab157af9a83a7a403f460203ea21c..39ee68fcf7bf3361b25ee74cb1c27923ad37e1df 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -8,7 +8,7 @@ options: []const Option = &.{},
positionals: Positionals = .none,
help: HelpConfig = .{},
-const Option = struct {
+pub const Option = struct {
/// Should be formatted as the flag name: '--dump-ir'
name: []const u8,
/// Displayed in generated help menu
@@ -32,6 +32,10 @@ const Option = struct {
}
};
+ pub fn optional(comptime cond: bool, opts: []const Option) []const Option {
+ return if (cond) opts else &.{};
+ }
+
const help: Option = .{
.name = "--help",
.short = 'h',
@@ -631,8 +635,7 @@ pub fn main() !void {
const program_name, const args = try Cli.programArgsAlloc(alloc);
defer Cli.programArgsFree(alloc, args);
- const parsed = config.parse(args[1..]) catch
- config.showHelpAndExit(program_name);
+ const parsed = config.parse(program_name, args[1..]);
std.debug.print("--flag: {any}\n", .{parsed.flag});
std.debug.print("--str: {?s}\n", .{parsed.str});
diff --git a/src/cli_compiler.zig b/src/cli_compiler.zig
new file mode 100644
index 0000000000000000000000000000000000000000..4e1a6a189acbea0aa9b2d29bd0f9e23f5845872e
--- /dev/null
+++ b/src/cli_compiler.zig
@@ -0,0 +1,146 @@
+//! For testing the core compiler with minimal feature and
+//! additional debugging parameters.
+
+const config = @import("config");
+
+const arg_config: Cli = .{
+ .options = &.{
+ .{ .short = 'i', .name = "--dump-ir", .desc = "Dump intermediate representations" },
+ .{ .name = "--dump-state", .desc = "Dump compiler state to JSON", .kind = .string },
+ .{ .short = 'r', .name = "--report-trace", .desc = "Print stack traces to errors" },
+ .{ .short = 'L', .name = "--no-leak-checks", .desc = "" },
+ },
+ .positionals = .single,
+ .help = .{
+ .usage = "[options...] [entry file]",
+ },
+};
+
+pub fn main() !u8 {
+ try tracer.init();
+ defer tracer.deinit();
+ try tracer.init_thread(null);
+ defer tracer.deinit_thread();
+
+ // unicode props is asserted to be initialized before core.Tokenizer is used
+ // this will result in a static page allocation, which is never cleaned up.
+ try core.unicode_props.init();
+
+ const use_gpa = builtin.mode == .Debug;
+ var gpa = if (use_gpa)
+ std.heap.GeneralPurposeAllocator(.{
+ .stack_trace_frames = 100,
+ }){}
+ else
+ std.heap.ArenaAllocator.init(std.heap.page_allocator);
+ var no_leak_checks = false;
+ defer _ = if (use_gpa and !no_leak_checks) gpa.deinit();
+ const alloc = gpa.allocator();
+
+ // Current working directory is required for CLI resolution.
+ var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
+ const cwd = try std.posix.getcwd(&cwd_buf);
+
+ // Command line argument parsing.
+ const program_name = "zig build compiler --";
+ _, const args = try Cli.programArgsAlloc(alloc, program_name);
+ defer Cli.programArgsFree(alloc, args);
+ const opts = arg_config.parse(program_name, args);
+
+ const file_path = try std.fs.path.resolve(alloc, &.{
+ cwd,
+ opts.positionals orelse
+ arg_config.showHelpAndExit(program_name),
+ });
+ defer alloc.free(file_path);
+
+ no_leak_checks = opts.no_leak_checks orelse false;
+
+ // Execute compiler
+ {
+ var js_backend = core.Js{};
+
+ var comp = try core.Comp.init(.{
+ .gpa = tracer.tracingAllocator(&alloc),
+ .options = .{
+ .dump_ir = opts.dump_ir orelse false,
+ },
+ .backend = .{ .js = &js_backend },
+ });
+ defer comp.deinit();
+
+ defer if (opts.dump_state) |path| {
+ dumpState(&comp, path) catch |err| {
+ std.debug.print("Failed to dump compilation state: {}", .{err});
+ };
+ };
+
+ const main_file = try comp.getFile(file_path);
+
+ var fail_report: Report = undefined;
+ const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) {
+ error.TransformFail => {
+ std.debug.print("Transform failed:\n", .{});
+ try fail_report.dump(.initComp(&comp, main_file), comp.out);
+ fail_report.deinit(comp.gpa);
+ return 1;
+ },
+ error.ParseFail => {
+ // Already printed.
+ return 1;
+ },
+ else => return err,
+ };
+
+ if (comp.options.dump_ir) {
+ const stderr = std.io.getStdErr().writer();
+ try teg.dumpToWriter(stderr, null);
+ }
+
+ const entry_point = try comp.enqueueEntryModule(main_file);
+ comp.runAllJobs() catch |err| switch (err) {
+ error.AnalysisFail => {
+ // error is already printed
+ return 1;
+ },
+ else => return err,
+ };
+
+ {
+ // Output to standard out
+ const output = std.io.getStdOut().writer();
+ var bufw = std.io.bufferedWriter(output);
+ const writer = bufw.writer();
+ try js_backend.link(&comp, entry_point, writer.any());
+ try bufw.flush();
+ }
+ }
+
+ std.process.cleanExit();
+ return 0;
+}
+
+const core = @import("core");
+const Comp = core.Comp;
+const Report = core.Report;
+
+const tracer = @import("tracer");
+pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode));
+
+const std = @import("std");
+const builtin = @import("builtin");
+const Cli = @import("Cli.zig");
+
+fn dumpState(comp: *const Comp, dump_path: []const u8) !void {
+ const file = try std.fs.cwd().createFile(dump_path, .{});
+ defer file.close();
+
+ var bufw = std.io.bufferedWriter(file.writer());
+ var jw = std.json.writeStream(bufw.writer(), .{
+ .whitespace = .indent_2,
+ });
+
+ try comp.jsonStringify(&jw);
+ try bufw.writer().writeAll("\n");
+ try bufw.flush();
+}
diff --git a/src/cli_transform.zig b/src/cli_transform.zig
index 44920293e9b2d00e4fbde53476d3b694c332b957..76200d6457a22fefd34076def4e44cfe3c58c97e 100644
--- a/src/cli_transform.zig
+++ b/src/cli_transform.zig
@@ -25,8 +25,7 @@ pub fn main() !u8 {
const program_name, const raw_args = try Cli.programArgsAlloc(alloc, "transform");
defer Cli.programArgsFree(alloc, raw_args);
- const args = arg_config.parse(raw_args) catch
- arg_config.showHelpAndExit(program_name);
+ const args = arg_config.parse(program_name, raw_args);
const file_path = args.positionals orelse
arg_config.fatalWithHelp(program_name, "missing filepath argument\n", .{});
diff --git a/src/core/Air.Dump.zig b/src/core/Air.Dump.zig
index 79eb0df165eace069097f479434403224e853b77..03f29ece0026eeec8b67714a9531b6b2ed612b33 100644
--- a/src/core/Air.Dump.zig
+++ b/src/core/Air.Dump.zig
@@ -130,6 +130,16 @@ pub const State = struct {
try tty.setColor(w, .reset);
try w.writeAll(")");
},
+ .field_tuple => {
+ const field: Air.FieldStruct = air.instData(.field_tuple, inst);
+ try w.writeAll("(");
+ try s.dumpInstRef(w, field.base);
+ try w.writeAll(", ");
+ try tty.setColor(w, .magenta);
+ try w.print("{d}", .{field.field_index});
+ try tty.setColor(w, .reset);
+ try w.writeAll(")");
+ },
inline .call, .js_new => |t| {
const call: Air.Call = air.instData(t, inst);
const call_data = call.call_data.get(air);
@@ -182,6 +192,15 @@ pub const State = struct {
try s.dumpRefList(w, array_init.list.get(air).slice(air), '[', ']');
try w.writeAll(")");
},
+ .tuple_init => {
+ // const array_init: Air.ArrayInit = air.instData(.array_init, inst);
+ // const array_type = array_init.ty.toArrayType(s.ip).?;
+
+ // try w.writeAll("(");
+ // try s.dumpValue(w, array_type.item_type.value());
+ // try s.dumpRefList(w, array_init.list.get(air).slice(air), '[', ']');
+ // try w.writeAll(")");
+ },
}
}
@@ -233,7 +252,10 @@ pub const State = struct {
try tty.setColor(w, .blue);
try w.writeAll(") -> ");
try tty.setColor(w, .reset);
- try s.dumpValue(w, ty.return_type.value());
+ if (ty.return_type) |return_type|
+ try s.dumpValue(w, return_type.value())
+ else
+ try w.writeAll("[inferred]");
},
inline .float, .int32, .comptime_float => |data, tag| {
try tty.setColor(w, .bright_black);
@@ -379,6 +401,22 @@ pub const State = struct {
}
try w.writeAll("'");
},
+ .tuple_type => |tuple_type| {
+ try w.writeAll("(");
+ for (tuple_type.items, 0..) |item, i| {
+ if (i != 0) try w.writeAll(", ");
+ try s.dumpValue(w, item.value());
+ }
+ try w.writeAll(")");
+ },
+ .tuple_value => |tuple_value| {
+ try w.writeAll("(");
+ for (tuple_value.items, 0..) |item, i| {
+ if (i != 0) try w.writeAll(", ");
+ try s.dumpValue(w, item);
+ }
+ try w.writeAll(")");
+ },
}
}
diff --git a/src/core/Air.zig b/src/core/Air.zig
index 53e7c894ba144b612322ae306eb80c2d99fd608d..35f72e7cbbeb7e7802307d9935ba5488259f9c48 100644
--- a/src/core/Air.zig
+++ b/src/core/Air.zig
@@ -25,9 +25,11 @@ pub const Inst = struct {
struct_init,
array_init,
+ tuple_init,
field_opaque,
field_struct,
+ field_tuple,
field_array,
array_len,
@@ -58,7 +60,8 @@ pub const Inst = struct {
.store => Store,
.struct_init => StructInit,
.array_init => ArrayInit,
- .field_struct => FieldStruct,
+ .tuple_init => TupleInit,
+ .field_struct, .field_tuple => FieldStruct,
.field_opaque,
.field_array,
.add,
@@ -286,6 +289,18 @@ pub const ArrayInit = struct {
list: ExtraIndex(RefList),
};
+pub const TupleInit = struct {
+ ty: Type,
+ field_values: u32,
+
+ pub fn valuesPtr(
+ init: *const TupleInit,
+ air: *const Air,
+ ) [*]const Air.Inst.Ref {
+ return @ptrCast(air.extra.list.items[init.field_values..].ptr);
+ }
+};
+
pub const FieldStruct = struct {
base: Inst.Index,
field_index: u32,
@@ -342,6 +357,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In
.@"if" => air.instData(.@"if", index).data.get(air).union_ty,
.struct_init => air.instData(.struct_init, index).ty,
.array_init => air.instData(.array_init, index).ty,
+ .tuple_init => air.instData(.tuple_init, index).ty,
.arg => air.instData(.arg, index).type,
// A handful of binary operators are lowered to a form where both sides
@@ -372,7 +388,9 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In
.call => {
const base_type = air.typeOf(air.instData(.call, index).base, ip);
if (base_type == .@"opaque") return .@"opaque";
- return base_type.toFunctionPrototype(ip).?.return_type;
+ return base_type.toFunctionPrototype(ip).?.return_type orelse
+ // unreachable // return type was not resolved
+ @panic("TODO: resolve return type");
},
// Field accesses on structs are special-cased by storing the field
@@ -391,6 +409,14 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In
.toArrayType(ip) orelse unreachable; // field_array demands array type
return base_type.item_type;
},
+
+ // Tuple accesses use the tuple they are accessing into.
+ .field_tuple => {
+ const inst = air.instData(.field_tuple, index);
+ const base_type = air.typeOfIndex(inst.base, ip)
+ .toTupleType(ip) orelse unreachable; // field_tuple demands tuple type
+ return base_type.items[inst.field_index];
+ },
};
}
diff --git a/src/core/Ast.Dump.zig b/src/core/Ast.Dump.zig
index 6eebfd5f2230937fb9089326de97c82562e07a88..820004af937a755181a10d82406db9e852f8d755 100644
--- a/src/core/Ast.Dump.zig
+++ b/src/core/Ast.Dump.zig
@@ -126,6 +126,8 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err
.struct_decl,
=> try dumpNodeList(writer, state, list, '{', '}'),
.builtin_call,
+ .tuple_init,
+ .tuple_type,
=> try dumpNodeList(writer, state, list, '(', ')'),
.array_init,
=> try dumpNodeList(writer, state, list, '[', ']'),
diff --git a/src/core/InternPool.zig b/src/core/InternPool.zig
index 679e01f3ad3122666d05b6eedda99389fd0b6f6c..a52dd2e72de5d1981a818216355cad6d8bc8846b 100644
--- a/src/core/InternPool.zig
+++ b/src/core/InternPool.zig
@@ -101,6 +101,8 @@ pub const Tag = enum(u8) {
struct_type,
/// An array type
array_type,
+ /// A tuple type
+ tuple_type,
/// A type that can be represented with only an enum tag.
simple_type,
@@ -129,6 +131,8 @@ pub const Tag = enum(u8) {
external_value,
/// A value masked as "opaque" type
opaque_value,
+ /// A tuple value
+ tuple_value,
pub fn Data(comptime tag: Tag) type {
return switch (tag) {
@@ -154,13 +158,15 @@ pub const Tag = enum(u8) {
=> unreachable, // does not have a payload
.float, .comptime_float => Float,
.func => FuncDecl,
- .function_type => TypeFunction,
+ .function_type => FunctionType,
.string => String.WithLen,
.struct_value => StructValue,
.struct_type => StructType,
.array_type => ArrayType,
.array_value => ArrayValue,
.external_value => ExternalValue,
+ .tuple_type => TupleType,
+ .tuple_value => TupleValue,
};
}
@@ -174,9 +180,9 @@ pub const Tag = enum(u8) {
/// Trailing:
/// 0. param_type: Index to param type. Repeated .params_len
- pub const TypeFunction = struct {
+ pub const FunctionType = struct {
params_len: u32,
- return_type: Type,
+ return_type: Type.Optional,
};
/// Trailing:
@@ -199,6 +205,20 @@ pub const Tag = enum(u8) {
items_len: u32,
};
+ /// Trailing:
+ /// 0. item: Type. Repeated per .items_len
+ pub const TupleType = struct {
+ items_len: u32,
+ };
+
+ /// Trailing:
+ /// 0. value: Value. Repeated per .items_len
+ pub const TupleValue = struct {
+ /// This is the tuple type, not the item type
+ type: Type,
+ items_len: u32,
+ };
+
/// Trailing:
/// 0. value: Value. Repeated .ty.structInfo().field_len
pub const StructValue = struct {
@@ -216,9 +236,9 @@ pub const Tag = enum(u8) {
/// This is an index into .items
pub const Value = enum(u32) {
pub const first_type: Value = .opaque_type;
- pub const last_type: Value = .numeric_literal_array_type;
+ pub const last_type: Value = .empty_tuple_type;
pub const first_value: Value = .void;
- pub const last_value: Value = .empty_array;
+ pub const last_value: Value = .empty_tuple;
opaque_type,
bool_type,
@@ -230,6 +250,7 @@ pub const Value = enum(u32) {
numeric_literal_type,
numeric_literal_array_type,
never_array_type,
+ empty_tuple_type,
void,
bool_true,
@@ -239,6 +260,7 @@ pub const Value = enum(u32) {
nan,
empty_string,
empty_array,
+ empty_tuple,
none = std.math.maxInt(u32),
_,
@@ -316,6 +338,7 @@ pub const Value = enum(u32) {
.simple_type,
.struct_type,
.array_type,
+ .tuple_type,
=> .type,
.simple_value => switch (value.valueData(ip, .simple_value)) {
@@ -332,6 +355,7 @@ pub const Value = enum(u32) {
.struct_value => value.toKey(ip).struct_value.ty,
.array_value => value.toKey(ip).array_value.type,
.external_value, .opaque_value => .@"opaque",
+ .tuple_value => value.toKey(ip).tuple_value.type,
};
}
@@ -345,6 +369,7 @@ pub const Value = enum(u32) {
.struct_type,
.simple_type,
.array_type,
+ .tuple_type,
=> @enumFromInt(@intFromEnum(value)),
// exception: void is a value AND a type
@@ -395,6 +420,7 @@ pub const Type = enum(u32) {
numeric_literal = @intFromEnum(Value.numeric_literal_type),
numeric_literal_array = @intFromEnum(Value.numeric_literal_array_type),
never_array = @intFromEnum(Value.never_array_type),
+ empty_tuple = @intFromEnum(Value.empty_tuple_type),
void = @intFromEnum(Value.void),
_,
@@ -404,6 +430,7 @@ pub const Type = enum(u32) {
struct_type = @intFromEnum(InternPool.Tag.struct_type),
array_type = @intFromEnum(InternPool.Tag.array_type),
simple_type = @intFromEnum(InternPool.Tag.simple_type),
+ tuple_type = @intFromEnum(InternPool.Tag.tuple_type),
};
/// An array of `Index` existing within the `extra` array.
@@ -446,6 +473,11 @@ pub const Type = enum(u32) {
return ip.valueToKey(ty.value()).array_type;
}
+ pub fn toTupleType(ty: Type, ip: *const InternPool) ?Key.TupleType {
+ if (ty.tag(ip) != .tuple_type) return null;
+ return ip.valueToKey(ty.value()).tuple_type;
+ }
+
/// If it is possible to represent this type at runtime. Some types do not
/// have a defined representation, such as structs (to allow field renaming, unwrapping).
pub fn hasPossibleRepresentation(ty: Type, ip: *const InternPool) bool {
@@ -466,6 +498,15 @@ pub const Type = enum(u32) {
.struct_type,
.function_type,
=> true,
+ .tuple_type => {
+ const tuple_type = ty.toTupleType(ip).?;
+ for (tuple_type.items) |item| {
+ if (!item.hasPossibleRepresentation(ip)) {
+ return false;
+ }
+ }
+ return true;
+ },
.array_type => ty.toArrayType(ip).?.item_type.hasPossibleRepresentation(ip),
},
};
@@ -490,6 +531,7 @@ pub const Type = enum(u32) {
.simple_type => unreachable, // all simple types are already printed
.struct_type,
.function_type,
+ .tuple_type,
=> false,
.array_type => ty.toArrayType(ip).?.item_type.hasDefinedRepresentation(ip),
},
@@ -536,7 +578,10 @@ pub const Type = enum(u32) {
try writer.print("{}", .{param.displayName(ip)});
}
try writer.writeAll(") -> ");
- try writer.print("{}", .{function_type.return_type.displayName(ip)});
+ if (function_type.return_type) |return_type|
+ try writer.print("{}", .{return_type.displayName(ip)})
+ else
+ try writer.writeAll("[inferred]");
},
.struct_type => {
try writer.writeAll("[struct]");
@@ -546,8 +591,29 @@ pub const Type = enum(u32) {
const array_type = ty.toArrayType(ip).?;
try writer.print("{}", .{array_type.item_type.displayName(ip)});
},
+ .tuple_type => {
+ try writer.writeAll("(");
+ for (ty.toTupleType(ip).?.items, 0..) |item, i| {
+ if (i != 0) try writer.writeAll(", ");
+ try writer.print("{}", .{item.displayName(ip)});
+ }
+ try writer.writeAll(")");
+ },
}
}
+
+ pub const Optional = enum(u32) {
+ none = std.math.maxInt(u32),
+ _,
+
+ pub fn init(opt: ?Type) Optional {
+ return if (opt) |o| @enumFromInt(@intFromEnum(o)) else .none;
+ }
+
+ pub fn unwrap(opt: Optional) ?Type {
+ return if (opt != .none) @enumFromInt(@intFromEnum(opt)) else null;
+ }
+ };
};
pub const Item = struct {
@@ -584,6 +650,7 @@ pub const static_keys = [_]Key{
SimpleType.numeric_literal.key(),
.{ .array_type = .{ .item_type = Type.numeric_literal } },
.{ .array_type = .{ .item_type = Type.never } },
+ .{ .tuple_type = .{ .items = &.{} } },
SimpleValue.void.key(),
SimpleValue.true.key(),
@@ -596,6 +663,10 @@ pub const static_keys = [_]Key{
.items = &.{},
} },
.{ .string = "" },
+ .{ .tuple_value = .{
+ .type = .empty_tuple,
+ .items = &.{},
+ } },
};
/// State that is mutable during semantic analysis. This data is not used for
@@ -876,11 +947,13 @@ pub const Key = union(enum) {
func: Func,
array_value: ArrayValue,
opaque_value: Value,
+ tuple_value: TupleValue,
// Types
simple_type: SimpleType,
function_type: FunctionType,
struct_type: StructType,
array_type: ArrayType,
+ tuple_type: TupleType,
external_value: ExternalValue,
const Adapter = struct {
@@ -899,7 +972,10 @@ pub const Key = union(enum) {
pub const FunctionType = struct {
param_types: []Type,
- return_type: Type,
+ /// If a return type is missing, it is inferred and not resolved yet.
+ /// When analysis figures out a return type, a new `FunctionType` with
+ /// the resolved return type.
+ return_type: ?Type,
pub fn hash(self: FunctionType, hasher: *Hash) void {
const t = tracer.trace(@src());
@@ -946,6 +1022,14 @@ pub const Key = union(enum) {
}
};
+ pub const TupleType = struct {
+ items: []const Type,
+
+ pub fn hash(self: TupleType, hasher: *Hash) void {
+ std.hash.autoHashStrat(hasher, self.items, .Deep);
+ }
+ };
+
pub const ExternalValue = struct {
/// A list of strings to generate the property access
/// so `console.log` would be .{ "console", "log" }
@@ -967,6 +1051,17 @@ pub const Key = union(enum) {
}
};
+ pub const TupleValue = struct {
+ /// This is the tuple type, not the item type
+ type: Type,
+ items: []const Value,
+
+ pub fn hash(self: TupleValue, hasher: *Hash) void {
+ std.hash.autoHash(hasher, self.type);
+ std.hash.autoHashStrat(hasher, self.items, .Deep);
+ }
+ };
+
pub const Func = struct {
ty: Type,
owner_decl: DeclIndex,
@@ -1008,6 +1103,8 @@ pub const Key = union(enum) {
.array_value,
.array_type,
.external_value,
+ .tuple_type,
+ .tuple_value,
=> |x| {
var hasher = Hash.init(seed);
x.hash(&hasher);
@@ -1047,7 +1144,7 @@ pub const Key = union(enum) {
return false;
},
.struct_type => {
- return false; // structs are nomial
+ return false; // structs are nominal
},
.struct_value => |a_info| {
const b_info = b.struct_value;
@@ -1066,6 +1163,15 @@ pub const Key = union(enum) {
const b_info = b.external_value;
return std.mem.eql(NullTerminatedString, a_info.key, b_info.key);
},
+ .tuple_type => |a_info| {
+ const b_info = b.tuple_type;
+ return a_info.items.len == b_info.items.len and std.mem.eql(Type, a_info.items, b_info.items);
+ },
+ .tuple_value => |a_info| {
+ const b_info = b.tuple_value;
+ return a_info.type == b_info.type and
+ std.mem.eql(Value, a_info.items, b_info.items);
+ },
};
}
};
@@ -1336,6 +1442,19 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key {
.key = trail.readNullTerminatedStringSlice(payload.key_count).get(ip),
} };
},
+ .tuple_type => {
+ const payload: Tag.TupleType, var trail = data.decode(.tuple_type, item.tag).getTrail(ip);
+ return .{ .tuple_type = .{
+ .items = trail.readTypeSlice(payload.items_len).get(ip),
+ } };
+ },
+ .tuple_value => {
+ const payload: Tag.TupleValue, var trail = data.decode(.tuple_value, item.tag).getTrail(ip);
+ return .{ .tuple_value = .{
+ .type = payload.type,
+ .items = trail.readValueSlice(payload.items_len).get(ip),
+ } };
+ },
};
}
@@ -1391,9 +1510,9 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {
.function_type => |f| brk: {
for (f.param_types) |ty| ty.assertValid(ip);
- const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TypeFunction, .{
+ const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.FunctionType, .{
.params_len = @intCast(f.param_types.len),
- .return_type = f.return_type,
+ .return_type = .init(f.return_type),
}, f.param_types.len);
defer trail.end();
trail.writeSlice(f.param_types);
@@ -1439,6 +1558,21 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {
trail.writeSlice(s.key);
break :brk Item.encode(.external_value, extra);
},
+ .tuple_type => |s| brk: {
+ const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TupleType, .{
+ .items_len = @intCast(s.items.len),
+ }, s.items.len);
+ trail.writeSlice(s.items);
+ break :brk Item.encode(.tuple_type, extra);
+ },
+ .tuple_value => |s| brk: {
+ const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TupleValue, .{
+ .type = s.type,
+ .items_len = @intCast(s.items.len),
+ }, s.items.len);
+ trail.writeSlice(s.items);
+ break :brk Item.encode(.tuple_value, extra);
+ },
};
ip.items.appendAssumeCapacity(item);
const index: Value = @enumFromInt(ip.items.len - 1);
@@ -1451,12 +1585,12 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {
fn extraTypeFunction(
ip: *const InternPool,
- extra_index: ExtraIndex(Tag.TypeFunction),
+ extra_index: ExtraIndex(Tag.FunctionType),
) Key.FunctionType {
const function_type, var trail = extra_index.getTrail(ip);
const param_types = trail.readTypeSlice(function_type.params_len);
const result: Key.FunctionType = .{
- .return_type = function_type.return_type,
+ .return_type = function_type.return_type.unwrap(),
.param_types = param_types.get(ip),
};
return result;
diff --git a/src/core/Parse.zig b/src/core/Parse.zig
index f933f3e58c3aaaf1e5610b2909b8ff3543cd9797..120626eac16464a5bea13eff869cf72a4757c3d0 100644
--- a/src/core/Parse.zig
+++ b/src/core/Parse.zig
@@ -104,15 +104,216 @@ pub const ExpectedContext = enum {
}
};
+const Operator = enum(u8) {
+ add = @intFromEnum(Token.Tag.plus),
+ sub = @intFromEnum(Token.Tag.minus),
+ mul = @intFromEnum(Token.Tag.star),
+ div = @intFromEnum(Token.Tag.slash),
+ rem = @intFromEnum(Token.Tag.percent),
+ pow = @intFromEnum(Token.Tag.star_star),
+ @"and" = @intFromEnum(Token.Tag.and_and),
+ @"or" = @intFromEnum(Token.Tag.pipe_pipe),
+ bit_and = @intFromEnum(Token.Tag.@"and"),
+ bit_or = @intFromEnum(Token.Tag.pipe),
+ bit_xor = @intFromEnum(Token.Tag.caret),
+ shl = @intFromEnum(Token.Tag.lt_lt),
+ shr = @intFromEnum(Token.Tag.gt_gt),
+ eq = @intFromEnum(Token.Tag.equal_equal),
+ ne = @intFromEnum(Token.Tag.bang_equal),
+ lt = @intFromEnum(Token.Tag.lt),
+ le = @intFromEnum(Token.Tag.lt_equal),
+ gt = @intFromEnum(Token.Tag.gt),
+ ge = @intFromEnum(Token.Tag.gt_equal),
+ // Following operators are assignment operators
+ assign = @intFromEnum(Token.Tag.equal),
+ add_assign = @intFromEnum(Token.Tag.plus_equal),
+ sub_assign = @intFromEnum(Token.Tag.minus_equal),
+ mul_assign = @intFromEnum(Token.Tag.star_equal),
+ div_assign = @intFromEnum(Token.Tag.slash_equal),
+ rem_assign = @intFromEnum(Token.Tag.percent_equal),
+ pow_assign = @intFromEnum(Token.Tag.star_star_equal),
+ and_assign = @intFromEnum(Token.Tag.and_and_equal),
+ or_assign = @intFromEnum(Token.Tag.pipe_pipe_equal),
+ bit_and_assign = @intFromEnum(Token.Tag.and_equal),
+ bit_or_assign = @intFromEnum(Token.Tag.pipe_equal),
+ bit_xor_assign = @intFromEnum(Token.Tag.caret_equal),
+ shl_assign = @intFromEnum(Token.Tag.lt_lt_equal),
+ shr_assign = @intFromEnum(Token.Tag.gt_gt_equal),
+
+ const Direction = enum { ltr, rtl };
+
+ /// Only the binary operator levels are actually used,
+ /// but all are listed for reference.
+ const Level = enum {
+ /// e. e[] e()
+ accessor,
+ /// new
+ new,
+ /// ++e, --e, !e, ~e, +e, -e, typeof, void, delete
+ prefix,
+ /// **
+ exponentiation,
+ /// *, /, %
+ multiplicative,
+ /// +, -
+ additive,
+ /// <<, >>, >>>
+ bitwise_shift,
+ /// <, <=, >, >=
+ relational,
+ /// ==, !=
+ equality,
+ /// &
+ bit_and,
+ /// ^
+ bit_xor,
+ /// |
+ bit_or,
+ /// &&
+ logical_and,
+ /// ||, ??
+ logical_or,
+ /// =, +=, -=, *=, /=, %=, / <<=, >>=, >>>=, &=, ^=, |=
+ assign,
+ };
+
+ pub fn direction(op: Operator) Direction {
+ return switch (op) {
+ .pow => .rtl,
+
+ .assign,
+ .add_assign,
+ .sub_assign,
+ .mul_assign,
+ .div_assign,
+ .rem_assign,
+ .pow_assign,
+ .and_assign,
+ .or_assign,
+ .bit_and_assign,
+ .bit_or_assign,
+ .bit_xor_assign,
+ .shl_assign,
+ .shr_assign,
+ => .rtl,
+
+ else => .ltr,
+ };
+ }
+
+ pub fn level(op: Operator) Level {
+ return switch (op) {
+ .pow => .exponentiation,
+ .mul, .div, .rem => .multiplicative,
+ .add, .sub => .additive,
+ .shl, .shr => .bitwise_shift,
+ .lt, .le, .gt, .ge => .relational,
+ .eq, .ne => .equality,
+ .@"and" => .logical_and,
+ .@"or" => .logical_or,
+ .bit_and => .bit_and,
+ .bit_xor => .bit_xor,
+ .bit_or => .bit_or,
+ .assign,
+ .add_assign,
+ .sub_assign,
+ .mul_assign,
+ .div_assign,
+ .rem_assign,
+ .pow_assign,
+ .and_assign,
+ .or_assign,
+ .bit_and_assign,
+ .bit_or_assign,
+ .bit_xor_assign,
+ .shl_assign,
+ .shr_assign,
+ => .assign,
+ };
+ }
+
+ const lut = brk: {
+ const token_tags = std.enums.values(Token.Tag);
+ const op_tags = std.enums.values(Operator);
+
+ var is_operator_lut: [token_tags.len]bool = .{false} ** token_tags.len;
+ for (op_tags) |op| {
+ is_operator_lut[@intFromEnum(op)] = true;
+ }
+
+ var ast_tag_lut: [token_tags.len]Node.Tag = .{.root} ** token_tags.len;
+ for (op_tags) |op| {
+ ast_tag_lut[@intFromEnum(op)] = @field(Node.Tag, @tagName(op));
+ }
+
+ break :brk struct {
+ const is_operator = is_operator_lut;
+ const ast_tag = ast_tag_lut;
+ };
+ };
+
+ pub fn fromToken(tag: Token.Tag) ?Operator {
+ return if (lut.is_operator[@intFromEnum(tag)])
+ @enumFromInt(@intFromEnum(tag))
+ else
+ null;
+ }
+
+ pub fn astTag(op: Operator) Node.Tag {
+ const tag = lut.ast_tag[@intFromEnum(op)];
+ assert(tag != .root); // invalid operator or lut construction
+ return tag;
+ }
+
+ pub fn isAssign(op: Operator) bool {
+ return switch (op) {
+ .assign,
+ .add_assign,
+ .sub_assign,
+ .mul_assign,
+ .div_assign,
+ .rem_assign,
+ .pow_assign,
+ .and_assign,
+ .or_assign,
+ .bit_and_assign,
+ .bit_or_assign,
+ .bit_xor_assign,
+ .shl_assign,
+ .shr_assign,
+ => true,
+ else => false,
+ };
+ }
+};
+
+const ParseExprMode = enum {
+ /// Standard parsing
+ expression,
+ /// Statements allow assignment binary operators in addition to other expressions.
+ statement,
+ /// This bans struct initializations so that parsing stops at a '{'
+ /// Example: "if hello {" must not be parsed as a struct initialization.
+ preceding_block,
+};
+
+/// Some constructs are valid expressions as well as valid top-level
+/// declarations. Top level declarations must have associated names, but the
+/// anonymous versions of these must not have names.
+const ExprParseContext = enum {
+ declaration,
+ expression,
+};
+
+const TypePresence = enum {
+ types_optional,
+ types_required,
+};
+
/// When error.SyntaxError is returned, `p.err` is set to this as well as `p.pos`
/// When printing these, token list and Ast is not actually available, so full tokens
/// are stored in the errors.
pub const AstError = union(enum) {
- // /// Failure during tokenization
- // tokenization: struct {
- // err: Tokenizer.TokenizeError,
- // byte: ByteOffset,
- // },
/// Unexpected data, expected a token.
unexpected_token: struct {
expected: Token.Tag,
@@ -134,23 +335,6 @@ pub const AstError = union(enum) {
pub fn build(err: AstError, ctx: *Report.MsgContext) !void {
switch (err) {
- // .tokenization => |data| {
- // // TODO: consider making tokenizer emit AstError more directly,
- // // so that it can just annotate start and ends
- // try ctx.msg(switch (data.err) {
- // error.UnterminatedString => .{ .codepoint_eol = data.byte },
- // else => .{ .codepoint = data.byte },
- // }, "{s}", .{switch (data.err) {
- // error.OutOfMemory => unreachable,
- // error.InvalidSyntax => "unexpected token",
- // error.TrailingNumericSeparator => "trailing numeric separator",
- // error.UnterminatedString => "unterminated string literal",
- // error.InvalidEscapeSequence => "invalid escape sequence",
- // error.InvalidEscapeCodepoint => "invalid escape codepoint",
- // error.InvalidUtf8 => "file is not valid utf8",
- // error.GitConflict => "git merge conflict encountered",
- // }});
- // },
.unexpected_token => |data| {
try ctx.msg(.{ .token_stored = data.got }, "expected {s}, but found {s}", .{
data.expected.description(),
@@ -230,7 +414,8 @@ fn parseModuleMembers(p: *Parse) Error!SubList {
.kw_pub,
=> try p.scratch.append(p.gpa, try p.parseModuleDecl()),
- .kw_struct => try p.scratch.append(p.gpa, try p.parseStructDecl()),
+ .kw_struct => try p.scratch.append(p.gpa, try p.parseStructDecl(.declaration)),
+ .kw_tuple => try p.scratch.append(p.gpa, try p.parseStructDecl(.declaration)),
.id => if (p.isContextualKeyword("test")) {
try p.scratch.append(p.gpa, try p.parseTestDecl());
@@ -292,14 +477,6 @@ fn parseModuleDecl(p: *Parse) Error!NodeIndex {
};
}
-/// Some constructs are valid expressions as well as valid top-level
-/// declarations. Top level declarations must have associated names, but the
-/// anonymous versions of these must not have names.
-const ExprParseContext = enum {
- declaration,
- expression,
-};
-
fn parseFnDecl(p: *Parse) Error!NodeIndex {
const trace = t.trace(@src());
defer trace.end();
@@ -339,8 +516,6 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {
});
}
-const TypePresence = enum { types_optional, types_required };
-
fn parseParamList(p: *Parse, types: TypePresence) Error!SubList {
const trace = t.trace(@src());
defer trace.end();
@@ -390,13 +565,16 @@ fn parseParamDecl(p: *Parse, types: TypePresence) Error!NodeIndex {
});
}
-fn parseStructDecl(p: *Parse) Error!NodeIndex {
+fn parseStructDecl(p: *Parse, ctx: ExprParseContext) Error!NodeIndex {
const trace = t.trace(@src());
defer trace.end();
const main_token = p.assertToken(.kw_struct);
- _ = try p.expect(.id);
+ switch (ctx) {
+ .declaration => _ = try p.expect(.id),
+ else => {},
+ }
_ = try p.expect(.start_brace);
@@ -409,6 +587,28 @@ fn parseStructDecl(p: *Parse) Error!NodeIndex {
});
}
+fn parseTupleDecl(p: *Parse, ctx: ExprParseContext) Error!NodeIndex {
+ const trace = t.trace(@src());
+ defer trace.end();
+
+ const main_token = p.assertToken(.kw_tuple);
+
+ switch (ctx) {
+ .declaration => _ = try p.expect(.id),
+ else => {},
+ }
+
+ _ = try p.expect(.start_paren);
+
+ const fields = try p.parseExpressionList(.end_paren, .expression);
+
+ return try p.addNode(.{
+ .tag = .tuple_type,
+ .main_token = main_token,
+ .data = .{ .list = fields },
+ });
+}
+
fn parseTestDecl(p: *Parse) Error!NodeIndex {
const trace = t.trace(@src());
defer trace.end();
@@ -447,9 +647,7 @@ fn parseStructFieldList(p: *Parse) Error!SubList {
try p.scratch.append(p.gpa, try p.parseStructField());
switch (p.currentTag()) {
- .comma => {
- _ = p.nextToken();
- },
+ .comma => _ = p.nextToken(),
.end_brace => break,
else => return p.failUnexpectedToken(.struct_field),
}
@@ -578,13 +776,13 @@ fn parseVarDecl(p: *Parse) Error!NodeIndex {
_ = try p.expect(.id);
const data_type: OptionalNodeIndex = if (p.eatToken(.colon)) |_|
- optionalNodeIndex(try p.tryParseExpression(.expression))
+ optionalNodeIndex(try p.parseExpressionOrFail(.expression))
else
.none;
_ = try p.expect(.equal);
- const data_init = try p.tryParseExpression(.expression);
+ const data_init = try p.parseExpressionOrFail(.expression);
_ = try p.expect(.semi);
@@ -619,204 +817,13 @@ fn isExpressionStart(p: *Parse) bool {
.string,
.tilde,
.kw_fn,
+ .kw_struct,
+ .kw_tuple,
=> true,
else => false,
};
}
-const Operator = enum(u8) {
- add = @intFromEnum(Token.Tag.plus),
- sub = @intFromEnum(Token.Tag.minus),
- mul = @intFromEnum(Token.Tag.star),
- div = @intFromEnum(Token.Tag.slash),
- rem = @intFromEnum(Token.Tag.percent),
- pow = @intFromEnum(Token.Tag.star_star),
- @"and" = @intFromEnum(Token.Tag.and_and),
- @"or" = @intFromEnum(Token.Tag.pipe_pipe),
- bit_and = @intFromEnum(Token.Tag.@"and"),
- bit_or = @intFromEnum(Token.Tag.pipe),
- bit_xor = @intFromEnum(Token.Tag.caret),
- shl = @intFromEnum(Token.Tag.lt_lt),
- shr = @intFromEnum(Token.Tag.gt_gt),
- eq = @intFromEnum(Token.Tag.equal_equal),
- ne = @intFromEnum(Token.Tag.bang_equal),
- lt = @intFromEnum(Token.Tag.lt),
- le = @intFromEnum(Token.Tag.lt_equal),
- gt = @intFromEnum(Token.Tag.gt),
- ge = @intFromEnum(Token.Tag.gt_equal),
- // Following operators are assignment operators
- assign = @intFromEnum(Token.Tag.equal),
- add_assign = @intFromEnum(Token.Tag.plus_equal),
- sub_assign = @intFromEnum(Token.Tag.minus_equal),
- mul_assign = @intFromEnum(Token.Tag.star_equal),
- div_assign = @intFromEnum(Token.Tag.slash_equal),
- rem_assign = @intFromEnum(Token.Tag.percent_equal),
- pow_assign = @intFromEnum(Token.Tag.star_star_equal),
- and_assign = @intFromEnum(Token.Tag.and_and_equal),
- or_assign = @intFromEnum(Token.Tag.pipe_pipe_equal),
- bit_and_assign = @intFromEnum(Token.Tag.and_equal),
- bit_or_assign = @intFromEnum(Token.Tag.pipe_equal),
- bit_xor_assign = @intFromEnum(Token.Tag.caret_equal),
- shl_assign = @intFromEnum(Token.Tag.lt_lt_equal),
- shr_assign = @intFromEnum(Token.Tag.gt_gt_equal),
-
- const Direction = enum { ltr, rtl };
-
- /// Only the binary operator levels are actually used,
- /// but all are listed for reference.
- const Level = enum {
- /// e. e[] e()
- accessor,
- /// new
- new,
- /// ++e, --e, !e, ~e, +e, -e, typeof, void, delete
- prefix,
- /// **
- exponentiation,
- /// *, /, %
- multiplicative,
- /// +, -
- additive,
- /// <<, >>, >>>
- bitwise_shift,
- /// <, <=, >, >=
- relational,
- /// ==, !=
- equality,
- /// &
- bit_and,
- /// ^
- bit_xor,
- /// |
- bit_or,
- /// &&
- logical_and,
- /// ||, ??
- logical_or,
- /// =, +=, -=, *=, /=, %=, / <<=, >>=, >>>=, &=, ^=, |=
- assign,
- };
-
- pub fn direction(op: Operator) Direction {
- return switch (op) {
- .pow => .rtl,
-
- .assign,
- .add_assign,
- .sub_assign,
- .mul_assign,
- .div_assign,
- .rem_assign,
- .pow_assign,
- .and_assign,
- .or_assign,
- .bit_and_assign,
- .bit_or_assign,
- .bit_xor_assign,
- .shl_assign,
- .shr_assign,
- => .rtl,
-
- else => .ltr,
- };
- }
-
- pub fn level(op: Operator) Level {
- return switch (op) {
- .pow => .exponentiation,
- .mul, .div, .rem => .multiplicative,
- .add, .sub => .additive,
- .shl, .shr => .bitwise_shift,
- .lt, .le, .gt, .ge => .relational,
- .eq, .ne => .equality,
- .@"and" => .logical_and,
- .@"or" => .logical_or,
- .bit_and => .bit_and,
- .bit_xor => .bit_xor,
- .bit_or => .bit_or,
- .assign,
- .add_assign,
- .sub_assign,
- .mul_assign,
- .div_assign,
- .rem_assign,
- .pow_assign,
- .and_assign,
- .or_assign,
- .bit_and_assign,
- .bit_or_assign,
- .bit_xor_assign,
- .shl_assign,
- .shr_assign,
- => .assign,
- };
- }
-
- const lut = brk: {
- const token_tags = std.enums.values(Token.Tag);
- const op_tags = std.enums.values(Operator);
-
- var is_operator_lut: [token_tags.len]bool = .{false} ** token_tags.len;
- for (op_tags) |op| {
- is_operator_lut[@intFromEnum(op)] = true;
- }
-
- var ast_tag_lut: [token_tags.len]Node.Tag = .{.root} ** token_tags.len;
- for (op_tags) |op| {
- ast_tag_lut[@intFromEnum(op)] = @field(Node.Tag, @tagName(op));
- }
-
- break :brk struct {
- const is_operator = is_operator_lut;
- const ast_tag = ast_tag_lut;
- };
- };
-
- pub fn fromToken(tag: Token.Tag) ?Operator {
- return if (lut.is_operator[@intFromEnum(tag)])
- @enumFromInt(@intFromEnum(tag))
- else
- null;
- }
-
- pub fn astTag(op: Operator) Node.Tag {
- const tag = lut.ast_tag[@intFromEnum(op)];
- assert(tag != .root); // invalid operator or lut construction
- return tag;
- }
-
- pub fn isAssign(op: Operator) bool {
- return switch (op) {
- .assign,
- .add_assign,
- .sub_assign,
- .mul_assign,
- .div_assign,
- .rem_assign,
- .pow_assign,
- .and_assign,
- .or_assign,
- .bit_and_assign,
- .bit_or_assign,
- .bit_xor_assign,
- .shl_assign,
- .shr_assign,
- => true,
- else => false,
- };
- }
-};
-
-const ParseExprMode = enum {
- /// Standard parsing
- expression,
- /// Statements allow assignment binary operators in addition to other expressions.
- statement,
- /// This bans struct initializations so that parsing stops at a '{'
- /// Example: "if hello {" must not be parsed as a struct initialization.
- preceding_block,
-};
-
fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
const trace = t.trace(@src());
defer trace.end();
@@ -831,8 +838,13 @@ fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
// These unary operators are greedy, so handle them first.
switch (p.currentTag()) {
- .kw_return => return p.parseReturn(),
- .kw_break => return p.parseBreak(),
+ .kw_return => return p.parseReturn(mode),
+ .kw_break => return p.parseBreak(mode),
+
+ .kw_assert => return p.parseGreedyUnary(.assert, mode),
+ .kw_comptime => return p.parseGreedyUnary(.@"comptime", mode),
+ .kw_pure => return p.parseGreedyUnary(.pure, mode),
+
else => {},
}
@@ -846,7 +858,7 @@ fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
return expr;
}
-fn tryParseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
+fn parseExpressionOrFail(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
return if (p.isExpressionStart())
p.parseExpression(mode)
else
@@ -858,7 +870,6 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
defer trace.end();
var expr = switch (p.currentTag()) {
- // Literals
.id => if (p.nextTag() == .colon)
try p.parseLabelExpression()
else
@@ -867,24 +878,27 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
.number_float => try p.blankNode(.number_float),
.string => try p.blankNode(.string),
- // Complex expressions
.builtin_id => try p.parseBuiltinCall(),
- .dot => try p.parseAnonInstantiation(),
- .kw_break => try p.parseBreak(),
+ .dot => try p.parseAnonStructInit(),
.kw_fn => try p.parseAnonFunction(),
.kw_if => try p.parseIf(),
- .kw_return => try p.parseReturn(),
+ .kw_tuple => try p.parseTupleType(),
+
.start_brace => try p.parseBlock(),
.start_bracket => try p.parseArrayValueOrType(mode),
- .start_paren => try p.parseTupleType(),
+ .start_paren => try p.parseTupleInit(),
+
+ .kw_struct => try p.parseStructDecl(.expression),
+
+ .kw_return => return p.parseReturn(mode),
+ .kw_break => return p.parseBreak(mode),
+ .kw_assert => return p.parseGreedyUnary(.assert, mode),
+ .kw_comptime => return p.parseGreedyUnary(.@"comptime", mode),
+ .kw_pure => return p.parseGreedyUnary(.pure, mode),
- // Unary operators
inline .bang,
.tilde,
.minus,
- .kw_comptime,
- .kw_pure,
- .kw_assert,
=> |tag| unary: {
const main_token = p.nextToken();
const expr = try p.parseExpressionPart(mode);
@@ -955,7 +969,7 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
return p.fail(.{ .array_type_flipped_hint = p.fullToken(p.pos) });
}
- const index = try p.tryParseExpression(.expression);
+ const index = try p.parseExpressionOrFail(.expression);
_ = try p.expect(.end_bracket);
@@ -1020,14 +1034,14 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, mode: ParseExprMod
});
}
-fn parseReturn(p: *Parse) Error!NodeIndex {
+fn parseReturn(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
const trace = t.trace(@src());
defer trace.end();
const main_token = p.assertToken(.kw_return);
const expr = if (p.isExpressionStart())
- try p.parseExpression(.expression)
+ try p.parseExpression(mode)
else
null;
@@ -1040,7 +1054,7 @@ fn parseReturn(p: *Parse) Error!NodeIndex {
});
}
-fn parseBreak(p: *Parse) Error!NodeIndex {
+fn parseBreak(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
const trace = t.trace(@src());
defer trace.end();
@@ -1052,7 +1066,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex {
null;
const expr = if (p.isExpressionStart())
- try p.parseExpression(.expression)
+ try p.parseExpression(mode)
else
null;
@@ -1066,6 +1080,23 @@ fn parseBreak(p: *Parse) Error!NodeIndex {
});
}
+fn parseGreedyUnary(p: *Parse, tag: Node.Tag, mode: ParseExprMode) Error!NodeIndex {
+ const trace = t.trace(@src());
+ defer trace.end();
+
+ const main_token = p.nextToken();
+
+ const expr = try p.parseExpressionOrFail(mode);
+
+ return try p.addNode(.{
+ .tag = tag,
+ .main_token = main_token,
+ .data = .{ .un = .{
+ .expr = expr,
+ } },
+ });
+}
+
fn parseBuiltinCall(p: *Parse) Error!NodeIndex {
const trace = t.trace(@src());
defer trace.end();
@@ -1083,11 +1114,7 @@ fn parseBuiltinCall(p: *Parse) Error!NodeIndex {
});
}
-fn parseExpressionList(
- p: *Parse,
- comptime end_token: Token.Tag,
- comptime fail_context: ExpectedContext,
-) Error!SubList {
+fn parseExpressionList(p: *Parse, comptime end_token: Token.Tag, fail_context: ExpectedContext) Error!SubList {
const trace = t.trace(@src());
defer trace.end();
@@ -1161,7 +1188,7 @@ fn parseElseValue(p: *Parse) Error!NodeIndex {
};
}
-fn parseAnonInstantiation(p: *Parse) Error!NodeIndex {
+fn parseAnonStructInit(p: *Parse) Error!NodeIndex {
const trace = t.trace(@src());
defer trace.end();
@@ -1272,9 +1299,20 @@ fn parseArrayValueOrType(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
});
}
-fn parseTupleType(p: *Parse) Error!NodeIndex {
+fn parseTupleInit(p: *Parse) Error!NodeIndex {
const main_token = p.assertToken(.start_paren);
const list = try p.parseExpressionList(.end_paren, .expression);
+ return p.addNode(.{
+ .tag = .tuple_init,
+ .main_token = main_token,
+ .data = .{ .list = list },
+ });
+}
+
+fn parseTupleType(p: *Parse) Error!NodeIndex {
+ const main_token = p.assertToken(.kw_tuple);
+ _ = try p.expect(.start_paren);
+ const list = try p.parseExpressionList(.end_paren, .expression);
return p.addNode(.{
.tag = .tuple_type,
.main_token = main_token,
diff --git a/src/core/Sema.zig b/src/core/Sema.zig
index cd5881ac5810b77ffc6fb5b0da7d2f552fd074e0..8a578ffdd2a2d1cb68474ac078a1d4f9081e0c7b 100644
--- a/src/core/Sema.zig
+++ b/src/core/Sema.zig
@@ -1,10 +1,13 @@
-//! Semantic Analysis
+//! Semantic Analysis converts `Teg`
//!
-//! Designed as a nonrecursive pause/resumable state machine, Sema converts
-//! declarations to Intern Pool `Value`s and function bodies to `Air`.
+//! Designed as a nonrecursive state machine which can be paused when an
+//! unfinished dependency is encountered, allowing the thread to work
+//! on other analysis jobs.
//!
-//! - Compile time resolution of values
-//! - Type checking
+//! Sema's responsibilities include
+//! - Operation validation and reporting of most compile errors.
+//! - Evaluation of `comptime` code.
+//! - Declaring dependencies on other analysis units.
comp: *Comp,
ip: *InternPool,
@@ -175,6 +178,7 @@ const Frame = struct {
undefined: void,
conditional: Conditional,
+ func: Func,
};
const Conditional = enum {
@@ -183,7 +187,16 @@ const Frame = struct {
runtime_no,
};
+ const Func = union(enum) {
+ return_type,
+ argument: u16,
+ };
+
fn initFromBlock(s: *Sema, block_index: Teg.Inst.Index, mode: AnalysisMode, data: Data) Frame {
+ switch (s.teg.instTag(block_index)) {
+ else => unreachable, // not a block
+ .block, .@"comptime" => {},
+ }
const list = s.teg.instEncodedData(block_index)
.decodeUnsafe(Teg.PlNode(Teg.IndexList), s.teg.instTag(block_index))
.extra_index.get(&s.teg)
@@ -476,7 +489,8 @@ const InstructionKind = enum {
.@"comptime",
.@"if",
=> .control_flow,
- // these pop frames, which may end the compilation
+ // break is used to pop a frame, which has the
+ // possibility to end analysis
.@"break",
=> .pop_control_flow,
// these are statements which do not evaluate to anything (void)
@@ -501,6 +515,12 @@ const PopControlFlowState = enum { stop, @"continue" };
fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag).Result() {
switch (tag) {
+ .block => unreachable, // frame must already be pushed
+ .declaration,
+ .variable_declaration,
+ .test_declaration,
+ => unreachable, // declarations are not analzed by Sema
+
// TODO: pre-evaluate `.param` nodes, since they are always at the start of a function.
// this way we can avoid repeating the non-trivial call into InternPool
.param => {
@@ -666,6 +686,8 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
return try s.fieldAccessArray(lhs, lhs_type, field.rhs);
} else if (lhs_type.toStructType(s.ip)) |struct_type| {
return try s.fieldAccessStruct(lhs, lhs_type, struct_type, field.rhs);
+ } else if (lhs_type.toTupleType(s.ip)) |tuple_type| {
+ return try s.fieldAccessTuple(lhs, lhs_type, tuple_type, field.rhs);
} else if (lhs.value()) |value| {
if (value.valueTag(s.ip) == .external_value) {
const rhs = try s.getCoercedValue(s.lazyNode(.rhs), field.rhs, .string);
@@ -877,8 +899,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
// return Value.void.toAir();
// },
- // control flow
- .block => unreachable, // frame should already be pushed
.@"comptime" => {
const index = s.frame.currentIndex();
try s.pushFrame();
@@ -892,50 +912,66 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
.{ .params = Type, .captures = Air.Inst.Ref },
.{ data.params.len, data.captures.len },
);
- defer reserve.deinit();
- if (data.captures.len > 0) @panic("captures");
+ if (data.captures.len > 0) @panic("TODO: captures");
+
+ var handled_reserve: std.debug.SafetyLock = .{};
+ handled_reserve.lock();
+ defer handled_reserve.assertUnlocked();
+ errdefer handled_reserve.unlock();
for (data.params.slice(&s.teg), 0..) |param, i| {
const src = s.lazyNode(.argument(@intCast(i)));
- const ty = try s.getType(src, param.unwrap() orelse @panic("TODO: inferred param sets"));
- if (!ty.hasPossibleRepresentation(s.ip)) {
- return s.fail(.param_comptime_only, .{
- .src = src,
- .type = ty,
- });
+
+ const known = param.unwrap() orelse @panic("TODO: inferred param sets");
+ switch (known.decode()) {
+ .value => |val| {
+ const ty = (try s.coerceValue(src, val, .type)).toTypeUnsafe(s.ip);
+ if (!ty.hasPossibleRepresentation(s.ip)) {
+ return s.fail(.param_comptime_only, .{
+ .src = src,
+ .type = ty,
+ });
+ }
+ reserve.items(.params)[i] = ty;
+ },
+ .index => |block| {
+ const mode = s.frame.mode;
+ try s.pushFrame();
+ s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .{ .argument = @intCast(i) } });
+ s.frame.mark_index = reserve.index();
+ handled_reserve.unlock();
+ return;
+ },
}
- reserve.items(.params)[i] = ty;
}
- const return_type = try s.getType(
- s.lazyNode(.return_type),
- data.return_type.unwrap() orelse .void,
- );
+ const return_type = ret: {
+ switch ((data.return_type.unwrap() orelse break :ret null).decode()) {
+ .value => |val| {
+ const ty = (try s.coerceValue(s.lazyNode(.return_type), val, .type)).toTypeUnsafe(s.ip);
+ if (!ty.hasPossibleRepresentation(s.ip)) {
+ return s.fail(.runtime_incompatible_type, .{
+ .src = s.lazyNode(.return_type),
+ .type = ty,
+ });
+ }
+ break :ret ty;
+ },
+ .index => |block| {
+ const mode = s.frame.mode;
+ try s.pushFrame();
+ s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .return_type });
+ s.frame.mark_index = reserve.index();
+ handled_reserve.unlock();
+ return;
+ },
+ }
+ };
- const tracked_inst = try s.ip.trackInst(s.gpa, s.file, s.frame.currentIndex());
- const func_type = try s.makeType(.{
- .function_type = .{
- .param_types = reserve.items(.params),
- .return_type = return_type,
- },
- });
- const func_value = try s.makeValue(.{
- .func = .{
- .ty = func_type,
- .owner_decl = s.techDebtDeclIndex(), // TODO: ooh, this is a fun one!
- .teg_body_inst = tracked_inst,
- },
- });
-
- s.inst_map.add(s.frame.currentIndex(), func_value.toAir());
-
- s.frame.advance();
-
- // TODO: move this queue once we perform one of these
- // - depend on the return type of an inferred-return-type function
- // - use the value in any runtime context ()
- try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } });
+ try s.postVisitFunctionPrototype(reserve, return_type);
+ handled_reserve.unlock();
},
+
.@"break" => {
const brk: Teg.Break = s.frame.currentPayload(.@"break");
const operand = s.getRef(brk.operand, .marked);
@@ -978,12 +1014,14 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
switch (data.conditional) {
.comptime_condition => {
+ assert(operand.kind == .value); // must be comptime
s.inst_map.add(s.frame.currentIndex(), operand);
s.frame.advance();
return .@"continue";
},
.runtime_yes => {
try s.emitRuntimeBlock(if_data.yes.decode().index, operand, mark);
+ mark.deinit();
switch (if_data.no.decode()) {
.value => {
@@ -1025,6 +1063,79 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
}));
s.frame.advance();
},
+ .func => {
+ const func_data: Teg.Func = s.frame.currentPayload(.func);
+
+ const reserve: FunctionReserve = .fromIndex(
+ &s.scratch,
+ mark.index,
+ .{ func_data.params.len, func_data.captures.len },
+ );
+
+ const return_type = switch (data.func) {
+ .argument => |arg_num| ret: {
+ for (
+ func_data.params.slice(&s.teg)[arg_num + 1 ..],
+ reserve.items(.params)[arg_num + 1 ..],
+ arg_num + 1..,
+ ) |param, *dst, i| {
+ const known = param.unwrap() orelse @panic("TODO: inferred param sets");
+ switch (known.decode()) {
+ .value => |val| {
+ const ty = (try s.coerceValue(s.lazyNode(.argument(@intCast(i))), val, .type)).toTypeUnsafe(s.ip);
+ if (!ty.hasPossibleRepresentation(s.ip)) {
+ return s.fail(.param_comptime_only, .{
+ .src = s.lazyNode(.argument(@intCast(i))),
+ .type = ty,
+ });
+ }
+ dst.* = ty;
+ },
+ .index => |block| {
+ const mode = s.frame.mode;
+ try s.pushFrame();
+ s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .{ .argument = @intCast(i) } });
+ return .@"continue";
+ },
+ }
+ }
+
+ switch ((func_data.return_type.unwrap() orelse break :ret null).decode()) {
+ .value => |val| {
+ const ty = (try s.coerceValue(s.lazyNode(.return_type), val, .type)).toTypeUnsafe(s.ip);
+ if (!ty.hasPossibleRepresentation(s.ip)) {
+ return s.fail(.runtime_incompatible_type, .{
+ .src = s.lazyNode(.return_type),
+ .type = ty,
+ });
+ }
+ break :ret ty;
+ },
+ .index => |block| {
+ const mode = s.frame.mode;
+ try s.pushFrame();
+ s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .return_type });
+ s.frame.mark_index = reserve.index();
+ return .@"continue";
+ },
+ }
+ },
+ .return_type => ret: {
+ const val = operand.value() orelse unreachable; // must be comptime
+ const ty = (try s.coerceValue(s.lazyNode(.return_type), val, .type)).toTypeUnsafe(s.ip);
+ if (!ty.hasPossibleRepresentation(s.ip)) {
+ return s.fail(.runtime_incompatible_type, .{
+ .src = s.lazyNode(.return_type),
+ .type = ty,
+ });
+ }
+ break :ret ty;
+ },
+ };
+
+ try s.postVisitFunctionPrototype(reserve, return_type);
+ return .@"continue";
+ },
else => |t| std.debug.panic("TODO: post-visit .{s}", .{@tagName(t)}),
}
@@ -1141,7 +1252,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
var has_errors = false;
var is_constant = true;
- // Allocate scratch space to store the values as they are being prepared.
const scratch = try s.scratch.reserve(
s.arena(),
Air.Inst.Ref.Optional,
@@ -1150,11 +1260,10 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
defer scratch.deinit();
@memset(scratch.slice(), .none);
+ // The ordering of `storage` is defined by the struct's type, which
+ // may be different than the instantiation. The fields are visited in
+ // order of the instantiation.
for (node.keys(&s.teg), node.inits(&s.teg), 0..) |init_key, init_value, i| {
- // The order of `storage` is as ordered by the type, but this
- // can be in a different ordering than the initialization. We
- // guarantee the ordering the initializers are visited is in the
- // order they are declared.
const found_index = index: for (struct_type.field_keys, 0..) |decl_key, j| {
if (decl_key.eqlSlice(init_key.slice(s.teg.string_bytes), s.ip))
break :index j;
@@ -1181,7 +1290,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
.marked,
) catch |e| switch (e) {
error.AnalysisFail => {
- // Instead of bailing out with `try`, we will continue the loop.
+ // Instead of bailing out with `try`, all fields will be analyzed
has_errors = true;
// Mark something non-null so that duplicate field errors are emitted.
scratch.at(found_index).* = comptime Value.void.toAir().toOptional();
@@ -1193,29 +1302,29 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
scratch.at(found_index).* = coerced.toOptional();
}
- // Check for missing fields
- {
- const storage = scratch.slice();
- var i: usize = 0;
- while (mem.indexOfScalarPos(Air.Inst.Ref.Optional, storage, i, .none)) |index| {
- try s.addError(.struct_init_missing_field, .{
- .ty = ty,
- .field = @intCast(index),
- .src = s.lazyNode(.main_node),
- });
- i = index + 1;
- }
- if (i != 0) has_errors = true;
+ const storage = scratch.slice();
+ var i: usize = 0;
+ while (mem.indexOfScalarPos(Air.Inst.Ref.Optional, storage, i, .none)) |index| {
+ try s.addError(.struct_init_missing_field, .{
+ .ty = ty,
+ .field = @intCast(index),
+ .src = s.lazyNode(.main_node),
+ });
+ i = index + 1;
}
+ if (i != 0) has_errors = true;
if (has_errors)
return Error.AnalysisFail;
+ // Now that there are definitely no missing fields, it is safe to
+ // interpret `scratch` as `Air.Inst.Ref`
+
if (is_constant) {
- // Convert all items in storage into a Value type
const result_storage = scratch.sliceReinterpret(Value);
- for (scratch.slice(), scratch.sliceReinterpret(Value)) |ref, *value| {
- value.* = ref.unwrap().?.decode().value;
+ for (scratch.slice(), result_storage) |ref, *value| {
+ // `ref` and `value` alias; this coercion is performed in-place
+ value.* = (ref.unwrap() orelse unreachable).decode().value;
}
return try s.makeValueAir(.{ .struct_value = .{
.ty = ty,
@@ -1224,7 +1333,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
} else {
try s.ensureAtRuntime(s.lazyNode(.main_node));
- // Create a runtime struct instantiation
const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice());
return try s.addRef(.struct_init, .{
.ty = ty,
@@ -1239,6 +1347,73 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
return s.makeValueAir(.{ .array_type = .{ .item_type = operand } });
},
+ .tuple_type => {
+ const data: Teg.TupleType = s.frame.currentPayload(.tuple_type);
+
+ const scratch = try s.scratch.reserve(s.arena(), Type, data.items.len);
+ defer scratch.deinit();
+
+ for (data.items.slice(&s.teg), scratch.slice(), 0..) |src, *dst, i| {
+ dst.* = try s.getType(s.lazyNode(.argument(@intCast(i))), src);
+ }
+
+ return s.makeValueAir(.{ .tuple_type = .{ .items = scratch.slice() } });
+ },
+
+ .tuple_init => {
+ const data: Teg.TupleInit = s.frame.currentPayload(.tuple_init);
+
+ _ = data.type_hint; // TODO: use type hint
+
+ const scratch = try s.scratch.reserve(s.arena(), Air.Inst.Ref, data.items.len);
+ defer scratch.deinit();
+
+ var is_const = true;
+ for (data.items.slice(&s.teg), scratch.slice()) |src, *dst| {
+ const result = s.getRef(src, .marked);
+ dst.* = result;
+ if (result.kind == .index) is_const = false;
+ }
+
+ if (!is_const) {
+ try s.ensureAtRuntime(s.lazyNode(.main_node));
+
+ for (scratch.slice(), 0..) |*ref, i| {
+ ref.* = try s.ensureValueRuntime(s.lazyNode(.argument(@intCast(i))), ref.*);
+ }
+ }
+
+ const ty = ty: {
+ const type_scratch = try s.scratch.reserve(s.arena(), Type, data.items.len);
+ defer type_scratch.deinit();
+
+ for (scratch.slice(), type_scratch.slice()) |val, *ty| {
+ ty.* = s.typeOfRef(val);
+ }
+
+ break :ty try s.makeType(.{ .tuple_type = .{ .items = type_scratch.slice() } });
+ };
+
+ if (is_const) {
+ const result_storage = scratch.sliceReinterpret(Value);
+ for (scratch.slice(), result_storage) |ref, *value| {
+ // `ref` and `value` alias; this coercion is performed in-place
+ value.* = ref.decode().value;
+ }
+
+ return try s.makeValueAir(.{ .tuple_value = .{
+ .items = result_storage,
+ .type = ty,
+ } });
+ } else {
+ const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice());
+ return try s.addRef(.struct_init, .{
+ .field_values = value_index,
+ .ty = ty,
+ });
+ }
+ },
+
.result_type_argument => {
const result: Teg.ResultTypeArgument = s.frame.currentData(.result_type_argument);
@@ -1605,6 +1780,11 @@ fn coerceValue(s: *Sema, src: LazyNode, value: Value, to: Type) Error!Value {
return s.coerceArrayValue(src, value, to_array_type.item_type);
}
}
+ if (to.toTupleType(s.ip)) |to_tuple_type| {
+ if (value.valueTag(s.ip) == .tuple_value) {
+ return s.coerceTupleValue(src, value, from, to, to_tuple_type);
+ }
+ }
},
}
return s.coercionError(src, from, to);
@@ -1615,6 +1795,14 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst
.value => |v| {
const ty = v.typeOf(s.ip);
switch (ty) {
+ // Allowed at runtime
+ .bool,
+ .float,
+ .int32,
+ .string,
+ .void,
+ .empty_tuple,
+ => {},
.numeric_literal => {
const float = v.toFloat(s.ip) orelse unreachable;
@@ -1649,13 +1837,6 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst
.type = ty,
}),
.@"opaque" => @panic("TODO: represent comptime-known opaques better"),
- // Allowed at runtime
- .bool,
- .float,
- .int32,
- .string,
- .void,
- => {},
_ => if (!ty.hasPossibleRepresentation(s.ip)) {
return s.fail(.runtime_incompatible_type, .{
.src = src,
@@ -1728,6 +1909,44 @@ fn coerceArrayValue(s: *Sema, src: LazyNode, value: Value, item_type: Type) !Val
} });
}
+fn coerceTupleValue(
+ s: *Sema,
+ src: LazyNode,
+ value: Value,
+ value_type: Type,
+ target_type: Type,
+ target_key: InternPool.Key.TupleType,
+) !Value {
+ const tuple_value = value.toKey(s.ip).tuple_value;
+ if (tuple_value.items.len != target_key.items.len)
+ return s.coercionError(src, value_type, target_type);
+
+ const len = tuple_value.items.len;
+
+ const scratch = try s.scratch.reserveMulti(
+ s.arena(),
+ .{ .values = Value, .types = Type },
+ .{ @intCast(len), @intCast(len) },
+ );
+ defer scratch.deinit();
+
+ // This function will append new items to the intern pool, invalidating
+ // existing ones. Copy all data into a stable slice first.
+ @memcpy(scratch.items(.values), tuple_value.items);
+ @memcpy(scratch.items(.types), target_key.items);
+
+ for (0..len) |i| {
+ // `s.scratch` can be invalidated by having a tuple of tuples and coercing it.
+ const coerced = try s.coerceValue(src, scratch.at(.values, i).*, scratch.at(.types, i).*);
+ scratch.at(.values, i).* = coerced;
+ }
+
+ return try s.makeValue(.{ .tuple_value = .{
+ .type = target_type,
+ .items = scratch.items(.values),
+ } });
+}
+
fn lookupDecl(s: *Sema, name: NullTerminatedString) InternPool.DeclIndex {
var nsi = s.ns_index;
while (true) {
@@ -1861,6 +2080,68 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Teg.In
}
}
+fn fieldAccessTuple(
+ s: *Sema,
+ lhs: Air.Inst.Ref,
+ lhs_type: Type,
+ tuple_type: InternPool.Key.TupleType,
+ field: Teg.Inst.Ref,
+) !Air.Inst.Ref {
+ const index_value = try s.getCoercedValue(s.lazyNode(.rhs), field, .int32);
+ const index_signed = index_value.toInt32(s.ip) orelse unreachable;
+ if (index_signed < 0 or index_signed >= tuple_type.items.len) {
+ return s.fail(.field_lookup_failed, .{
+ .ty = lhs_type,
+ .src = s.lazyNode(.lhs),
+ .field = index_value,
+ });
+ }
+ const index: u32 = @intCast(index_signed);
+
+ switch (lhs.decode()) {
+ .value => |value| {
+ return value.toKey(s.ip).tuple_value.items[index].toAir();
+ },
+ .index => |lhs_index| {
+ try s.ensureAtRuntime(s.lazyNode(.lhs));
+ return s.addRef(.field_tuple, .{
+ .base = lhs_index,
+ .field_index = index,
+ });
+ },
+ }
+}
+
+const FunctionReserve = core.U32Stack.ReserveMulti(.{ .params = Type, .captures = Air.Inst.Ref });
+
+fn postVisitFunctionPrototype(s: *Sema, reserve: FunctionReserve, return_type: ?Type) !void {
+ const tracked_inst = try s.ip.trackInst(s.gpa, s.file, s.frame.currentIndex());
+ const func_type = try s.makeType(.{
+ .function_type = .{
+ .param_types = reserve.items(.params),
+ .return_type = return_type,
+ },
+ });
+ const func_value = try s.makeValue(.{
+ .func = .{
+ .ty = func_type,
+ .owner_decl = s.techDebtDeclIndex(), // TODO: ooh, this is a fun one!
+ .teg_body_inst = tracked_inst,
+ },
+ });
+
+ s.inst_map.add(s.frame.currentIndex(), func_value.toAir());
+
+ s.frame.advance();
+
+ reserve.deinit();
+
+ // TODO: move this queue once we perform one of these
+ // - depend on the return type of an inferred-return-type function
+ // - use the value in any runtime context ()
+ try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } });
+}
+
//
fn tempAir(s: *const Sema) Air {
diff --git a/src/core/Teg.Dump.zig b/src/core/Teg.Dump.zig
index 222c7f1beed361a53fc35f22e250a0335b73a4a0..71bccf7dbebad6757c530ff9bf2a823f2b64467d 100644
--- a/src/core/Teg.Dump.zig
+++ b/src/core/Teg.Dump.zig
@@ -319,6 +319,24 @@ const State = struct {
if (data.type_hint != .none)
try writer.writeAll(")");
},
+ .tuple_type => {
+ const data: Teg.TupleType = teg.instPayload(.tuple_type, inst);
+
+ try writer.writeAll("(");
+ try s.dumpRefMarkerList(writer, data.items.slice(teg), '[', ']');
+ try writer.writeAll(")");
+ },
+ .tuple_init => {
+ const data: Teg.TupleInit = teg.instPayload(.tuple_init, inst);
+
+ try writer.writeAll("(");
+ if (data.type_hint.unwrap()) |type_hint| {
+ try s.dumpRef(writer, type_hint);
+ try writer.writeAll(", ");
+ }
+ try s.dumpRefMarkerList(writer, data.items.slice(teg), '[', ']');
+ try writer.writeAll(")");
+ },
.result_type_argument => {
const data: Teg.ResultTypeArgument = teg.instData(.result_type_argument, inst);
diff --git a/src/core/Teg.zig b/src/core/Teg.zig
index e6e2b340e3386a9622c72ddf865abcfaa1c75c94..d13225a790ad79472320df30366cc39e8ad4f597 100644
--- a/src/core/Teg.zig
+++ b/src/core/Teg.zig
@@ -75,6 +75,9 @@ pub const Tag = enum(u8) {
struct_init,
array_init,
+ tuple_init,
+ tuple_type,
+
add,
sub,
mul,
@@ -123,6 +126,7 @@ pub const Tag = enum(u8) {
.variable_declaration,
.test_declaration,
=> PlNode(Decl),
+
.coercion => PlNode(Coercion),
.@"break" => PlNode(Break),
.number => PlNode(Float),
@@ -136,6 +140,8 @@ pub const Tag = enum(u8) {
.struct_decl => PlNode(StructDecl),
.struct_init => PlNode(StructInit),
.array_init => PlNode(ArrayInit),
+ .tuple_type => PlNode(TupleType),
+ .tuple_init => PlNode(TupleInit),
.func => PlNode(Func),
.load_decl => StrNode,
@@ -146,7 +152,7 @@ pub const Tag = enum(u8) {
.result_type_tuple_field => ResultTypeTupleField,
.result_type_array_value => ResultTypeArrayValue,
- // Single instruction operand
+ // One operand instructions
.memory,
.discard,
.ensure_void,
@@ -162,7 +168,7 @@ pub const Tag = enum(u8) {
.assert,
=> RefNode,
- // Two instruction operands
+ // Two operand instructions
.field,
.index,
.add,
@@ -230,7 +236,7 @@ pub const Inst = struct {
main_module_inst = 0,
_,
- pub const static_len = @intFromEnum(Ref.empty_array) + 1;
+ pub const static_len = @intFromEnum(Ref.empty_tuple) + 1;
pub fn toRef(i: Index) Inst.Ref {
return @enumFromInt(static_len + @intFromEnum(i));
@@ -269,6 +275,7 @@ pub const Inst = struct {
numeric_literal_type,
numeric_literal_array_type,
never_array_type,
+ empty_tuple_type,
void,
bool_true,
@@ -278,7 +285,7 @@ pub const Inst = struct {
nan,
empty_string,
empty_array,
-
+ empty_tuple,
_,
pub const Decoded = union(enum) {
@@ -423,6 +430,20 @@ pub const ArrayInit = struct {
list: RefList,
};
+pub const TupleType = struct {
+ /// The items in the tuple
+ items: RefList,
+};
+
+pub const TupleInit = struct {
+ /// Optionally provide a result type for more reliable type coercion.
+ /// If specified, this must be an array type. Otherwise, the first
+ /// element's type is used.
+ type_hint: Inst.Ref.Optional,
+ /// The items in the tuple
+ items: RefList,
+};
+
pub const Decl = struct {
name: NullTerminatedString,
hash: Cache.BinDigest,
@@ -697,6 +718,8 @@ pub fn writeToHasher(
.struct_decl,
.struct_init,
.array_init,
+ .tuple_init,
+ .tuple_type,
.func,
.store_decl,
.store_field,
@@ -902,6 +925,19 @@ fn writePlNodeInnerToHasher(
hasher.add(isolatedIndexValue(decl_index, data.body));
hasher.add(data.instruction_count);
},
+ TupleType => {
+ hasher.add(data.items.len);
+ for (data.items.slice(teg)) |item| {
+ hasher.add(isolatedRefValue(decl_index, item));
+ }
+ },
+ TupleInit => {
+ if (data.type_hint.unwrap()) |type_hint|
+ hasher.add(isolatedRefValue(decl_index, type_hint))
+ else
+ hasher.add(@as(i32, std.math.maxInt(i32)));
+ writeRefListToHasher(hasher, decl_index, data.items.slice(teg));
+ },
else => @compileError("hash not implemented for " ++ @typeName(T)),
}
}
diff --git a/src/core/Tokenizer.zig b/src/core/Tokenizer.zig
index 2ed112a4e62969157e0190d5fcb6f0da3e1520b3..f14b847ebe7fa4f7f511f737fc50f4468295aed4 100644
--- a/src/core/Tokenizer.zig
+++ b/src/core/Tokenizer.zig
@@ -75,6 +75,7 @@ pub const Token = struct {
kw_return,
kw_struct,
kw_trait,
+ kw_tuple,
kw_var,
kw_while,
// sort-lines: end
@@ -134,7 +135,7 @@ pub const Token = struct {
/// Represents a tokenization error. Instead of reporting an error during
/// tokenization, parsing will begin, and an error will be reported when
- /// this token is hit. That way, the error can tell you what it expected.
+ /// this token is hit. That way, the error .
invalid,
invalid_number,
invalid_string,
@@ -275,8 +276,8 @@ pub const Token = struct {
/// Instead of storing the length of a token, we can recompute this
/// information on demand, as it is not often needed.
///
- /// If you are computing the length of something that is knowable
- /// without the source code, use token.tag.staticLen() instead.
+ /// When computing the length of something that can be known without
+ /// the source code, use token.tag.staticLen() instead.
pub fn len(token: Token, utf8_source: [:0]const u8) u32 {
return token.tag.staticLen() orelse switch (token.tag) {
diff --git a/src/core/Transform.zig b/src/core/Transform.zig
index 32509ffc2eba9d367bf18111af3fbe6ec57ee987..4db0d59b9feab0f6da84f31d2a72b20038ada801 100644
--- a/src/core/Transform.zig
+++ b/src/core/Transform.zig
@@ -137,10 +137,9 @@ const ResultType = union(enum) {
.used => unreachable, // Use after free
.ref => |r| r,
.type_of => |r| try t.addRef(.type_of, .{
- // Result types don't have associated nodes, which is ok because
- // type_of cannot fail in the context we are using it. Instead,
- // we will use a predictable poison value, so that an attempt
- // to print this will crash.
+ // Result types do not have associated nodes which is okay as `type_of` cannot
+ // fail in the context it is used in. A predictable poison value is used instead
+ // so that an attempt to print the following will cause a crash.
.src_node = std.math.maxInt(i32),
.operand = r,
}),
@@ -281,6 +280,7 @@ const Symbol = struct {
value: union {
mutable: Inst.Index,
immutable: Inst.Ref,
+ param_index: u32,
none: void,
},
result_type_ref: Inst.Ref,
@@ -903,6 +903,7 @@ fn transformExpression(
switch (node_tag) {
.root,
.param_decl,
+ .struct_field,
=> unreachable, // not an expression
.id => {
@@ -1520,6 +1521,37 @@ fn transformExpression(
});
},
+ .tuple_init => {
+ const tuple_init = t.ast.nodeData(node).list;
+
+ const items = try t.transformExpressionList(ctx, .none, tuple_init.slice(&t.ast)) orelse return null;
+
+ if (items.len == 0) {
+ return .empty_tuple;
+ }
+
+ return try t.addRef(.tuple_init, .{
+ .src_node = ctx.nodeOffset(node),
+ .extra_index = try t.extra.append(t.gpa, Teg.TupleInit, .{
+ .type_hint = .none,
+ .items = items,
+ }),
+ });
+ },
+
+ .tuple_type => {
+ const tuple_type = t.ast.nodeData(node).list;
+
+ const items = try t.transformExpressionList(ctx, .none, tuple_type.slice(&t.ast)) orelse return null;
+
+ return try t.addRef(.tuple_type, .{
+ .src_node = ctx.nodeOffset(node),
+ .extra_index = try t.extra.append(t.gpa, Teg.TupleType, .{
+ .items = items,
+ }),
+ });
+ },
+
inline .fn_decl, .fn_expr => |tag| {
const data = switch (tag) {
.fn_decl => t.ast.fullFnDecl(node),
diff --git a/src/core/js/Js.zig b/src/core/js/Js.zig
index a4e4d0e445471e24baf6af562a47c835af89923d..71c01f168567a2b5d06203c1fd067d0b2a898666 100644
--- a/src/core/js/Js.zig
+++ b/src/core/js/Js.zig
@@ -43,7 +43,7 @@ pub fn link(
entry_point: Comp.Decl.Index,
writer: std.io.AnyWriter,
) !void {
- var state = Link{
+ var state: Link = .{
.comp = comp,
.backend = backend,
};
diff --git a/src/core/js/Link.zig b/src/core/js/Link.zig
index 1fbb16b6fb7618dd551f650de75c2c7eeabd5d59..a60404f119611e2fd5e5823044f36caebf92c403 100644
--- a/src/core/js/Link.zig
+++ b/src/core/js/Link.zig
@@ -309,7 +309,18 @@ fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytyp
if (i != 0)
try writer.writeAll(",");
- // TODO: handle special identifiers
+ try link.printValue(inner_value, .assignment, writer);
+ }
+ try writer.writeAll("]");
+ },
+
+ .tuple_value => |tuple_value| {
+ try writer.writeAll("[");
+
+ for (tuple_value.items, 0..) |inner_value, i| {
+ if (i != 0)
+ try writer.writeAll(",");
+
try link.printValue(inner_value, .assignment, writer);
}
try writer.writeAll("]");
diff --git a/src/core/js/NodeGen.zig b/src/core/js/NodeGen.zig
index 5ed8f7017f896b336b79b901ef3c3c3b721e508f..72a3411dab72b76f9ddc6d5398e4cbefe36dff0d 100644
--- a/src/core/js/NodeGen.zig
+++ b/src/core/js/NodeGen.zig
@@ -283,6 +283,15 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
try c.markExpression(inst, node);
},
+ .field_tuple => {
+ const data: Air.FieldStruct = c.air.instData(.field_tuple, inst);
+ const node = try c.add(.index, .{
+ .lhs = try c.fetchInstExpression(data.base),
+ .rhs = try c.makeValueInst(.{ .int32 = @intCast(data.field_index) }),
+ });
+ try c.markExpression(inst, node);
+ },
+
.array_len => {
const data: Air.Un = c.air.instData(.array_len, inst);
const node = try c.add(.field, .{
@@ -459,9 +468,9 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
},
.array_init => {
- const struct_init: Air.ArrayInit = c.air.instData(.array_init, inst);
+ const array_init: Air.ArrayInit = c.air.instData(.array_init, inst);
- const items = struct_init.list.get(&c.air).slice(&c.air);
+ const items = array_init.list.get(&c.air).slice(&c.air);
const store_offset = try c.extra.alloc(c.gpa, items.len);
for (
@@ -478,6 +487,28 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
});
try c.markExpression(inst, node);
},
+
+ .tuple_init => {
+ const tuple_init: Air.TupleInit = c.air.instData(.tuple_init, inst);
+
+ const field_count = tuple_init.ty.toTupleType(c.ip).?.items.len;
+ const items = tuple_init.field_values;
+ const store_offset = try c.extra.alloc(c.gpa, field_count);
+
+ for (
+ items..items + field_count,
+ store_offset..,
+ ) |in_i, expr_i| {
+ const in = try c.fetchExpression(@bitCast(c.air.extra.list.items[in_i]));
+ c.extra.list.items[expr_i] = @intFromEnum(in);
+ }
+
+ const node = try c.add(.array, .{
+ .start = store_offset,
+ .len = @intCast(field_count),
+ });
+ try c.markExpression(inst, node);
+ },
}
}
@@ -557,6 +588,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index {
.simple_type,
.struct_type,
.array_type,
+ .tuple_type,
=> unreachable, // Type values are not allowed here
.int32,
@@ -566,6 +598,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index {
.simple_value,
.struct_value,
.array_value,
+ .tuple_value,
=> return try c.add(.plain_value, .{ .value = v }),
.external_value => {
@@ -585,6 +618,13 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index {
}
}
+fn makeValueInst(c: *NodeGen, k: InternPool.Key) !Node.Index {
+ return try c.valueToExpression(c.ip.get(c.gpa, k) catch |err| switch (err) {
+ error.TooManyValues => @panic("TODO: handle TooManyValues"),
+ else => |e| return e,
+ });
+}
+
fn addTemporary(c: *NodeGen) !Node.Index {
const t = tracer.trace(@src());
defer t.end();
@@ -725,6 +765,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
.arg,
.field_opaque,
.field_struct,
+ .field_tuple,
.field_array,
.add,
.sub,
@@ -739,6 +780,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
.struct_init,
.array_init,
.array_len,
+ .tuple_init,
=> true,
// JavaScript variable declarations require statements.
diff --git a/src/core/stack.zig b/src/core/stack.zig
index 6ce630a36106d8fdb64b70b2bf836b71c9e8cf58..3e8b00dc1d26c7e2f93a1fe985abf20eee83f88b 100644
--- a/src/core/stack.zig
+++ b/src/core/stack.zig
@@ -15,7 +15,7 @@ pub fn Stack(comptime Base: type) type {
pub const Mark = struct {
stack: *Self,
- index: usize,
+ index: u32,
pub fn deinit(m: Mark) void {
if (core.runtime_safety)
@@ -23,8 +23,8 @@ pub fn Stack(comptime Base: type) type {
m.stack.list.shrinkRetainingCapacity(m.index);
}
- pub fn len(m: Mark) usize {
- return m.stack.list.items.len - m.index;
+ pub fn len(m: Mark) u32 {
+ return @intCast(m.stack.list.items.len - m.index);
}
pub fn slice(m: Mark, comptime T: type) []T {
@@ -39,7 +39,7 @@ pub fn Stack(comptime Base: type) type {
pub fn mark(stack: *Self) Mark {
return .{
.stack = stack,
- .index = stack.list.items.len,
+ .index = @intCast(stack.list.items.len),
};
}
@@ -101,8 +101,8 @@ pub fn Stack(comptime Base: type) type {
return struct {
stack: *Self,
- starts: [fields.len]usize,
- lengths: [fields.len]usize,
+ starts: [fields.len]u32,
+ lengths: [fields.len]u32,
fn Type(comptime field: FieldEnum) type {
return @field(types, @tagName(field));
@@ -128,6 +128,25 @@ pub fn Stack(comptime Base: type) type {
std.mem.copyForwards(u32, dest, from);
r.stack.list.shrinkRetainingCapacity(start + from.len);
}
+
+ pub fn index(r: @This()) u32 {
+ return r.starts[0];
+ }
+
+ pub fn fromIndex(
+ stack: *Self,
+ start: u32,
+ lengths: [std.meta.fields(@TypeOf(types)).len]u32,
+ ) @This() {
+ var all_lengths: u32 = 0;
+ var starts: [std.meta.fields(@TypeOf(types)).len]u32 = undefined;
+ inline for (lengths, &starts) |len, *slice_start| {
+ slice_start.* = start + all_lengths;
+ all_lengths += len;
+ }
+ stack.list.items.len += all_lengths;
+ return .{ .stack = stack, .starts = starts, .lengths = lengths };
+ }
};
}
@@ -137,13 +156,13 @@ pub fn Stack(comptime Base: type) type {
stack: *Self,
allocator: Allocator,
comptime types: anytype,
- lengths: [std.meta.fields(@TypeOf(types)).len]usize,
+ lengths: [std.meta.fields(@TypeOf(types)).len]u32,
) !ReserveMulti(types) {
var all_lengths: usize = 0;
- var starts: [std.meta.fields(@TypeOf(types)).len]usize = undefined;
+ var starts: [std.meta.fields(@TypeOf(types)).len]u32 = undefined;
const start = stack.list.items.len;
inline for (lengths, &starts) |len, *slice_start| {
- slice_start.* = start + all_lengths;
+ slice_start.* = @intCast(start + all_lengths);
all_lengths += len;
}
try stack.list.ensureUnusedCapacity(allocator, all_lengths);
diff --git a/src/main.zig b/src/main.zig
index ebbe1e8800327ce7b1a243bec9b8b4780c79dd1d..b94445681e3b7951c67522860be9ecd2d134e635 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -4,9 +4,18 @@
//! for example, test the `core` module's compilation features without
//! needing to compile the formatter.
-const have_debug_features = builtin.mode == .Debug;
const config = @import("config");
+const arg_config: Cli = .{
+ .options = .{
+ .{ .short = 'i', .name = "--dump-ir", .desc = "Dump intermediate representations" },
+ .{ .name = "--dump-state", .desc = "Dump compiler state to JSON", .kind = .string },
+ } ++ Cli.Option.optional(config.have_embedded_quickjs, &.{
+ .{ .name = "--run", .desc = "Execute with QuickJS" },
+ }),
+ .positionals = .single,
+};
+
pub fn main() !u8 {
try tracer.init();
defer tracer.deinit();
@@ -24,8 +33,7 @@ pub fn main() !u8 {
}){}
else
std.heap.ArenaAllocator.init(std.heap.page_allocator);
- var no_leak_checks = false;
- defer _ = if (use_gpa and !no_leak_checks) gpa.deinit();
+ defer _ = if (use_gpa) gpa.deinit();
const alloc = gpa.allocator();
// Current working directory is required for CLI resolution.
@@ -33,95 +41,17 @@ pub fn main() !u8 {
const cwd = try std.posix.getcwd(&cwd_buf);
// Command line argument parsing.
- var maybe_file_path: ?[]const u8 = null;
- defer if (maybe_file_path) |path| alloc.free(path);
- var exe_name: ?[]const u8 = null;
- defer if (exe_name) |path| alloc.free(path);
- var dump_ir = false;
- var dump_state: ?[]const u8 = null;
- var run = false;
- var has_errors = false;
- {
- var args = try std.process.argsWithAllocator(alloc);
- defer args.deinit();
-
- exe_name = try alloc.dupe(u8, std.fs.path.basename(args.next() orelse "cli"));
-
- while (args.next()) |arg| {
- if (std.mem.startsWith(u8, arg, "-")) {
- if (have_debug_features) {
- if (std.mem.eql(u8, arg, "--report-trace")) {
- core.Report.debug_show_report_trace = true;
- continue;
- }
- if (std.mem.eql(u8, arg, "--no-leak-checks")) {
- no_leak_checks = true;
- continue;
- }
- }
-
- if (std.mem.eql(u8, arg, "--dump-ir")) {
- dump_ir = true;
- continue;
- }
-
- if (std.mem.startsWith(u8, arg, "--dump-state=")) {
- dump_state = arg["--dump-state=".len..];
- continue;
- }
-
- if (config.have_embedded_quickjs and std.mem.eql(u8, arg, "--run")) {
- run = true;
- continue;
- }
-
- std.debug.print("error: unknown flag '{s}'\n", .{arg});
- has_errors = true;
- break;
- }
-
- if (maybe_file_path == null) {
- maybe_file_path = try std.fs.path.resolve(alloc, &.{ cwd, arg });
- } else {
- std.debug.print("error: multiple files specified\n", .{});
- maybe_file_path = null;
- break;
- }
- }
- }
-
- if (maybe_file_path == null) has_errors = true;
- if (has_errors) {
- std.debug.print("Usage: {s} [flags] \n", .{exe_name.?});
- {
- std.debug.print(
- // | 60 chars wrap point|
- \\
- \\Flags:
- \\ --dump-ir Dump intermediate representations
- \\ --dump-state= Dump compiler state to JSON
- \\
- , .{});
- }
- if (config.have_embedded_quickjs) {
- std.debug.print(
- \\ --run Run program using QuickJS
- \\
- , .{});
- }
- if (have_debug_features) {
- std.debug.print(
- \\
- \\Debug Mode Flags:
- \\ --report-trace Print stack traces per error
- \\ --no-leak-checks
- \\
- , .{});
- }
- std.debug.print("\nLanguage SDK v" ++ config.version_string ++ "\n", .{});
- return 1;
- }
- const file_path = maybe_file_path.?;
+ const program_name = "zig build compiler --";
+ _, const args = try Cli.programArgsAlloc(alloc, program_name);
+ defer Cli.programArgsFree(alloc, args);
+ const opts = arg_config.parse(program_name, args);
+
+ const file_path = try std.fs.path.resolve(alloc, &.{
+ cwd,
+ opts.positionals orelse
+ arg_config.showHelpAndExit(program_name),
+ });
+ defer alloc.free(file_path);
// Execute compiler
{
@@ -130,13 +60,13 @@ pub fn main() !u8 {
var comp = try core.Comp.init(.{
.gpa = tracer.tracingAllocator(&alloc),
.options = .{
- .dump_ir = dump_ir,
+ .dump_ir = opts.dump_ir orelse false,
},
.backend = .{ .js = &js_backend },
});
defer comp.deinit();
- defer if (dump_state) |path| {
+ defer if (opts.dump_state) |path| {
dumpState(&comp, path) catch |err| {
std.debug.print("Failed to dump compilation state: {}", .{err});
};
@@ -173,7 +103,7 @@ pub fn main() !u8 {
else => return err,
};
- if (config.have_embedded_quickjs and run) {
+ if (config.have_embedded_quickjs and (opts.run orelse false)) {
// Execute with QuickJS
var out: std.ArrayListUnmanaged(u8) = .{};
defer out.deinit(alloc);
@@ -244,6 +174,7 @@ pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode))
const std = @import("std");
const builtin = @import("builtin");
+const Cli = @import("Cli.zig");
fn dumpState(comp: *const Comp, dump_path: []const u8) !void {
const file = try std.fs.cwd().createFile(dump_path, .{});
diff --git a/test/behavior/tuple b/test/behavior/tuple
new file mode 100644
index 0000000000000000000000000000000000000000..088915e7301707580c30f035b94ae81f2a5dd980
--- /dev/null
+++ b/test/behavior/tuple
@@ -0,0 +1,15 @@
+let assertEqual = @extern("assertEqual");
+
+fn hello() -> tuple (int, float) {
+ let x: tuple (int, float) = (5, 2.5);
+ x
+}
+
+fn main {
+ let x = hello();
+ assertEqual(x.0, i(5));
+ assertEqual(x.1, f(2.5));
+}
+
+fn f(a: float) -> float { a }
+fn i(a: int) -> int { a }