| author | |
| committer | |
| log | 15d77cb85855a5a5fedbfc51ac3a76b6f937db30 |
| tree | f00d3c26bc8cceec5e3c7cd8c3b571c413946451 |
| parent | 03d5aa6a575bcd5237473b8f87d273504e6ed4b9 |
| signature | Signed by PGP key B5690EEEBB952194 |
this regresses return types by making analysis of a function's return
type when it has not been inferred yet panic. it does not provide
any code to prevent the panic
20 files changed, 1245 insertions(+), 472 deletions(-)
CHANGELOG.md+2-1| ... | @@ -10,7 +10,8 @@ | ... | @@ -10,7 +10,8 @@ |
| 10 | - feat: add array index syntax via `x[1]` and `x.1` | 10 | - feat: add array index syntax via `x[1]` and `x.1` |
| 11 | - feat: add `len` property to arrays | 11 | - feat: add `len` property to arrays |
| 12 | - feat: rename `noreturn` to `never` | 12 | - feat: rename `noreturn` to `never` |
| 13 | - feat: allow instantiating `@extern` | 13 | - feat: allow instantiating `@extern` at compile time |
| 14 | - feat: implement tuples | ||
| 14 | - fix: crash when printing unused struct declaration error | 15 | - fix: crash when printing unused struct declaration error |
| 15 | - fix: provide array result type to sema | 16 | - fix: provide array result type to sema |
| 16 | - fix: crash when analyzing an empty array without a result type | 17 | - fix: crash when analyzing an empty array without a result type |
src/cli.zig+6-3| ... | @@ -8,7 +8,7 @@ options: []const Option = &.{}, | ... | @@ -8,7 +8,7 @@ options: []const Option = &.{}, |
| 8 | positionals: Positionals = .none, | 8 | positionals: Positionals = .none, |
| 9 | help: HelpConfig = .{}, | 9 | help: HelpConfig = .{}, |
| 10 | 10 | ||
| 11 | const Option = struct { | 11 | pub const Option = struct { |
| 12 | /// Should be formatted as the flag name: '--dump-ir' | 12 | /// Should be formatted as the flag name: '--dump-ir' |
| 13 | name: []const u8, | 13 | name: []const u8, |
| 14 | /// Displayed in generated help menu | 14 | /// Displayed in generated help menu |
| ... | @@ -32,6 +32,10 @@ const Option = struct { | ... | @@ -32,6 +32,10 @@ const Option = struct { |
| 32 | } | 32 | } |
| 33 | }; | 33 | }; |
| 34 | 34 | ||
| 35 | pub fn optional(comptime cond: bool, opts: []const Option) []const Option { | ||
| 36 | return if (cond) opts else &.{}; | ||
| 37 | } | ||
| 38 | |||
| 35 | const help: Option = .{ | 39 | const help: Option = .{ |
| 36 | .name = "--help", | 40 | .name = "--help", |
| 37 | .short = 'h', | 41 | .short = 'h', |
| ... | @@ -631,8 +635,7 @@ pub fn main() !void { | ... | @@ -631,8 +635,7 @@ pub fn main() !void { |
| 631 | const program_name, const args = try Cli.programArgsAlloc(alloc); | 635 | const program_name, const args = try Cli.programArgsAlloc(alloc); |
| 632 | defer Cli.programArgsFree(alloc, args); | 636 | defer Cli.programArgsFree(alloc, args); |
| 633 | 637 | ||
| 634 | const parsed = config.parse(args[1..]) catch | 638 | const parsed = config.parse(program_name, args[1..]); |
| 635 | config.showHelpAndExit(program_name); | ||
| 636 | 639 | ||
| 637 | std.debug.print("--flag: {any}\n", .{parsed.flag}); | 640 | std.debug.print("--flag: {any}\n", .{parsed.flag}); |
| 638 | std.debug.print("--str: {?s}\n", .{parsed.str}); | 641 | std.debug.print("--str: {?s}\n", .{parsed.str}); |
src/cli_compiler.zig created+146| ... | @@ -0,0 +1,146 @@ | ||
| 1 | //! For testing the core compiler with minimal feature and | ||
| 2 | //! additional debugging parameters. | ||
| 3 | |||
| 4 | const config = @import("config"); | ||
| 5 | |||
| 6 | const arg_config: Cli = .{ | ||
| 7 | .options = &.{ | ||
| 8 | .{ .short = 'i', .name = "--dump-ir", .desc = "Dump intermediate representations" }, | ||
| 9 | .{ .name = "--dump-state", .desc = "Dump compiler state to JSON", .kind = .string }, | ||
| 10 | .{ .short = 'r', .name = "--report-trace", .desc = "Print stack traces to errors" }, | ||
| 11 | .{ .short = 'L', .name = "--no-leak-checks", .desc = "" }, | ||
| 12 | }, | ||
| 13 | .positionals = .single, | ||
| 14 | .help = .{ | ||
| 15 | .usage = "[options...] [entry file]", | ||
| 16 | }, | ||
| 17 | }; | ||
| 18 | |||
| 19 | pub fn main() !u8 { | ||
| 20 | try tracer.init(); | ||
| 21 | defer tracer.deinit(); | ||
| 22 | try tracer.init_thread(null); | ||
| 23 | defer tracer.deinit_thread(); | ||
| 24 | |||
| 25 | // unicode props is asserted to be initialized before core.Tokenizer is used | ||
| 26 | // this will result in a static page allocation, which is never cleaned up. | ||
| 27 | try core.unicode_props.init(); | ||
| 28 | |||
| 29 | const use_gpa = builtin.mode == .Debug; | ||
| 30 | var gpa = if (use_gpa) | ||
| 31 | std.heap.GeneralPurposeAllocator(.{ | ||
| 32 | .stack_trace_frames = 100, | ||
| 33 | }){} | ||
| 34 | else | ||
| 35 | std.heap.ArenaAllocator.init(std.heap.page_allocator); | ||
| 36 | var no_leak_checks = false; | ||
| 37 | defer _ = if (use_gpa and !no_leak_checks) gpa.deinit(); | ||
| 38 | const alloc = gpa.allocator(); | ||
| 39 | |||
| 40 | // Current working directory is required for CLI resolution. | ||
| 41 | var cwd_buf: [std.fs.max_path_bytes]u8 = undefined; | ||
| 42 | const cwd = try std.posix.getcwd(&cwd_buf); | ||
| 43 | |||
| 44 | // Command line argument parsing. | ||
| 45 | const program_name = "zig build compiler --"; | ||
| 46 | _, const args = try Cli.programArgsAlloc(alloc, program_name); | ||
| 47 | defer Cli.programArgsFree(alloc, args); | ||
| 48 | const opts = arg_config.parse(program_name, args); | ||
| 49 | |||
| 50 | const file_path = try std.fs.path.resolve(alloc, &.{ | ||
| 51 | cwd, | ||
| 52 | opts.positionals orelse | ||
| 53 | arg_config.showHelpAndExit(program_name), | ||
| 54 | }); | ||
| 55 | defer alloc.free(file_path); | ||
| 56 | |||
| 57 | no_leak_checks = opts.no_leak_checks orelse false; | ||
| 58 | |||
| 59 | // Execute compiler | ||
| 60 | { | ||
| 61 | var js_backend = core.Js{}; | ||
| 62 | |||
| 63 | var comp = try core.Comp.init(.{ | ||
| 64 | .gpa = tracer.tracingAllocator(&alloc), | ||
| 65 | .options = .{ | ||
| 66 | .dump_ir = opts.dump_ir orelse false, | ||
| 67 | }, | ||
| 68 | .backend = .{ .js = &js_backend }, | ||
| 69 | }); | ||
| 70 | defer comp.deinit(); | ||
| 71 | |||
| 72 | defer if (opts.dump_state) |path| { | ||
| 73 | dumpState(&comp, path) catch |err| { | ||
| 74 | std.debug.print("Failed to dump compilation state: {}", .{err}); | ||
| 75 | }; | ||
| 76 | }; | ||
| 77 | |||
| 78 | const main_file = try comp.getFile(file_path); | ||
| 79 | |||
| 80 | var fail_report: Report = undefined; | ||
| 81 | const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) { | ||
| 82 | error.TransformFail => { | ||
| 83 | std.debug.print("Transform failed:\n", .{}); | ||
| 84 | try fail_report.dump(.initComp(&comp, main_file), comp.out); | ||
| 85 | fail_report.deinit(comp.gpa); | ||
| 86 | return 1; | ||
| 87 | }, | ||
| 88 | error.ParseFail => { | ||
| 89 | // Already printed. | ||
| 90 | return 1; | ||
| 91 | }, | ||
| 92 | else => return err, | ||
| 93 | }; | ||
| 94 | |||
| 95 | if (comp.options.dump_ir) { | ||
| 96 | const stderr = std.io.getStdErr().writer(); | ||
| 97 | try teg.dumpToWriter(stderr, null); | ||
| 98 | } | ||
| 99 | |||
| 100 | const entry_point = try comp.enqueueEntryModule(main_file); | ||
| 101 | comp.runAllJobs() catch |err| switch (err) { | ||
| 102 | error.AnalysisFail => { | ||
| 103 | // error is already printed | ||
| 104 | return 1; | ||
| 105 | }, | ||
| 106 | else => return err, | ||
| 107 | }; | ||
| 108 | |||
| 109 | { | ||
| 110 | // Output to standard out | ||
| 111 | const output = std.io.getStdOut().writer(); | ||
| 112 | var bufw = std.io.bufferedWriter(output); | ||
| 113 | const writer = bufw.writer(); | ||
| 114 | try js_backend.link(&comp, entry_point, writer.any()); | ||
| 115 | try bufw.flush(); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | std.process.cleanExit(); | ||
| 120 | return 0; | ||
| 121 | } | ||
| 122 | |||
| 123 | const core = @import("core"); | ||
| 124 | const Comp = core.Comp; | ||
| 125 | const Report = core.Report; | ||
| 126 | |||
| 127 | const tracer = @import("tracer"); | ||
| 128 | pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)); | ||
| 129 | |||
| 130 | const std = @import("std"); | ||
| 131 | const builtin = @import("builtin"); | ||
| 132 | const Cli = @import("Cli.zig"); | ||
| 133 | |||
| 134 | fn dumpState(comp: *const Comp, dump_path: []const u8) !void { | ||
| 135 | const file = try std.fs.cwd().createFile(dump_path, .{}); | ||
| 136 | defer file.close(); | ||
| 137 | |||
| 138 | var bufw = std.io.bufferedWriter(file.writer()); | ||
| 139 | var jw = std.json.writeStream(bufw.writer(), .{ | ||
| 140 | .whitespace = .indent_2, | ||
| 141 | }); | ||
| 142 | |||
| 143 | try comp.jsonStringify(&jw); | ||
| 144 | try bufw.writer().writeAll("\n"); | ||
| 145 | try bufw.flush(); | ||
| 146 | } | ||
src/cli_transform.zig+1-2| ... | @@ -25,8 +25,7 @@ pub fn main() !u8 { | ... | @@ -25,8 +25,7 @@ pub fn main() !u8 { |
| 25 | const program_name, const raw_args = try Cli.programArgsAlloc(alloc, "transform"); | 25 | const program_name, const raw_args = try Cli.programArgsAlloc(alloc, "transform"); |
| 26 | defer Cli.programArgsFree(alloc, raw_args); | 26 | defer Cli.programArgsFree(alloc, raw_args); |
| 27 | 27 | ||
| 28 | const args = arg_config.parse(raw_args) catch | 28 | const args = arg_config.parse(program_name, raw_args); |
| 29 | arg_config.showHelpAndExit(program_name); | ||
| 30 | 29 | ||
| 31 | const file_path = args.positionals orelse | 30 | const file_path = args.positionals orelse |
| 32 | arg_config.fatalWithHelp(program_name, "missing filepath argument\n", .{}); | 31 | arg_config.fatalWithHelp(program_name, "missing filepath argument\n", .{}); |
src/core/Air.Dump.zig+39-1| ... | @@ -130,6 +130,16 @@ pub const State = struct { | ... | @@ -130,6 +130,16 @@ pub const State = struct { |
| 130 | try tty.setColor(w, .reset); | 130 | try tty.setColor(w, .reset); |
| 131 | try w.writeAll(")"); | 131 | try w.writeAll(")"); |
| 132 | }, | 132 | }, |
| 133 | .field_tuple => { | ||
| 134 | const field: Air.FieldStruct = air.instData(.field_tuple, inst); | ||
| 135 | try w.writeAll("("); | ||
| 136 | try s.dumpInstRef(w, field.base); | ||
| 137 | try w.writeAll(", "); | ||
| 138 | try tty.setColor(w, .magenta); | ||
| 139 | try w.print("{d}", .{field.field_index}); | ||
| 140 | try tty.setColor(w, .reset); | ||
| 141 | try w.writeAll(")"); | ||
| 142 | }, | ||
| 133 | inline .call, .js_new => |t| { | 143 | inline .call, .js_new => |t| { |
| 134 | const call: Air.Call = air.instData(t, inst); | 144 | const call: Air.Call = air.instData(t, inst); |
| 135 | const call_data = call.call_data.get(air); | 145 | const call_data = call.call_data.get(air); |
| ... | @@ -182,6 +192,15 @@ pub const State = struct { | ... | @@ -182,6 +192,15 @@ pub const State = struct { |
| 182 | try s.dumpRefList(w, array_init.list.get(air).slice(air), '[', ']'); | 192 | try s.dumpRefList(w, array_init.list.get(air).slice(air), '[', ']'); |
| 183 | try w.writeAll(")"); | 193 | try w.writeAll(")"); |
| 184 | }, | 194 | }, |
| 195 | .tuple_init => { | ||
| 196 | // const array_init: Air.ArrayInit = air.instData(.array_init, inst); | ||
| 197 | // const array_type = array_init.ty.toArrayType(s.ip).?; | ||
| 198 | |||
| 199 | // try w.writeAll("("); | ||
| 200 | // try s.dumpValue(w, array_type.item_type.value()); | ||
| 201 | // try s.dumpRefList(w, array_init.list.get(air).slice(air), '[', ']'); | ||
| 202 | // try w.writeAll(")"); | ||
| 203 | }, | ||
| 185 | } | 204 | } |
| 186 | } | 205 | } |
| 187 | 206 | ||
| ... | @@ -233,7 +252,10 @@ pub const State = struct { | ... | @@ -233,7 +252,10 @@ pub const State = struct { |
| 233 | try tty.setColor(w, .blue); | 252 | try tty.setColor(w, .blue); |
| 234 | try w.writeAll(") -> "); | 253 | try w.writeAll(") -> "); |
| 235 | try tty.setColor(w, .reset); | 254 | try tty.setColor(w, .reset); |
| 236 | try s.dumpValue(w, ty.return_type.value()); | 255 | if (ty.return_type) |return_type| |
| 256 | try s.dumpValue(w, return_type.value()) | ||
| 257 | else | ||
| 258 | try w.writeAll("[inferred]"); | ||
| 237 | }, | 259 | }, |
| 238 | inline .float, .int32, .comptime_float => |data, tag| { | 260 | inline .float, .int32, .comptime_float => |data, tag| { |
| 239 | try tty.setColor(w, .bright_black); | 261 | try tty.setColor(w, .bright_black); |
| ... | @@ -379,6 +401,22 @@ pub const State = struct { | ... | @@ -379,6 +401,22 @@ pub const State = struct { |
| 379 | } | 401 | } |
| 380 | try w.writeAll("'"); | 402 | try w.writeAll("'"); |
| 381 | }, | 403 | }, |
| 404 | .tuple_type => |tuple_type| { | ||
| 405 | try w.writeAll("("); | ||
| 406 | for (tuple_type.items, 0..) |item, i| { | ||
| 407 | if (i != 0) try w.writeAll(", "); | ||
| 408 | try s.dumpValue(w, item.value()); | ||
| 409 | } | ||
| 410 | try w.writeAll(")"); | ||
| 411 | }, | ||
| 412 | .tuple_value => |tuple_value| { | ||
| 413 | try w.writeAll("("); | ||
| 414 | for (tuple_value.items, 0..) |item, i| { | ||
| 415 | if (i != 0) try w.writeAll(", "); | ||
| 416 | try s.dumpValue(w, item); | ||
| 417 | } | ||
| 418 | try w.writeAll(")"); | ||
| 419 | }, | ||
| 382 | } | 420 | } |
| 383 | } | 421 | } |
| 384 | 422 |
src/core/Air.zig+28-2| ... | @@ -25,9 +25,11 @@ pub const Inst = struct { | ... | @@ -25,9 +25,11 @@ pub const Inst = struct { |
| 25 | 25 | ||
| 26 | struct_init, | 26 | struct_init, |
| 27 | array_init, | 27 | array_init, |
| 28 | tuple_init, | ||
| 28 | 29 | ||
| 29 | field_opaque, | 30 | field_opaque, |
| 30 | field_struct, | 31 | field_struct, |
| 32 | field_tuple, | ||
| 31 | field_array, | 33 | field_array, |
| 32 | 34 | ||
| 33 | array_len, | 35 | array_len, |
| ... | @@ -58,7 +60,8 @@ pub const Inst = struct { | ... | @@ -58,7 +60,8 @@ pub const Inst = struct { |
| 58 | .store => Store, | 60 | .store => Store, |
| 59 | .struct_init => StructInit, | 61 | .struct_init => StructInit, |
| 60 | .array_init => ArrayInit, | 62 | .array_init => ArrayInit, |
| 61 | .field_struct => FieldStruct, | 63 | .tuple_init => TupleInit, |
| 64 | .field_struct, .field_tuple => FieldStruct, | ||
| 62 | .field_opaque, | 65 | .field_opaque, |
| 63 | .field_array, | 66 | .field_array, |
| 64 | .add, | 67 | .add, |
| ... | @@ -286,6 +289,18 @@ pub const ArrayInit = struct { | ... | @@ -286,6 +289,18 @@ pub const ArrayInit = struct { |
| 286 | list: ExtraIndex(RefList), | 289 | list: ExtraIndex(RefList), |
| 287 | }; | 290 | }; |
| 288 | 291 | ||
| 292 | pub const TupleInit = struct { | ||
| 293 | ty: Type, | ||
| 294 | field_values: u32, | ||
| 295 | |||
| 296 | pub fn valuesPtr( | ||
| 297 | init: *const TupleInit, | ||
| 298 | air: *const Air, | ||
| 299 | ) [*]const Air.Inst.Ref { | ||
| 300 | return @ptrCast(air.extra.list.items[init.field_values..].ptr); | ||
| 301 | } | ||
| 302 | }; | ||
| 303 | |||
| 289 | pub const FieldStruct = struct { | 304 | pub const FieldStruct = struct { |
| 290 | base: Inst.Index, | 305 | base: Inst.Index, |
| 291 | field_index: u32, | 306 | field_index: u32, |
| ... | @@ -342,6 +357,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In | ... | @@ -342,6 +357,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In |
| 342 | .@"if" => air.instData(.@"if", index).data.get(air).union_ty, | 357 | .@"if" => air.instData(.@"if", index).data.get(air).union_ty, |
| 343 | .struct_init => air.instData(.struct_init, index).ty, | 358 | .struct_init => air.instData(.struct_init, index).ty, |
| 344 | .array_init => air.instData(.array_init, index).ty, | 359 | .array_init => air.instData(.array_init, index).ty, |
| 360 | .tuple_init => air.instData(.tuple_init, index).ty, | ||
| 345 | .arg => air.instData(.arg, index).type, | 361 | .arg => air.instData(.arg, index).type, |
| 346 | 362 | ||
| 347 | // A handful of binary operators are lowered to a form where both sides | 363 | // 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 | ... | @@ -372,7 +388,9 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In |
| 372 | .call => { | 388 | .call => { |
| 373 | const base_type = air.typeOf(air.instData(.call, index).base, ip); | 389 | const base_type = air.typeOf(air.instData(.call, index).base, ip); |
| 374 | if (base_type == .@"opaque") return .@"opaque"; | 390 | if (base_type == .@"opaque") return .@"opaque"; |
| 375 | return base_type.toFunctionPrototype(ip).?.return_type; | 391 | return base_type.toFunctionPrototype(ip).?.return_type orelse |
| 392 | // unreachable // return type was not resolved | ||
| 393 | @panic("TODO: resolve return type"); | ||
| 376 | }, | 394 | }, |
| 377 | 395 | ||
| 378 | // Field accesses on structs are special-cased by storing the field | 396 | // 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 | ... | @@ -391,6 +409,14 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In |
| 391 | .toArrayType(ip) orelse unreachable; // field_array demands array type | 409 | .toArrayType(ip) orelse unreachable; // field_array demands array type |
| 392 | return base_type.item_type; | 410 | return base_type.item_type; |
| 393 | }, | 411 | }, |
| 412 | |||
| 413 | // Tuple accesses use the tuple they are accessing into. | ||
| 414 | .field_tuple => { | ||
| 415 | const inst = air.instData(.field_tuple, index); | ||
| 416 | const base_type = air.typeOfIndex(inst.base, ip) | ||
| 417 | .toTupleType(ip) orelse unreachable; // field_tuple demands tuple type | ||
| 418 | return base_type.items[inst.field_index]; | ||
| 419 | }, | ||
| 394 | }; | 420 | }; |
| 395 | } | 421 | } |
| 396 | 422 |
src/core/Ast.Dump.zig+2| ... | @@ -126,6 +126,8 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err | ... | @@ -126,6 +126,8 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err |
| 126 | .struct_decl, | 126 | .struct_decl, |
| 127 | => try dumpNodeList(writer, state, list, '{', '}'), | 127 | => try dumpNodeList(writer, state, list, '{', '}'), |
| 128 | .builtin_call, | 128 | .builtin_call, |
| 129 | .tuple_init, | ||
| 130 | .tuple_type, | ||
| 129 | => try dumpNodeList(writer, state, list, '(', ')'), | 131 | => try dumpNodeList(writer, state, list, '(', ')'), |
| 130 | .array_init, | 132 | .array_init, |
| 131 | => try dumpNodeList(writer, state, list, '[', ']'), | 133 | => try dumpNodeList(writer, state, list, '[', ']'), |
src/core/InternPool.zig+146-12| ... | @@ -101,6 +101,8 @@ pub const Tag = enum(u8) { | ... | @@ -101,6 +101,8 @@ pub const Tag = enum(u8) { |
| 101 | struct_type, | 101 | struct_type, |
| 102 | /// An array type | 102 | /// An array type |
| 103 | array_type, | 103 | array_type, |
| 104 | /// A tuple type | ||
| 105 | tuple_type, | ||
| 104 | /// A type that can be represented with only an enum tag. | 106 | /// A type that can be represented with only an enum tag. |
| 105 | simple_type, | 107 | simple_type, |
| 106 | 108 | ||
| ... | @@ -129,6 +131,8 @@ pub const Tag = enum(u8) { | ... | @@ -129,6 +131,8 @@ pub const Tag = enum(u8) { |
| 129 | external_value, | 131 | external_value, |
| 130 | /// A value masked as "opaque" type | 132 | /// A value masked as "opaque" type |
| 131 | opaque_value, | 133 | opaque_value, |
| 134 | /// A tuple value | ||
| 135 | tuple_value, | ||
| 132 | 136 | ||
| 133 | pub fn Data(comptime tag: Tag) type { | 137 | pub fn Data(comptime tag: Tag) type { |
| 134 | return switch (tag) { | 138 | return switch (tag) { |
| ... | @@ -154,13 +158,15 @@ pub const Tag = enum(u8) { | ... | @@ -154,13 +158,15 @@ pub const Tag = enum(u8) { |
| 154 | => unreachable, // does not have a payload | 158 | => unreachable, // does not have a payload |
| 155 | .float, .comptime_float => Float, | 159 | .float, .comptime_float => Float, |
| 156 | .func => FuncDecl, | 160 | .func => FuncDecl, |
| 157 | .function_type => TypeFunction, | 161 | .function_type => FunctionType, |
| 158 | .string => String.WithLen, | 162 | .string => String.WithLen, |
| 159 | .struct_value => StructValue, | 163 | .struct_value => StructValue, |
| 160 | .struct_type => StructType, | 164 | .struct_type => StructType, |
| 161 | .array_type => ArrayType, | 165 | .array_type => ArrayType, |
| 162 | .array_value => ArrayValue, | 166 | .array_value => ArrayValue, |
| 163 | .external_value => ExternalValue, | 167 | .external_value => ExternalValue, |
| 168 | .tuple_type => TupleType, | ||
| 169 | .tuple_value => TupleValue, | ||
| 164 | }; | 170 | }; |
| 165 | } | 171 | } |
| 166 | 172 | ||
| ... | @@ -174,9 +180,9 @@ pub const Tag = enum(u8) { | ... | @@ -174,9 +180,9 @@ pub const Tag = enum(u8) { |
| 174 | 180 | ||
| 175 | /// Trailing: | 181 | /// Trailing: |
| 176 | /// 0. param_type: Index to param type. Repeated .params_len | 182 | /// 0. param_type: Index to param type. Repeated .params_len |
| 177 | pub const TypeFunction = struct { | 183 | pub const FunctionType = struct { |
| 178 | params_len: u32, | 184 | params_len: u32, |
| 179 | return_type: Type, | 185 | return_type: Type.Optional, |
| 180 | }; | 186 | }; |
| 181 | 187 | ||
| 182 | /// Trailing: | 188 | /// Trailing: |
| ... | @@ -199,6 +205,20 @@ pub const Tag = enum(u8) { | ... | @@ -199,6 +205,20 @@ pub const Tag = enum(u8) { |
| 199 | items_len: u32, | 205 | items_len: u32, |
| 200 | }; | 206 | }; |
| 201 | 207 | ||
| 208 | /// Trailing: | ||
| 209 | /// 0. item: Type. Repeated per .items_len | ||
| 210 | pub const TupleType = struct { | ||
| 211 | items_len: u32, | ||
| 212 | }; | ||
| 213 | |||
| 214 | /// Trailing: | ||
| 215 | /// 0. value: Value. Repeated per .items_len | ||
| 216 | pub const TupleValue = struct { | ||
| 217 | /// This is the tuple type, not the item type | ||
| 218 | type: Type, | ||
| 219 | items_len: u32, | ||
| 220 | }; | ||
| 221 | |||
| 202 | /// Trailing: | 222 | /// Trailing: |
| 203 | /// 0. value: Value. Repeated .ty.structInfo().field_len | 223 | /// 0. value: Value. Repeated .ty.structInfo().field_len |
| 204 | pub const StructValue = struct { | 224 | pub const StructValue = struct { |
| ... | @@ -216,9 +236,9 @@ pub const Tag = enum(u8) { | ... | @@ -216,9 +236,9 @@ pub const Tag = enum(u8) { |
| 216 | /// This is an index into .items | 236 | /// This is an index into .items |
| 217 | pub const Value = enum(u32) { | 237 | pub const Value = enum(u32) { |
| 218 | pub const first_type: Value = .opaque_type; | 238 | pub const first_type: Value = .opaque_type; |
| 219 | pub const last_type: Value = .numeric_literal_array_type; | 239 | pub const last_type: Value = .empty_tuple_type; |
| 220 | pub const first_value: Value = .void; | 240 | pub const first_value: Value = .void; |
| 221 | pub const last_value: Value = .empty_array; | 241 | pub const last_value: Value = .empty_tuple; |
| 222 | 242 | ||
| 223 | opaque_type, | 243 | opaque_type, |
| 224 | bool_type, | 244 | bool_type, |
| ... | @@ -230,6 +250,7 @@ pub const Value = enum(u32) { | ... | @@ -230,6 +250,7 @@ pub const Value = enum(u32) { |
| 230 | numeric_literal_type, | 250 | numeric_literal_type, |
| 231 | numeric_literal_array_type, | 251 | numeric_literal_array_type, |
| 232 | never_array_type, | 252 | never_array_type, |
| 253 | empty_tuple_type, | ||
| 233 | 254 | ||
| 234 | void, | 255 | void, |
| 235 | bool_true, | 256 | bool_true, |
| ... | @@ -239,6 +260,7 @@ pub const Value = enum(u32) { | ... | @@ -239,6 +260,7 @@ pub const Value = enum(u32) { |
| 239 | nan, | 260 | nan, |
| 240 | empty_string, | 261 | empty_string, |
| 241 | empty_array, | 262 | empty_array, |
| 263 | empty_tuple, | ||
| 242 | 264 | ||
| 243 | none = std.math.maxInt(u32), | 265 | none = std.math.maxInt(u32), |
| 244 | _, | 266 | _, |
| ... | @@ -316,6 +338,7 @@ pub const Value = enum(u32) { | ... | @@ -316,6 +338,7 @@ pub const Value = enum(u32) { |
| 316 | .simple_type, | 338 | .simple_type, |
| 317 | .struct_type, | 339 | .struct_type, |
| 318 | .array_type, | 340 | .array_type, |
| 341 | .tuple_type, | ||
| 319 | => .type, | 342 | => .type, |
| 320 | 343 | ||
| 321 | .simple_value => switch (value.valueData(ip, .simple_value)) { | 344 | .simple_value => switch (value.valueData(ip, .simple_value)) { |
| ... | @@ -332,6 +355,7 @@ pub const Value = enum(u32) { | ... | @@ -332,6 +355,7 @@ pub const Value = enum(u32) { |
| 332 | .struct_value => value.toKey(ip).struct_value.ty, | 355 | .struct_value => value.toKey(ip).struct_value.ty, |
| 333 | .array_value => value.toKey(ip).array_value.type, | 356 | .array_value => value.toKey(ip).array_value.type, |
| 334 | .external_value, .opaque_value => .@"opaque", | 357 | .external_value, .opaque_value => .@"opaque", |
| 358 | .tuple_value => value.toKey(ip).tuple_value.type, | ||
| 335 | }; | 359 | }; |
| 336 | } | 360 | } |
| 337 | 361 | ||
| ... | @@ -345,6 +369,7 @@ pub const Value = enum(u32) { | ... | @@ -345,6 +369,7 @@ pub const Value = enum(u32) { |
| 345 | .struct_type, | 369 | .struct_type, |
| 346 | .simple_type, | 370 | .simple_type, |
| 347 | .array_type, | 371 | .array_type, |
| 372 | .tuple_type, | ||
| 348 | => @enumFromInt(@intFromEnum(value)), | 373 | => @enumFromInt(@intFromEnum(value)), |
| 349 | 374 | ||
| 350 | // exception: void is a value AND a type | 375 | // exception: void is a value AND a type |
| ... | @@ -395,6 +420,7 @@ pub const Type = enum(u32) { | ... | @@ -395,6 +420,7 @@ pub const Type = enum(u32) { |
| 395 | numeric_literal = @intFromEnum(Value.numeric_literal_type), | 420 | numeric_literal = @intFromEnum(Value.numeric_literal_type), |
| 396 | numeric_literal_array = @intFromEnum(Value.numeric_literal_array_type), | 421 | numeric_literal_array = @intFromEnum(Value.numeric_literal_array_type), |
| 397 | never_array = @intFromEnum(Value.never_array_type), | 422 | never_array = @intFromEnum(Value.never_array_type), |
| 423 | empty_tuple = @intFromEnum(Value.empty_tuple_type), | ||
| 398 | void = @intFromEnum(Value.void), | 424 | void = @intFromEnum(Value.void), |
| 399 | _, | 425 | _, |
| 400 | 426 | ||
| ... | @@ -404,6 +430,7 @@ pub const Type = enum(u32) { | ... | @@ -404,6 +430,7 @@ pub const Type = enum(u32) { |
| 404 | struct_type = @intFromEnum(InternPool.Tag.struct_type), | 430 | struct_type = @intFromEnum(InternPool.Tag.struct_type), |
| 405 | array_type = @intFromEnum(InternPool.Tag.array_type), | 431 | array_type = @intFromEnum(InternPool.Tag.array_type), |
| 406 | simple_type = @intFromEnum(InternPool.Tag.simple_type), | 432 | simple_type = @intFromEnum(InternPool.Tag.simple_type), |
| 433 | tuple_type = @intFromEnum(InternPool.Tag.tuple_type), | ||
| 407 | }; | 434 | }; |
| 408 | 435 | ||
| 409 | /// An array of `Index` existing within the `extra` array. | 436 | /// An array of `Index` existing within the `extra` array. |
| ... | @@ -446,6 +473,11 @@ pub const Type = enum(u32) { | ... | @@ -446,6 +473,11 @@ pub const Type = enum(u32) { |
| 446 | return ip.valueToKey(ty.value()).array_type; | 473 | return ip.valueToKey(ty.value()).array_type; |
| 447 | } | 474 | } |
| 448 | 475 | ||
| 476 | pub fn toTupleType(ty: Type, ip: *const InternPool) ?Key.TupleType { | ||
| 477 | if (ty.tag(ip) != .tuple_type) return null; | ||
| 478 | return ip.valueToKey(ty.value()).tuple_type; | ||
| 479 | } | ||
| 480 | |||
| 449 | /// If it is possible to represent this type at runtime. Some types do not | 481 | /// If it is possible to represent this type at runtime. Some types do not |
| 450 | /// have a defined representation, such as structs (to allow field renaming, unwrapping). | 482 | /// have a defined representation, such as structs (to allow field renaming, unwrapping). |
| 451 | pub fn hasPossibleRepresentation(ty: Type, ip: *const InternPool) bool { | 483 | pub fn hasPossibleRepresentation(ty: Type, ip: *const InternPool) bool { |
| ... | @@ -466,6 +498,15 @@ pub const Type = enum(u32) { | ... | @@ -466,6 +498,15 @@ pub const Type = enum(u32) { |
| 466 | .struct_type, | 498 | .struct_type, |
| 467 | .function_type, | 499 | .function_type, |
| 468 | => true, | 500 | => true, |
| 501 | .tuple_type => { | ||
| 502 | const tuple_type = ty.toTupleType(ip).?; | ||
| 503 | for (tuple_type.items) |item| { | ||
| 504 | if (!item.hasPossibleRepresentation(ip)) { | ||
| 505 | return false; | ||
| 506 | } | ||
| 507 | } | ||
| 508 | return true; | ||
| 509 | }, | ||
| 469 | .array_type => ty.toArrayType(ip).?.item_type.hasPossibleRepresentation(ip), | 510 | .array_type => ty.toArrayType(ip).?.item_type.hasPossibleRepresentation(ip), |
| 470 | }, | 511 | }, |
| 471 | }; | 512 | }; |
| ... | @@ -490,6 +531,7 @@ pub const Type = enum(u32) { | ... | @@ -490,6 +531,7 @@ pub const Type = enum(u32) { |
| 490 | .simple_type => unreachable, // all simple types are already printed | 531 | .simple_type => unreachable, // all simple types are already printed |
| 491 | .struct_type, | 532 | .struct_type, |
| 492 | .function_type, | 533 | .function_type, |
| 534 | .tuple_type, | ||
| 493 | => false, | 535 | => false, |
| 494 | .array_type => ty.toArrayType(ip).?.item_type.hasDefinedRepresentation(ip), | 536 | .array_type => ty.toArrayType(ip).?.item_type.hasDefinedRepresentation(ip), |
| 495 | }, | 537 | }, |
| ... | @@ -536,7 +578,10 @@ pub const Type = enum(u32) { | ... | @@ -536,7 +578,10 @@ pub const Type = enum(u32) { |
| 536 | try writer.print("{}", .{param.displayName(ip)}); | 578 | try writer.print("{}", .{param.displayName(ip)}); |
| 537 | } | 579 | } |
| 538 | try writer.writeAll(") -> "); | 580 | try writer.writeAll(") -> "); |
| 539 | try writer.print("{}", .{function_type.return_type.displayName(ip)}); | 581 | if (function_type.return_type) |return_type| |
| 582 | try writer.print("{}", .{return_type.displayName(ip)}) | ||
| 583 | else | ||
| 584 | try writer.writeAll("[inferred]"); | ||
| 540 | }, | 585 | }, |
| 541 | .struct_type => { | 586 | .struct_type => { |
| 542 | try writer.writeAll("[struct]"); | 587 | try writer.writeAll("[struct]"); |
| ... | @@ -546,8 +591,29 @@ pub const Type = enum(u32) { | ... | @@ -546,8 +591,29 @@ pub const Type = enum(u32) { |
| 546 | const array_type = ty.toArrayType(ip).?; | 591 | const array_type = ty.toArrayType(ip).?; |
| 547 | try writer.print("{}", .{array_type.item_type.displayName(ip)}); | 592 | try writer.print("{}", .{array_type.item_type.displayName(ip)}); |
| 548 | }, | 593 | }, |
| 594 | .tuple_type => { | ||
| 595 | try writer.writeAll("("); | ||
| 596 | for (ty.toTupleType(ip).?.items, 0..) |item, i| { | ||
| 597 | if (i != 0) try writer.writeAll(", "); | ||
| 598 | try writer.print("{}", .{item.displayName(ip)}); | ||
| 599 | } | ||
| 600 | try writer.writeAll(")"); | ||
| 601 | }, | ||
| 549 | } | 602 | } |
| 550 | } | 603 | } |
| 604 | |||
| 605 | pub const Optional = enum(u32) { | ||
| 606 | none = std.math.maxInt(u32), | ||
| 607 | _, | ||
| 608 | |||
| 609 | pub fn init(opt: ?Type) Optional { | ||
| 610 | return if (opt) |o| @enumFromInt(@intFromEnum(o)) else .none; | ||
| 611 | } | ||
| 612 | |||
| 613 | pub fn unwrap(opt: Optional) ?Type { | ||
| 614 | return if (opt != .none) @enumFromInt(@intFromEnum(opt)) else null; | ||
| 615 | } | ||
| 616 | }; | ||
| 551 | }; | 617 | }; |
| 552 | 618 | ||
| 553 | pub const Item = struct { | 619 | pub const Item = struct { |
| ... | @@ -584,6 +650,7 @@ pub const static_keys = [_]Key{ | ... | @@ -584,6 +650,7 @@ pub const static_keys = [_]Key{ |
| 584 | SimpleType.numeric_literal.key(), | 650 | SimpleType.numeric_literal.key(), |
| 585 | .{ .array_type = .{ .item_type = Type.numeric_literal } }, | 651 | .{ .array_type = .{ .item_type = Type.numeric_literal } }, |
| 586 | .{ .array_type = .{ .item_type = Type.never } }, | 652 | .{ .array_type = .{ .item_type = Type.never } }, |
| 653 | .{ .tuple_type = .{ .items = &.{} } }, | ||
| 587 | 654 | ||
| 588 | SimpleValue.void.key(), | 655 | SimpleValue.void.key(), |
| 589 | SimpleValue.true.key(), | 656 | SimpleValue.true.key(), |
| ... | @@ -596,6 +663,10 @@ pub const static_keys = [_]Key{ | ... | @@ -596,6 +663,10 @@ pub const static_keys = [_]Key{ |
| 596 | .items = &.{}, | 663 | .items = &.{}, |
| 597 | } }, | 664 | } }, |
| 598 | .{ .string = "" }, | 665 | .{ .string = "" }, |
| 666 | .{ .tuple_value = .{ | ||
| 667 | .type = .empty_tuple, | ||
| 668 | .items = &.{}, | ||
| 669 | } }, | ||
| 599 | }; | 670 | }; |
| 600 | 671 | ||
| 601 | /// State that is mutable during semantic analysis. This data is not used for | 672 | /// State that is mutable during semantic analysis. This data is not used for |
| ... | @@ -876,11 +947,13 @@ pub const Key = union(enum) { | ... | @@ -876,11 +947,13 @@ pub const Key = union(enum) { |
| 876 | func: Func, | 947 | func: Func, |
| 877 | array_value: ArrayValue, | 948 | array_value: ArrayValue, |
| 878 | opaque_value: Value, | 949 | opaque_value: Value, |
| 950 | tuple_value: TupleValue, | ||
| 879 | // Types | 951 | // Types |
| 880 | simple_type: SimpleType, | 952 | simple_type: SimpleType, |
| 881 | function_type: FunctionType, | 953 | function_type: FunctionType, |
| 882 | struct_type: StructType, | 954 | struct_type: StructType, |
| 883 | array_type: ArrayType, | 955 | array_type: ArrayType, |
| 956 | tuple_type: TupleType, | ||
| 884 | external_value: ExternalValue, | 957 | external_value: ExternalValue, |
| 885 | 958 | ||
| 886 | const Adapter = struct { | 959 | const Adapter = struct { |
| ... | @@ -899,7 +972,10 @@ pub const Key = union(enum) { | ... | @@ -899,7 +972,10 @@ pub const Key = union(enum) { |
| 899 | 972 | ||
| 900 | pub const FunctionType = struct { | 973 | pub const FunctionType = struct { |
| 901 | param_types: []Type, | 974 | param_types: []Type, |
| 902 | return_type: Type, | 975 | /// If a return type is missing, it is inferred and not resolved yet. |
| 976 | /// When analysis figures out a return type, a new `FunctionType` with | ||
| 977 | /// the resolved return type. | ||
| 978 | return_type: ?Type, | ||
| 903 | 979 | ||
| 904 | pub fn hash(self: FunctionType, hasher: *Hash) void { | 980 | pub fn hash(self: FunctionType, hasher: *Hash) void { |
| 905 | const t = tracer.trace(@src()); | 981 | const t = tracer.trace(@src()); |
| ... | @@ -946,6 +1022,14 @@ pub const Key = union(enum) { | ... | @@ -946,6 +1022,14 @@ pub const Key = union(enum) { |
| 946 | } | 1022 | } |
| 947 | }; | 1023 | }; |
| 948 | 1024 | ||
| 1025 | pub const TupleType = struct { | ||
| 1026 | items: []const Type, | ||
| 1027 | |||
| 1028 | pub fn hash(self: TupleType, hasher: *Hash) void { | ||
| 1029 | std.hash.autoHashStrat(hasher, self.items, .Deep); | ||
| 1030 | } | ||
| 1031 | }; | ||
| 1032 | |||
| 949 | pub const ExternalValue = struct { | 1033 | pub const ExternalValue = struct { |
| 950 | /// A list of strings to generate the property access | 1034 | /// A list of strings to generate the property access |
| 951 | /// so `console.log` would be .{ "console", "log" } | 1035 | /// so `console.log` would be .{ "console", "log" } |
| ... | @@ -967,6 +1051,17 @@ pub const Key = union(enum) { | ... | @@ -967,6 +1051,17 @@ pub const Key = union(enum) { |
| 967 | } | 1051 | } |
| 968 | }; | 1052 | }; |
| 969 | 1053 | ||
| 1054 | pub const TupleValue = struct { | ||
| 1055 | /// This is the tuple type, not the item type | ||
| 1056 | type: Type, | ||
| 1057 | items: []const Value, | ||
| 1058 | |||
| 1059 | pub fn hash(self: TupleValue, hasher: *Hash) void { | ||
| 1060 | std.hash.autoHash(hasher, self.type); | ||
| 1061 | std.hash.autoHashStrat(hasher, self.items, .Deep); | ||
| 1062 | } | ||
| 1063 | }; | ||
| 1064 | |||
| 970 | pub const Func = struct { | 1065 | pub const Func = struct { |
| 971 | ty: Type, | 1066 | ty: Type, |
| 972 | owner_decl: DeclIndex, | 1067 | owner_decl: DeclIndex, |
| ... | @@ -1008,6 +1103,8 @@ pub const Key = union(enum) { | ... | @@ -1008,6 +1103,8 @@ pub const Key = union(enum) { |
| 1008 | .array_value, | 1103 | .array_value, |
| 1009 | .array_type, | 1104 | .array_type, |
| 1010 | .external_value, | 1105 | .external_value, |
| 1106 | .tuple_type, | ||
| 1107 | .tuple_value, | ||
| 1011 | => |x| { | 1108 | => |x| { |
| 1012 | var hasher = Hash.init(seed); | 1109 | var hasher = Hash.init(seed); |
| 1013 | x.hash(&hasher); | 1110 | x.hash(&hasher); |
| ... | @@ -1047,7 +1144,7 @@ pub const Key = union(enum) { | ... | @@ -1047,7 +1144,7 @@ pub const Key = union(enum) { |
| 1047 | return false; | 1144 | return false; |
| 1048 | }, | 1145 | }, |
| 1049 | .struct_type => { | 1146 | .struct_type => { |
| 1050 | return false; // structs are nomial | 1147 | return false; // structs are nominal |
| 1051 | }, | 1148 | }, |
| 1052 | .struct_value => |a_info| { | 1149 | .struct_value => |a_info| { |
| 1053 | const b_info = b.struct_value; | 1150 | const b_info = b.struct_value; |
| ... | @@ -1066,6 +1163,15 @@ pub const Key = union(enum) { | ... | @@ -1066,6 +1163,15 @@ pub const Key = union(enum) { |
| 1066 | const b_info = b.external_value; | 1163 | const b_info = b.external_value; |
| 1067 | return std.mem.eql(NullTerminatedString, a_info.key, b_info.key); | 1164 | return std.mem.eql(NullTerminatedString, a_info.key, b_info.key); |
| 1068 | }, | 1165 | }, |
| 1166 | .tuple_type => |a_info| { | ||
| 1167 | const b_info = b.tuple_type; | ||
| 1168 | return a_info.items.len == b_info.items.len and std.mem.eql(Type, a_info.items, b_info.items); | ||
| 1169 | }, | ||
| 1170 | .tuple_value => |a_info| { | ||
| 1171 | const b_info = b.tuple_value; | ||
| 1172 | return a_info.type == b_info.type and | ||
| 1173 | std.mem.eql(Value, a_info.items, b_info.items); | ||
| 1174 | }, | ||
| 1069 | }; | 1175 | }; |
| 1070 | } | 1176 | } |
| 1071 | }; | 1177 | }; |
| ... | @@ -1336,6 +1442,19 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key { | ... | @@ -1336,6 +1442,19 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key { |
| 1336 | .key = trail.readNullTerminatedStringSlice(payload.key_count).get(ip), | 1442 | .key = trail.readNullTerminatedStringSlice(payload.key_count).get(ip), |
| 1337 | } }; | 1443 | } }; |
| 1338 | }, | 1444 | }, |
| 1445 | .tuple_type => { | ||
| 1446 | const payload: Tag.TupleType, var trail = data.decode(.tuple_type, item.tag).getTrail(ip); | ||
| 1447 | return .{ .tuple_type = .{ | ||
| 1448 | .items = trail.readTypeSlice(payload.items_len).get(ip), | ||
| 1449 | } }; | ||
| 1450 | }, | ||
| 1451 | .tuple_value => { | ||
| 1452 | const payload: Tag.TupleValue, var trail = data.decode(.tuple_value, item.tag).getTrail(ip); | ||
| 1453 | return .{ .tuple_value = .{ | ||
| 1454 | .type = payload.type, | ||
| 1455 | .items = trail.readValueSlice(payload.items_len).get(ip), | ||
| 1456 | } }; | ||
| 1457 | }, | ||
| 1339 | }; | 1458 | }; |
| 1340 | } | 1459 | } |
| 1341 | 1460 | ||
| ... | @@ -1391,9 +1510,9 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { | ... | @@ -1391,9 +1510,9 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { |
| 1391 | .function_type => |f| brk: { | 1510 | .function_type => |f| brk: { |
| 1392 | for (f.param_types) |ty| ty.assertValid(ip); | 1511 | for (f.param_types) |ty| ty.assertValid(ip); |
| 1393 | 1512 | ||
| 1394 | const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TypeFunction, .{ | 1513 | const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.FunctionType, .{ |
| 1395 | .params_len = @intCast(f.param_types.len), | 1514 | .params_len = @intCast(f.param_types.len), |
| 1396 | .return_type = f.return_type, | 1515 | .return_type = .init(f.return_type), |
| 1397 | }, f.param_types.len); | 1516 | }, f.param_types.len); |
| 1398 | defer trail.end(); | 1517 | defer trail.end(); |
| 1399 | trail.writeSlice(f.param_types); | 1518 | trail.writeSlice(f.param_types); |
| ... | @@ -1439,6 +1558,21 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { | ... | @@ -1439,6 +1558,21 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { |
| 1439 | trail.writeSlice(s.key); | 1558 | trail.writeSlice(s.key); |
| 1440 | break :brk Item.encode(.external_value, extra); | 1559 | break :brk Item.encode(.external_value, extra); |
| 1441 | }, | 1560 | }, |
| 1561 | .tuple_type => |s| brk: { | ||
| 1562 | const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TupleType, .{ | ||
| 1563 | .items_len = @intCast(s.items.len), | ||
| 1564 | }, s.items.len); | ||
| 1565 | trail.writeSlice(s.items); | ||
| 1566 | break :brk Item.encode(.tuple_type, extra); | ||
| 1567 | }, | ||
| 1568 | .tuple_value => |s| brk: { | ||
| 1569 | const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TupleValue, .{ | ||
| 1570 | .type = s.type, | ||
| 1571 | .items_len = @intCast(s.items.len), | ||
| 1572 | }, s.items.len); | ||
| 1573 | trail.writeSlice(s.items); | ||
| 1574 | break :brk Item.encode(.tuple_value, extra); | ||
| 1575 | }, | ||
| 1442 | }; | 1576 | }; |
| 1443 | ip.items.appendAssumeCapacity(item); | 1577 | ip.items.appendAssumeCapacity(item); |
| 1444 | const index: Value = @enumFromInt(ip.items.len - 1); | 1578 | const index: Value = @enumFromInt(ip.items.len - 1); |
| ... | @@ -1451,12 +1585,12 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { | ... | @@ -1451,12 +1585,12 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { |
| 1451 | 1585 | ||
| 1452 | fn extraTypeFunction( | 1586 | fn extraTypeFunction( |
| 1453 | ip: *const InternPool, | 1587 | ip: *const InternPool, |
| 1454 | extra_index: ExtraIndex(Tag.TypeFunction), | 1588 | extra_index: ExtraIndex(Tag.FunctionType), |
| 1455 | ) Key.FunctionType { | 1589 | ) Key.FunctionType { |
| 1456 | const function_type, var trail = extra_index.getTrail(ip); | 1590 | const function_type, var trail = extra_index.getTrail(ip); |
| 1457 | const param_types = trail.readTypeSlice(function_type.params_len); | 1591 | const param_types = trail.readTypeSlice(function_type.params_len); |
| 1458 | const result: Key.FunctionType = .{ | 1592 | const result: Key.FunctionType = .{ |
| 1459 | .return_type = function_type.return_type, | 1593 | .return_type = function_type.return_type.unwrap(), |
| 1460 | .param_types = param_types.get(ip), | 1594 | .param_types = param_types.get(ip), |
| 1461 | }; | 1595 | }; |
| 1462 | return result; | 1596 | return result; |
src/core/Parse.zig+296-258| ... | @@ -104,15 +104,216 @@ pub const ExpectedContext = enum { | ... | @@ -104,15 +104,216 @@ pub const ExpectedContext = enum { |
| 104 | } | 104 | } |
| 105 | }; | 105 | }; |
| 106 | 106 | ||
| 107 | const Operator = enum(u8) { | ||
| 108 | add = @intFromEnum(Token.Tag.plus), | ||
| 109 | sub = @intFromEnum(Token.Tag.minus), | ||
| 110 | mul = @intFromEnum(Token.Tag.star), | ||
| 111 | div = @intFromEnum(Token.Tag.slash), | ||
| 112 | rem = @intFromEnum(Token.Tag.percent), | ||
| 113 | pow = @intFromEnum(Token.Tag.star_star), | ||
| 114 | @"and" = @intFromEnum(Token.Tag.and_and), | ||
| 115 | @"or" = @intFromEnum(Token.Tag.pipe_pipe), | ||
| 116 | bit_and = @intFromEnum(Token.Tag.@"and"), | ||
| 117 | bit_or = @intFromEnum(Token.Tag.pipe), | ||
| 118 | bit_xor = @intFromEnum(Token.Tag.caret), | ||
| 119 | shl = @intFromEnum(Token.Tag.lt_lt), | ||
| 120 | shr = @intFromEnum(Token.Tag.gt_gt), | ||
| 121 | eq = @intFromEnum(Token.Tag.equal_equal), | ||
| 122 | ne = @intFromEnum(Token.Tag.bang_equal), | ||
| 123 | lt = @intFromEnum(Token.Tag.lt), | ||
| 124 | le = @intFromEnum(Token.Tag.lt_equal), | ||
| 125 | gt = @intFromEnum(Token.Tag.gt), | ||
| 126 | ge = @intFromEnum(Token.Tag.gt_equal), | ||
| 127 | // Following operators are assignment operators | ||
| 128 | assign = @intFromEnum(Token.Tag.equal), | ||
| 129 | add_assign = @intFromEnum(Token.Tag.plus_equal), | ||
| 130 | sub_assign = @intFromEnum(Token.Tag.minus_equal), | ||
| 131 | mul_assign = @intFromEnum(Token.Tag.star_equal), | ||
| 132 | div_assign = @intFromEnum(Token.Tag.slash_equal), | ||
| 133 | rem_assign = @intFromEnum(Token.Tag.percent_equal), | ||
| 134 | pow_assign = @intFromEnum(Token.Tag.star_star_equal), | ||
| 135 | and_assign = @intFromEnum(Token.Tag.and_and_equal), | ||
| 136 | or_assign = @intFromEnum(Token.Tag.pipe_pipe_equal), | ||
| 137 | bit_and_assign = @intFromEnum(Token.Tag.and_equal), | ||
| 138 | bit_or_assign = @intFromEnum(Token.Tag.pipe_equal), | ||
| 139 | bit_xor_assign = @intFromEnum(Token.Tag.caret_equal), | ||
| 140 | shl_assign = @intFromEnum(Token.Tag.lt_lt_equal), | ||
| 141 | shr_assign = @intFromEnum(Token.Tag.gt_gt_equal), | ||
| 142 | |||
| 143 | const Direction = enum { ltr, rtl }; | ||
| 144 | |||
| 145 | /// Only the binary operator levels are actually used, | ||
| 146 | /// but all are listed for reference. | ||
| 147 | const Level = enum { | ||
| 148 | /// e. e[] e() | ||
| 149 | accessor, | ||
| 150 | /// new | ||
| 151 | new, | ||
| 152 | /// ++e, --e, !e, ~e, +e, -e, typeof, void, delete | ||
| 153 | prefix, | ||
| 154 | /// ** | ||
| 155 | exponentiation, | ||
| 156 | /// *, /, % | ||
| 157 | multiplicative, | ||
| 158 | /// +, - | ||
| 159 | additive, | ||
| 160 | /// <<, >>, >>> | ||
| 161 | bitwise_shift, | ||
| 162 | /// <, <=, >, >= | ||
| 163 | relational, | ||
| 164 | /// ==, != | ||
| 165 | equality, | ||
| 166 | /// & | ||
| 167 | bit_and, | ||
| 168 | /// ^ | ||
| 169 | bit_xor, | ||
| 170 | /// | | ||
| 171 | bit_or, | ||
| 172 | /// && | ||
| 173 | logical_and, | ||
| 174 | /// ||, ?? | ||
| 175 | logical_or, | ||
| 176 | /// =, +=, -=, *=, /=, %=, / <<=, >>=, >>>=, &=, ^=, |= | ||
| 177 | assign, | ||
| 178 | }; | ||
| 179 | |||
| 180 | pub fn direction(op: Operator) Direction { | ||
| 181 | return switch (op) { | ||
| 182 | .pow => .rtl, | ||
| 183 | |||
| 184 | .assign, | ||
| 185 | .add_assign, | ||
| 186 | .sub_assign, | ||
| 187 | .mul_assign, | ||
| 188 | .div_assign, | ||
| 189 | .rem_assign, | ||
| 190 | .pow_assign, | ||
| 191 | .and_assign, | ||
| 192 | .or_assign, | ||
| 193 | .bit_and_assign, | ||
| 194 | .bit_or_assign, | ||
| 195 | .bit_xor_assign, | ||
| 196 | .shl_assign, | ||
| 197 | .shr_assign, | ||
| 198 | => .rtl, | ||
| 199 | |||
| 200 | else => .ltr, | ||
| 201 | }; | ||
| 202 | } | ||
| 203 | |||
| 204 | pub fn level(op: Operator) Level { | ||
| 205 | return switch (op) { | ||
| 206 | .pow => .exponentiation, | ||
| 207 | .mul, .div, .rem => .multiplicative, | ||
| 208 | .add, .sub => .additive, | ||
| 209 | .shl, .shr => .bitwise_shift, | ||
| 210 | .lt, .le, .gt, .ge => .relational, | ||
| 211 | .eq, .ne => .equality, | ||
| 212 | .@"and" => .logical_and, | ||
| 213 | .@"or" => .logical_or, | ||
| 214 | .bit_and => .bit_and, | ||
| 215 | .bit_xor => .bit_xor, | ||
| 216 | .bit_or => .bit_or, | ||
| 217 | .assign, | ||
| 218 | .add_assign, | ||
| 219 | .sub_assign, | ||
| 220 | .mul_assign, | ||
| 221 | .div_assign, | ||
| 222 | .rem_assign, | ||
| 223 | .pow_assign, | ||
| 224 | .and_assign, | ||
| 225 | .or_assign, | ||
| 226 | .bit_and_assign, | ||
| 227 | .bit_or_assign, | ||
| 228 | .bit_xor_assign, | ||
| 229 | .shl_assign, | ||
| 230 | .shr_assign, | ||
| 231 | => .assign, | ||
| 232 | }; | ||
| 233 | } | ||
| 234 | |||
| 235 | const lut = brk: { | ||
| 236 | const token_tags = std.enums.values(Token.Tag); | ||
| 237 | const op_tags = std.enums.values(Operator); | ||
| 238 | |||
| 239 | var is_operator_lut: [token_tags.len]bool = .{false} ** token_tags.len; | ||
| 240 | for (op_tags) |op| { | ||
| 241 | is_operator_lut[@intFromEnum(op)] = true; | ||
| 242 | } | ||
| 243 | |||
| 244 | var ast_tag_lut: [token_tags.len]Node.Tag = .{.root} ** token_tags.len; | ||
| 245 | for (op_tags) |op| { | ||
| 246 | ast_tag_lut[@intFromEnum(op)] = @field(Node.Tag, @tagName(op)); | ||
| 247 | } | ||
| 248 | |||
| 249 | break :brk struct { | ||
| 250 | const is_operator = is_operator_lut; | ||
| 251 | const ast_tag = ast_tag_lut; | ||
| 252 | }; | ||
| 253 | }; | ||
| 254 | |||
| 255 | pub fn fromToken(tag: Token.Tag) ?Operator { | ||
| 256 | return if (lut.is_operator[@intFromEnum(tag)]) | ||
| 257 | @enumFromInt(@intFromEnum(tag)) | ||
| 258 | else | ||
| 259 | null; | ||
| 260 | } | ||
| 261 | |||
| 262 | pub fn astTag(op: Operator) Node.Tag { | ||
| 263 | const tag = lut.ast_tag[@intFromEnum(op)]; | ||
| 264 | assert(tag != .root); // invalid operator or lut construction | ||
| 265 | return tag; | ||
| 266 | } | ||
| 267 | |||
| 268 | pub fn isAssign(op: Operator) bool { | ||
| 269 | return switch (op) { | ||
| 270 | .assign, | ||
| 271 | .add_assign, | ||
| 272 | .sub_assign, | ||
| 273 | .mul_assign, | ||
| 274 | .div_assign, | ||
| 275 | .rem_assign, | ||
| 276 | .pow_assign, | ||
| 277 | .and_assign, | ||
| 278 | .or_assign, | ||
| 279 | .bit_and_assign, | ||
| 280 | .bit_or_assign, | ||
| 281 | .bit_xor_assign, | ||
| 282 | .shl_assign, | ||
| 283 | .shr_assign, | ||
| 284 | => true, | ||
| 285 | else => false, | ||
| 286 | }; | ||
| 287 | } | ||
| 288 | }; | ||
| 289 | |||
| 290 | const ParseExprMode = enum { | ||
| 291 | /// Standard parsing | ||
| 292 | expression, | ||
| 293 | /// Statements allow assignment binary operators in addition to other expressions. | ||
| 294 | statement, | ||
| 295 | /// This bans struct initializations so that parsing stops at a '{' | ||
| 296 | /// Example: "if hello {" must not be parsed as a struct initialization. | ||
| 297 | preceding_block, | ||
| 298 | }; | ||
| 299 | |||
| 300 | /// Some constructs are valid expressions as well as valid top-level | ||
| 301 | /// declarations. Top level declarations must have associated names, but the | ||
| 302 | /// anonymous versions of these must not have names. | ||
| 303 | const ExprParseContext = enum { | ||
| 304 | declaration, | ||
| 305 | expression, | ||
| 306 | }; | ||
| 307 | |||
| 308 | const TypePresence = enum { | ||
| 309 | types_optional, | ||
| 310 | types_required, | ||
| 311 | }; | ||
| 312 | |||
| 107 | /// When error.SyntaxError is returned, `p.err` is set to this as well as `p.pos` | 313 | /// When error.SyntaxError is returned, `p.err` is set to this as well as `p.pos` |
| 108 | /// When printing these, token list and Ast is not actually available, so full tokens | 314 | /// When printing these, token list and Ast is not actually available, so full tokens |
| 109 | /// are stored in the errors. | 315 | /// are stored in the errors. |
| 110 | pub const AstError = union(enum) { | 316 | pub const AstError = union(enum) { |
| 111 | // /// Failure during tokenization | ||
| 112 | // tokenization: struct { | ||
| 113 | // err: Tokenizer.TokenizeError, | ||
| 114 | // byte: ByteOffset, | ||
| 115 | // }, | ||
| 116 | /// Unexpected data, expected a token. | 317 | /// Unexpected data, expected a token. |
| 117 | unexpected_token: struct { | 318 | unexpected_token: struct { |
| 118 | expected: Token.Tag, | 319 | expected: Token.Tag, |
| ... | @@ -134,23 +335,6 @@ pub const AstError = union(enum) { | ... | @@ -134,23 +335,6 @@ pub const AstError = union(enum) { |
| 134 | 335 | ||
| 135 | pub fn build(err: AstError, ctx: *Report.MsgContext) !void { | 336 | pub fn build(err: AstError, ctx: *Report.MsgContext) !void { |
| 136 | switch (err) { | 337 | switch (err) { |
| 137 | // .tokenization => |data| { | ||
| 138 | // // TODO: consider making tokenizer emit AstError more directly, | ||
| 139 | // // so that it can just annotate start and ends | ||
| 140 | // try ctx.msg(switch (data.err) { | ||
| 141 | // error.UnterminatedString => .{ .codepoint_eol = data.byte }, | ||
| 142 | // else => .{ .codepoint = data.byte }, | ||
| 143 | // }, "{s}", .{switch (data.err) { | ||
| 144 | // error.OutOfMemory => unreachable, | ||
| 145 | // error.InvalidSyntax => "unexpected token", | ||
| 146 | // error.TrailingNumericSeparator => "trailing numeric separator", | ||
| 147 | // error.UnterminatedString => "unterminated string literal", | ||
| 148 | // error.InvalidEscapeSequence => "invalid escape sequence", | ||
| 149 | // error.InvalidEscapeCodepoint => "invalid escape codepoint", | ||
| 150 | // error.InvalidUtf8 => "file is not valid utf8", | ||
| 151 | // error.GitConflict => "git merge conflict encountered", | ||
| 152 | // }}); | ||
| 153 | // }, | ||
| 154 | .unexpected_token => |data| { | 338 | .unexpected_token => |data| { |
| 155 | try ctx.msg(.{ .token_stored = data.got }, "expected {s}, but found {s}", .{ | 339 | try ctx.msg(.{ .token_stored = data.got }, "expected {s}, but found {s}", .{ |
| 156 | data.expected.description(), | 340 | data.expected.description(), |
| ... | @@ -230,7 +414,8 @@ fn parseModuleMembers(p: *Parse) Error!SubList { | ... | @@ -230,7 +414,8 @@ fn parseModuleMembers(p: *Parse) Error!SubList { |
| 230 | .kw_pub, | 414 | .kw_pub, |
| 231 | => try p.scratch.append(p.gpa, try p.parseModuleDecl()), | 415 | => try p.scratch.append(p.gpa, try p.parseModuleDecl()), |
| 232 | 416 | ||
| 233 | .kw_struct => try p.scratch.append(p.gpa, try p.parseStructDecl()), | 417 | .kw_struct => try p.scratch.append(p.gpa, try p.parseStructDecl(.declaration)), |
| 418 | .kw_tuple => try p.scratch.append(p.gpa, try p.parseStructDecl(.declaration)), | ||
| 234 | 419 | ||
| 235 | .id => if (p.isContextualKeyword("test")) { | 420 | .id => if (p.isContextualKeyword("test")) { |
| 236 | try p.scratch.append(p.gpa, try p.parseTestDecl()); | 421 | try p.scratch.append(p.gpa, try p.parseTestDecl()); |
| ... | @@ -292,14 +477,6 @@ fn parseModuleDecl(p: *Parse) Error!NodeIndex { | ... | @@ -292,14 +477,6 @@ fn parseModuleDecl(p: *Parse) Error!NodeIndex { |
| 292 | }; | 477 | }; |
| 293 | } | 478 | } |
| 294 | 479 | ||
| 295 | /// Some constructs are valid expressions as well as valid top-level | ||
| 296 | /// declarations. Top level declarations must have associated names, but the | ||
| 297 | /// anonymous versions of these must not have names. | ||
| 298 | const ExprParseContext = enum { | ||
| 299 | declaration, | ||
| 300 | expression, | ||
| 301 | }; | ||
| 302 | |||
| 303 | fn parseFnDecl(p: *Parse) Error!NodeIndex { | 480 | fn parseFnDecl(p: *Parse) Error!NodeIndex { |
| 304 | const trace = t.trace(@src()); | 481 | const trace = t.trace(@src()); |
| 305 | defer trace.end(); | 482 | defer trace.end(); |
| ... | @@ -339,8 +516,6 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex { | ... | @@ -339,8 +516,6 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex { |
| 339 | }); | 516 | }); |
| 340 | } | 517 | } |
| 341 | 518 | ||
| 342 | const TypePresence = enum { types_optional, types_required }; | ||
| 343 | |||
| 344 | fn parseParamList(p: *Parse, types: TypePresence) Error!SubList { | 519 | fn parseParamList(p: *Parse, types: TypePresence) Error!SubList { |
| 345 | const trace = t.trace(@src()); | 520 | const trace = t.trace(@src()); |
| 346 | defer trace.end(); | 521 | defer trace.end(); |
| ... | @@ -390,13 +565,16 @@ fn parseParamDecl(p: *Parse, types: TypePresence) Error!NodeIndex { | ... | @@ -390,13 +565,16 @@ fn parseParamDecl(p: *Parse, types: TypePresence) Error!NodeIndex { |
| 390 | }); | 565 | }); |
| 391 | } | 566 | } |
| 392 | 567 | ||
| 393 | fn parseStructDecl(p: *Parse) Error!NodeIndex { | 568 | fn parseStructDecl(p: *Parse, ctx: ExprParseContext) Error!NodeIndex { |
| 394 | const trace = t.trace(@src()); | 569 | const trace = t.trace(@src()); |
| 395 | defer trace.end(); | 570 | defer trace.end(); |
| 396 | 571 | ||
| 397 | const main_token = p.assertToken(.kw_struct); | 572 | const main_token = p.assertToken(.kw_struct); |
| 398 | 573 | ||
| 399 | _ = try p.expect(.id); | 574 | switch (ctx) { |
| 575 | .declaration => _ = try p.expect(.id), | ||
| 576 | else => {}, | ||
| 577 | } | ||
| 400 | 578 | ||
| 401 | _ = try p.expect(.start_brace); | 579 | _ = try p.expect(.start_brace); |
| 402 | 580 | ||
| ... | @@ -409,6 +587,28 @@ fn parseStructDecl(p: *Parse) Error!NodeIndex { | ... | @@ -409,6 +587,28 @@ fn parseStructDecl(p: *Parse) Error!NodeIndex { |
| 409 | }); | 587 | }); |
| 410 | } | 588 | } |
| 411 | 589 | ||
| 590 | fn parseTupleDecl(p: *Parse, ctx: ExprParseContext) Error!NodeIndex { | ||
| 591 | const trace = t.trace(@src()); | ||
| 592 | defer trace.end(); | ||
| 593 | |||
| 594 | const main_token = p.assertToken(.kw_tuple); | ||
| 595 | |||
| 596 | switch (ctx) { | ||
| 597 | .declaration => _ = try p.expect(.id), | ||
| 598 | else => {}, | ||
| 599 | } | ||
| 600 | |||
| 601 | _ = try p.expect(.start_paren); | ||
| 602 | |||
| 603 | const fields = try p.parseExpressionList(.end_paren, .expression); | ||
| 604 | |||
| 605 | return try p.addNode(.{ | ||
| 606 | .tag = .tuple_type, | ||
| 607 | .main_token = main_token, | ||
| 608 | .data = .{ .list = fields }, | ||
| 609 | }); | ||
| 610 | } | ||
| 611 | |||
| 412 | fn parseTestDecl(p: *Parse) Error!NodeIndex { | 612 | fn parseTestDecl(p: *Parse) Error!NodeIndex { |
| 413 | const trace = t.trace(@src()); | 613 | const trace = t.trace(@src()); |
| 414 | defer trace.end(); | 614 | defer trace.end(); |
| ... | @@ -447,9 +647,7 @@ fn parseStructFieldList(p: *Parse) Error!SubList { | ... | @@ -447,9 +647,7 @@ fn parseStructFieldList(p: *Parse) Error!SubList { |
| 447 | try p.scratch.append(p.gpa, try p.parseStructField()); | 647 | try p.scratch.append(p.gpa, try p.parseStructField()); |
| 448 | 648 | ||
| 449 | switch (p.currentTag()) { | 649 | switch (p.currentTag()) { |
| 450 | .comma => { | 650 | .comma => _ = p.nextToken(), |
| 451 | _ = p.nextToken(); | ||
| 452 | }, | ||
| 453 | .end_brace => break, | 651 | .end_brace => break, |
| 454 | else => return p.failUnexpectedToken(.struct_field), | 652 | else => return p.failUnexpectedToken(.struct_field), |
| 455 | } | 653 | } |
| ... | @@ -578,13 +776,13 @@ fn parseVarDecl(p: *Parse) Error!NodeIndex { | ... | @@ -578,13 +776,13 @@ fn parseVarDecl(p: *Parse) Error!NodeIndex { |
| 578 | _ = try p.expect(.id); | 776 | _ = try p.expect(.id); |
| 579 | 777 | ||
| 580 | const data_type: OptionalNodeIndex = if (p.eatToken(.colon)) |_| | 778 | const data_type: OptionalNodeIndex = if (p.eatToken(.colon)) |_| |
| 581 | optionalNodeIndex(try p.tryParseExpression(.expression)) | 779 | optionalNodeIndex(try p.parseExpressionOrFail(.expression)) |
| 582 | else | 780 | else |
| 583 | .none; | 781 | .none; |
| 584 | 782 | ||
| 585 | _ = try p.expect(.equal); | 783 | _ = try p.expect(.equal); |
| 586 | 784 | ||
| 587 | const data_init = try p.tryParseExpression(.expression); | 785 | const data_init = try p.parseExpressionOrFail(.expression); |
| 588 | 786 | ||
| 589 | _ = try p.expect(.semi); | 787 | _ = try p.expect(.semi); |
| 590 | 788 | ||
| ... | @@ -619,204 +817,13 @@ fn isExpressionStart(p: *Parse) bool { | ... | @@ -619,204 +817,13 @@ fn isExpressionStart(p: *Parse) bool { |
| 619 | .string, | 817 | .string, |
| 620 | .tilde, | 818 | .tilde, |
| 621 | .kw_fn, | 819 | .kw_fn, |
| 820 | .kw_struct, | ||
| 821 | .kw_tuple, | ||
| 622 | => true, | 822 | => true, |
| 623 | else => false, | 823 | else => false, |
| 624 | }; | 824 | }; |
| 625 | } | 825 | } |
| 626 | 826 | ||
| 627 | const Operator = enum(u8) { | ||
| 628 | add = @intFromEnum(Token.Tag.plus), | ||
| 629 | sub = @intFromEnum(Token.Tag.minus), | ||
| 630 | mul = @intFromEnum(Token.Tag.star), | ||
| 631 | div = @intFromEnum(Token.Tag.slash), | ||
| 632 | rem = @intFromEnum(Token.Tag.percent), | ||
| 633 | pow = @intFromEnum(Token.Tag.star_star), | ||
| 634 | @"and" = @intFromEnum(Token.Tag.and_and), | ||
| 635 | @"or" = @intFromEnum(Token.Tag.pipe_pipe), | ||
| 636 | bit_and = @intFromEnum(Token.Tag.@"and"), | ||
| 637 | bit_or = @intFromEnum(Token.Tag.pipe), | ||
| 638 | bit_xor = @intFromEnum(Token.Tag.caret), | ||
| 639 | shl = @intFromEnum(Token.Tag.lt_lt), | ||
| 640 | shr = @intFromEnum(Token.Tag.gt_gt), | ||
| 641 | eq = @intFromEnum(Token.Tag.equal_equal), | ||
| 642 | ne = @intFromEnum(Token.Tag.bang_equal), | ||
| 643 | lt = @intFromEnum(Token.Tag.lt), | ||
| 644 | le = @intFromEnum(Token.Tag.lt_equal), | ||
| 645 | gt = @intFromEnum(Token.Tag.gt), | ||
| 646 | ge = @intFromEnum(Token.Tag.gt_equal), | ||
| 647 | // Following operators are assignment operators | ||
| 648 | assign = @intFromEnum(Token.Tag.equal), | ||
| 649 | add_assign = @intFromEnum(Token.Tag.plus_equal), | ||
| 650 | sub_assign = @intFromEnum(Token.Tag.minus_equal), | ||
| 651 | mul_assign = @intFromEnum(Token.Tag.star_equal), | ||
| 652 | div_assign = @intFromEnum(Token.Tag.slash_equal), | ||
| 653 | rem_assign = @intFromEnum(Token.Tag.percent_equal), | ||
| 654 | pow_assign = @intFromEnum(Token.Tag.star_star_equal), | ||
| 655 | and_assign = @intFromEnum(Token.Tag.and_and_equal), | ||
| 656 | or_assign = @intFromEnum(Token.Tag.pipe_pipe_equal), | ||
| 657 | bit_and_assign = @intFromEnum(Token.Tag.and_equal), | ||
| 658 | bit_or_assign = @intFromEnum(Token.Tag.pipe_equal), | ||
| 659 | bit_xor_assign = @intFromEnum(Token.Tag.caret_equal), | ||
| 660 | shl_assign = @intFromEnum(Token.Tag.lt_lt_equal), | ||
| 661 | shr_assign = @intFromEnum(Token.Tag.gt_gt_equal), | ||
| 662 | |||
| 663 | const Direction = enum { ltr, rtl }; | ||
| 664 | |||
| 665 | /// Only the binary operator levels are actually used, | ||
| 666 | /// but all are listed for reference. | ||
| 667 | const Level = enum { | ||
| 668 | /// e. e[] e() | ||
| 669 | accessor, | ||
| 670 | /// new | ||
| 671 | new, | ||
| 672 | /// ++e, --e, !e, ~e, +e, -e, typeof, void, delete | ||
| 673 | prefix, | ||
| 674 | /// ** | ||
| 675 | exponentiation, | ||
| 676 | /// *, /, % | ||
| 677 | multiplicative, | ||
| 678 | /// +, - | ||
| 679 | additive, | ||
| 680 | /// <<, >>, >>> | ||
| 681 | bitwise_shift, | ||
| 682 | /// <, <=, >, >= | ||
| 683 | relational, | ||
| 684 | /// ==, != | ||
| 685 | equality, | ||
| 686 | /// & | ||
| 687 | bit_and, | ||
| 688 | /// ^ | ||
| 689 | bit_xor, | ||
| 690 | /// | | ||
| 691 | bit_or, | ||
| 692 | /// && | ||
| 693 | logical_and, | ||
| 694 | /// ||, ?? | ||
| 695 | logical_or, | ||
| 696 | /// =, +=, -=, *=, /=, %=, / <<=, >>=, >>>=, &=, ^=, |= | ||
| 697 | assign, | ||
| 698 | }; | ||
| 699 | |||
| 700 | pub fn direction(op: Operator) Direction { | ||
| 701 | return switch (op) { | ||
| 702 | .pow => .rtl, | ||
| 703 | |||
| 704 | .assign, | ||
| 705 | .add_assign, | ||
| 706 | .sub_assign, | ||
| 707 | .mul_assign, | ||
| 708 | .div_assign, | ||
| 709 | .rem_assign, | ||
| 710 | .pow_assign, | ||
| 711 | .and_assign, | ||
| 712 | .or_assign, | ||
| 713 | .bit_and_assign, | ||
| 714 | .bit_or_assign, | ||
| 715 | .bit_xor_assign, | ||
| 716 | .shl_assign, | ||
| 717 | .shr_assign, | ||
| 718 | => .rtl, | ||
| 719 | |||
| 720 | else => .ltr, | ||
| 721 | }; | ||
| 722 | } | ||
| 723 | |||
| 724 | pub fn level(op: Operator) Level { | ||
| 725 | return switch (op) { | ||
| 726 | .pow => .exponentiation, | ||
| 727 | .mul, .div, .rem => .multiplicative, | ||
| 728 | .add, .sub => .additive, | ||
| 729 | .shl, .shr => .bitwise_shift, | ||
| 730 | .lt, .le, .gt, .ge => .relational, | ||
| 731 | .eq, .ne => .equality, | ||
| 732 | .@"and" => .logical_and, | ||
| 733 | .@"or" => .logical_or, | ||
| 734 | .bit_and => .bit_and, | ||
| 735 | .bit_xor => .bit_xor, | ||
| 736 | .bit_or => .bit_or, | ||
| 737 | .assign, | ||
| 738 | .add_assign, | ||
| 739 | .sub_assign, | ||
| 740 | .mul_assign, | ||
| 741 | .div_assign, | ||
| 742 | .rem_assign, | ||
| 743 | .pow_assign, | ||
| 744 | .and_assign, | ||
| 745 | .or_assign, | ||
| 746 | .bit_and_assign, | ||
| 747 | .bit_or_assign, | ||
| 748 | .bit_xor_assign, | ||
| 749 | .shl_assign, | ||
| 750 | .shr_assign, | ||
| 751 | => .assign, | ||
| 752 | }; | ||
| 753 | } | ||
| 754 | |||
| 755 | const lut = brk: { | ||
| 756 | const token_tags = std.enums.values(Token.Tag); | ||
| 757 | const op_tags = std.enums.values(Operator); | ||
| 758 | |||
| 759 | var is_operator_lut: [token_tags.len]bool = .{false} ** token_tags.len; | ||
| 760 | for (op_tags) |op| { | ||
| 761 | is_operator_lut[@intFromEnum(op)] = true; | ||
| 762 | } | ||
| 763 | |||
| 764 | var ast_tag_lut: [token_tags.len]Node.Tag = .{.root} ** token_tags.len; | ||
| 765 | for (op_tags) |op| { | ||
| 766 | ast_tag_lut[@intFromEnum(op)] = @field(Node.Tag, @tagName(op)); | ||
| 767 | } | ||
| 768 | |||
| 769 | break :brk struct { | ||
| 770 | const is_operator = is_operator_lut; | ||
| 771 | const ast_tag = ast_tag_lut; | ||
| 772 | }; | ||
| 773 | }; | ||
| 774 | |||
| 775 | pub fn fromToken(tag: Token.Tag) ?Operator { | ||
| 776 | return if (lut.is_operator[@intFromEnum(tag)]) | ||
| 777 | @enumFromInt(@intFromEnum(tag)) | ||
| 778 | else | ||
| 779 | null; | ||
| 780 | } | ||
| 781 | |||
| 782 | pub fn astTag(op: Operator) Node.Tag { | ||
| 783 | const tag = lut.ast_tag[@intFromEnum(op)]; | ||
| 784 | assert(tag != .root); // invalid operator or lut construction | ||
| 785 | return tag; | ||
| 786 | } | ||
| 787 | |||
| 788 | pub fn isAssign(op: Operator) bool { | ||
| 789 | return switch (op) { | ||
| 790 | .assign, | ||
| 791 | .add_assign, | ||
| 792 | .sub_assign, | ||
| 793 | .mul_assign, | ||
| 794 | .div_assign, | ||
| 795 | .rem_assign, | ||
| 796 | .pow_assign, | ||
| 797 | .and_assign, | ||
| 798 | .or_assign, | ||
| 799 | .bit_and_assign, | ||
| 800 | .bit_or_assign, | ||
| 801 | .bit_xor_assign, | ||
| 802 | .shl_assign, | ||
| 803 | .shr_assign, | ||
| 804 | => true, | ||
| 805 | else => false, | ||
| 806 | }; | ||
| 807 | } | ||
| 808 | }; | ||
| 809 | |||
| 810 | const ParseExprMode = enum { | ||
| 811 | /// Standard parsing | ||
| 812 | expression, | ||
| 813 | /// Statements allow assignment binary operators in addition to other expressions. | ||
| 814 | statement, | ||
| 815 | /// This bans struct initializations so that parsing stops at a '{' | ||
| 816 | /// Example: "if hello {" must not be parsed as a struct initialization. | ||
| 817 | preceding_block, | ||
| 818 | }; | ||
| 819 | |||
| 820 | fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { | 827 | fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 821 | const trace = t.trace(@src()); | 828 | const trace = t.trace(@src()); |
| 822 | defer trace.end(); | 829 | defer trace.end(); |
| ... | @@ -831,8 +838,13 @@ fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { | ... | @@ -831,8 +838,13 @@ fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 831 | 838 | ||
| 832 | // These unary operators are greedy, so handle them first. | 839 | // These unary operators are greedy, so handle them first. |
| 833 | switch (p.currentTag()) { | 840 | switch (p.currentTag()) { |
| 834 | .kw_return => return p.parseReturn(), | 841 | .kw_return => return p.parseReturn(mode), |
| 835 | .kw_break => return p.parseBreak(), | 842 | .kw_break => return p.parseBreak(mode), |
| 843 | |||
| 844 | .kw_assert => return p.parseGreedyUnary(.assert, mode), | ||
| 845 | .kw_comptime => return p.parseGreedyUnary(.@"comptime", mode), | ||
| 846 | .kw_pure => return p.parseGreedyUnary(.pure, mode), | ||
| 847 | |||
| 836 | else => {}, | 848 | else => {}, |
| 837 | } | 849 | } |
| 838 | 850 | ||
| ... | @@ -846,7 +858,7 @@ fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { | ... | @@ -846,7 +858,7 @@ fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 846 | return expr; | 858 | return expr; |
| 847 | } | 859 | } |
| 848 | 860 | ||
| 849 | fn tryParseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { | 861 | fn parseExpressionOrFail(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 850 | return if (p.isExpressionStart()) | 862 | return if (p.isExpressionStart()) |
| 851 | p.parseExpression(mode) | 863 | p.parseExpression(mode) |
| 852 | else | 864 | else |
| ... | @@ -858,7 +870,6 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { | ... | @@ -858,7 +870,6 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 858 | defer trace.end(); | 870 | defer trace.end(); |
| 859 | 871 | ||
| 860 | var expr = switch (p.currentTag()) { | 872 | var expr = switch (p.currentTag()) { |
| 861 | // Literals | ||
| 862 | .id => if (p.nextTag() == .colon) | 873 | .id => if (p.nextTag() == .colon) |
| 863 | try p.parseLabelExpression() | 874 | try p.parseLabelExpression() |
| 864 | else | 875 | else |
| ... | @@ -867,24 +878,27 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { | ... | @@ -867,24 +878,27 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 867 | .number_float => try p.blankNode(.number_float), | 878 | .number_float => try p.blankNode(.number_float), |
| 868 | .string => try p.blankNode(.string), | 879 | .string => try p.blankNode(.string), |
| 869 | 880 | ||
| 870 | // Complex expressions | ||
| 871 | .builtin_id => try p.parseBuiltinCall(), | 881 | .builtin_id => try p.parseBuiltinCall(), |
| 872 | .dot => try p.parseAnonInstantiation(), | 882 | .dot => try p.parseAnonStructInit(), |
| 873 | .kw_break => try p.parseBreak(), | ||
| 874 | .kw_fn => try p.parseAnonFunction(), | 883 | .kw_fn => try p.parseAnonFunction(), |
| 875 | .kw_if => try p.parseIf(), | 884 | .kw_if => try p.parseIf(), |
| 876 | .kw_return => try p.parseReturn(), | 885 | .kw_tuple => try p.parseTupleType(), |
| 886 | |||
| 877 | .start_brace => try p.parseBlock(), | 887 | .start_brace => try p.parseBlock(), |
| 878 | .start_bracket => try p.parseArrayValueOrType(mode), | 888 | .start_bracket => try p.parseArrayValueOrType(mode), |
| 879 | .start_paren => try p.parseTupleType(), | 889 | .start_paren => try p.parseTupleInit(), |
| 890 | |||
| 891 | .kw_struct => try p.parseStructDecl(.expression), | ||
| 892 | |||
| 893 | .kw_return => return p.parseReturn(mode), | ||
| 894 | .kw_break => return p.parseBreak(mode), | ||
| 895 | .kw_assert => return p.parseGreedyUnary(.assert, mode), | ||
| 896 | .kw_comptime => return p.parseGreedyUnary(.@"comptime", mode), | ||
| 897 | .kw_pure => return p.parseGreedyUnary(.pure, mode), | ||
| 880 | 898 | ||
| 881 | // Unary operators | ||
| 882 | inline .bang, | 899 | inline .bang, |
| 883 | .tilde, | 900 | .tilde, |
| 884 | .minus, | 901 | .minus, |
| 885 | .kw_comptime, | ||
| 886 | .kw_pure, | ||
| 887 | .kw_assert, | ||
| 888 | => |tag| unary: { | 902 | => |tag| unary: { |
| 889 | const main_token = p.nextToken(); | 903 | const main_token = p.nextToken(); |
| 890 | const expr = try p.parseExpressionPart(mode); | 904 | const expr = try p.parseExpressionPart(mode); |
| ... | @@ -955,7 +969,7 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { | ... | @@ -955,7 +969,7 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 955 | return p.fail(.{ .array_type_flipped_hint = p.fullToken(p.pos) }); | 969 | return p.fail(.{ .array_type_flipped_hint = p.fullToken(p.pos) }); |
| 956 | } | 970 | } |
| 957 | 971 | ||
| 958 | const index = try p.tryParseExpression(.expression); | 972 | const index = try p.parseExpressionOrFail(.expression); |
| 959 | 973 | ||
| 960 | _ = try p.expect(.end_bracket); | 974 | _ = try p.expect(.end_bracket); |
| 961 | 975 | ||
| ... | @@ -1020,14 +1034,14 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, mode: ParseExprMod | ... | @@ -1020,14 +1034,14 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, mode: ParseExprMod |
| 1020 | }); | 1034 | }); |
| 1021 | } | 1035 | } |
| 1022 | 1036 | ||
| 1023 | fn parseReturn(p: *Parse) Error!NodeIndex { | 1037 | fn parseReturn(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 1024 | const trace = t.trace(@src()); | 1038 | const trace = t.trace(@src()); |
| 1025 | defer trace.end(); | 1039 | defer trace.end(); |
| 1026 | 1040 | ||
| 1027 | const main_token = p.assertToken(.kw_return); | 1041 | const main_token = p.assertToken(.kw_return); |
| 1028 | 1042 | ||
| 1029 | const expr = if (p.isExpressionStart()) | 1043 | const expr = if (p.isExpressionStart()) |
| 1030 | try p.parseExpression(.expression) | 1044 | try p.parseExpression(mode) |
| 1031 | else | 1045 | else |
| 1032 | null; | 1046 | null; |
| 1033 | 1047 | ||
| ... | @@ -1040,7 +1054,7 @@ fn parseReturn(p: *Parse) Error!NodeIndex { | ... | @@ -1040,7 +1054,7 @@ fn parseReturn(p: *Parse) Error!NodeIndex { |
| 1040 | }); | 1054 | }); |
| 1041 | } | 1055 | } |
| 1042 | 1056 | ||
| 1043 | fn parseBreak(p: *Parse) Error!NodeIndex { | 1057 | fn parseBreak(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 1044 | const trace = t.trace(@src()); | 1058 | const trace = t.trace(@src()); |
| 1045 | defer trace.end(); | 1059 | defer trace.end(); |
| 1046 | 1060 | ||
| ... | @@ -1052,7 +1066,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex { | ... | @@ -1052,7 +1066,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex { |
| 1052 | null; | 1066 | null; |
| 1053 | 1067 | ||
| 1054 | const expr = if (p.isExpressionStart()) | 1068 | const expr = if (p.isExpressionStart()) |
| 1055 | try p.parseExpression(.expression) | 1069 | try p.parseExpression(mode) |
| 1056 | else | 1070 | else |
| 1057 | null; | 1071 | null; |
| 1058 | 1072 | ||
| ... | @@ -1066,6 +1080,23 @@ fn parseBreak(p: *Parse) Error!NodeIndex { | ... | @@ -1066,6 +1080,23 @@ fn parseBreak(p: *Parse) Error!NodeIndex { |
| 1066 | }); | 1080 | }); |
| 1067 | } | 1081 | } |
| 1068 | 1082 | ||
| 1083 | fn parseGreedyUnary(p: *Parse, tag: Node.Tag, mode: ParseExprMode) Error!NodeIndex { | ||
| 1084 | const trace = t.trace(@src()); | ||
| 1085 | defer trace.end(); | ||
| 1086 | |||
| 1087 | const main_token = p.nextToken(); | ||
| 1088 | |||
| 1089 | const expr = try p.parseExpressionOrFail(mode); | ||
| 1090 | |||
| 1091 | return try p.addNode(.{ | ||
| 1092 | .tag = tag, | ||
| 1093 | .main_token = main_token, | ||
| 1094 | .data = .{ .un = .{ | ||
| 1095 | .expr = expr, | ||
| 1096 | } }, | ||
| 1097 | }); | ||
| 1098 | } | ||
| 1099 | |||
| 1069 | fn parseBuiltinCall(p: *Parse) Error!NodeIndex { | 1100 | fn parseBuiltinCall(p: *Parse) Error!NodeIndex { |
| 1070 | const trace = t.trace(@src()); | 1101 | const trace = t.trace(@src()); |
| 1071 | defer trace.end(); | 1102 | defer trace.end(); |
| ... | @@ -1083,11 +1114,7 @@ fn parseBuiltinCall(p: *Parse) Error!NodeIndex { | ... | @@ -1083,11 +1114,7 @@ fn parseBuiltinCall(p: *Parse) Error!NodeIndex { |
| 1083 | }); | 1114 | }); |
| 1084 | } | 1115 | } |
| 1085 | 1116 | ||
| 1086 | fn parseExpressionList( | 1117 | fn parseExpressionList(p: *Parse, comptime end_token: Token.Tag, fail_context: ExpectedContext) Error!SubList { |
| 1087 | p: *Parse, | ||
| 1088 | comptime end_token: Token.Tag, | ||
| 1089 | comptime fail_context: ExpectedContext, | ||
| 1090 | ) Error!SubList { | ||
| 1091 | const trace = t.trace(@src()); | 1118 | const trace = t.trace(@src()); |
| 1092 | defer trace.end(); | 1119 | defer trace.end(); |
| 1093 | 1120 | ||
| ... | @@ -1161,7 +1188,7 @@ fn parseElseValue(p: *Parse) Error!NodeIndex { | ... | @@ -1161,7 +1188,7 @@ fn parseElseValue(p: *Parse) Error!NodeIndex { |
| 1161 | }; | 1188 | }; |
| 1162 | } | 1189 | } |
| 1163 | 1190 | ||
| 1164 | fn parseAnonInstantiation(p: *Parse) Error!NodeIndex { | 1191 | fn parseAnonStructInit(p: *Parse) Error!NodeIndex { |
| 1165 | const trace = t.trace(@src()); | 1192 | const trace = t.trace(@src()); |
| 1166 | defer trace.end(); | 1193 | defer trace.end(); |
| 1167 | 1194 | ||
| ... | @@ -1272,9 +1299,20 @@ fn parseArrayValueOrType(p: *Parse, mode: ParseExprMode) Error!NodeIndex { | ... | @@ -1272,9 +1299,20 @@ fn parseArrayValueOrType(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 1272 | }); | 1299 | }); |
| 1273 | } | 1300 | } |
| 1274 | 1301 | ||
| 1275 | fn parseTupleType(p: *Parse) Error!NodeIndex { | 1302 | fn parseTupleInit(p: *Parse) Error!NodeIndex { |
| 1276 | const main_token = p.assertToken(.start_paren); | 1303 | const main_token = p.assertToken(.start_paren); |
| 1277 | const list = try p.parseExpressionList(.end_paren, .expression); | 1304 | const list = try p.parseExpressionList(.end_paren, .expression); |
| 1305 | return p.addNode(.{ | ||
| 1306 | .tag = .tuple_init, | ||
| 1307 | .main_token = main_token, | ||
| 1308 | .data = .{ .list = list }, | ||
| 1309 | }); | ||
| 1310 | } | ||
| 1311 | |||
| 1312 | fn parseTupleType(p: *Parse) Error!NodeIndex { | ||
| 1313 | const main_token = p.assertToken(.kw_tuple); | ||
| 1314 | _ = try p.expect(.start_paren); | ||
| 1315 | const list = try p.parseExpressionList(.end_paren, .expression); | ||
| 1278 | return p.addNode(.{ | 1316 | return p.addNode(.{ |
| 1279 | .tag = .tuple_type, | 1317 | .tag = .tuple_type, |
| 1280 | .main_token = main_token, | 1318 | .main_token = main_token, |
src/core/Sema.zig+355-74| ... | @@ -1,10 +1,13 @@ | ... | @@ -1,10 +1,13 @@ |
| 1 | //! Semantic Analysis | 1 | //! Semantic Analysis converts `Teg` |
| 2 | //! | 2 | //! |
| 3 | //! Designed as a nonrecursive pause/resumable state machine, Sema converts | 3 | //! Designed as a nonrecursive state machine which can be paused when an |
| 4 | //! declarations to Intern Pool `Value`s and function bodies to `Air`. | 4 | //! unfinished dependency is encountered, allowing the thread to work |
| 5 | //! on other analysis jobs. | ||
| 5 | //! | 6 | //! |
| 6 | //! - Compile time resolution of values | 7 | //! Sema's responsibilities include |
| 7 | //! - Type checking | 8 | //! - Operation validation and reporting of most compile errors. |
| 9 | //! - Evaluation of `comptime` code. | ||
| 10 | //! - Declaring dependencies on other analysis units. | ||
| 8 | 11 | ||
| 9 | comp: *Comp, | 12 | comp: *Comp, |
| 10 | ip: *InternPool, | 13 | ip: *InternPool, |
| ... | @@ -175,6 +178,7 @@ const Frame = struct { | ... | @@ -175,6 +178,7 @@ const Frame = struct { |
| 175 | 178 | ||
| 176 | undefined: void, | 179 | undefined: void, |
| 177 | conditional: Conditional, | 180 | conditional: Conditional, |
| 181 | func: Func, | ||
| 178 | }; | 182 | }; |
| 179 | 183 | ||
| 180 | const Conditional = enum { | 184 | const Conditional = enum { |
| ... | @@ -183,7 +187,16 @@ const Frame = struct { | ... | @@ -183,7 +187,16 @@ const Frame = struct { |
| 183 | runtime_no, | 187 | runtime_no, |
| 184 | }; | 188 | }; |
| 185 | 189 | ||
| 190 | const Func = union(enum) { | ||
| 191 | return_type, | ||
| 192 | argument: u16, | ||
| 193 | }; | ||
| 194 | |||
| 186 | fn initFromBlock(s: *Sema, block_index: Teg.Inst.Index, mode: AnalysisMode, data: Data) Frame { | 195 | fn initFromBlock(s: *Sema, block_index: Teg.Inst.Index, mode: AnalysisMode, data: Data) Frame { |
| 196 | switch (s.teg.instTag(block_index)) { | ||
| 197 | else => unreachable, // not a block | ||
| 198 | .block, .@"comptime" => {}, | ||
| 199 | } | ||
| 187 | const list = s.teg.instEncodedData(block_index) | 200 | const list = s.teg.instEncodedData(block_index) |
| 188 | .decodeUnsafe(Teg.PlNode(Teg.IndexList), s.teg.instTag(block_index)) | 201 | .decodeUnsafe(Teg.PlNode(Teg.IndexList), s.teg.instTag(block_index)) |
| 189 | .extra_index.get(&s.teg) | 202 | .extra_index.get(&s.teg) |
| ... | @@ -476,7 +489,8 @@ const InstructionKind = enum { | ... | @@ -476,7 +489,8 @@ const InstructionKind = enum { |
| 476 | .@"comptime", | 489 | .@"comptime", |
| 477 | .@"if", | 490 | .@"if", |
| 478 | => .control_flow, | 491 | => .control_flow, |
| 479 | // these pop frames, which may end the compilation | 492 | // break is used to pop a frame, which has the |
| 493 | // possibility to end analysis | ||
| 480 | .@"break", | 494 | .@"break", |
| 481 | => .pop_control_flow, | 495 | => .pop_control_flow, |
| 482 | // these are statements which do not evaluate to anything (void) | 496 | // these are statements which do not evaluate to anything (void) |
| ... | @@ -501,6 +515,12 @@ const PopControlFlowState = enum { stop, @"continue" }; | ... | @@ -501,6 +515,12 @@ const PopControlFlowState = enum { stop, @"continue" }; |
| 501 | 515 | ||
| 502 | fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag).Result() { | 516 | fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag).Result() { |
| 503 | switch (tag) { | 517 | switch (tag) { |
| 518 | .block => unreachable, // frame must already be pushed | ||
| 519 | .declaration, | ||
| 520 | .variable_declaration, | ||
| 521 | .test_declaration, | ||
| 522 | => unreachable, // declarations are not analzed by Sema | ||
| 523 | |||
| 504 | // TODO: pre-evaluate `.param` nodes, since they are always at the start of a function. | 524 | // TODO: pre-evaluate `.param` nodes, since they are always at the start of a function. |
| 505 | // this way we can avoid repeating the non-trivial call into InternPool | 525 | // this way we can avoid repeating the non-trivial call into InternPool |
| 506 | .param => { | 526 | .param => { |
| ... | @@ -666,6 +686,8 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -666,6 +686,8 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 666 | return try s.fieldAccessArray(lhs, lhs_type, field.rhs); | 686 | return try s.fieldAccessArray(lhs, lhs_type, field.rhs); |
| 667 | } else if (lhs_type.toStructType(s.ip)) |struct_type| { | 687 | } else if (lhs_type.toStructType(s.ip)) |struct_type| { |
| 668 | return try s.fieldAccessStruct(lhs, lhs_type, struct_type, field.rhs); | 688 | return try s.fieldAccessStruct(lhs, lhs_type, struct_type, field.rhs); |
| 689 | } else if (lhs_type.toTupleType(s.ip)) |tuple_type| { | ||
| 690 | return try s.fieldAccessTuple(lhs, lhs_type, tuple_type, field.rhs); | ||
| 669 | } else if (lhs.value()) |value| { | 691 | } else if (lhs.value()) |value| { |
| 670 | if (value.valueTag(s.ip) == .external_value) { | 692 | if (value.valueTag(s.ip) == .external_value) { |
| 671 | const rhs = try s.getCoercedValue(s.lazyNode(.rhs), field.rhs, .string); | 693 | 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) | ... | @@ -877,8 +899,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 877 | // return Value.void.toAir(); | 899 | // return Value.void.toAir(); |
| 878 | // }, | 900 | // }, |
| 879 | 901 | ||
| 880 | // control flow | ||
| 881 | .block => unreachable, // frame should already be pushed | ||
| 882 | .@"comptime" => { | 902 | .@"comptime" => { |
| 883 | const index = s.frame.currentIndex(); | 903 | const index = s.frame.currentIndex(); |
| 884 | try s.pushFrame(); | 904 | try s.pushFrame(); |
| ... | @@ -892,50 +912,66 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -892,50 +912,66 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 892 | .{ .params = Type, .captures = Air.Inst.Ref }, | 912 | .{ .params = Type, .captures = Air.Inst.Ref }, |
| 893 | .{ data.params.len, data.captures.len }, | 913 | .{ data.params.len, data.captures.len }, |
| 894 | ); | 914 | ); |
| 895 | defer reserve.deinit(); | 915 | if (data.captures.len > 0) @panic("TODO: captures"); |
| 896 | if (data.captures.len > 0) @panic("captures"); | 916 | |
| 917 | var handled_reserve: std.debug.SafetyLock = .{}; | ||
| 918 | handled_reserve.lock(); | ||
| 919 | defer handled_reserve.assertUnlocked(); | ||
| 920 | errdefer handled_reserve.unlock(); | ||
| 897 | 921 | ||
| 898 | for (data.params.slice(&s.teg), 0..) |param, i| { | 922 | for (data.params.slice(&s.teg), 0..) |param, i| { |
| 899 | const src = s.lazyNode(.argument(@intCast(i))); | 923 | const src = s.lazyNode(.argument(@intCast(i))); |
| 900 | const ty = try s.getType(src, param.unwrap() orelse @panic("TODO: inferred param sets")); | 924 | |
| 901 | if (!ty.hasPossibleRepresentation(s.ip)) { | 925 | const known = param.unwrap() orelse @panic("TODO: inferred param sets"); |
| 902 | return s.fail(.param_comptime_only, .{ | 926 | switch (known.decode()) { |
| 903 | .src = src, | 927 | .value => |val| { |
| 904 | .type = ty, | 928 | const ty = (try s.coerceValue(src, val, .type)).toTypeUnsafe(s.ip); |
| 905 | }); | 929 | if (!ty.hasPossibleRepresentation(s.ip)) { |
| 930 | return s.fail(.param_comptime_only, .{ | ||
| 931 | .src = src, | ||
| 932 | .type = ty, | ||
| 933 | }); | ||
| 934 | } | ||
| 935 | reserve.items(.params)[i] = ty; | ||
| 936 | }, | ||
| 937 | .index => |block| { | ||
| 938 | const mode = s.frame.mode; | ||
| 939 | try s.pushFrame(); | ||
| 940 | s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .{ .argument = @intCast(i) } }); | ||
| 941 | s.frame.mark_index = reserve.index(); | ||
| 942 | handled_reserve.unlock(); | ||
| 943 | return; | ||
| 944 | }, | ||
| 906 | } | 945 | } |
| 907 | reserve.items(.params)[i] = ty; | ||
| 908 | } | 946 | } |
| 909 | 947 | ||
| 910 | const return_type = try s.getType( | 948 | const return_type = ret: { |
| 911 | s.lazyNode(.return_type), | 949 | switch ((data.return_type.unwrap() orelse break :ret null).decode()) { |
| 912 | data.return_type.unwrap() orelse .void, | 950 | .value => |val| { |
| 913 | ); | 951 | const ty = (try s.coerceValue(s.lazyNode(.return_type), val, .type)).toTypeUnsafe(s.ip); |
| 914 | 952 | if (!ty.hasPossibleRepresentation(s.ip)) { | |
| 915 | const tracked_inst = try s.ip.trackInst(s.gpa, s.file, s.frame.currentIndex()); | 953 | return s.fail(.runtime_incompatible_type, .{ |
| 916 | const func_type = try s.makeType(.{ | 954 | .src = s.lazyNode(.return_type), |
| 917 | .function_type = .{ | 955 | .type = ty, |
| 918 | .param_types = reserve.items(.params), | 956 | }); |
| 919 | .return_type = return_type, | 957 | } |
| 920 | }, | 958 | break :ret ty; |
| 921 | }); | 959 | }, |
| 922 | const func_value = try s.makeValue(.{ | 960 | .index => |block| { |
| 923 | .func = .{ | 961 | const mode = s.frame.mode; |
| 924 | .ty = func_type, | 962 | try s.pushFrame(); |
| 925 | .owner_decl = s.techDebtDeclIndex(), // TODO: ooh, this is a fun one! | 963 | s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .return_type }); |
| 926 | .teg_body_inst = tracked_inst, | 964 | s.frame.mark_index = reserve.index(); |
| 927 | }, | 965 | handled_reserve.unlock(); |
| 928 | }); | 966 | return; |
| 929 | 967 | }, | |
| 930 | s.inst_map.add(s.frame.currentIndex(), func_value.toAir()); | 968 | } |
| 931 | 969 | }; | |
| 932 | s.frame.advance(); | ||
| 933 | 970 | ||
| 934 | // TODO: move this queue once we perform one of these | 971 | try s.postVisitFunctionPrototype(reserve, return_type); |
| 935 | // - depend on the return type of an inferred-return-type function | 972 | handled_reserve.unlock(); |
| 936 | // - use the value in any runtime context () | ||
| 937 | try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } }); | ||
| 938 | }, | 973 | }, |
| 974 | |||
| 939 | .@"break" => { | 975 | .@"break" => { |
| 940 | const brk: Teg.Break = s.frame.currentPayload(.@"break"); | 976 | const brk: Teg.Break = s.frame.currentPayload(.@"break"); |
| 941 | const operand = s.getRef(brk.operand, .marked); | 977 | const operand = s.getRef(brk.operand, .marked); |
| ... | @@ -978,12 +1014,14 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -978,12 +1014,14 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 978 | 1014 | ||
| 979 | switch (data.conditional) { | 1015 | switch (data.conditional) { |
| 980 | .comptime_condition => { | 1016 | .comptime_condition => { |
| 1017 | assert(operand.kind == .value); // must be comptime | ||
| 981 | s.inst_map.add(s.frame.currentIndex(), operand); | 1018 | s.inst_map.add(s.frame.currentIndex(), operand); |
| 982 | s.frame.advance(); | 1019 | s.frame.advance(); |
| 983 | return .@"continue"; | 1020 | return .@"continue"; |
| 984 | }, | 1021 | }, |
| 985 | .runtime_yes => { | 1022 | .runtime_yes => { |
| 986 | try s.emitRuntimeBlock(if_data.yes.decode().index, operand, mark); | 1023 | try s.emitRuntimeBlock(if_data.yes.decode().index, operand, mark); |
| 1024 | mark.deinit(); | ||
| 987 | 1025 | ||
| 988 | switch (if_data.no.decode()) { | 1026 | switch (if_data.no.decode()) { |
| 989 | .value => { | 1027 | .value => { |
| ... | @@ -1025,6 +1063,79 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1025,6 +1063,79 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1025 | })); | 1063 | })); |
| 1026 | s.frame.advance(); | 1064 | s.frame.advance(); |
| 1027 | }, | 1065 | }, |
| 1066 | .func => { | ||
| 1067 | const func_data: Teg.Func = s.frame.currentPayload(.func); | ||
| 1068 | |||
| 1069 | const reserve: FunctionReserve = .fromIndex( | ||
| 1070 | &s.scratch, | ||
| 1071 | mark.index, | ||
| 1072 | .{ func_data.params.len, func_data.captures.len }, | ||
| 1073 | ); | ||
| 1074 | |||
| 1075 | const return_type = switch (data.func) { | ||
| 1076 | .argument => |arg_num| ret: { | ||
| 1077 | for ( | ||
| 1078 | func_data.params.slice(&s.teg)[arg_num + 1 ..], | ||
| 1079 | reserve.items(.params)[arg_num + 1 ..], | ||
| 1080 | arg_num + 1.., | ||
| 1081 | ) |param, *dst, i| { | ||
| 1082 | const known = param.unwrap() orelse @panic("TODO: inferred param sets"); | ||
| 1083 | switch (known.decode()) { | ||
| 1084 | .value => |val| { | ||
| 1085 | const ty = (try s.coerceValue(s.lazyNode(.argument(@intCast(i))), val, .type)).toTypeUnsafe(s.ip); | ||
| 1086 | if (!ty.hasPossibleRepresentation(s.ip)) { | ||
| 1087 | return s.fail(.param_comptime_only, .{ | ||
| 1088 | .src = s.lazyNode(.argument(@intCast(i))), | ||
| 1089 | .type = ty, | ||
| 1090 | }); | ||
| 1091 | } | ||
| 1092 | dst.* = ty; | ||
| 1093 | }, | ||
| 1094 | .index => |block| { | ||
| 1095 | const mode = s.frame.mode; | ||
| 1096 | try s.pushFrame(); | ||
| 1097 | s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .{ .argument = @intCast(i) } }); | ||
| 1098 | return .@"continue"; | ||
| 1099 | }, | ||
| 1100 | } | ||
| 1101 | } | ||
| 1102 | |||
| 1103 | switch ((func_data.return_type.unwrap() orelse break :ret null).decode()) { | ||
| 1104 | .value => |val| { | ||
| 1105 | const ty = (try s.coerceValue(s.lazyNode(.return_type), val, .type)).toTypeUnsafe(s.ip); | ||
| 1106 | if (!ty.hasPossibleRepresentation(s.ip)) { | ||
| 1107 | return s.fail(.runtime_incompatible_type, .{ | ||
| 1108 | .src = s.lazyNode(.return_type), | ||
| 1109 | .type = ty, | ||
| 1110 | }); | ||
| 1111 | } | ||
| 1112 | break :ret ty; | ||
| 1113 | }, | ||
| 1114 | .index => |block| { | ||
| 1115 | const mode = s.frame.mode; | ||
| 1116 | try s.pushFrame(); | ||
| 1117 | s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .return_type }); | ||
| 1118 | s.frame.mark_index = reserve.index(); | ||
| 1119 | return .@"continue"; | ||
| 1120 | }, | ||
| 1121 | } | ||
| 1122 | }, | ||
| 1123 | .return_type => ret: { | ||
| 1124 | const val = operand.value() orelse unreachable; // must be comptime | ||
| 1125 | const ty = (try s.coerceValue(s.lazyNode(.return_type), val, .type)).toTypeUnsafe(s.ip); | ||
| 1126 | if (!ty.hasPossibleRepresentation(s.ip)) { | ||
| 1127 | return s.fail(.runtime_incompatible_type, .{ | ||
| 1128 | .src = s.lazyNode(.return_type), | ||
| 1129 | .type = ty, | ||
| 1130 | }); | ||
| 1131 | } | ||
| 1132 | break :ret ty; | ||
| 1133 | }, | ||
| 1134 | }; | ||
| 1135 | |||
| 1136 | try s.postVisitFunctionPrototype(reserve, return_type); | ||
| 1137 | return .@"continue"; | ||
| 1138 | }, | ||
| 1028 | else => |t| std.debug.panic("TODO: post-visit .{s}", .{@tagName(t)}), | 1139 | else => |t| std.debug.panic("TODO: post-visit .{s}", .{@tagName(t)}), |
| 1029 | } | 1140 | } |
| 1030 | 1141 | ||
| ... | @@ -1141,7 +1252,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1141,7 +1252,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1141 | var has_errors = false; | 1252 | var has_errors = false; |
| 1142 | var is_constant = true; | 1253 | var is_constant = true; |
| 1143 | 1254 | ||
| 1144 | // Allocate scratch space to store the values as they are being prepared. | ||
| 1145 | const scratch = try s.scratch.reserve( | 1255 | const scratch = try s.scratch.reserve( |
| 1146 | s.arena(), | 1256 | s.arena(), |
| 1147 | Air.Inst.Ref.Optional, | 1257 | Air.Inst.Ref.Optional, |
| ... | @@ -1150,11 +1260,10 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1150,11 +1260,10 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1150 | defer scratch.deinit(); | 1260 | defer scratch.deinit(); |
| 1151 | @memset(scratch.slice(), .none); | 1261 | @memset(scratch.slice(), .none); |
| 1152 | 1262 | ||
| 1263 | // The ordering of `storage` is defined by the struct's type, which | ||
| 1264 | // may be different than the instantiation. The fields are visited in | ||
| 1265 | // order of the instantiation. | ||
| 1153 | for (node.keys(&s.teg), node.inits(&s.teg), 0..) |init_key, init_value, i| { | 1266 | for (node.keys(&s.teg), node.inits(&s.teg), 0..) |init_key, init_value, i| { |
| 1154 | // The order of `storage` is as ordered by the type, but this | ||
| 1155 | // can be in a different ordering than the initialization. We | ||
| 1156 | // guarantee the ordering the initializers are visited is in the | ||
| 1157 | // order they are declared. | ||
| 1158 | const found_index = index: for (struct_type.field_keys, 0..) |decl_key, j| { | 1267 | const found_index = index: for (struct_type.field_keys, 0..) |decl_key, j| { |
| 1159 | if (decl_key.eqlSlice(init_key.slice(s.teg.string_bytes), s.ip)) | 1268 | if (decl_key.eqlSlice(init_key.slice(s.teg.string_bytes), s.ip)) |
| 1160 | break :index j; | 1269 | break :index j; |
| ... | @@ -1181,7 +1290,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1181,7 +1290,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1181 | .marked, | 1290 | .marked, |
| 1182 | ) catch |e| switch (e) { | 1291 | ) catch |e| switch (e) { |
| 1183 | error.AnalysisFail => { | 1292 | error.AnalysisFail => { |
| 1184 | // Instead of bailing out with `try`, we will continue the loop. | 1293 | // Instead of bailing out with `try`, all fields will be analyzed |
| 1185 | has_errors = true; | 1294 | has_errors = true; |
| 1186 | // Mark something non-null so that duplicate field errors are emitted. | 1295 | // Mark something non-null so that duplicate field errors are emitted. |
| 1187 | scratch.at(found_index).* = comptime Value.void.toAir().toOptional(); | 1296 | 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) | ... | @@ -1193,29 +1302,29 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1193 | scratch.at(found_index).* = coerced.toOptional(); | 1302 | scratch.at(found_index).* = coerced.toOptional(); |
| 1194 | } | 1303 | } |
| 1195 | 1304 | ||
| 1196 | // Check for missing fields | 1305 | const storage = scratch.slice(); |
| 1197 | { | 1306 | var i: usize = 0; |
| 1198 | const storage = scratch.slice(); | 1307 | while (mem.indexOfScalarPos(Air.Inst.Ref.Optional, storage, i, .none)) |index| { |
| 1199 | var i: usize = 0; | 1308 | try s.addError(.struct_init_missing_field, .{ |
| 1200 | while (mem.indexOfScalarPos(Air.Inst.Ref.Optional, storage, i, .none)) |index| { | 1309 | .ty = ty, |
| 1201 | try s.addError(.struct_init_missing_field, .{ | 1310 | .field = @intCast(index), |
| 1202 | .ty = ty, | 1311 | .src = s.lazyNode(.main_node), |
| 1203 | .field = @intCast(index), | 1312 | }); |
| 1204 | .src = s.lazyNode(.main_node), | 1313 | i = index + 1; |
| 1205 | }); | ||
| 1206 | i = index + 1; | ||
| 1207 | } | ||
| 1208 | if (i != 0) has_errors = true; | ||
| 1209 | } | 1314 | } |
| 1315 | if (i != 0) has_errors = true; | ||
| 1210 | 1316 | ||
| 1211 | if (has_errors) | 1317 | if (has_errors) |
| 1212 | return Error.AnalysisFail; | 1318 | return Error.AnalysisFail; |
| 1213 | 1319 | ||
| 1320 | // Now that there are definitely no missing fields, it is safe to | ||
| 1321 | // interpret `scratch` as `Air.Inst.Ref` | ||
| 1322 | |||
| 1214 | if (is_constant) { | 1323 | if (is_constant) { |
| 1215 | // Convert all items in storage into a Value type | ||
| 1216 | const result_storage = scratch.sliceReinterpret(Value); | 1324 | const result_storage = scratch.sliceReinterpret(Value); |
| 1217 | for (scratch.slice(), scratch.sliceReinterpret(Value)) |ref, *value| { | 1325 | for (scratch.slice(), result_storage) |ref, *value| { |
| 1218 | value.* = ref.unwrap().?.decode().value; | 1326 | // `ref` and `value` alias; this coercion is performed in-place |
| 1327 | value.* = (ref.unwrap() orelse unreachable).decode().value; | ||
| 1219 | } | 1328 | } |
| 1220 | return try s.makeValueAir(.{ .struct_value = .{ | 1329 | return try s.makeValueAir(.{ .struct_value = .{ |
| 1221 | .ty = ty, | 1330 | .ty = ty, |
| ... | @@ -1224,7 +1333,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1224,7 +1333,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1224 | } else { | 1333 | } else { |
| 1225 | try s.ensureAtRuntime(s.lazyNode(.main_node)); | 1334 | try s.ensureAtRuntime(s.lazyNode(.main_node)); |
| 1226 | 1335 | ||
| 1227 | // Create a runtime struct instantiation | ||
| 1228 | const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice()); | 1336 | const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice()); |
| 1229 | return try s.addRef(.struct_init, .{ | 1337 | return try s.addRef(.struct_init, .{ |
| 1230 | .ty = ty, | 1338 | .ty = ty, |
| ... | @@ -1239,6 +1347,73 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1239,6 +1347,73 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1239 | return s.makeValueAir(.{ .array_type = .{ .item_type = operand } }); | 1347 | return s.makeValueAir(.{ .array_type = .{ .item_type = operand } }); |
| 1240 | }, | 1348 | }, |
| 1241 | 1349 | ||
| 1350 | .tuple_type => { | ||
| 1351 | const data: Teg.TupleType = s.frame.currentPayload(.tuple_type); | ||
| 1352 | |||
| 1353 | const scratch = try s.scratch.reserve(s.arena(), Type, data.items.len); | ||
| 1354 | defer scratch.deinit(); | ||
| 1355 | |||
| 1356 | for (data.items.slice(&s.teg), scratch.slice(), 0..) |src, *dst, i| { | ||
| 1357 | dst.* = try s.getType(s.lazyNode(.argument(@intCast(i))), src); | ||
| 1358 | } | ||
| 1359 | |||
| 1360 | return s.makeValueAir(.{ .tuple_type = .{ .items = scratch.slice() } }); | ||
| 1361 | }, | ||
| 1362 | |||
| 1363 | .tuple_init => { | ||
| 1364 | const data: Teg.TupleInit = s.frame.currentPayload(.tuple_init); | ||
| 1365 | |||
| 1366 | _ = data.type_hint; // TODO: use type hint | ||
| 1367 | |||
| 1368 | const scratch = try s.scratch.reserve(s.arena(), Air.Inst.Ref, data.items.len); | ||
| 1369 | defer scratch.deinit(); | ||
| 1370 | |||
| 1371 | var is_const = true; | ||
| 1372 | for (data.items.slice(&s.teg), scratch.slice()) |src, *dst| { | ||
| 1373 | const result = s.getRef(src, .marked); | ||
| 1374 | dst.* = result; | ||
| 1375 | if (result.kind == .index) is_const = false; | ||
| 1376 | } | ||
| 1377 | |||
| 1378 | if (!is_const) { | ||
| 1379 | try s.ensureAtRuntime(s.lazyNode(.main_node)); | ||
| 1380 | |||
| 1381 | for (scratch.slice(), 0..) |*ref, i| { | ||
| 1382 | ref.* = try s.ensureValueRuntime(s.lazyNode(.argument(@intCast(i))), ref.*); | ||
| 1383 | } | ||
| 1384 | } | ||
| 1385 | |||
| 1386 | const ty = ty: { | ||
| 1387 | const type_scratch = try s.scratch.reserve(s.arena(), Type, data.items.len); | ||
| 1388 | defer type_scratch.deinit(); | ||
| 1389 | |||
| 1390 | for (scratch.slice(), type_scratch.slice()) |val, *ty| { | ||
| 1391 | ty.* = s.typeOfRef(val); | ||
| 1392 | } | ||
| 1393 | |||
| 1394 | break :ty try s.makeType(.{ .tuple_type = .{ .items = type_scratch.slice() } }); | ||
| 1395 | }; | ||
| 1396 | |||
| 1397 | if (is_const) { | ||
| 1398 | const result_storage = scratch.sliceReinterpret(Value); | ||
| 1399 | for (scratch.slice(), result_storage) |ref, *value| { | ||
| 1400 | // `ref` and `value` alias; this coercion is performed in-place | ||
| 1401 | value.* = ref.decode().value; | ||
| 1402 | } | ||
| 1403 | |||
| 1404 | return try s.makeValueAir(.{ .tuple_value = .{ | ||
| 1405 | .items = result_storage, | ||
| 1406 | .type = ty, | ||
| 1407 | } }); | ||
| 1408 | } else { | ||
| 1409 | const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice()); | ||
| 1410 | return try s.addRef(.struct_init, .{ | ||
| 1411 | .field_values = value_index, | ||
| 1412 | .ty = ty, | ||
| 1413 | }); | ||
| 1414 | } | ||
| 1415 | }, | ||
| 1416 | |||
| 1242 | .result_type_argument => { | 1417 | .result_type_argument => { |
| 1243 | const result: Teg.ResultTypeArgument = s.frame.currentData(.result_type_argument); | 1418 | const result: Teg.ResultTypeArgument = s.frame.currentData(.result_type_argument); |
| 1244 | 1419 | ||
| ... | @@ -1605,6 +1780,11 @@ fn coerceValue(s: *Sema, src: LazyNode, value: Value, to: Type) Error!Value { | ... | @@ -1605,6 +1780,11 @@ fn coerceValue(s: *Sema, src: LazyNode, value: Value, to: Type) Error!Value { |
| 1605 | return s.coerceArrayValue(src, value, to_array_type.item_type); | 1780 | return s.coerceArrayValue(src, value, to_array_type.item_type); |
| 1606 | } | 1781 | } |
| 1607 | } | 1782 | } |
| 1783 | if (to.toTupleType(s.ip)) |to_tuple_type| { | ||
| 1784 | if (value.valueTag(s.ip) == .tuple_value) { | ||
| 1785 | return s.coerceTupleValue(src, value, from, to, to_tuple_type); | ||
| 1786 | } | ||
| 1787 | } | ||
| 1608 | }, | 1788 | }, |
| 1609 | } | 1789 | } |
| 1610 | return s.coercionError(src, from, to); | 1790 | return s.coercionError(src, from, to); |
| ... | @@ -1615,6 +1795,14 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst | ... | @@ -1615,6 +1795,14 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst |
| 1615 | .value => |v| { | 1795 | .value => |v| { |
| 1616 | const ty = v.typeOf(s.ip); | 1796 | const ty = v.typeOf(s.ip); |
| 1617 | switch (ty) { | 1797 | switch (ty) { |
| 1798 | // Allowed at runtime | ||
| 1799 | .bool, | ||
| 1800 | .float, | ||
| 1801 | .int32, | ||
| 1802 | .string, | ||
| 1803 | .void, | ||
| 1804 | .empty_tuple, | ||
| 1805 | => {}, | ||
| 1618 | .numeric_literal => { | 1806 | .numeric_literal => { |
| 1619 | const float = v.toFloat(s.ip) orelse unreachable; | 1807 | const float = v.toFloat(s.ip) orelse unreachable; |
| 1620 | 1808 | ||
| ... | @@ -1649,13 +1837,6 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst | ... | @@ -1649,13 +1837,6 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst |
| 1649 | .type = ty, | 1837 | .type = ty, |
| 1650 | }), | 1838 | }), |
| 1651 | .@"opaque" => @panic("TODO: represent comptime-known opaques better"), | 1839 | .@"opaque" => @panic("TODO: represent comptime-known opaques better"), |
| 1652 | // Allowed at runtime | ||
| 1653 | .bool, | ||
| 1654 | .float, | ||
| 1655 | .int32, | ||
| 1656 | .string, | ||
| 1657 | .void, | ||
| 1658 | => {}, | ||
| 1659 | _ => if (!ty.hasPossibleRepresentation(s.ip)) { | 1840 | _ => if (!ty.hasPossibleRepresentation(s.ip)) { |
| 1660 | return s.fail(.runtime_incompatible_type, .{ | 1841 | return s.fail(.runtime_incompatible_type, .{ |
| 1661 | .src = src, | 1842 | .src = src, |
| ... | @@ -1728,6 +1909,44 @@ fn coerceArrayValue(s: *Sema, src: LazyNode, value: Value, item_type: Type) !Val | ... | @@ -1728,6 +1909,44 @@ fn coerceArrayValue(s: *Sema, src: LazyNode, value: Value, item_type: Type) !Val |
| 1728 | } }); | 1909 | } }); |
| 1729 | } | 1910 | } |
| 1730 | 1911 | ||
| 1912 | fn coerceTupleValue( | ||
| 1913 | s: *Sema, | ||
| 1914 | src: LazyNode, | ||
| 1915 | value: Value, | ||
| 1916 | value_type: Type, | ||
| 1917 | target_type: Type, | ||
| 1918 | target_key: InternPool.Key.TupleType, | ||
| 1919 | ) !Value { | ||
| 1920 | const tuple_value = value.toKey(s.ip).tuple_value; | ||
| 1921 | if (tuple_value.items.len != target_key.items.len) | ||
| 1922 | return s.coercionError(src, value_type, target_type); | ||
| 1923 | |||
| 1924 | const len = tuple_value.items.len; | ||
| 1925 | |||
| 1926 | const scratch = try s.scratch.reserveMulti( | ||
| 1927 | s.arena(), | ||
| 1928 | .{ .values = Value, .types = Type }, | ||
| 1929 | .{ @intCast(len), @intCast(len) }, | ||
| 1930 | ); | ||
| 1931 | defer scratch.deinit(); | ||
| 1932 | |||
| 1933 | // This function will append new items to the intern pool, invalidating | ||
| 1934 | // existing ones. Copy all data into a stable slice first. | ||
| 1935 | @memcpy(scratch.items(.values), tuple_value.items); | ||
| 1936 | @memcpy(scratch.items(.types), target_key.items); | ||
| 1937 | |||
| 1938 | for (0..len) |i| { | ||
| 1939 | // `s.scratch` can be invalidated by having a tuple of tuples and coercing it. | ||
| 1940 | const coerced = try s.coerceValue(src, scratch.at(.values, i).*, scratch.at(.types, i).*); | ||
| 1941 | scratch.at(.values, i).* = coerced; | ||
| 1942 | } | ||
| 1943 | |||
| 1944 | return try s.makeValue(.{ .tuple_value = .{ | ||
| 1945 | .type = target_type, | ||
| 1946 | .items = scratch.items(.values), | ||
| 1947 | } }); | ||
| 1948 | } | ||
| 1949 | |||
| 1731 | fn lookupDecl(s: *Sema, name: NullTerminatedString) InternPool.DeclIndex { | 1950 | fn lookupDecl(s: *Sema, name: NullTerminatedString) InternPool.DeclIndex { |
| 1732 | var nsi = s.ns_index; | 1951 | var nsi = s.ns_index; |
| 1733 | while (true) { | 1952 | while (true) { |
| ... | @@ -1861,6 +2080,68 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Teg.In | ... | @@ -1861,6 +2080,68 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Teg.In |
| 1861 | } | 2080 | } |
| 1862 | } | 2081 | } |
| 1863 | 2082 | ||
| 2083 | fn fieldAccessTuple( | ||
| 2084 | s: *Sema, | ||
| 2085 | lhs: Air.Inst.Ref, | ||
| 2086 | lhs_type: Type, | ||
| 2087 | tuple_type: InternPool.Key.TupleType, | ||
| 2088 | field: Teg.Inst.Ref, | ||
| 2089 | ) !Air.Inst.Ref { | ||
| 2090 | const index_value = try s.getCoercedValue(s.lazyNode(.rhs), field, .int32); | ||
| 2091 | const index_signed = index_value.toInt32(s.ip) orelse unreachable; | ||
| 2092 | if (index_signed < 0 or index_signed >= tuple_type.items.len) { | ||
| 2093 | return s.fail(.field_lookup_failed, .{ | ||
| 2094 | .ty = lhs_type, | ||
| 2095 | .src = s.lazyNode(.lhs), | ||
| 2096 | .field = index_value, | ||
| 2097 | }); | ||
| 2098 | } | ||
| 2099 | const index: u32 = @intCast(index_signed); | ||
| 2100 | |||
| 2101 | switch (lhs.decode()) { | ||
| 2102 | .value => |value| { | ||
| 2103 | return value.toKey(s.ip).tuple_value.items[index].toAir(); | ||
| 2104 | }, | ||
| 2105 | .index => |lhs_index| { | ||
| 2106 | try s.ensureAtRuntime(s.lazyNode(.lhs)); | ||
| 2107 | return s.addRef(.field_tuple, .{ | ||
| 2108 | .base = lhs_index, | ||
| 2109 | .field_index = index, | ||
| 2110 | }); | ||
| 2111 | }, | ||
| 2112 | } | ||
| 2113 | } | ||
| 2114 | |||
| 2115 | const FunctionReserve = core.U32Stack.ReserveMulti(.{ .params = Type, .captures = Air.Inst.Ref }); | ||
| 2116 | |||
| 2117 | fn postVisitFunctionPrototype(s: *Sema, reserve: FunctionReserve, return_type: ?Type) !void { | ||
| 2118 | const tracked_inst = try s.ip.trackInst(s.gpa, s.file, s.frame.currentIndex()); | ||
| 2119 | const func_type = try s.makeType(.{ | ||
| 2120 | .function_type = .{ | ||
| 2121 | .param_types = reserve.items(.params), | ||
| 2122 | .return_type = return_type, | ||
| 2123 | }, | ||
| 2124 | }); | ||
| 2125 | const func_value = try s.makeValue(.{ | ||
| 2126 | .func = .{ | ||
| 2127 | .ty = func_type, | ||
| 2128 | .owner_decl = s.techDebtDeclIndex(), // TODO: ooh, this is a fun one! | ||
| 2129 | .teg_body_inst = tracked_inst, | ||
| 2130 | }, | ||
| 2131 | }); | ||
| 2132 | |||
| 2133 | s.inst_map.add(s.frame.currentIndex(), func_value.toAir()); | ||
| 2134 | |||
| 2135 | s.frame.advance(); | ||
| 2136 | |||
| 2137 | reserve.deinit(); | ||
| 2138 | |||
| 2139 | // TODO: move this queue once we perform one of these | ||
| 2140 | // - depend on the return type of an inferred-return-type function | ||
| 2141 | // - use the value in any runtime context () | ||
| 2142 | try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } }); | ||
| 2143 | } | ||
| 2144 | |||
| 1864 | // | 2145 | // |
| 1865 | 2146 | ||
| 1866 | fn tempAir(s: *const Sema) Air { | 2147 | fn tempAir(s: *const Sema) Air { |
src/core/Teg.Dump.zig+18| ... | @@ -319,6 +319,24 @@ const State = struct { | ... | @@ -319,6 +319,24 @@ const State = struct { |
| 319 | if (data.type_hint != .none) | 319 | if (data.type_hint != .none) |
| 320 | try writer.writeAll(")"); | 320 | try writer.writeAll(")"); |
| 321 | }, | 321 | }, |
| 322 | .tuple_type => { | ||
| 323 | const data: Teg.TupleType = teg.instPayload(.tuple_type, inst); | ||
| 324 | |||
| 325 | try writer.writeAll("("); | ||
| 326 | try s.dumpRefMarkerList(writer, data.items.slice(teg), '[', ']'); | ||
| 327 | try writer.writeAll(")"); | ||
| 328 | }, | ||
| 329 | .tuple_init => { | ||
| 330 | const data: Teg.TupleInit = teg.instPayload(.tuple_init, inst); | ||
| 331 | |||
| 332 | try writer.writeAll("("); | ||
| 333 | if (data.type_hint.unwrap()) |type_hint| { | ||
| 334 | try s.dumpRef(writer, type_hint); | ||
| 335 | try writer.writeAll(", "); | ||
| 336 | } | ||
| 337 | try s.dumpRefMarkerList(writer, data.items.slice(teg), '[', ']'); | ||
| 338 | try writer.writeAll(")"); | ||
| 339 | }, | ||
| 322 | .result_type_argument => { | 340 | .result_type_argument => { |
| 323 | const data: Teg.ResultTypeArgument = teg.instData(.result_type_argument, inst); | 341 | const data: Teg.ResultTypeArgument = teg.instData(.result_type_argument, inst); |
| 324 | 342 |
src/core/Teg.zig+40-4| ... | @@ -75,6 +75,9 @@ pub const Tag = enum(u8) { | ... | @@ -75,6 +75,9 @@ pub const Tag = enum(u8) { |
| 75 | struct_init, | 75 | struct_init, |
| 76 | array_init, | 76 | array_init, |
| 77 | 77 | ||
| 78 | tuple_init, | ||
| 79 | tuple_type, | ||
| 80 | |||
| 78 | add, | 81 | add, |
| 79 | sub, | 82 | sub, |
| 80 | mul, | 83 | mul, |
| ... | @@ -123,6 +126,7 @@ pub const Tag = enum(u8) { | ... | @@ -123,6 +126,7 @@ pub const Tag = enum(u8) { |
| 123 | .variable_declaration, | 126 | .variable_declaration, |
| 124 | .test_declaration, | 127 | .test_declaration, |
| 125 | => PlNode(Decl), | 128 | => PlNode(Decl), |
| 129 | |||
| 126 | .coercion => PlNode(Coercion), | 130 | .coercion => PlNode(Coercion), |
| 127 | .@"break" => PlNode(Break), | 131 | .@"break" => PlNode(Break), |
| 128 | .number => PlNode(Float), | 132 | .number => PlNode(Float), |
| ... | @@ -136,6 +140,8 @@ pub const Tag = enum(u8) { | ... | @@ -136,6 +140,8 @@ pub const Tag = enum(u8) { |
| 136 | .struct_decl => PlNode(StructDecl), | 140 | .struct_decl => PlNode(StructDecl), |
| 137 | .struct_init => PlNode(StructInit), | 141 | .struct_init => PlNode(StructInit), |
| 138 | .array_init => PlNode(ArrayInit), | 142 | .array_init => PlNode(ArrayInit), |
| 143 | .tuple_type => PlNode(TupleType), | ||
| 144 | .tuple_init => PlNode(TupleInit), | ||
| 139 | .func => PlNode(Func), | 145 | .func => PlNode(Func), |
| 140 | 146 | ||
| 141 | .load_decl => StrNode, | 147 | .load_decl => StrNode, |
| ... | @@ -146,7 +152,7 @@ pub const Tag = enum(u8) { | ... | @@ -146,7 +152,7 @@ pub const Tag = enum(u8) { |
| 146 | .result_type_tuple_field => ResultTypeTupleField, | 152 | .result_type_tuple_field => ResultTypeTupleField, |
| 147 | .result_type_array_value => ResultTypeArrayValue, | 153 | .result_type_array_value => ResultTypeArrayValue, |
| 148 | 154 | ||
| 149 | // Single instruction operand | 155 | // One operand instructions |
| 150 | .memory, | 156 | .memory, |
| 151 | .discard, | 157 | .discard, |
| 152 | .ensure_void, | 158 | .ensure_void, |
| ... | @@ -162,7 +168,7 @@ pub const Tag = enum(u8) { | ... | @@ -162,7 +168,7 @@ pub const Tag = enum(u8) { |
| 162 | .assert, | 168 | .assert, |
| 163 | => RefNode, | 169 | => RefNode, |
| 164 | 170 | ||
| 165 | // Two instruction operands | 171 | // Two operand instructions |
| 166 | .field, | 172 | .field, |
| 167 | .index, | 173 | .index, |
| 168 | .add, | 174 | .add, |
| ... | @@ -230,7 +236,7 @@ pub const Inst = struct { | ... | @@ -230,7 +236,7 @@ pub const Inst = struct { |
| 230 | main_module_inst = 0, | 236 | main_module_inst = 0, |
| 231 | _, | 237 | _, |
| 232 | 238 | ||
| 233 | pub const static_len = @intFromEnum(Ref.empty_array) + 1; | 239 | pub const static_len = @intFromEnum(Ref.empty_tuple) + 1; |
| 234 | 240 | ||
| 235 | pub fn toRef(i: Index) Inst.Ref { | 241 | pub fn toRef(i: Index) Inst.Ref { |
| 236 | return @enumFromInt(static_len + @intFromEnum(i)); | 242 | return @enumFromInt(static_len + @intFromEnum(i)); |
| ... | @@ -269,6 +275,7 @@ pub const Inst = struct { | ... | @@ -269,6 +275,7 @@ pub const Inst = struct { |
| 269 | numeric_literal_type, | 275 | numeric_literal_type, |
| 270 | numeric_literal_array_type, | 276 | numeric_literal_array_type, |
| 271 | never_array_type, | 277 | never_array_type, |
| 278 | empty_tuple_type, | ||
| 272 | 279 | ||
| 273 | void, | 280 | void, |
| 274 | bool_true, | 281 | bool_true, |
| ... | @@ -278,7 +285,7 @@ pub const Inst = struct { | ... | @@ -278,7 +285,7 @@ pub const Inst = struct { |
| 278 | nan, | 285 | nan, |
| 279 | empty_string, | 286 | empty_string, |
| 280 | empty_array, | 287 | empty_array, |
| 281 | 288 | empty_tuple, | |
| 282 | _, | 289 | _, |
| 283 | 290 | ||
| 284 | pub const Decoded = union(enum) { | 291 | pub const Decoded = union(enum) { |
| ... | @@ -423,6 +430,20 @@ pub const ArrayInit = struct { | ... | @@ -423,6 +430,20 @@ pub const ArrayInit = struct { |
| 423 | list: RefList, | 430 | list: RefList, |
| 424 | }; | 431 | }; |
| 425 | 432 | ||
| 433 | pub const TupleType = struct { | ||
| 434 | /// The items in the tuple | ||
| 435 | items: RefList, | ||
| 436 | }; | ||
| 437 | |||
| 438 | pub const TupleInit = struct { | ||
| 439 | /// Optionally provide a result type for more reliable type coercion. | ||
| 440 | /// If specified, this must be an array type. Otherwise, the first | ||
| 441 | /// element's type is used. | ||
| 442 | type_hint: Inst.Ref.Optional, | ||
| 443 | /// The items in the tuple | ||
| 444 | items: RefList, | ||
| 445 | }; | ||
| 446 | |||
| 426 | pub const Decl = struct { | 447 | pub const Decl = struct { |
| 427 | name: NullTerminatedString, | 448 | name: NullTerminatedString, |
| 428 | hash: Cache.BinDigest, | 449 | hash: Cache.BinDigest, |
| ... | @@ -697,6 +718,8 @@ pub fn writeToHasher( | ... | @@ -697,6 +718,8 @@ pub fn writeToHasher( |
| 697 | .struct_decl, | 718 | .struct_decl, |
| 698 | .struct_init, | 719 | .struct_init, |
| 699 | .array_init, | 720 | .array_init, |
| 721 | .tuple_init, | ||
| 722 | .tuple_type, | ||
| 700 | .func, | 723 | .func, |
| 701 | .store_decl, | 724 | .store_decl, |
| 702 | .store_field, | 725 | .store_field, |
| ... | @@ -902,6 +925,19 @@ fn writePlNodeInnerToHasher( | ... | @@ -902,6 +925,19 @@ fn writePlNodeInnerToHasher( |
| 902 | hasher.add(isolatedIndexValue(decl_index, data.body)); | 925 | hasher.add(isolatedIndexValue(decl_index, data.body)); |
| 903 | hasher.add(data.instruction_count); | 926 | hasher.add(data.instruction_count); |
| 904 | }, | 927 | }, |
| 928 | TupleType => { | ||
| 929 | hasher.add(data.items.len); | ||
| 930 | for (data.items.slice(teg)) |item| { | ||
| 931 | hasher.add(isolatedRefValue(decl_index, item)); | ||
| 932 | } | ||
| 933 | }, | ||
| 934 | TupleInit => { | ||
| 935 | if (data.type_hint.unwrap()) |type_hint| | ||
| 936 | hasher.add(isolatedRefValue(decl_index, type_hint)) | ||
| 937 | else | ||
| 938 | hasher.add(@as(i32, std.math.maxInt(i32))); | ||
| 939 | writeRefListToHasher(hasher, decl_index, data.items.slice(teg)); | ||
| 940 | }, | ||
| 905 | else => @compileError("hash not implemented for " ++ @typeName(T)), | 941 | else => @compileError("hash not implemented for " ++ @typeName(T)), |
| 906 | } | 942 | } |
| 907 | } | 943 | } |
src/core/Tokenizer.zig+4-3| ... | @@ -75,6 +75,7 @@ pub const Token = struct { | ... | @@ -75,6 +75,7 @@ pub const Token = struct { |
| 75 | kw_return, | 75 | kw_return, |
| 76 | kw_struct, | 76 | kw_struct, |
| 77 | kw_trait, | 77 | kw_trait, |
| 78 | kw_tuple, | ||
| 78 | kw_var, | 79 | kw_var, |
| 79 | kw_while, | 80 | kw_while, |
| 80 | // sort-lines: end | 81 | // sort-lines: end |
| ... | @@ -134,7 +135,7 @@ pub const Token = struct { | ... | @@ -134,7 +135,7 @@ pub const Token = struct { |
| 134 | 135 | ||
| 135 | /// Represents a tokenization error. Instead of reporting an error during | 136 | /// Represents a tokenization error. Instead of reporting an error during |
| 136 | /// tokenization, parsing will begin, and an error will be reported when | 137 | /// tokenization, parsing will begin, and an error will be reported when |
| 137 | /// this token is hit. That way, the error can tell you what it expected. | 138 | /// this token is hit. That way, the error . |
| 138 | invalid, | 139 | invalid, |
| 139 | invalid_number, | 140 | invalid_number, |
| 140 | invalid_string, | 141 | invalid_string, |
| ... | @@ -275,8 +276,8 @@ pub const Token = struct { | ... | @@ -275,8 +276,8 @@ pub const Token = struct { |
| 275 | /// Instead of storing the length of a token, we can recompute this | 276 | /// Instead of storing the length of a token, we can recompute this |
| 276 | /// information on demand, as it is not often needed. | 277 | /// information on demand, as it is not often needed. |
| 277 | /// | 278 | /// |
| 278 | /// If you are computing the length of something that is knowable | 279 | /// When computing the length of something that can be known without |
| 279 | /// without the source code, use token.tag.staticLen() instead. | 280 | /// the source code, use token.tag.staticLen() instead. |
| 280 | pub fn len(token: Token, utf8_source: [:0]const u8) u32 { | 281 | pub fn len(token: Token, utf8_source: [:0]const u8) u32 { |
| 281 | return token.tag.staticLen() orelse switch (token.tag) { | 282 | return token.tag.staticLen() orelse switch (token.tag) { |
| 282 | 283 |
src/core/Transform.zig+36-4| ... | @@ -137,10 +137,9 @@ const ResultType = union(enum) { | ... | @@ -137,10 +137,9 @@ const ResultType = union(enum) { |
| 137 | .used => unreachable, // Use after free | 137 | .used => unreachable, // Use after free |
| 138 | .ref => |r| r, | 138 | .ref => |r| r, |
| 139 | .type_of => |r| try t.addRef(.type_of, .{ | 139 | .type_of => |r| try t.addRef(.type_of, .{ |
| 140 | // Result types don't have associated nodes, which is ok because | 140 | // Result types do not have associated nodes which is okay as `type_of` cannot |
| 141 | // type_of cannot fail in the context we are using it. Instead, | 141 | // fail in the context it is used in. A predictable poison value is used instead |
| 142 | // we will use a predictable poison value, so that an attempt | 142 | // so that an attempt to print the following will cause a crash. |
| 143 | // to print this will crash. | ||
| 144 | .src_node = std.math.maxInt(i32), | 143 | .src_node = std.math.maxInt(i32), |
| 145 | .operand = r, | 144 | .operand = r, |
| 146 | }), | 145 | }), |
| ... | @@ -281,6 +280,7 @@ const Symbol = struct { | ... | @@ -281,6 +280,7 @@ const Symbol = struct { |
| 281 | value: union { | 280 | value: union { |
| 282 | mutable: Inst.Index, | 281 | mutable: Inst.Index, |
| 283 | immutable: Inst.Ref, | 282 | immutable: Inst.Ref, |
| 283 | param_index: u32, | ||
| 284 | none: void, | 284 | none: void, |
| 285 | }, | 285 | }, |
| 286 | result_type_ref: Inst.Ref, | 286 | result_type_ref: Inst.Ref, |
| ... | @@ -903,6 +903,7 @@ fn transformExpression( | ... | @@ -903,6 +903,7 @@ fn transformExpression( |
| 903 | switch (node_tag) { | 903 | switch (node_tag) { |
| 904 | .root, | 904 | .root, |
| 905 | .param_decl, | 905 | .param_decl, |
| 906 | .struct_field, | ||
| 906 | => unreachable, // not an expression | 907 | => unreachable, // not an expression |
| 907 | 908 | ||
| 908 | .id => { | 909 | .id => { |
| ... | @@ -1520,6 +1521,37 @@ fn transformExpression( | ... | @@ -1520,6 +1521,37 @@ fn transformExpression( |
| 1520 | }); | 1521 | }); |
| 1521 | }, | 1522 | }, |
| 1522 | 1523 | ||
| 1524 | .tuple_init => { | ||
| 1525 | const tuple_init = t.ast.nodeData(node).list; | ||
| 1526 | |||
| 1527 | const items = try t.transformExpressionList(ctx, .none, tuple_init.slice(&t.ast)) orelse return null; | ||
| 1528 | |||
| 1529 | if (items.len == 0) { | ||
| 1530 | return .empty_tuple; | ||
| 1531 | } | ||
| 1532 | |||
| 1533 | return try t.addRef(.tuple_init, .{ | ||
| 1534 | .src_node = ctx.nodeOffset(node), | ||
| 1535 | .extra_index = try t.extra.append(t.gpa, Teg.TupleInit, .{ | ||
| 1536 | .type_hint = .none, | ||
| 1537 | .items = items, | ||
| 1538 | }), | ||
| 1539 | }); | ||
| 1540 | }, | ||
| 1541 | |||
| 1542 | .tuple_type => { | ||
| 1543 | const tuple_type = t.ast.nodeData(node).list; | ||
| 1544 | |||
| 1545 | const items = try t.transformExpressionList(ctx, .none, tuple_type.slice(&t.ast)) orelse return null; | ||
| 1546 | |||
| 1547 | return try t.addRef(.tuple_type, .{ | ||
| 1548 | .src_node = ctx.nodeOffset(node), | ||
| 1549 | .extra_index = try t.extra.append(t.gpa, Teg.TupleType, .{ | ||
| 1550 | .items = items, | ||
| 1551 | }), | ||
| 1552 | }); | ||
| 1553 | }, | ||
| 1554 | |||
| 1523 | inline .fn_decl, .fn_expr => |tag| { | 1555 | inline .fn_decl, .fn_expr => |tag| { |
| 1524 | const data = switch (tag) { | 1556 | const data = switch (tag) { |
| 1525 | .fn_decl => t.ast.fullFnDecl(node), | 1557 | .fn_decl => t.ast.fullFnDecl(node), |
src/core/js/Js.zig+1-1| ... | @@ -43,7 +43,7 @@ pub fn link( | ... | @@ -43,7 +43,7 @@ pub fn link( |
| 43 | entry_point: Comp.Decl.Index, | 43 | entry_point: Comp.Decl.Index, |
| 44 | writer: std.io.AnyWriter, | 44 | writer: std.io.AnyWriter, |
| 45 | ) !void { | 45 | ) !void { |
| 46 | var state = Link{ | 46 | var state: Link = .{ |
| 47 | .comp = comp, | 47 | .comp = comp, |
| 48 | .backend = backend, | 48 | .backend = backend, |
| 49 | }; | 49 | }; |
src/core/js/Link.zig+12-1| ... | @@ -309,7 +309,18 @@ fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytyp | ... | @@ -309,7 +309,18 @@ fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytyp |
| 309 | if (i != 0) | 309 | if (i != 0) |
| 310 | try writer.writeAll(","); | 310 | try writer.writeAll(","); |
| 311 | 311 | ||
| 312 | // TODO: handle special identifiers | 312 | try link.printValue(inner_value, .assignment, writer); |
| 313 | } | ||
| 314 | try writer.writeAll("]"); | ||
| 315 | }, | ||
| 316 | |||
| 317 | .tuple_value => |tuple_value| { | ||
| 318 | try writer.writeAll("["); | ||
| 319 | |||
| 320 | for (tuple_value.items, 0..) |inner_value, i| { | ||
| 321 | if (i != 0) | ||
| 322 | try writer.writeAll(","); | ||
| 323 | |||
| 313 | try link.printValue(inner_value, .assignment, writer); | 324 | try link.printValue(inner_value, .assignment, writer); |
| 314 | } | 325 | } |
| 315 | try writer.writeAll("]"); | 326 | try writer.writeAll("]"); |
src/core/js/NodeGen.zig+44-2| ... | @@ -283,6 +283,15 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { | ... | @@ -283,6 +283,15 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 283 | try c.markExpression(inst, node); | 283 | try c.markExpression(inst, node); |
| 284 | }, | 284 | }, |
| 285 | 285 | ||
| 286 | .field_tuple => { | ||
| 287 | const data: Air.FieldStruct = c.air.instData(.field_tuple, inst); | ||
| 288 | const node = try c.add(.index, .{ | ||
| 289 | .lhs = try c.fetchInstExpression(data.base), | ||
| 290 | .rhs = try c.makeValueInst(.{ .int32 = @intCast(data.field_index) }), | ||
| 291 | }); | ||
| 292 | try c.markExpression(inst, node); | ||
| 293 | }, | ||
| 294 | |||
| 286 | .array_len => { | 295 | .array_len => { |
| 287 | const data: Air.Un = c.air.instData(.array_len, inst); | 296 | const data: Air.Un = c.air.instData(.array_len, inst); |
| 288 | const node = try c.add(.field, .{ | 297 | const node = try c.add(.field, .{ |
| ... | @@ -459,9 +468,9 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { | ... | @@ -459,9 +468,9 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 459 | }, | 468 | }, |
| 460 | 469 | ||
| 461 | .array_init => { | 470 | .array_init => { |
| 462 | const struct_init: Air.ArrayInit = c.air.instData(.array_init, inst); | 471 | const array_init: Air.ArrayInit = c.air.instData(.array_init, inst); |
| 463 | 472 | ||
| 464 | const items = struct_init.list.get(&c.air).slice(&c.air); | 473 | const items = array_init.list.get(&c.air).slice(&c.air); |
| 465 | const store_offset = try c.extra.alloc(c.gpa, items.len); | 474 | const store_offset = try c.extra.alloc(c.gpa, items.len); |
| 466 | 475 | ||
| 467 | for ( | 476 | for ( |
| ... | @@ -478,6 +487,28 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { | ... | @@ -478,6 +487,28 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 478 | }); | 487 | }); |
| 479 | try c.markExpression(inst, node); | 488 | try c.markExpression(inst, node); |
| 480 | }, | 489 | }, |
| 490 | |||
| 491 | .tuple_init => { | ||
| 492 | const tuple_init: Air.TupleInit = c.air.instData(.tuple_init, inst); | ||
| 493 | |||
| 494 | const field_count = tuple_init.ty.toTupleType(c.ip).?.items.len; | ||
| 495 | const items = tuple_init.field_values; | ||
| 496 | const store_offset = try c.extra.alloc(c.gpa, field_count); | ||
| 497 | |||
| 498 | for ( | ||
| 499 | items..items + field_count, | ||
| 500 | store_offset.., | ||
| 501 | ) |in_i, expr_i| { | ||
| 502 | const in = try c.fetchExpression(@bitCast(c.air.extra.list.items[in_i])); | ||
| 503 | c.extra.list.items[expr_i] = @intFromEnum(in); | ||
| 504 | } | ||
| 505 | |||
| 506 | const node = try c.add(.array, .{ | ||
| 507 | .start = store_offset, | ||
| 508 | .len = @intCast(field_count), | ||
| 509 | }); | ||
| 510 | try c.markExpression(inst, node); | ||
| 511 | }, | ||
| 481 | } | 512 | } |
| 482 | } | 513 | } |
| 483 | 514 | ||
| ... | @@ -557,6 +588,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { | ... | @@ -557,6 +588,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { |
| 557 | .simple_type, | 588 | .simple_type, |
| 558 | .struct_type, | 589 | .struct_type, |
| 559 | .array_type, | 590 | .array_type, |
| 591 | .tuple_type, | ||
| 560 | => unreachable, // Type values are not allowed here | 592 | => unreachable, // Type values are not allowed here |
| 561 | 593 | ||
| 562 | .int32, | 594 | .int32, |
| ... | @@ -566,6 +598,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { | ... | @@ -566,6 +598,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { |
| 566 | .simple_value, | 598 | .simple_value, |
| 567 | .struct_value, | 599 | .struct_value, |
| 568 | .array_value, | 600 | .array_value, |
| 601 | .tuple_value, | ||
| 569 | => return try c.add(.plain_value, .{ .value = v }), | 602 | => return try c.add(.plain_value, .{ .value = v }), |
| 570 | 603 | ||
| 571 | .external_value => { | 604 | .external_value => { |
| ... | @@ -585,6 +618,13 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { | ... | @@ -585,6 +618,13 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { |
| 585 | } | 618 | } |
| 586 | } | 619 | } |
| 587 | 620 | ||
| 621 | fn makeValueInst(c: *NodeGen, k: InternPool.Key) !Node.Index { | ||
| 622 | return try c.valueToExpression(c.ip.get(c.gpa, k) catch |err| switch (err) { | ||
| 623 | error.TooManyValues => @panic("TODO: handle TooManyValues"), | ||
| 624 | else => |e| return e, | ||
| 625 | }); | ||
| 626 | } | ||
| 627 | |||
| 588 | fn addTemporary(c: *NodeGen) !Node.Index { | 628 | fn addTemporary(c: *NodeGen) !Node.Index { |
| 589 | const t = tracer.trace(@src()); | 629 | const t = tracer.trace(@src()); |
| 590 | defer t.end(); | 630 | defer t.end(); |
| ... | @@ -725,6 +765,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { | ... | @@ -725,6 +765,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { |
| 725 | .arg, | 765 | .arg, |
| 726 | .field_opaque, | 766 | .field_opaque, |
| 727 | .field_struct, | 767 | .field_struct, |
| 768 | .field_tuple, | ||
| 728 | .field_array, | 769 | .field_array, |
| 729 | .add, | 770 | .add, |
| 730 | .sub, | 771 | .sub, |
| ... | @@ -739,6 +780,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { | ... | @@ -739,6 +780,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { |
| 739 | .struct_init, | 780 | .struct_init, |
| 740 | .array_init, | 781 | .array_init, |
| 741 | .array_len, | 782 | .array_len, |
| 783 | .tuple_init, | ||
| 742 | => true, | 784 | => true, |
| 743 | 785 | ||
| 744 | // JavaScript variable declarations require statements. | 786 | // JavaScript variable declarations require statements. |
src/core/stack.zig+28-9| ... | @@ -15,7 +15,7 @@ pub fn Stack(comptime Base: type) type { | ... | @@ -15,7 +15,7 @@ pub fn Stack(comptime Base: type) type { |
| 15 | 15 | ||
| 16 | pub const Mark = struct { | 16 | pub const Mark = struct { |
| 17 | stack: *Self, | 17 | stack: *Self, |
| 18 | index: usize, | 18 | index: u32, |
| 19 | 19 | ||
| 20 | pub fn deinit(m: Mark) void { | 20 | pub fn deinit(m: Mark) void { |
| 21 | if (core.runtime_safety) | 21 | if (core.runtime_safety) |
| ... | @@ -23,8 +23,8 @@ pub fn Stack(comptime Base: type) type { | ... | @@ -23,8 +23,8 @@ pub fn Stack(comptime Base: type) type { |
| 23 | m.stack.list.shrinkRetainingCapacity(m.index); | 23 | m.stack.list.shrinkRetainingCapacity(m.index); |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | pub fn len(m: Mark) usize { | 26 | pub fn len(m: Mark) u32 { |
| 27 | return m.stack.list.items.len - m.index; | 27 | return @intCast(m.stack.list.items.len - m.index); |
| 28 | } | 28 | } |
| 29 | 29 | ||
| 30 | pub fn slice(m: Mark, comptime T: type) []T { | 30 | pub fn slice(m: Mark, comptime T: type) []T { |
| ... | @@ -39,7 +39,7 @@ pub fn Stack(comptime Base: type) type { | ... | @@ -39,7 +39,7 @@ pub fn Stack(comptime Base: type) type { |
| 39 | pub fn mark(stack: *Self) Mark { | 39 | pub fn mark(stack: *Self) Mark { |
| 40 | return .{ | 40 | return .{ |
| 41 | .stack = stack, | 41 | .stack = stack, |
| 42 | .index = stack.list.items.len, | 42 | .index = @intCast(stack.list.items.len), |
| 43 | }; | 43 | }; |
| 44 | } | 44 | } |
| 45 | 45 | ||
| ... | @@ -101,8 +101,8 @@ pub fn Stack(comptime Base: type) type { | ... | @@ -101,8 +101,8 @@ pub fn Stack(comptime Base: type) type { |
| 101 | 101 | ||
| 102 | return struct { | 102 | return struct { |
| 103 | stack: *Self, | 103 | stack: *Self, |
| 104 | starts: [fields.len]usize, | 104 | starts: [fields.len]u32, |
| 105 | lengths: [fields.len]usize, | 105 | lengths: [fields.len]u32, |
| 106 | 106 | ||
| 107 | fn Type(comptime field: FieldEnum) type { | 107 | fn Type(comptime field: FieldEnum) type { |
| 108 | return @field(types, @tagName(field)); | 108 | return @field(types, @tagName(field)); |
| ... | @@ -128,6 +128,25 @@ pub fn Stack(comptime Base: type) type { | ... | @@ -128,6 +128,25 @@ pub fn Stack(comptime Base: type) type { |
| 128 | std.mem.copyForwards(u32, dest, from); | 128 | std.mem.copyForwards(u32, dest, from); |
| 129 | r.stack.list.shrinkRetainingCapacity(start + from.len); | 129 | r.stack.list.shrinkRetainingCapacity(start + from.len); |
| 130 | } | 130 | } |
| 131 | |||
| 132 | pub fn index(r: @This()) u32 { | ||
| 133 | return r.starts[0]; | ||
| 134 | } | ||
| 135 | |||
| 136 | pub fn fromIndex( | ||
| 137 | stack: *Self, | ||
| 138 | start: u32, | ||
| 139 | lengths: [std.meta.fields(@TypeOf(types)).len]u32, | ||
| 140 | ) @This() { | ||
| 141 | var all_lengths: u32 = 0; | ||
| 142 | var starts: [std.meta.fields(@TypeOf(types)).len]u32 = undefined; | ||
| 143 | inline for (lengths, &starts) |len, *slice_start| { | ||
| 144 | slice_start.* = start + all_lengths; | ||
| 145 | all_lengths += len; | ||
| 146 | } | ||
| 147 | stack.list.items.len += all_lengths; | ||
| 148 | return .{ .stack = stack, .starts = starts, .lengths = lengths }; | ||
| 149 | } | ||
| 131 | }; | 150 | }; |
| 132 | } | 151 | } |
| 133 | 152 | ||
| ... | @@ -137,13 +156,13 @@ pub fn Stack(comptime Base: type) type { | ... | @@ -137,13 +156,13 @@ pub fn Stack(comptime Base: type) type { |
| 137 | stack: *Self, | 156 | stack: *Self, |
| 138 | allocator: Allocator, | 157 | allocator: Allocator, |
| 139 | comptime types: anytype, | 158 | comptime types: anytype, |
| 140 | lengths: [std.meta.fields(@TypeOf(types)).len]usize, | 159 | lengths: [std.meta.fields(@TypeOf(types)).len]u32, |
| 141 | ) !ReserveMulti(types) { | 160 | ) !ReserveMulti(types) { |
| 142 | var all_lengths: usize = 0; | 161 | var all_lengths: usize = 0; |
| 143 | var starts: [std.meta.fields(@TypeOf(types)).len]usize = undefined; | 162 | var starts: [std.meta.fields(@TypeOf(types)).len]u32 = undefined; |
| 144 | const start = stack.list.items.len; | 163 | const start = stack.list.items.len; |
| 145 | inline for (lengths, &starts) |len, *slice_start| { | 164 | inline for (lengths, &starts) |len, *slice_start| { |
| 146 | slice_start.* = start + all_lengths; | 165 | slice_start.* = @intCast(start + all_lengths); |
| 147 | all_lengths += len; | 166 | all_lengths += len; |
| 148 | } | 167 | } |
| 149 | try stack.list.ensureUnusedCapacity(allocator, all_lengths); | 168 | try stack.list.ensureUnusedCapacity(allocator, all_lengths); |
src/main.zig+26-95| ... | @@ -4,9 +4,18 @@ | ... | @@ -4,9 +4,18 @@ |
| 4 | //! for example, test the `core` module's compilation features without | 4 | //! for example, test the `core` module's compilation features without |
| 5 | //! needing to compile the formatter. | 5 | //! needing to compile the formatter. |
| 6 | 6 | ||
| 7 | const have_debug_features = builtin.mode == .Debug; | ||
| 8 | const config = @import("config"); | 7 | const config = @import("config"); |
| 9 | 8 | ||
| 9 | const arg_config: Cli = .{ | ||
| 10 | .options = .{ | ||
| 11 | .{ .short = 'i', .name = "--dump-ir", .desc = "Dump intermediate representations" }, | ||
| 12 | .{ .name = "--dump-state", .desc = "Dump compiler state to JSON", .kind = .string }, | ||
| 13 | } ++ Cli.Option.optional(config.have_embedded_quickjs, &.{ | ||
| 14 | .{ .name = "--run", .desc = "Execute with QuickJS" }, | ||
| 15 | }), | ||
| 16 | .positionals = .single, | ||
| 17 | }; | ||
| 18 | |||
| 10 | pub fn main() !u8 { | 19 | pub fn main() !u8 { |
| 11 | try tracer.init(); | 20 | try tracer.init(); |
| 12 | defer tracer.deinit(); | 21 | defer tracer.deinit(); |
| ... | @@ -24,8 +33,7 @@ pub fn main() !u8 { | ... | @@ -24,8 +33,7 @@ pub fn main() !u8 { |
| 24 | }){} | 33 | }){} |
| 25 | else | 34 | else |
| 26 | std.heap.ArenaAllocator.init(std.heap.page_allocator); | 35 | std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| 27 | var no_leak_checks = false; | 36 | defer _ = if (use_gpa) gpa.deinit(); |
| 28 | defer _ = if (use_gpa and !no_leak_checks) gpa.deinit(); | ||
| 29 | const alloc = gpa.allocator(); | 37 | const alloc = gpa.allocator(); |
| 30 | 38 | ||
| 31 | // Current working directory is required for CLI resolution. | 39 | // Current working directory is required for CLI resolution. |
| ... | @@ -33,95 +41,17 @@ pub fn main() !u8 { | ... | @@ -33,95 +41,17 @@ pub fn main() !u8 { |
| 33 | const cwd = try std.posix.getcwd(&cwd_buf); | 41 | const cwd = try std.posix.getcwd(&cwd_buf); |
| 34 | 42 | ||
| 35 | // Command line argument parsing. | 43 | // Command line argument parsing. |
| 36 | var maybe_file_path: ?[]const u8 = null; | 44 | const program_name = "zig build compiler --"; |
| 37 | defer if (maybe_file_path) |path| alloc.free(path); | 45 | _, const args = try Cli.programArgsAlloc(alloc, program_name); |
| 38 | var exe_name: ?[]const u8 = null; | 46 | defer Cli.programArgsFree(alloc, args); |
| 39 | defer if (exe_name) |path| alloc.free(path); | 47 | const opts = arg_config.parse(program_name, args); |
| 40 | var dump_ir = false; | 48 | |
| 41 | var dump_state: ?[]const u8 = null; | 49 | const file_path = try std.fs.path.resolve(alloc, &.{ |
| 42 | var run = false; | 50 | cwd, |
| 43 | var has_errors = false; | 51 | opts.positionals orelse |
| 44 | { | 52 | arg_config.showHelpAndExit(program_name), |
| 45 | var args = try std.process.argsWithAllocator(alloc); | 53 | }); |
| 46 | defer args.deinit(); | 54 | defer alloc.free(file_path); |
| 47 | |||
| 48 | exe_name = try alloc.dupe(u8, std.fs.path.basename(args.next() orelse "cli")); | ||
| 49 | |||
| 50 | while (args.next()) |arg| { | ||
| 51 | if (std.mem.startsWith(u8, arg, "-")) { | ||
| 52 | if (have_debug_features) { | ||
| 53 | if (std.mem.eql(u8, arg, "--report-trace")) { | ||
| 54 | core.Report.debug_show_report_trace = true; | ||
| 55 | continue; | ||
| 56 | } | ||
| 57 | if (std.mem.eql(u8, arg, "--no-leak-checks")) { | ||
| 58 | no_leak_checks = true; | ||
| 59 | continue; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | if (std.mem.eql(u8, arg, "--dump-ir")) { | ||
| 64 | dump_ir = true; | ||
| 65 | continue; | ||
| 66 | } | ||
| 67 | |||
| 68 | if (std.mem.startsWith(u8, arg, "--dump-state=")) { | ||
| 69 | dump_state = arg["--dump-state=".len..]; | ||
| 70 | continue; | ||
| 71 | } | ||
| 72 | |||
| 73 | if (config.have_embedded_quickjs and std.mem.eql(u8, arg, "--run")) { | ||
| 74 | run = true; | ||
| 75 | continue; | ||
| 76 | } | ||
| 77 | |||
| 78 | std.debug.print("error: unknown flag '{s}'\n", .{arg}); | ||
| 79 | has_errors = true; | ||
| 80 | break; | ||
| 81 | } | ||
| 82 | |||
| 83 | if (maybe_file_path == null) { | ||
| 84 | maybe_file_path = try std.fs.path.resolve(alloc, &.{ cwd, arg }); | ||
| 85 | } else { | ||
| 86 | std.debug.print("error: multiple files specified\n", .{}); | ||
| 87 | maybe_file_path = null; | ||
| 88 | break; | ||
| 89 | } | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | if (maybe_file_path == null) has_errors = true; | ||
| 94 | if (has_errors) { | ||
| 95 | std.debug.print("Usage: {s} [flags] <input file>\n", .{exe_name.?}); | ||
| 96 | { | ||
| 97 | std.debug.print( | ||
| 98 | // | 60 chars wrap point| | ||
| 99 | \\ | ||
| 100 | \\Flags: | ||
| 101 | \\ --dump-ir Dump intermediate representations | ||
| 102 | \\ --dump-state=<path> Dump compiler state to JSON | ||
| 103 | \\ | ||
| 104 | , .{}); | ||
| 105 | } | ||
| 106 | if (config.have_embedded_quickjs) { | ||
| 107 | std.debug.print( | ||
| 108 | \\ --run Run program using QuickJS | ||
| 109 | \\ | ||
| 110 | , .{}); | ||
| 111 | } | ||
| 112 | if (have_debug_features) { | ||
| 113 | std.debug.print( | ||
| 114 | \\ | ||
| 115 | \\Debug Mode Flags: | ||
| 116 | \\ --report-trace Print stack traces per error | ||
| 117 | \\ --no-leak-checks | ||
| 118 | \\ | ||
| 119 | , .{}); | ||
| 120 | } | ||
| 121 | std.debug.print("\nLanguage SDK v" ++ config.version_string ++ "\n", .{}); | ||
| 122 | return 1; | ||
| 123 | } | ||
| 124 | const file_path = maybe_file_path.?; | ||
| 125 | 55 | ||
| 126 | // Execute compiler | 56 | // Execute compiler |
| 127 | { | 57 | { |
| ... | @@ -130,13 +60,13 @@ pub fn main() !u8 { | ... | @@ -130,13 +60,13 @@ pub fn main() !u8 { |
| 130 | var comp = try core.Comp.init(.{ | 60 | var comp = try core.Comp.init(.{ |
| 131 | .gpa = tracer.tracingAllocator(&alloc), | 61 | .gpa = tracer.tracingAllocator(&alloc), |
| 132 | .options = .{ | 62 | .options = .{ |
| 133 | .dump_ir = dump_ir, | 63 | .dump_ir = opts.dump_ir orelse false, |
| 134 | }, | 64 | }, |
| 135 | .backend = .{ .js = &js_backend }, | 65 | .backend = .{ .js = &js_backend }, |
| 136 | }); | 66 | }); |
| 137 | defer comp.deinit(); | 67 | defer comp.deinit(); |
| 138 | 68 | ||
| 139 | defer if (dump_state) |path| { | 69 | defer if (opts.dump_state) |path| { |
| 140 | dumpState(&comp, path) catch |err| { | 70 | dumpState(&comp, path) catch |err| { |
| 141 | std.debug.print("Failed to dump compilation state: {}", .{err}); | 71 | std.debug.print("Failed to dump compilation state: {}", .{err}); |
| 142 | }; | 72 | }; |
| ... | @@ -173,7 +103,7 @@ pub fn main() !u8 { | ... | @@ -173,7 +103,7 @@ pub fn main() !u8 { |
| 173 | else => return err, | 103 | else => return err, |
| 174 | }; | 104 | }; |
| 175 | 105 | ||
| 176 | if (config.have_embedded_quickjs and run) { | 106 | if (config.have_embedded_quickjs and (opts.run orelse false)) { |
| 177 | // Execute with QuickJS | 107 | // Execute with QuickJS |
| 178 | var out: std.ArrayListUnmanaged(u8) = .{}; | 108 | var out: std.ArrayListUnmanaged(u8) = .{}; |
| 179 | defer out.deinit(alloc); | 109 | defer out.deinit(alloc); |
| ... | @@ -244,6 +174,7 @@ pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)) | ... | @@ -244,6 +174,7 @@ pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)) |
| 244 | 174 | ||
| 245 | const std = @import("std"); | 175 | const std = @import("std"); |
| 246 | const builtin = @import("builtin"); | 176 | const builtin = @import("builtin"); |
| 177 | const Cli = @import("Cli.zig"); | ||
| 247 | 178 | ||
| 248 | fn dumpState(comp: *const Comp, dump_path: []const u8) !void { | 179 | fn dumpState(comp: *const Comp, dump_path: []const u8) !void { |
| 249 | const file = try std.fs.cwd().createFile(dump_path, .{}); | 180 | const file = try std.fs.cwd().createFile(dump_path, .{}); |
test/behavior/tuple created+15| ... | @@ -0,0 +1,15 @@ | ||
| 1 | let assertEqual = @extern("assertEqual"); | ||
| 2 | |||
| 3 | fn hello() -> tuple (int, float) { | ||
| 4 | let x: tuple (int, float) = (5, 2.5); | ||
| 5 | x | ||
| 6 | } | ||
| 7 | |||
| 8 | fn main { | ||
| 9 | let x = hello(); | ||
| 10 | assertEqual(x.0, i(5)); | ||
| 11 | assertEqual(x.1, f(2.5)); | ||
| 12 | } | ||
| 13 | |||
| 14 | fn f(a: float) -> float { a } | ||
| 15 | fn i(a: int) -> int { a } | ||