authorchloe caruso <git@paperclover.net> 2024-07-29 01:15:50-07:00
committerchloe caruso <git@paperclover.net> 2024-07-29 01:15:50-07:00
log1a86087b164d2b3d8fbdfa9b09dae0bcef1b333c
tree2b80758a08373445412b0de6614a9eec7e5d6176
parenta48b4aaf01270f1fd4f562c8265e4edbe910db19

add array index syntax


11 files changed, 212 insertions(+), 35 deletions(-)

src/core/Air.Dump.zig+2-1
......@@ -104,6 +104,7 @@ pub const State = struct {
104104 },
105105 inline .negate,
106106 .bool_not,
107 .array_len,
107108 => |comptime_tag| {
108109 const bin: Air.Un = air.instData(comptime_tag, inst);
109110 try w.writeAll("(");
......@@ -193,7 +194,7 @@ pub const State = struct {
193194
194195 try w.writeAll("(");
195196 try s.dumpValue(w, array_type.item_type.value());
196 try s.dumpRefList(w, array_init.list.slice(air), '[', ']');
197 try s.dumpRefList(w, array_init.list.get(air).slice(air), '[', ']');
197198 try w.writeAll(")");
198199 },
199200 }
src/core/Air.zig+10-4
......@@ -30,6 +30,8 @@ pub const Inst = struct {
3030 field_opaque,
3131 field_struct,
3232
33 array_len,
34
3335 index,
3436
3537 // bin
......@@ -70,7 +72,10 @@ pub const Inst = struct {
7072 .gt,
7173 .eq,
7274 => Bin,
73 .negate, .bool_not => Un,
75 .negate,
76 .bool_not,
77 .array_len,
78 => Un,
7479 };
7580 }
7681 };
......@@ -98,8 +103,8 @@ pub const Inst = struct {
98103 return if (ref.kind == .value) return @enumFromInt(ref.data) else null;
99104 }
100105
101 pub fn index(ref: Ref) ?Value {
102 return if (ref.kind == .value) return @enumFromInt(ref.data) else null;
106 pub fn index(ref: Ref) ?Index {
107 return if (ref.kind == .index) return @enumFromInt(ref.data) else null;
103108 }
104109
105110 pub fn toOptional(ref: Ref) Optional {
......@@ -279,7 +284,7 @@ pub const StructInit = struct {
279284
280285pub const ArrayInit = struct {
281286 ty: Type,
282 list: RefList,
287 list: ExtraIndex(RefList),
283288};
284289
285290pub const FieldStruct = struct {
......@@ -329,6 +334,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In
329334 .lt, .gt, .eq, .bool_not => .bool,
330335 .@"break" => .noreturn,
331336 .field_opaque => .@"opaque",
337 .array_len => .int32,
332338
333339 // For many instructions, the type is stored on it's data directly. This
334340 // is avoided when possible to reduce the size of Air.
src/core/Comp.zig+1-1
......@@ -185,7 +185,7 @@ pub const File = struct {
185185 error.ParseFail => {
186186 // TODO: let this merge with other report
187187 var report = try err_data.toReport(comp, file);
188 report.dumpToStdErr(comp) catch {};
188 report.dumpToWriter(comp, comp.out.writer, comp.out.tty) catch {};
189189 report.deinit(comp.gpa);
190190 return err;
191191 },
src/core/Parse.zig+8-1
......@@ -105,6 +105,7 @@ pub const ExpectedContext = enum {
105105 else_clause,
106106 struct_field,
107107 param_list,
108 static_field,
108109
109110 pub fn description(tag: ExpectedContext) []const u8 {
110111 return switch (tag) {
......@@ -115,6 +116,7 @@ pub const ExpectedContext = enum {
115116 .expression => "an expression",
116117 .else_clause => "an else clause",
117118 .struct_field => "a field definition",
119 .static_field => "a field name or number",
118120 };
119121 }
120122};
......@@ -897,7 +899,10 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
897899 switch (p.currentTag()) {
898900 .dot => {
899901 const main_token = p.nextToken();
900 const field = try p.expect(.id);
902 const field = switch (p.currentTag()) {
903 .id, .number_int => p.nextToken(),
904 else => return p.failUnexpectedToken(.static_field),
905 };
901906
902907 expr = try p.addNode(.{
903908 .tag = .field,
......@@ -934,6 +939,8 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
934939
935940 const index = try p.tryParseExpression(.expression);
936941
942 _ = try p.expect(.end_bracket);
943
937944 expr = try p.addNode(.{
938945 .tag = .index,
939946 .main_token = main_token,
src/core/Report.zig+21-4
......@@ -770,18 +770,36 @@ pub const Builder = struct {
770770 current_file_str: ?String = null,
771771
772772 pub fn err(b: *Builder, comptime kind: Err.FieldEnum, data: std.meta.FieldType(Err, kind)) !void {
773 try errAt(b, kind, data, @returnAddress());
774 }
775
776 pub fn warn(b: *Builder, comptime kind: Warn.FieldEnum, data: std.meta.FieldType(Warn, kind)) !void {
777 try warnAt(b, kind, data, @returnAddress());
778 }
779
780 pub fn errAt(
781 b: *Builder,
782 comptime kind: Err.FieldEnum,
783 data: std.meta.FieldType(Err, kind),
784 return_address: usize,
785 ) !void {
773786 try b.errors.append(b.gpa, .{
774787 .data = .{ .@"error" = @unionInit(Err, @tagName(kind), data) },
775788 .url = try b.currentFileString(),
776 .debug_stack_trace = DebugStoredTrace.capture(@returnAddress()),
789 .debug_stack_trace = DebugStoredTrace.capture(return_address),
777790 });
778791 }
779792
780 pub fn warn(b: *Builder, comptime kind: Warn.FieldEnum, data: std.meta.FieldType(Warn, kind)) !void {
793 pub fn warnAt(
794 b: *Builder,
795 comptime kind: Warn.FieldEnum,
796 data: std.meta.FieldType(Warn, kind),
797 return_address: usize,
798 ) !void {
781799 try b.warnings.append(b.gpa, .{
782800 .data = .{ .warn = @unionInit(Warn, @tagName(kind), data) },
783801 .url = try b.currentFileString(),
784 .debug_stack_trace = DebugStoredTrace.capture(@returnAddress()),
802 .debug_stack_trace = DebugStoredTrace.capture(return_address),
785803 });
786804 }
787805
......@@ -1078,7 +1096,6 @@ pub const LazyNode = struct {
10781096 return items[lazy.operand.getArgId()];
10791097 },
10801098 .field => {
1081 assert(node_tag == .field);
10821099 return base_node;
10831100 },
10841101 else => {},
src/core/Sema.zig+38-8
......@@ -389,12 +389,42 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
389389 .field => {
390390 const field: Teg.Bin = s.code.instPayload(.field, in);
391391 const base = try s.resolveRef(field.lhs);
392 const ty = s.typeOfRef(base);
393
394 const rhs_raw = try s.resolveRef(field.rhs);
395
396 if (ty.toArrayType(s.ip)) |_| {
397 // Special case `.len`
398 get_array_len: {
399 const value = rhs_raw.value() orelse break :get_array_len;
400 if (value.valueTag(s.ip) != .string) break :get_array_len;
401 const string = value.asString(s.ip).toSlice(s.ip);
402 if (!std.mem.eql(u8, string, "len")) break :get_array_len;
403
404 switch (base.decode()) {
405 .value => |v| {
406 const items_len = v.valueData(s.ip, .array_value).get(s.ip).items_len;
407 return s.makeValueAir(.{ .int32 = @intCast(items_len) });
408 },
409 .index => {
410 return s.addInstRef(.array_len, .{
411 .operand = base,
412 });
413 },
414 }
415 }
416
417 // Arrays use runtime known int32s
418 const rhs = try s.coerceRef(in.op(.rhs), rhs_raw, .int32);
419 return s.addInstRef(.index, .{
420 .lhs = base,
421 .rhs = rhs,
422 });
423 }
392424
393425 const rhs = try s.resolveCoerceValue(in.op(.rhs), field.rhs, .string);
394426 const string = rhs.asString(s.ip);
395427
396 const ty = s.typeOfRef(base);
397
398428 if (ty == .@"opaque") {
399429 return s.addInstRef(.field_opaque, .{
400430 .base = base,
......@@ -930,10 +960,10 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
930960 // Create a runtime array instantiation
931961 return try s.addInstRef(.array_init, .{
932962 .ty = ty,
933 .list = .{
963 .list = try s.air_extra.append(s.gpa, Air.RefList, .{
934964 .start = try s.air_extra.appendSlice(s.gpa, scratch.slice()),
935965 .len = node.list.len,
936 },
966 }),
937967 });
938968 }
939969 },
......@@ -1277,7 +1307,7 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R
12771307 .numeric_literal => {
12781308 const float = v.toFloat(s.ip) orelse unreachable;
12791309
1280 if (!core.inInt32Range(float)) {
1310 if (core.inInt32Range(float)) {
12811311 try s.report.warn(.numeric_int_var_ambiguity, .{
12821312 .src = s.lazyNode(src),
12831313 });
......@@ -1687,15 +1717,15 @@ fn nodeLoc(s: *const Sema, in: Teg.Inst.Index, op: Report.Operand) Report.LazyNo
16871717}
16881718
16891719fn addWarn(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) !void {
1690 try s.report.warn(kind, data);
1720 try s.report.warnAt(kind, data, @returnAddress());
16911721}
16921722
16931723fn addError(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) !void {
1694 try s.report.err(kind, data);
1724 try s.report.errAt(kind, data, @returnAddress());
16951725}
16961726
16971727fn fail(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) Error {
1698 try s.report.err(kind, data);
1728 try s.report.errAt(kind, data, @returnAddress());
16991729 return Error.AnalysisFail;
17001730}
17011731
src/core/Tokenizer.zig+38-5
......@@ -344,11 +344,6 @@ pub fn next(t: *Tokenizer) !?Token {
344344
345345 '0'...'9' => return try t.tokenizeNumber(),
346346
347 '.' => switch (t.nextByte()) {
348 '0'...'9' => return try t.tokenizeNumber(),
349 else => return t.simpleToken(.dot),
350 },
351
352347 '/' => switch (t.nextByte()) {
353348 '=' => return t.simpleToken(.slash_equal),
354349 '/' => {
......@@ -490,6 +485,7 @@ pub fn next(t: *Tokenizer) !?Token {
490485 '{' => return t.simpleToken(.start_brace),
491486 '}' => return t.simpleToken(.end_brace),
492487 ',' => return t.simpleToken(.comma),
488 '.' => return t.simpleToken(.dot),
493489
494490 0x80...0xFF => {
495491 // need to decode utf-8
......@@ -1250,6 +1246,43 @@ test "basic tokenizer example" {
12501246 try testing.expectEqual(null, (try tokenizer.next()));
12511247}
12521248
1249test "field access" {
1250 const testing = std.testing;
1251 try unicode_props.init();
1252
1253 const source =
1254 \\.0 hello.1.2
1255 ;
1256 const expected_tokens = [_]Token{
1257 .{ .tag = .kw_var, .offset = 0 },
1258 .{ .tag = .id, .offset = 4 },
1259 .{ .tag = .equal, .offset = 10 },
1260 .{ .tag = .number_float, .offset = 12 },
1261 .{ .tag = .number_int, .offset = 20 },
1262 .{ .tag = .semi, .offset = 21 },
1263 .{ .tag = .kw_pub, .offset = 42 },
1264 .{ .tag = .kw_fn, .offset = 46 },
1265 .{ .tag = .id, .offset = 49 },
1266 .{ .tag = .start_paren, .offset = 54 },
1267 .{ .tag = .colon, .offset = 55 },
1268 .{ .tag = .plus, .offset = 56 },
1269 .{ .tag = .id, .offset = 58 },
1270 .{ .tag = .end_paren, .offset = 62 },
1271 .{ .tag = .id, .offset = 64 },
1272 .{ .tag = .kw_return, .offset = 67 },
1273 .{ .tag = .id, .offset = 74 },
1274 .{ .tag = .id, .offset = 77 },
1275 .{ .tag = .string, .offset = 86 },
1276 };
1277 var tokenizer = Tokenizer{ .code = source };
1278 for (expected_tokens) |tok| {
1279 tokenizer.skipWhitespace();
1280 try testing.expectEqual(tok, (try tokenizer.next()).?);
1281 }
1282 tokenizer.skipWhitespace();
1283 try testing.expectEqual(null, (try tokenizer.next()));
1284}
1285
12531286test "string escapes" {
12541287 try testStringDecode("hello world", "hello world");
12551288 try testStringDecode("hello\n\rw\x00or\x5ald", "hello\\n\\rw\\0or\\x5ald");
src/core/Transform.zig+44-7
......@@ -1174,20 +1174,57 @@ fn transformExpression(
11741174 var base_result: ResultType = .none;
11751175 const base = try t.transformExpression(ctx, &base_result, field.base) orelse return null;
11761176
1177 // TODO: handle identifier escapes
1178 const id = t.ast.tokenSlice(field.field_token);
1179 const prop_name_str = try t.addSlicedString(id);
1177 const rhs = switch (t.ast.tokens.items(.tag)[field.field_token]) {
1178 .id => rhs: {
1179 const id = t.ast.tokenSlice(field.field_token);
1180 const id_decoded = try t.decodeIdentifier(id);
1181 const prop_name_str = try t.addSlicedString(id_decoded);
11801182
1181 const string_ref = try t.addRef(.string, .{
1183 break :rhs try t.addRef(.string, .{
1184 .src_node = ctx.nodeOffset(node),
1185 .extra_index = try t.extra.append(t.gpa, SlicedString, prop_name_str),
1186 });
1187 },
1188 .number_int => rhs: {
1189 const text = t.ast.tokenSlice(field.field_token);
1190
1191 break :rhs try t.addRef(.number, .{
1192 .src_node = ctx.nodeOffset(node),
1193 .extra_index = try t.extra.append(
1194 t.gpa,
1195 Float,
1196 Float.encode(std.fmt.parseFloat(f64, text) catch @panic("TODO")),
1197 ),
1198 });
1199 },
1200 else => unreachable, // `Parse` filters out all other token tags
1201 };
1202
1203 return try t.addRef(.field, .{
11821204 .src_node = ctx.nodeOffset(node),
1183 .extra_index = try t.extra.append(t.gpa, SlicedString, prop_name_str),
1205 .extra_index = try t.extra.append(t.gpa, Teg.Bin, .{
1206 .lhs = base,
1207 .rhs = rhs,
1208 }),
11841209 });
1210 },
1211
1212 .index => {
1213 const bin = t.ast.nodeData(node).bin;
1214
1215 var lhs_result: ResultType = .none;
1216 const lhs = try t.transformExpression(ctx, &lhs_result, bin.lhs) orelse
1217 return null;
1218
1219 var rhs_result: ResultType = .none;
1220 const rhs = try t.transformExpression(ctx, &rhs_result, bin.rhs) orelse
1221 return null;
11851222
11861223 return try t.addRef(.field, .{
11871224 .src_node = ctx.nodeOffset(node),
11881225 .extra_index = try t.extra.append(t.gpa, Teg.Bin, .{
1189 .lhs = base,
1190 .rhs = string_ref,
1226 .lhs = lhs,
1227 .rhs = rhs,
11911228 }),
11921229 });
11931230 },
src/core/array.zig+2-1
......@@ -168,7 +168,8 @@ pub fn EncodedData(comptime Tag: type, backing_type: type) type {
168168 return @enumFromInt(@intFromEnum(data));
169169 }
170170 if (info == .Struct) {
171 assert(backing_type == u64);
171 comptime assert(backing_type == u64);
172 comptime assert(@sizeOf(Data) == 8);
172173 var fields: [2]u32 = undefined;
173174 setAsU32(&fields, Data, 0, data);
174175 return @enumFromInt(@as(backing_type, @bitCast(fields)));
src/core/js/NodeGen.zig+36-3
......@@ -2,7 +2,7 @@
22gpa: Allocator,
33arena: Allocator,
44air: *const Air,
5ip: *const InternPool,
5ip: *InternPool,
66
77nodes: Node.List,
88extra: dod.ExtraStore(.js_tree, .@"var"),
......@@ -28,7 +28,7 @@ const MapEntry = union {
2828 },
2929};
3030
31pub fn init(gpa: Allocator, arena: Allocator, ip: *const InternPool, air: *const Air) !NodeGen {
31pub fn init(gpa: Allocator, arena: Allocator, ip: *InternPool, air: *const Air) !NodeGen {
3232 const t = tracer.trace(@src());
3333 defer t.end();
3434
......@@ -171,6 +171,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
171171 .sub,
172172 .mul,
173173 .div,
174 .index,
174175 => |tag| {
175176 const data: Air.Bin = c.air.instData(tag, inst);
176177 const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{
......@@ -273,6 +274,18 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
273274 try c.markExpression(inst, node);
274275 },
275276
277 .array_len => {
278 const data: Air.Un = c.air.instData(.array_len, inst);
279 const node = try c.add(.field, .{
280 .base = try c.fetchInstExpression(
281 data.operand.index() orelse
282 unreachable, // TODO: make a second version of Un
283 ),
284 .field = try c.ip.getOrPutString(c.gpa, "length", .no_embedded_nulls),
285 });
286 try c.markExpression(inst, node);
287 },
288
276289 inline .call, .js_new => |tag| {
277290 const data: Air.Call = c.air.instData(tag, inst);
278291 const call_data = data.call_data.get(c.air);
......@@ -422,7 +435,26 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
422435 try c.markExpression(inst, node);
423436 },
424437
425 else => |tag| std.debug.panic("TODO JS codeGenInst for .{s}", .{@tagName(tag)}),
438 .array_init => {
439 const struct_init: Air.ArrayInit = c.air.instData(.array_init, inst);
440
441 const items = struct_init.list.get(c.air).slice(c.air);
442 const store_offset = try c.extra.alloc(c.gpa, items.len);
443
444 for (
445 items,
446 store_offset..,
447 ) |item, item_i| {
448 const in = try c.fetchExpression(item);
449 c.extra.list.items[item_i] = @intFromEnum(in);
450 }
451
452 const node = try c.add(.array, .{
453 .start = store_offset,
454 .len = @intCast(items.len),
455 });
456 try c.markExpression(inst, node);
457 },
426458 }
427459}
428460
......@@ -676,6 +708,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
676708 .struct_init,
677709 .array_init,
678710 .index,
711 .array_len,
679712 => true,
680713
681714 // JavaScript variable declarations require statements.
test/behavior/array created+12
......@@ -0,0 +1,12 @@
1fn i(a: int) -> int { a }
2
3let array = [i(9), i(3), i(8), i(3), i(-1)];
4
5let assertEqual = @js("assertEqual");
6
7assertEqual(array.len, i(5));
8assertEqual(array[0], i(9));
9assertEqual(array[1], i(3));
10assertEqual(array.2, i(8));
11assertEqual(array.3, i(3));
12assertEqual(array[i(1) + 2] + array[i(2) + 2], i(2));