| 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 | 10 | - feat: add array index syntax via `x[1]` and `x.1` |
| 11 | 11 | - feat: add `len` property to arrays |
| 12 | 12 | - feat: rename `noreturn` to `never` |
| 13 | - feat: allow instantiating `@extern` | |
| 13 | - feat: allow instantiating `@extern` at compile time | |
| 14 | - feat: implement tuples | |
| 14 | 15 | - fix: crash when printing unused struct declaration error |
| 15 | 16 | - fix: provide array result type to sema |
| 16 | 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 | 8 | positionals: Positionals = .none, |
| 9 | 9 | help: HelpConfig = .{}, |
| 10 | 10 | |
| 11 | const Option = struct { | |
| 11 | pub const Option = struct { | |
| 12 | 12 | /// Should be formatted as the flag name: '--dump-ir' |
| 13 | 13 | name: []const u8, |
| 14 | 14 | /// Displayed in generated help menu |
| ... | ... | @@ -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 | 39 | const help: Option = .{ |
| 36 | 40 | .name = "--help", |
| 37 | 41 | .short = 'h', |
| ... | ... | @@ -631,8 +635,7 @@ pub fn main() !void { |
| 631 | 635 | const program_name, const args = try Cli.programArgsAlloc(alloc); |
| 632 | 636 | defer Cli.programArgsFree(alloc, args); |
| 633 | 637 | |
| 634 | const parsed = config.parse(args[1..]) catch | |
| 635 | config.showHelpAndExit(program_name); | |
| 638 | const parsed = config.parse(program_name, args[1..]); | |
| 636 | 639 | |
| 637 | 640 | std.debug.print("--flag: {any}\n", .{parsed.flag}); |
| 638 | 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 | 25 | const program_name, const raw_args = try Cli.programArgsAlloc(alloc, "transform"); |
| 26 | 26 | defer Cli.programArgsFree(alloc, raw_args); |
| 27 | 27 | |
| 28 | const args = arg_config.parse(raw_args) catch | |
| 29 | arg_config.showHelpAndExit(program_name); | |
| 28 | const args = arg_config.parse(program_name, raw_args); | |
| 30 | 29 | |
| 31 | 30 | const file_path = args.positionals orelse |
| 32 | 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 | 130 | try tty.setColor(w, .reset); |
| 131 | 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 | 143 | inline .call, .js_new => |t| { |
| 134 | 144 | const call: Air.Call = air.instData(t, inst); |
| 135 | 145 | const call_data = call.call_data.get(air); |
| ... | ... | @@ -182,6 +192,15 @@ pub const State = struct { |
| 182 | 192 | try s.dumpRefList(w, array_init.list.get(air).slice(air), '[', ']'); |
| 183 | 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 | 252 | try tty.setColor(w, .blue); |
| 234 | 253 | try w.writeAll(") -> "); |
| 235 | 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 | 260 | inline .float, .int32, .comptime_float => |data, tag| { |
| 239 | 261 | try tty.setColor(w, .bright_black); |
| ... | ... | @@ -379,6 +401,22 @@ pub const State = struct { |
| 379 | 401 | } |
| 380 | 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 | 25 | |
| 26 | 26 | struct_init, |
| 27 | 27 | array_init, |
| 28 | tuple_init, | |
| 28 | 29 | |
| 29 | 30 | field_opaque, |
| 30 | 31 | field_struct, |
| 32 | field_tuple, | |
| 31 | 33 | field_array, |
| 32 | 34 | |
| 33 | 35 | array_len, |
| ... | ... | @@ -58,7 +60,8 @@ pub const Inst = struct { |
| 58 | 60 | .store => Store, |
| 59 | 61 | .struct_init => StructInit, |
| 60 | 62 | .array_init => ArrayInit, |
| 61 | .field_struct => FieldStruct, | |
| 63 | .tuple_init => TupleInit, | |
| 64 | .field_struct, .field_tuple => FieldStruct, | |
| 62 | 65 | .field_opaque, |
| 63 | 66 | .field_array, |
| 64 | 67 | .add, |
| ... | ... | @@ -286,6 +289,18 @@ pub const ArrayInit = struct { |
| 286 | 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 | 304 | pub const FieldStruct = struct { |
| 290 | 305 | base: Inst.Index, |
| 291 | 306 | field_index: u32, |
| ... | ... | @@ -342,6 +357,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In |
| 342 | 357 | .@"if" => air.instData(.@"if", index).data.get(air).union_ty, |
| 343 | 358 | .struct_init => air.instData(.struct_init, index).ty, |
| 344 | 359 | .array_init => air.instData(.array_init, index).ty, |
| 360 | .tuple_init => air.instData(.tuple_init, index).ty, | |
| 345 | 361 | .arg => air.instData(.arg, index).type, |
| 346 | 362 | |
| 347 | 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 | 388 | .call => { |
| 373 | 389 | const base_type = air.typeOf(air.instData(.call, index).base, ip); |
| 374 | 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 | 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 | 409 | .toArrayType(ip) orelse unreachable; // field_array demands array type |
| 392 | 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 | 126 | .struct_decl, |
| 127 | 127 | => try dumpNodeList(writer, state, list, '{', '}'), |
| 128 | 128 | .builtin_call, |
| 129 | .tuple_init, | |
| 130 | .tuple_type, | |
| 129 | 131 | => try dumpNodeList(writer, state, list, '(', ')'), |
| 130 | 132 | .array_init, |
| 131 | 133 | => try dumpNodeList(writer, state, list, '[', ']'), |
src/core/InternPool.zig+146-12| ... | ... | @@ -101,6 +101,8 @@ pub const Tag = enum(u8) { |
| 101 | 101 | struct_type, |
| 102 | 102 | /// An array type |
| 103 | 103 | array_type, |
| 104 | /// A tuple type | |
| 105 | tuple_type, | |
| 104 | 106 | /// A type that can be represented with only an enum tag. |
| 105 | 107 | simple_type, |
| 106 | 108 | |
| ... | ... | @@ -129,6 +131,8 @@ pub const Tag = enum(u8) { |
| 129 | 131 | external_value, |
| 130 | 132 | /// A value masked as "opaque" type |
| 131 | 133 | opaque_value, |
| 134 | /// A tuple value | |
| 135 | tuple_value, | |
| 132 | 136 | |
| 133 | 137 | pub fn Data(comptime tag: Tag) type { |
| 134 | 138 | return switch (tag) { |
| ... | ... | @@ -154,13 +158,15 @@ pub const Tag = enum(u8) { |
| 154 | 158 | => unreachable, // does not have a payload |
| 155 | 159 | .float, .comptime_float => Float, |
| 156 | 160 | .func => FuncDecl, |
| 157 | .function_type => TypeFunction, | |
| 161 | .function_type => FunctionType, | |
| 158 | 162 | .string => String.WithLen, |
| 159 | 163 | .struct_value => StructValue, |
| 160 | 164 | .struct_type => StructType, |
| 161 | 165 | .array_type => ArrayType, |
| 162 | 166 | .array_value => ArrayValue, |
| 163 | 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 | 180 | |
| 175 | 181 | /// Trailing: |
| 176 | 182 | /// 0. param_type: Index to param type. Repeated .params_len |
| 177 | pub const TypeFunction = struct { | |
| 183 | pub const FunctionType = struct { | |
| 178 | 184 | params_len: u32, |
| 179 | return_type: Type, | |
| 185 | return_type: Type.Optional, | |
| 180 | 186 | }; |
| 181 | 187 | |
| 182 | 188 | /// Trailing: |
| ... | ... | @@ -199,6 +205,20 @@ pub const Tag = enum(u8) { |
| 199 | 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 | 222 | /// Trailing: |
| 203 | 223 | /// 0. value: Value. Repeated .ty.structInfo().field_len |
| 204 | 224 | pub const StructValue = struct { |
| ... | ... | @@ -216,9 +236,9 @@ pub const Tag = enum(u8) { |
| 216 | 236 | /// This is an index into .items |
| 217 | 237 | pub const Value = enum(u32) { |
| 218 | 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 | 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 | 243 | opaque_type, |
| 224 | 244 | bool_type, |
| ... | ... | @@ -230,6 +250,7 @@ pub const Value = enum(u32) { |
| 230 | 250 | numeric_literal_type, |
| 231 | 251 | numeric_literal_array_type, |
| 232 | 252 | never_array_type, |
| 253 | empty_tuple_type, | |
| 233 | 254 | |
| 234 | 255 | void, |
| 235 | 256 | bool_true, |
| ... | ... | @@ -239,6 +260,7 @@ pub const Value = enum(u32) { |
| 239 | 260 | nan, |
| 240 | 261 | empty_string, |
| 241 | 262 | empty_array, |
| 263 | empty_tuple, | |
| 242 | 264 | |
| 243 | 265 | none = std.math.maxInt(u32), |
| 244 | 266 | _, |
| ... | ... | @@ -316,6 +338,7 @@ pub const Value = enum(u32) { |
| 316 | 338 | .simple_type, |
| 317 | 339 | .struct_type, |
| 318 | 340 | .array_type, |
| 341 | .tuple_type, | |
| 319 | 342 | => .type, |
| 320 | 343 | |
| 321 | 344 | .simple_value => switch (value.valueData(ip, .simple_value)) { |
| ... | ... | @@ -332,6 +355,7 @@ pub const Value = enum(u32) { |
| 332 | 355 | .struct_value => value.toKey(ip).struct_value.ty, |
| 333 | 356 | .array_value => value.toKey(ip).array_value.type, |
| 334 | 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 | 369 | .struct_type, |
| 346 | 370 | .simple_type, |
| 347 | 371 | .array_type, |
| 372 | .tuple_type, | |
| 348 | 373 | => @enumFromInt(@intFromEnum(value)), |
| 349 | 374 | |
| 350 | 375 | // exception: void is a value AND a type |
| ... | ... | @@ -395,6 +420,7 @@ pub const Type = enum(u32) { |
| 395 | 420 | numeric_literal = @intFromEnum(Value.numeric_literal_type), |
| 396 | 421 | numeric_literal_array = @intFromEnum(Value.numeric_literal_array_type), |
| 397 | 422 | never_array = @intFromEnum(Value.never_array_type), |
| 423 | empty_tuple = @intFromEnum(Value.empty_tuple_type), | |
| 398 | 424 | void = @intFromEnum(Value.void), |
| 399 | 425 | _, |
| 400 | 426 | |
| ... | ... | @@ -404,6 +430,7 @@ pub const Type = enum(u32) { |
| 404 | 430 | struct_type = @intFromEnum(InternPool.Tag.struct_type), |
| 405 | 431 | array_type = @intFromEnum(InternPool.Tag.array_type), |
| 406 | 432 | simple_type = @intFromEnum(InternPool.Tag.simple_type), |
| 433 | tuple_type = @intFromEnum(InternPool.Tag.tuple_type), | |
| 407 | 434 | }; |
| 408 | 435 | |
| 409 | 436 | /// An array of `Index` existing within the `extra` array. |
| ... | ... | @@ -446,6 +473,11 @@ pub const Type = enum(u32) { |
| 446 | 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 | 481 | /// If it is possible to represent this type at runtime. Some types do not |
| 450 | 482 | /// have a defined representation, such as structs (to allow field renaming, unwrapping). |
| 451 | 483 | pub fn hasPossibleRepresentation(ty: Type, ip: *const InternPool) bool { |
| ... | ... | @@ -466,6 +498,15 @@ pub const Type = enum(u32) { |
| 466 | 498 | .struct_type, |
| 467 | 499 | .function_type, |
| 468 | 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 | 510 | .array_type => ty.toArrayType(ip).?.item_type.hasPossibleRepresentation(ip), |
| 470 | 511 | }, |
| 471 | 512 | }; |
| ... | ... | @@ -490,6 +531,7 @@ pub const Type = enum(u32) { |
| 490 | 531 | .simple_type => unreachable, // all simple types are already printed |
| 491 | 532 | .struct_type, |
| 492 | 533 | .function_type, |
| 534 | .tuple_type, | |
| 493 | 535 | => false, |
| 494 | 536 | .array_type => ty.toArrayType(ip).?.item_type.hasDefinedRepresentation(ip), |
| 495 | 537 | }, |
| ... | ... | @@ -536,7 +578,10 @@ pub const Type = enum(u32) { |
| 536 | 578 | try writer.print("{}", .{param.displayName(ip)}); |
| 537 | 579 | } |
| 538 | 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 | 586 | .struct_type => { |
| 542 | 587 | try writer.writeAll("[struct]"); |
| ... | ... | @@ -546,8 +591,29 @@ pub const Type = enum(u32) { |
| 546 | 591 | const array_type = ty.toArrayType(ip).?; |
| 547 | 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 | 619 | pub const Item = struct { |
| ... | ... | @@ -584,6 +650,7 @@ pub const static_keys = [_]Key{ |
| 584 | 650 | SimpleType.numeric_literal.key(), |
| 585 | 651 | .{ .array_type = .{ .item_type = Type.numeric_literal } }, |
| 586 | 652 | .{ .array_type = .{ .item_type = Type.never } }, |
| 653 | .{ .tuple_type = .{ .items = &.{} } }, | |
| 587 | 654 | |
| 588 | 655 | SimpleValue.void.key(), |
| 589 | 656 | SimpleValue.true.key(), |
| ... | ... | @@ -596,6 +663,10 @@ pub const static_keys = [_]Key{ |
| 596 | 663 | .items = &.{}, |
| 597 | 664 | } }, |
| 598 | 665 | .{ .string = "" }, |
| 666 | .{ .tuple_value = .{ | |
| 667 | .type = .empty_tuple, | |
| 668 | .items = &.{}, | |
| 669 | } }, | |
| 599 | 670 | }; |
| 600 | 671 | |
| 601 | 672 | /// State that is mutable during semantic analysis. This data is not used for |
| ... | ... | @@ -876,11 +947,13 @@ pub const Key = union(enum) { |
| 876 | 947 | func: Func, |
| 877 | 948 | array_value: ArrayValue, |
| 878 | 949 | opaque_value: Value, |
| 950 | tuple_value: TupleValue, | |
| 879 | 951 | // Types |
| 880 | 952 | simple_type: SimpleType, |
| 881 | 953 | function_type: FunctionType, |
| 882 | 954 | struct_type: StructType, |
| 883 | 955 | array_type: ArrayType, |
| 956 | tuple_type: TupleType, | |
| 884 | 957 | external_value: ExternalValue, |
| 885 | 958 | |
| 886 | 959 | const Adapter = struct { |
| ... | ... | @@ -899,7 +972,10 @@ pub const Key = union(enum) { |
| 899 | 972 | |
| 900 | 973 | pub const FunctionType = struct { |
| 901 | 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 | 980 | pub fn hash(self: FunctionType, hasher: *Hash) void { |
| 905 | 981 | const t = tracer.trace(@src()); |
| ... | ... | @@ -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 | 1033 | pub const ExternalValue = struct { |
| 950 | 1034 | /// A list of strings to generate the property access |
| 951 | 1035 | /// so `console.log` would be .{ "console", "log" } |
| ... | ... | @@ -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 | 1065 | pub const Func = struct { |
| 971 | 1066 | ty: Type, |
| 972 | 1067 | owner_decl: DeclIndex, |
| ... | ... | @@ -1008,6 +1103,8 @@ pub const Key = union(enum) { |
| 1008 | 1103 | .array_value, |
| 1009 | 1104 | .array_type, |
| 1010 | 1105 | .external_value, |
| 1106 | .tuple_type, | |
| 1107 | .tuple_value, | |
| 1011 | 1108 | => |x| { |
| 1012 | 1109 | var hasher = Hash.init(seed); |
| 1013 | 1110 | x.hash(&hasher); |
| ... | ... | @@ -1047,7 +1144,7 @@ pub const Key = union(enum) { |
| 1047 | 1144 | return false; |
| 1048 | 1145 | }, |
| 1049 | 1146 | .struct_type => { |
| 1050 | return false; // structs are nomial | |
| 1147 | return false; // structs are nominal | |
| 1051 | 1148 | }, |
| 1052 | 1149 | .struct_value => |a_info| { |
| 1053 | 1150 | const b_info = b.struct_value; |
| ... | ... | @@ -1066,6 +1163,15 @@ pub const Key = union(enum) { |
| 1066 | 1163 | const b_info = b.external_value; |
| 1067 | 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 | 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 | 1510 | .function_type => |f| brk: { |
| 1392 | 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 | 1514 | .params_len = @intCast(f.param_types.len), |
| 1396 | .return_type = f.return_type, | |
| 1515 | .return_type = .init(f.return_type), | |
| 1397 | 1516 | }, f.param_types.len); |
| 1398 | 1517 | defer trail.end(); |
| 1399 | 1518 | trail.writeSlice(f.param_types); |
| ... | ... | @@ -1439,6 +1558,21 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { |
| 1439 | 1558 | trail.writeSlice(s.key); |
| 1440 | 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 | 1577 | ip.items.appendAssumeCapacity(item); |
| 1444 | 1578 | const index: Value = @enumFromInt(ip.items.len - 1); |
| ... | ... | @@ -1451,12 +1585,12 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { |
| 1451 | 1585 | |
| 1452 | 1586 | fn extraTypeFunction( |
| 1453 | 1587 | ip: *const InternPool, |
| 1454 | extra_index: ExtraIndex(Tag.TypeFunction), | |
| 1588 | extra_index: ExtraIndex(Tag.FunctionType), | |
| 1455 | 1589 | ) Key.FunctionType { |
| 1456 | 1590 | const function_type, var trail = extra_index.getTrail(ip); |
| 1457 | 1591 | const param_types = trail.readTypeSlice(function_type.params_len); |
| 1458 | 1592 | const result: Key.FunctionType = .{ |
| 1459 | .return_type = function_type.return_type, | |
| 1593 | .return_type = function_type.return_type.unwrap(), | |
| 1460 | 1594 | .param_types = param_types.get(ip), |
| 1461 | 1595 | }; |
| 1462 | 1596 | return result; |
src/core/Parse.zig+296-258| ... | ... | @@ -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 | 313 | /// When error.SyntaxError is returned, `p.err` is set to this as well as `p.pos` |
| 108 | 314 | /// When printing these, token list and Ast is not actually available, so full tokens |
| 109 | 315 | /// are stored in the errors. |
| 110 | 316 | pub const AstError = union(enum) { |
| 111 | // /// Failure during tokenization | |
| 112 | // tokenization: struct { | |
| 113 | // err: Tokenizer.TokenizeError, | |
| 114 | // byte: ByteOffset, | |
| 115 | // }, | |
| 116 | 317 | /// Unexpected data, expected a token. |
| 117 | 318 | unexpected_token: struct { |
| 118 | 319 | expected: Token.Tag, |
| ... | ... | @@ -134,23 +335,6 @@ pub const AstError = union(enum) { |
| 134 | 335 | |
| 135 | 336 | pub fn build(err: AstError, ctx: *Report.MsgContext) !void { |
| 136 | 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 | 338 | .unexpected_token => |data| { |
| 155 | 339 | try ctx.msg(.{ .token_stored = data.got }, "expected {s}, but found {s}", .{ |
| 156 | 340 | data.expected.description(), |
| ... | ... | @@ -230,7 +414,8 @@ fn parseModuleMembers(p: *Parse) Error!SubList { |
| 230 | 414 | .kw_pub, |
| 231 | 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 | 420 | .id => if (p.isContextualKeyword("test")) { |
| 236 | 421 | try p.scratch.append(p.gpa, try p.parseTestDecl()); |
| ... | ... | @@ -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 | 480 | fn parseFnDecl(p: *Parse) Error!NodeIndex { |
| 304 | 481 | const trace = t.trace(@src()); |
| 305 | 482 | defer trace.end(); |
| ... | ... | @@ -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 | 519 | fn parseParamList(p: *Parse, types: TypePresence) Error!SubList { |
| 345 | 520 | const trace = t.trace(@src()); |
| 346 | 521 | defer trace.end(); |
| ... | ... | @@ -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 | 569 | const trace = t.trace(@src()); |
| 395 | 570 | defer trace.end(); |
| 396 | 571 | |
| 397 | 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 | 579 | _ = try p.expect(.start_brace); |
| 402 | 580 | |
| ... | ... | @@ -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 | 612 | fn parseTestDecl(p: *Parse) Error!NodeIndex { |
| 413 | 613 | const trace = t.trace(@src()); |
| 414 | 614 | defer trace.end(); |
| ... | ... | @@ -447,9 +647,7 @@ fn parseStructFieldList(p: *Parse) Error!SubList { |
| 447 | 647 | try p.scratch.append(p.gpa, try p.parseStructField()); |
| 448 | 648 | |
| 449 | 649 | switch (p.currentTag()) { |
| 450 | .comma => { | |
| 451 | _ = p.nextToken(); | |
| 452 | }, | |
| 650 | .comma => _ = p.nextToken(), | |
| 453 | 651 | .end_brace => break, |
| 454 | 652 | else => return p.failUnexpectedToken(.struct_field), |
| 455 | 653 | } |
| ... | ... | @@ -578,13 +776,13 @@ fn parseVarDecl(p: *Parse) Error!NodeIndex { |
| 578 | 776 | _ = try p.expect(.id); |
| 579 | 777 | |
| 580 | 778 | const data_type: OptionalNodeIndex = if (p.eatToken(.colon)) |_| |
| 581 | optionalNodeIndex(try p.tryParseExpression(.expression)) | |
| 779 | optionalNodeIndex(try p.parseExpressionOrFail(.expression)) | |
| 582 | 780 | else |
| 583 | 781 | .none; |
| 584 | 782 | |
| 585 | 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 | 787 | _ = try p.expect(.semi); |
| 590 | 788 | |
| ... | ... | @@ -619,204 +817,13 @@ fn isExpressionStart(p: *Parse) bool { |
| 619 | 817 | .string, |
| 620 | 818 | .tilde, |
| 621 | 819 | .kw_fn, |
| 820 | .kw_struct, | |
| 821 | .kw_tuple, | |
| 622 | 822 | => true, |
| 623 | 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 | 827 | fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 821 | 828 | const trace = t.trace(@src()); |
| 822 | 829 | defer trace.end(); |
| ... | ... | @@ -831,8 +838,13 @@ fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 831 | 838 | |
| 832 | 839 | // These unary operators are greedy, so handle them first. |
| 833 | 840 | switch (p.currentTag()) { |
| 834 | .kw_return => return p.parseReturn(), | |
| 835 | .kw_break => return p.parseBreak(), | |
| 841 | .kw_return => return p.parseReturn(mode), | |
| 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 | 848 | else => {}, |
| 837 | 849 | } |
| 838 | 850 | |
| ... | ... | @@ -846,7 +858,7 @@ fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 846 | 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 | 862 | return if (p.isExpressionStart()) |
| 851 | 863 | p.parseExpression(mode) |
| 852 | 864 | else |
| ... | ... | @@ -858,7 +870,6 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 858 | 870 | defer trace.end(); |
| 859 | 871 | |
| 860 | 872 | var expr = switch (p.currentTag()) { |
| 861 | // Literals | |
| 862 | 873 | .id => if (p.nextTag() == .colon) |
| 863 | 874 | try p.parseLabelExpression() |
| 864 | 875 | else |
| ... | ... | @@ -867,24 +878,27 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 867 | 878 | .number_float => try p.blankNode(.number_float), |
| 868 | 879 | .string => try p.blankNode(.string), |
| 869 | 880 | |
| 870 | // Complex expressions | |
| 871 | 881 | .builtin_id => try p.parseBuiltinCall(), |
| 872 | .dot => try p.parseAnonInstantiation(), | |
| 873 | .kw_break => try p.parseBreak(), | |
| 882 | .dot => try p.parseAnonStructInit(), | |
| 874 | 883 | .kw_fn => try p.parseAnonFunction(), |
| 875 | 884 | .kw_if => try p.parseIf(), |
| 876 | .kw_return => try p.parseReturn(), | |
| 885 | .kw_tuple => try p.parseTupleType(), | |
| 886 | ||
| 877 | 887 | .start_brace => try p.parseBlock(), |
| 878 | 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 | 899 | inline .bang, |
| 883 | 900 | .tilde, |
| 884 | 901 | .minus, |
| 885 | .kw_comptime, | |
| 886 | .kw_pure, | |
| 887 | .kw_assert, | |
| 888 | 902 | => |tag| unary: { |
| 889 | 903 | const main_token = p.nextToken(); |
| 890 | 904 | const expr = try p.parseExpressionPart(mode); |
| ... | ... | @@ -955,7 +969,7 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 955 | 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 | 974 | _ = try p.expect(.end_bracket); |
| 961 | 975 | |
| ... | ... | @@ -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 | 1038 | const trace = t.trace(@src()); |
| 1025 | 1039 | defer trace.end(); |
| 1026 | 1040 | |
| 1027 | 1041 | const main_token = p.assertToken(.kw_return); |
| 1028 | 1042 | |
| 1029 | 1043 | const expr = if (p.isExpressionStart()) |
| 1030 | try p.parseExpression(.expression) | |
| 1044 | try p.parseExpression(mode) | |
| 1031 | 1045 | else |
| 1032 | 1046 | null; |
| 1033 | 1047 | |
| ... | ... | @@ -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 | 1058 | const trace = t.trace(@src()); |
| 1045 | 1059 | defer trace.end(); |
| 1046 | 1060 | |
| ... | ... | @@ -1052,7 +1066,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex { |
| 1052 | 1066 | null; |
| 1053 | 1067 | |
| 1054 | 1068 | const expr = if (p.isExpressionStart()) |
| 1055 | try p.parseExpression(.expression) | |
| 1069 | try p.parseExpression(mode) | |
| 1056 | 1070 | else |
| 1057 | 1071 | null; |
| 1058 | 1072 | |
| ... | ... | @@ -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 | 1100 | fn parseBuiltinCall(p: *Parse) Error!NodeIndex { |
| 1070 | 1101 | const trace = t.trace(@src()); |
| 1071 | 1102 | defer trace.end(); |
| ... | ... | @@ -1083,11 +1114,7 @@ fn parseBuiltinCall(p: *Parse) Error!NodeIndex { |
| 1083 | 1114 | }); |
| 1084 | 1115 | } |
| 1085 | 1116 | |
| 1086 | fn parseExpressionList( | |
| 1087 | p: *Parse, | |
| 1088 | comptime end_token: Token.Tag, | |
| 1089 | comptime fail_context: ExpectedContext, | |
| 1090 | ) Error!SubList { | |
| 1117 | fn parseExpressionList(p: *Parse, comptime end_token: Token.Tag, fail_context: ExpectedContext) Error!SubList { | |
| 1091 | 1118 | const trace = t.trace(@src()); |
| 1092 | 1119 | defer trace.end(); |
| 1093 | 1120 | |
| ... | ... | @@ -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 | 1192 | const trace = t.trace(@src()); |
| 1166 | 1193 | defer trace.end(); |
| 1167 | 1194 | |
| ... | ... | @@ -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 | 1303 | const main_token = p.assertToken(.start_paren); |
| 1277 | 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 | 1316 | return p.addNode(.{ |
| 1279 | 1317 | .tag = .tuple_type, |
| 1280 | 1318 | .main_token = main_token, |
src/core/Sema.zig+355-74| ... | ... | @@ -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 | |
| 4 | //! declarations to Intern Pool `Value`s and function bodies to `Air`. | |
| 3 | //! Designed as a nonrecursive state machine which can be paused when an | |
| 4 | //! unfinished dependency is encountered, allowing the thread to work | |
| 5 | //! on other analysis jobs. | |
| 5 | 6 | //! |
| 6 | //! - Compile time resolution of values | |
| 7 | //! - Type checking | |
| 7 | //! Sema's responsibilities include | |
| 8 | //! - Operation validation and reporting of most compile errors. | |
| 9 | //! - Evaluation of `comptime` code. | |
| 10 | //! - Declaring dependencies on other analysis units. | |
| 8 | 11 | |
| 9 | 12 | comp: *Comp, |
| 10 | 13 | ip: *InternPool, |
| ... | ... | @@ -175,6 +178,7 @@ const Frame = struct { |
| 175 | 178 | |
| 176 | 179 | undefined: void, |
| 177 | 180 | conditional: Conditional, |
| 181 | func: Func, | |
| 178 | 182 | }; |
| 179 | 183 | |
| 180 | 184 | const Conditional = enum { |
| ... | ... | @@ -183,7 +187,16 @@ const Frame = struct { |
| 183 | 187 | runtime_no, |
| 184 | 188 | }; |
| 185 | 189 | |
| 190 | const Func = union(enum) { | |
| 191 | return_type, | |
| 192 | argument: u16, | |
| 193 | }; | |
| 194 | ||
| 186 | 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 | 200 | const list = s.teg.instEncodedData(block_index) |
| 188 | 201 | .decodeUnsafe(Teg.PlNode(Teg.IndexList), s.teg.instTag(block_index)) |
| 189 | 202 | .extra_index.get(&s.teg) |
| ... | ... | @@ -476,7 +489,8 @@ const InstructionKind = enum { |
| 476 | 489 | .@"comptime", |
| 477 | 490 | .@"if", |
| 478 | 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 | 494 | .@"break", |
| 481 | 495 | => .pop_control_flow, |
| 482 | 496 | // these are statements which do not evaluate to anything (void) |
| ... | ... | @@ -501,6 +515,12 @@ const PopControlFlowState = enum { stop, @"continue" }; |
| 501 | 515 | |
| 502 | 516 | fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag).Result() { |
| 503 | 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 | 524 | // TODO: pre-evaluate `.param` nodes, since they are always at the start of a function. |
| 505 | 525 | // this way we can avoid repeating the non-trivial call into InternPool |
| 506 | 526 | .param => { |
| ... | ... | @@ -666,6 +686,8 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 666 | 686 | return try s.fieldAccessArray(lhs, lhs_type, field.rhs); |
| 667 | 687 | } else if (lhs_type.toStructType(s.ip)) |struct_type| { |
| 668 | 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 | 691 | } else if (lhs.value()) |value| { |
| 670 | 692 | if (value.valueTag(s.ip) == .external_value) { |
| 671 | 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 | 899 | // return Value.void.toAir(); |
| 878 | 900 | // }, |
| 879 | 901 | |
| 880 | // control flow | |
| 881 | .block => unreachable, // frame should already be pushed | |
| 882 | 902 | .@"comptime" => { |
| 883 | 903 | const index = s.frame.currentIndex(); |
| 884 | 904 | try s.pushFrame(); |
| ... | ... | @@ -892,50 +912,66 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 892 | 912 | .{ .params = Type, .captures = Air.Inst.Ref }, |
| 893 | 913 | .{ data.params.len, data.captures.len }, |
| 894 | 914 | ); |
| 895 | defer reserve.deinit(); | |
| 896 | if (data.captures.len > 0) @panic("captures"); | |
| 915 | if (data.captures.len > 0) @panic("TODO: 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 | 922 | for (data.params.slice(&s.teg), 0..) |param, i| { |
| 899 | 923 | const src = s.lazyNode(.argument(@intCast(i))); |
| 900 | const ty = try s.getType(src, param.unwrap() orelse @panic("TODO: inferred param sets")); | |
| 901 | if (!ty.hasPossibleRepresentation(s.ip)) { | |
| 902 | return s.fail(.param_comptime_only, .{ | |
| 903 | .src = src, | |
| 904 | .type = ty, | |
| 905 | }); | |
| 924 | ||
| 925 | const known = param.unwrap() orelse @panic("TODO: inferred param sets"); | |
| 926 | switch (known.decode()) { | |
| 927 | .value => |val| { | |
| 928 | const ty = (try s.coerceValue(src, val, .type)).toTypeUnsafe(s.ip); | |
| 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( | |
| 911 | s.lazyNode(.return_type), | |
| 912 | data.return_type.unwrap() orelse .void, | |
| 913 | ); | |
| 914 | ||
| 915 | const tracked_inst = try s.ip.trackInst(s.gpa, s.file, s.frame.currentIndex()); | |
| 916 | const func_type = try s.makeType(.{ | |
| 917 | .function_type = .{ | |
| 918 | .param_types = reserve.items(.params), | |
| 919 | .return_type = return_type, | |
| 920 | }, | |
| 921 | }); | |
| 922 | const func_value = try s.makeValue(.{ | |
| 923 | .func = .{ | |
| 924 | .ty = func_type, | |
| 925 | .owner_decl = s.techDebtDeclIndex(), // TODO: ooh, this is a fun one! | |
| 926 | .teg_body_inst = tracked_inst, | |
| 927 | }, | |
| 928 | }); | |
| 929 | ||
| 930 | s.inst_map.add(s.frame.currentIndex(), func_value.toAir()); | |
| 931 | ||
| 932 | s.frame.advance(); | |
| 948 | const return_type = ret: { | |
| 949 | switch ((data.return_type.unwrap() orelse break :ret null).decode()) { | |
| 950 | .value => |val| { | |
| 951 | const ty = (try s.coerceValue(s.lazyNode(.return_type), val, .type)).toTypeUnsafe(s.ip); | |
| 952 | if (!ty.hasPossibleRepresentation(s.ip)) { | |
| 953 | return s.fail(.runtime_incompatible_type, .{ | |
| 954 | .src = s.lazyNode(.return_type), | |
| 955 | .type = ty, | |
| 956 | }); | |
| 957 | } | |
| 958 | break :ret ty; | |
| 959 | }, | |
| 960 | .index => |block| { | |
| 961 | const mode = s.frame.mode; | |
| 962 | try s.pushFrame(); | |
| 963 | s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .return_type }); | |
| 964 | s.frame.mark_index = reserve.index(); | |
| 965 | handled_reserve.unlock(); | |
| 966 | return; | |
| 967 | }, | |
| 968 | } | |
| 969 | }; | |
| 933 | 970 | |
| 934 | // TODO: move this queue once we perform one of these | |
| 935 | // - depend on the return type of an inferred-return-type function | |
| 936 | // - use the value in any runtime context () | |
| 937 | try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } }); | |
| 971 | try s.postVisitFunctionPrototype(reserve, return_type); | |
| 972 | handled_reserve.unlock(); | |
| 938 | 973 | }, |
| 974 | ||
| 939 | 975 | .@"break" => { |
| 940 | 976 | const brk: Teg.Break = s.frame.currentPayload(.@"break"); |
| 941 | 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 | 1014 | |
| 979 | 1015 | switch (data.conditional) { |
| 980 | 1016 | .comptime_condition => { |
| 1017 | assert(operand.kind == .value); // must be comptime | |
| 981 | 1018 | s.inst_map.add(s.frame.currentIndex(), operand); |
| 982 | 1019 | s.frame.advance(); |
| 983 | 1020 | return .@"continue"; |
| 984 | 1021 | }, |
| 985 | 1022 | .runtime_yes => { |
| 986 | 1023 | try s.emitRuntimeBlock(if_data.yes.decode().index, operand, mark); |
| 1024 | mark.deinit(); | |
| 987 | 1025 | |
| 988 | 1026 | switch (if_data.no.decode()) { |
| 989 | 1027 | .value => { |
| ... | ... | @@ -1025,6 +1063,79 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1025 | 1063 | })); |
| 1026 | 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 | 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 | 1252 | var has_errors = false; |
| 1142 | 1253 | var is_constant = true; |
| 1143 | 1254 | |
| 1144 | // Allocate scratch space to store the values as they are being prepared. | |
| 1145 | 1255 | const scratch = try s.scratch.reserve( |
| 1146 | 1256 | s.arena(), |
| 1147 | 1257 | Air.Inst.Ref.Optional, |
| ... | ... | @@ -1150,11 +1260,10 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1150 | 1260 | defer scratch.deinit(); |
| 1151 | 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 | 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 | 1267 | const found_index = index: for (struct_type.field_keys, 0..) |decl_key, j| { |
| 1159 | 1268 | if (decl_key.eqlSlice(init_key.slice(s.teg.string_bytes), s.ip)) |
| 1160 | 1269 | break :index j; |
| ... | ... | @@ -1181,7 +1290,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1181 | 1290 | .marked, |
| 1182 | 1291 | ) catch |e| switch (e) { |
| 1183 | 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 | 1294 | has_errors = true; |
| 1186 | 1295 | // Mark something non-null so that duplicate field errors are emitted. |
| 1187 | 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 | 1302 | scratch.at(found_index).* = coerced.toOptional(); |
| 1194 | 1303 | } |
| 1195 | 1304 | |
| 1196 | // Check for missing fields | |
| 1197 | { | |
| 1198 | const storage = scratch.slice(); | |
| 1199 | var i: usize = 0; | |
| 1200 | while (mem.indexOfScalarPos(Air.Inst.Ref.Optional, storage, i, .none)) |index| { | |
| 1201 | try s.addError(.struct_init_missing_field, .{ | |
| 1202 | .ty = ty, | |
| 1203 | .field = @intCast(index), | |
| 1204 | .src = s.lazyNode(.main_node), | |
| 1205 | }); | |
| 1206 | i = index + 1; | |
| 1207 | } | |
| 1208 | if (i != 0) has_errors = true; | |
| 1305 | const storage = scratch.slice(); | |
| 1306 | var i: usize = 0; | |
| 1307 | while (mem.indexOfScalarPos(Air.Inst.Ref.Optional, storage, i, .none)) |index| { | |
| 1308 | try s.addError(.struct_init_missing_field, .{ | |
| 1309 | .ty = ty, | |
| 1310 | .field = @intCast(index), | |
| 1311 | .src = s.lazyNode(.main_node), | |
| 1312 | }); | |
| 1313 | i = index + 1; | |
| 1209 | 1314 | } |
| 1315 | if (i != 0) has_errors = true; | |
| 1210 | 1316 | |
| 1211 | 1317 | if (has_errors) |
| 1212 | 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 | 1323 | if (is_constant) { |
| 1215 | // Convert all items in storage into a Value type | |
| 1216 | 1324 | const result_storage = scratch.sliceReinterpret(Value); |
| 1217 | for (scratch.slice(), scratch.sliceReinterpret(Value)) |ref, *value| { | |
| 1218 | value.* = ref.unwrap().?.decode().value; | |
| 1325 | for (scratch.slice(), result_storage) |ref, *value| { | |
| 1326 | // `ref` and `value` alias; this coercion is performed in-place | |
| 1327 | value.* = (ref.unwrap() orelse unreachable).decode().value; | |
| 1219 | 1328 | } |
| 1220 | 1329 | return try s.makeValueAir(.{ .struct_value = .{ |
| 1221 | 1330 | .ty = ty, |
| ... | ... | @@ -1224,7 +1333,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1224 | 1333 | } else { |
| 1225 | 1334 | try s.ensureAtRuntime(s.lazyNode(.main_node)); |
| 1226 | 1335 | |
| 1227 | // Create a runtime struct instantiation | |
| 1228 | 1336 | const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice()); |
| 1229 | 1337 | return try s.addRef(.struct_init, .{ |
| 1230 | 1338 | .ty = ty, |
| ... | ... | @@ -1239,6 +1347,73 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1239 | 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 | 1417 | .result_type_argument => { |
| 1243 | 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 | 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 | 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 | 1795 | .value => |v| { |
| 1616 | 1796 | const ty = v.typeOf(s.ip); |
| 1617 | 1797 | switch (ty) { |
| 1798 | // Allowed at runtime | |
| 1799 | .bool, | |
| 1800 | .float, | |
| 1801 | .int32, | |
| 1802 | .string, | |
| 1803 | .void, | |
| 1804 | .empty_tuple, | |
| 1805 | => {}, | |
| 1618 | 1806 | .numeric_literal => { |
| 1619 | 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 | 1837 | .type = ty, |
| 1650 | 1838 | }), |
| 1651 | 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 | 1840 | _ => if (!ty.hasPossibleRepresentation(s.ip)) { |
| 1660 | 1841 | return s.fail(.runtime_incompatible_type, .{ |
| 1661 | 1842 | .src = src, |
| ... | ... | @@ -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 | 1950 | fn lookupDecl(s: *Sema, name: NullTerminatedString) InternPool.DeclIndex { |
| 1732 | 1951 | var nsi = s.ns_index; |
| 1733 | 1952 | while (true) { |
| ... | ... | @@ -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 | 2147 | fn tempAir(s: *const Sema) Air { |
src/core/Teg.Dump.zig+18| ... | ... | @@ -319,6 +319,24 @@ const State = struct { |
| 319 | 319 | if (data.type_hint != .none) |
| 320 | 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 | 340 | .result_type_argument => { |
| 323 | 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 | 75 | struct_init, |
| 76 | 76 | array_init, |
| 77 | 77 | |
| 78 | tuple_init, | |
| 79 | tuple_type, | |
| 80 | ||
| 78 | 81 | add, |
| 79 | 82 | sub, |
| 80 | 83 | mul, |
| ... | ... | @@ -123,6 +126,7 @@ pub const Tag = enum(u8) { |
| 123 | 126 | .variable_declaration, |
| 124 | 127 | .test_declaration, |
| 125 | 128 | => PlNode(Decl), |
| 129 | ||
| 126 | 130 | .coercion => PlNode(Coercion), |
| 127 | 131 | .@"break" => PlNode(Break), |
| 128 | 132 | .number => PlNode(Float), |
| ... | ... | @@ -136,6 +140,8 @@ pub const Tag = enum(u8) { |
| 136 | 140 | .struct_decl => PlNode(StructDecl), |
| 137 | 141 | .struct_init => PlNode(StructInit), |
| 138 | 142 | .array_init => PlNode(ArrayInit), |
| 143 | .tuple_type => PlNode(TupleType), | |
| 144 | .tuple_init => PlNode(TupleInit), | |
| 139 | 145 | .func => PlNode(Func), |
| 140 | 146 | |
| 141 | 147 | .load_decl => StrNode, |
| ... | ... | @@ -146,7 +152,7 @@ pub const Tag = enum(u8) { |
| 146 | 152 | .result_type_tuple_field => ResultTypeTupleField, |
| 147 | 153 | .result_type_array_value => ResultTypeArrayValue, |
| 148 | 154 | |
| 149 | // Single instruction operand | |
| 155 | // One operand instructions | |
| 150 | 156 | .memory, |
| 151 | 157 | .discard, |
| 152 | 158 | .ensure_void, |
| ... | ... | @@ -162,7 +168,7 @@ pub const Tag = enum(u8) { |
| 162 | 168 | .assert, |
| 163 | 169 | => RefNode, |
| 164 | 170 | |
| 165 | // Two instruction operands | |
| 171 | // Two operand instructions | |
| 166 | 172 | .field, |
| 167 | 173 | .index, |
| 168 | 174 | .add, |
| ... | ... | @@ -230,7 +236,7 @@ pub const Inst = struct { |
| 230 | 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 | 241 | pub fn toRef(i: Index) Inst.Ref { |
| 236 | 242 | return @enumFromInt(static_len + @intFromEnum(i)); |
| ... | ... | @@ -269,6 +275,7 @@ pub const Inst = struct { |
| 269 | 275 | numeric_literal_type, |
| 270 | 276 | numeric_literal_array_type, |
| 271 | 277 | never_array_type, |
| 278 | empty_tuple_type, | |
| 272 | 279 | |
| 273 | 280 | void, |
| 274 | 281 | bool_true, |
| ... | ... | @@ -278,7 +285,7 @@ pub const Inst = struct { |
| 278 | 285 | nan, |
| 279 | 286 | empty_string, |
| 280 | 287 | empty_array, |
| 281 | ||
| 288 | empty_tuple, | |
| 282 | 289 | _, |
| 283 | 290 | |
| 284 | 291 | pub const Decoded = union(enum) { |
| ... | ... | @@ -423,6 +430,20 @@ pub const ArrayInit = struct { |
| 423 | 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 | 447 | pub const Decl = struct { |
| 427 | 448 | name: NullTerminatedString, |
| 428 | 449 | hash: Cache.BinDigest, |
| ... | ... | @@ -697,6 +718,8 @@ pub fn writeToHasher( |
| 697 | 718 | .struct_decl, |
| 698 | 719 | .struct_init, |
| 699 | 720 | .array_init, |
| 721 | .tuple_init, | |
| 722 | .tuple_type, | |
| 700 | 723 | .func, |
| 701 | 724 | .store_decl, |
| 702 | 725 | .store_field, |
| ... | ... | @@ -902,6 +925,19 @@ fn writePlNodeInnerToHasher( |
| 902 | 925 | hasher.add(isolatedIndexValue(decl_index, data.body)); |
| 903 | 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 | 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 | 75 | kw_return, |
| 76 | 76 | kw_struct, |
| 77 | 77 | kw_trait, |
| 78 | kw_tuple, | |
| 78 | 79 | kw_var, |
| 79 | 80 | kw_while, |
| 80 | 81 | // sort-lines: end |
| ... | ... | @@ -134,7 +135,7 @@ pub const Token = struct { |
| 134 | 135 | |
| 135 | 136 | /// Represents a tokenization error. Instead of reporting an error during |
| 136 | 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 | 139 | invalid, |
| 139 | 140 | invalid_number, |
| 140 | 141 | invalid_string, |
| ... | ... | @@ -275,8 +276,8 @@ pub const Token = struct { |
| 275 | 276 | /// Instead of storing the length of a token, we can recompute this |
| 276 | 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 | /// without the source code, use token.tag.staticLen() instead. | |
| 279 | /// When computing the length of something that can be known without | |
| 280 | /// the source code, use token.tag.staticLen() instead. | |
| 280 | 281 | pub fn len(token: Token, utf8_source: [:0]const u8) u32 { |
| 281 | 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 | 137 | .used => unreachable, // Use after free |
| 138 | 138 | .ref => |r| r, |
| 139 | 139 | .type_of => |r| try t.addRef(.type_of, .{ |
| 140 | // Result types don't have associated nodes, which is ok because | |
| 141 | // type_of cannot fail in the context we are using it. Instead, | |
| 142 | // we will use a predictable poison value, so that an attempt | |
| 143 | // to print this will crash. | |
| 140 | // Result types do not have associated nodes which is okay as `type_of` cannot | |
| 141 | // fail in the context it is used in. A predictable poison value is used instead | |
| 142 | // so that an attempt to print the following will cause a crash. | |
| 144 | 143 | .src_node = std.math.maxInt(i32), |
| 145 | 144 | .operand = r, |
| 146 | 145 | }), |
| ... | ... | @@ -281,6 +280,7 @@ const Symbol = struct { |
| 281 | 280 | value: union { |
| 282 | 281 | mutable: Inst.Index, |
| 283 | 282 | immutable: Inst.Ref, |
| 283 | param_index: u32, | |
| 284 | 284 | none: void, |
| 285 | 285 | }, |
| 286 | 286 | result_type_ref: Inst.Ref, |
| ... | ... | @@ -903,6 +903,7 @@ fn transformExpression( |
| 903 | 903 | switch (node_tag) { |
| 904 | 904 | .root, |
| 905 | 905 | .param_decl, |
| 906 | .struct_field, | |
| 906 | 907 | => unreachable, // not an expression |
| 907 | 908 | |
| 908 | 909 | .id => { |
| ... | ... | @@ -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 | 1555 | inline .fn_decl, .fn_expr => |tag| { |
| 1524 | 1556 | const data = switch (tag) { |
| 1525 | 1557 | .fn_decl => t.ast.fullFnDecl(node), |
src/core/js/Js.zig+1-1| ... | ... | @@ -43,7 +43,7 @@ pub fn link( |
| 43 | 43 | entry_point: Comp.Decl.Index, |
| 44 | 44 | writer: std.io.AnyWriter, |
| 45 | 45 | ) !void { |
| 46 | var state = Link{ | |
| 46 | var state: Link = .{ | |
| 47 | 47 | .comp = comp, |
| 48 | 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 | 309 | if (i != 0) |
| 310 | 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 | 324 | try link.printValue(inner_value, .assignment, writer); |
| 314 | 325 | } |
| 315 | 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 | 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 | 295 | .array_len => { |
| 287 | 296 | const data: Air.Un = c.air.instData(.array_len, inst); |
| 288 | 297 | const node = try c.add(.field, .{ |
| ... | ... | @@ -459,9 +468,9 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 459 | 468 | }, |
| 460 | 469 | |
| 461 | 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 | 474 | const store_offset = try c.extra.alloc(c.gpa, items.len); |
| 466 | 475 | |
| 467 | 476 | for ( |
| ... | ... | @@ -478,6 +487,28 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 478 | 487 | }); |
| 479 | 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 | 588 | .simple_type, |
| 558 | 589 | .struct_type, |
| 559 | 590 | .array_type, |
| 591 | .tuple_type, | |
| 560 | 592 | => unreachable, // Type values are not allowed here |
| 561 | 593 | |
| 562 | 594 | .int32, |
| ... | ... | @@ -566,6 +598,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { |
| 566 | 598 | .simple_value, |
| 567 | 599 | .struct_value, |
| 568 | 600 | .array_value, |
| 601 | .tuple_value, | |
| 569 | 602 | => return try c.add(.plain_value, .{ .value = v }), |
| 570 | 603 | |
| 571 | 604 | .external_value => { |
| ... | ... | @@ -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 | 628 | fn addTemporary(c: *NodeGen) !Node.Index { |
| 589 | 629 | const t = tracer.trace(@src()); |
| 590 | 630 | defer t.end(); |
| ... | ... | @@ -725,6 +765,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { |
| 725 | 765 | .arg, |
| 726 | 766 | .field_opaque, |
| 727 | 767 | .field_struct, |
| 768 | .field_tuple, | |
| 728 | 769 | .field_array, |
| 729 | 770 | .add, |
| 730 | 771 | .sub, |
| ... | ... | @@ -739,6 +780,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { |
| 739 | 780 | .struct_init, |
| 740 | 781 | .array_init, |
| 741 | 782 | .array_len, |
| 783 | .tuple_init, | |
| 742 | 784 | => true, |
| 743 | 785 | |
| 744 | 786 | // JavaScript variable declarations require statements. |
src/core/stack.zig+28-9| ... | ... | @@ -15,7 +15,7 @@ pub fn Stack(comptime Base: type) type { |
| 15 | 15 | |
| 16 | 16 | pub const Mark = struct { |
| 17 | 17 | stack: *Self, |
| 18 | index: usize, | |
| 18 | index: u32, | |
| 19 | 19 | |
| 20 | 20 | pub fn deinit(m: Mark) void { |
| 21 | 21 | if (core.runtime_safety) |
| ... | ... | @@ -23,8 +23,8 @@ pub fn Stack(comptime Base: type) type { |
| 23 | 23 | m.stack.list.shrinkRetainingCapacity(m.index); |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | pub fn len(m: Mark) usize { | |
| 27 | return m.stack.list.items.len - m.index; | |
| 26 | pub fn len(m: Mark) u32 { | |
| 27 | return @intCast(m.stack.list.items.len - m.index); | |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | 30 | pub fn slice(m: Mark, comptime T: type) []T { |
| ... | ... | @@ -39,7 +39,7 @@ pub fn Stack(comptime Base: type) type { |
| 39 | 39 | pub fn mark(stack: *Self) Mark { |
| 40 | 40 | return .{ |
| 41 | 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 | 101 | |
| 102 | 102 | return struct { |
| 103 | 103 | stack: *Self, |
| 104 | starts: [fields.len]usize, | |
| 105 | lengths: [fields.len]usize, | |
| 104 | starts: [fields.len]u32, | |
| 105 | lengths: [fields.len]u32, | |
| 106 | 106 | |
| 107 | 107 | fn Type(comptime field: FieldEnum) type { |
| 108 | 108 | return @field(types, @tagName(field)); |
| ... | ... | @@ -128,6 +128,25 @@ pub fn Stack(comptime Base: type) type { |
| 128 | 128 | std.mem.copyForwards(u32, dest, from); |
| 129 | 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 | 156 | stack: *Self, |
| 138 | 157 | allocator: Allocator, |
| 139 | 158 | comptime types: anytype, |
| 140 | lengths: [std.meta.fields(@TypeOf(types)).len]usize, | |
| 159 | lengths: [std.meta.fields(@TypeOf(types)).len]u32, | |
| 141 | 160 | ) !ReserveMulti(types) { |
| 142 | 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 | 163 | const start = stack.list.items.len; |
| 145 | 164 | inline for (lengths, &starts) |len, *slice_start| { |
| 146 | slice_start.* = start + all_lengths; | |
| 165 | slice_start.* = @intCast(start + all_lengths); | |
| 147 | 166 | all_lengths += len; |
| 148 | 167 | } |
| 149 | 168 | try stack.list.ensureUnusedCapacity(allocator, all_lengths); |
src/main.zig+26-95| ... | ... | @@ -4,9 +4,18 @@ |
| 4 | 4 | //! for example, test the `core` module's compilation features without |
| 5 | 5 | //! needing to compile the formatter. |
| 6 | 6 | |
| 7 | const have_debug_features = builtin.mode == .Debug; | |
| 8 | 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 | 19 | pub fn main() !u8 { |
| 11 | 20 | try tracer.init(); |
| 12 | 21 | defer tracer.deinit(); |
| ... | ... | @@ -24,8 +33,7 @@ pub fn main() !u8 { |
| 24 | 33 | }){} |
| 25 | 34 | else |
| 26 | 35 | std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| 27 | var no_leak_checks = false; | |
| 28 | defer _ = if (use_gpa and !no_leak_checks) gpa.deinit(); | |
| 36 | defer _ = if (use_gpa) gpa.deinit(); | |
| 29 | 37 | const alloc = gpa.allocator(); |
| 30 | 38 | |
| 31 | 39 | // Current working directory is required for CLI resolution. |
| ... | ... | @@ -33,95 +41,17 @@ pub fn main() !u8 { |
| 33 | 41 | const cwd = try std.posix.getcwd(&cwd_buf); |
| 34 | 42 | |
| 35 | 43 | // Command line argument parsing. |
| 36 | var maybe_file_path: ?[]const u8 = null; | |
| 37 | defer if (maybe_file_path) |path| alloc.free(path); | |
| 38 | var exe_name: ?[]const u8 = null; | |
| 39 | defer if (exe_name) |path| alloc.free(path); | |
| 40 | var dump_ir = false; | |
| 41 | var dump_state: ?[]const u8 = null; | |
| 42 | var run = false; | |
| 43 | var has_errors = false; | |
| 44 | { | |
| 45 | var args = try std.process.argsWithAllocator(alloc); | |
| 46 | defer args.deinit(); | |
| 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.?; | |
| 44 | const program_name = "zig build compiler --"; | |
| 45 | _, const args = try Cli.programArgsAlloc(alloc, program_name); | |
| 46 | defer Cli.programArgsFree(alloc, args); | |
| 47 | const opts = arg_config.parse(program_name, args); | |
| 48 | ||
| 49 | const file_path = try std.fs.path.resolve(alloc, &.{ | |
| 50 | cwd, | |
| 51 | opts.positionals orelse | |
| 52 | arg_config.showHelpAndExit(program_name), | |
| 53 | }); | |
| 54 | defer alloc.free(file_path); | |
| 125 | 55 | |
| 126 | 56 | // Execute compiler |
| 127 | 57 | { |
| ... | ... | @@ -130,13 +60,13 @@ pub fn main() !u8 { |
| 130 | 60 | var comp = try core.Comp.init(.{ |
| 131 | 61 | .gpa = tracer.tracingAllocator(&alloc), |
| 132 | 62 | .options = .{ |
| 133 | .dump_ir = dump_ir, | |
| 63 | .dump_ir = opts.dump_ir orelse false, | |
| 134 | 64 | }, |
| 135 | 65 | .backend = .{ .js = &js_backend }, |
| 136 | 66 | }); |
| 137 | 67 | defer comp.deinit(); |
| 138 | 68 | |
| 139 | defer if (dump_state) |path| { | |
| 69 | defer if (opts.dump_state) |path| { | |
| 140 | 70 | dumpState(&comp, path) catch |err| { |
| 141 | 71 | std.debug.print("Failed to dump compilation state: {}", .{err}); |
| 142 | 72 | }; |
| ... | ... | @@ -173,7 +103,7 @@ pub fn main() !u8 { |
| 173 | 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 | 107 | // Execute with QuickJS |
| 178 | 108 | var out: std.ArrayListUnmanaged(u8) = .{}; |
| 179 | 109 | defer out.deinit(alloc); |
| ... | ... | @@ -244,6 +174,7 @@ pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)) |
| 244 | 174 | |
| 245 | 175 | const std = @import("std"); |
| 246 | 176 | const builtin = @import("builtin"); |
| 177 | const Cli = @import("Cli.zig"); | |
| 247 | 178 | |
| 248 | 179 | fn dumpState(comp: *const Comp, dump_path: []const u8) !void { |
| 249 | 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 } |