diff --git a/src/core/Air.Dump.zig b/src/core/Air.Dump.zig index 79eb0df165eace069097f479434403224e853b77..4afd6e208c74fd17600da1a282a7b792a5688253 100644 --- a/src/core/Air.Dump.zig +++ b/src/core/Air.Dump.zig @@ -379,6 +379,22 @@ pub const State = struct { } try w.writeAll("'"); }, + .tuple_type => |tuple_type| { + try w.writeAll("("); + for (tuple_type.items, 0..) |item, i| { + if (i != 0) try w.writeAll(", "); + try s.dumpValue(w, item.value()); + } + try w.writeAll(")"); + }, + .tuple_value => |tuple_value| { + try w.writeAll("("); + for (tuple_value.items, 0..) |item, i| { + if (i != 0) try w.writeAll(", "); + try s.dumpValue(w, item); + } + try w.writeAll(")"); + }, } } diff --git a/src/core/InternPool.zig b/src/core/InternPool.zig index 679e01f3ad3122666d05b6eedda99389fd0b6f6c..28e33988dbaa1134e7149166be34c4438560573b 100644 --- a/src/core/InternPool.zig +++ b/src/core/InternPool.zig @@ -101,6 +101,8 @@ pub const Tag = enum(u8) { struct_type, /// An array type array_type, + /// A tuple type + tuple_type, /// A type that can be represented with only an enum tag. simple_type, @@ -129,6 +131,8 @@ pub const Tag = enum(u8) { external_value, /// A value masked as "opaque" type opaque_value, + /// A tuple value + tuple_value, pub fn Data(comptime tag: Tag) type { return switch (tag) { @@ -161,6 +165,8 @@ pub const Tag = enum(u8) { .array_type => ArrayType, .array_value => ArrayValue, .external_value => ExternalValue, + .tuple_type => TupleType, + .tuple_value => TupleValue, }; } @@ -199,6 +205,20 @@ pub const Tag = enum(u8) { items_len: u32, }; + /// Trailing: + /// 0. item: Type. Repeated per .items_len + pub const TupleType = struct { + items_len: u32, + }; + + /// Trailing: + /// 0. value: Value. Repeated per .items_len + pub const TupleValue = struct { + /// This is the tuple type, not the item type + type: Type, + items_len: u32, + }; + /// Trailing: /// 0. value: Value. Repeated .ty.structInfo().field_len pub const StructValue = struct { @@ -216,9 +236,9 @@ pub const Tag = enum(u8) { /// This is an index into .items pub const Value = enum(u32) { pub const first_type: Value = .opaque_type; - pub const last_type: Value = .numeric_literal_array_type; + pub const last_type: Value = .empty_tuple_type; pub const first_value: Value = .void; - pub const last_value: Value = .empty_array; + pub const last_value: Value = .empty_tuple; opaque_type, bool_type, @@ -230,6 +250,7 @@ pub const Value = enum(u32) { numeric_literal_type, numeric_literal_array_type, never_array_type, + empty_tuple_type, void, bool_true, @@ -239,6 +260,7 @@ pub const Value = enum(u32) { nan, empty_string, empty_array, + empty_tuple, none = std.math.maxInt(u32), _, @@ -316,6 +338,7 @@ pub const Value = enum(u32) { .simple_type, .struct_type, .array_type, + .tuple_type, => .type, .simple_value => switch (value.valueData(ip, .simple_value)) { @@ -332,6 +355,7 @@ pub const Value = enum(u32) { .struct_value => value.toKey(ip).struct_value.ty, .array_value => value.toKey(ip).array_value.type, .external_value, .opaque_value => .@"opaque", + .tuple_value => value.toKey(ip).tuple_value.type, }; } @@ -345,6 +369,7 @@ pub const Value = enum(u32) { .struct_type, .simple_type, .array_type, + .tuple_type, => @enumFromInt(@intFromEnum(value)), // exception: void is a value AND a type @@ -395,6 +420,7 @@ pub const Type = enum(u32) { numeric_literal = @intFromEnum(Value.numeric_literal_type), numeric_literal_array = @intFromEnum(Value.numeric_literal_array_type), never_array = @intFromEnum(Value.never_array_type), + empty_tuple = @intFromEnum(Value.empty_tuple_type), void = @intFromEnum(Value.void), _, @@ -404,6 +430,7 @@ pub const Type = enum(u32) { struct_type = @intFromEnum(InternPool.Tag.struct_type), array_type = @intFromEnum(InternPool.Tag.array_type), simple_type = @intFromEnum(InternPool.Tag.simple_type), + tuple_type = @intFromEnum(InternPool.Tag.tuple_type), }; /// An array of `Index` existing within the `extra` array. @@ -446,6 +473,11 @@ pub const Type = enum(u32) { return ip.valueToKey(ty.value()).array_type; } + pub fn toTupleType(ty: Type, ip: *const InternPool) ?Key.TupleType { + if (ty.tag(ip) != .tuple_type) return null; + return ip.valueToKey(ty.value()).tuple_type; + } + /// If it is possible to represent this type at runtime. Some types do not /// have a defined representation, such as structs (to allow field renaming, unwrapping). pub fn hasPossibleRepresentation(ty: Type, ip: *const InternPool) bool { @@ -466,6 +498,15 @@ pub const Type = enum(u32) { .struct_type, .function_type, => true, + .tuple_type => { + const tuple_type = ty.toTupleType(ip).?; + for (tuple_type.items) |item| { + if (!item.hasPossibleRepresentation(ip)) { + return false; + } + } + return true; + }, .array_type => ty.toArrayType(ip).?.item_type.hasPossibleRepresentation(ip), }, }; @@ -490,6 +531,7 @@ pub const Type = enum(u32) { .simple_type => unreachable, // all simple types are already printed .struct_type, .function_type, + .tuple_type, => false, .array_type => ty.toArrayType(ip).?.item_type.hasDefinedRepresentation(ip), }, @@ -546,6 +588,14 @@ pub const Type = enum(u32) { const array_type = ty.toArrayType(ip).?; try writer.print("{}", .{array_type.item_type.displayName(ip)}); }, + .tuple_type => { + try writer.writeAll("("); + for (ty.toTupleType(ip).?.items, 0..) |item, i| { + if (i != 0) try writer.writeAll(", "); + try writer.print("{}", .{item.displayName(ip)}); + } + try writer.writeAll(")"); + }, } } }; @@ -584,6 +634,7 @@ pub const static_keys = [_]Key{ SimpleType.numeric_literal.key(), .{ .array_type = .{ .item_type = Type.numeric_literal } }, .{ .array_type = .{ .item_type = Type.never } }, + .{ .tuple_type = .{ .items = &.{} } }, SimpleValue.void.key(), SimpleValue.true.key(), @@ -596,6 +647,10 @@ pub const static_keys = [_]Key{ .items = &.{}, } }, .{ .string = "" }, + .{ .tuple_value = .{ + .type = .empty_tuple, + .items = &.{}, + } }, }; /// State that is mutable during semantic analysis. This data is not used for @@ -876,11 +931,13 @@ pub const Key = union(enum) { func: Func, array_value: ArrayValue, opaque_value: Value, + tuple_value: TupleValue, // Types simple_type: SimpleType, function_type: FunctionType, struct_type: StructType, array_type: ArrayType, + tuple_type: TupleType, external_value: ExternalValue, const Adapter = struct { @@ -946,6 +1003,14 @@ pub const Key = union(enum) { } }; + pub const TupleType = struct { + items: []const Type, + + pub fn hash(self: TupleType, hasher: *Hash) void { + std.hash.autoHashStrat(hasher, self.items, .Deep); + } + }; + pub const ExternalValue = struct { /// A list of strings to generate the property access /// so `console.log` would be .{ "console", "log" } @@ -967,6 +1032,17 @@ pub const Key = union(enum) { } }; + pub const TupleValue = struct { + /// This is the tuple type, not the item type + type: Type, + items: []const Value, + + pub fn hash(self: TupleValue, hasher: *Hash) void { + std.hash.autoHash(hasher, self.type); + std.hash.autoHashStrat(hasher, self.items, .Deep); + } + }; + pub const Func = struct { ty: Type, owner_decl: DeclIndex, @@ -1008,6 +1084,8 @@ pub const Key = union(enum) { .array_value, .array_type, .external_value, + .tuple_type, + .tuple_value, => |x| { var hasher = Hash.init(seed); x.hash(&hasher); @@ -1047,7 +1125,7 @@ pub const Key = union(enum) { return false; }, .struct_type => { - return false; // structs are nomial + return false; // structs are nominal }, .struct_value => |a_info| { const b_info = b.struct_value; @@ -1066,6 +1144,15 @@ pub const Key = union(enum) { const b_info = b.external_value; return std.mem.eql(NullTerminatedString, a_info.key, b_info.key); }, + .tuple_type => |a_info| { + const b_info = b.tuple_type; + return a_info.items.len == b_info.items.len and std.mem.eql(Type, a_info.items, b_info.items); + }, + .tuple_value => |a_info| { + const b_info = b.tuple_value; + return a_info.type == b_info.type and + std.mem.eql(Value, a_info.items, b_info.items); + }, }; } }; @@ -1336,6 +1423,19 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key { .key = trail.readNullTerminatedStringSlice(payload.key_count).get(ip), } }; }, + .tuple_type => { + const payload: Tag.TupleType, var trail = data.decode(.tuple_type, item.tag).getTrail(ip); + return .{ .tuple_type = .{ + .items = trail.readTypeSlice(payload.items_len).get(ip), + } }; + }, + .tuple_value => { + const payload: Tag.TupleValue, var trail = data.decode(.tuple_value, item.tag).getTrail(ip); + return .{ .tuple_value = .{ + .type = payload.type, + .items = trail.readValueSlice(payload.items_len).get(ip), + } }; + }, }; } @@ -1439,6 +1539,21 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { trail.writeSlice(s.key); break :brk Item.encode(.external_value, extra); }, + .tuple_type => |s| brk: { + const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TupleType, .{ + .items_len = @intCast(s.items.len), + }, s.items.len); + trail.writeSlice(s.items); + break :brk Item.encode(.tuple_type, extra); + }, + .tuple_value => |s| brk: { + const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TupleValue, .{ + .type = s.type, + .items_len = @intCast(s.items.len), + }, s.items.len); + trail.writeSlice(s.items); + break :brk Item.encode(.tuple_value, extra); + }, }; ip.items.appendAssumeCapacity(item); const index: Value = @enumFromInt(ip.items.len - 1); diff --git a/src/core/Parse.zig b/src/core/Parse.zig index f933f3e58c3aaaf1e5610b2909b8ff3543cd9797..120626eac16464a5bea13eff869cf72a4757c3d0 100644 --- a/src/core/Parse.zig +++ b/src/core/Parse.zig @@ -104,15 +104,216 @@ pub const ExpectedContext = enum { } }; +const Operator = enum(u8) { + add = @intFromEnum(Token.Tag.plus), + sub = @intFromEnum(Token.Tag.minus), + mul = @intFromEnum(Token.Tag.star), + div = @intFromEnum(Token.Tag.slash), + rem = @intFromEnum(Token.Tag.percent), + pow = @intFromEnum(Token.Tag.star_star), + @"and" = @intFromEnum(Token.Tag.and_and), + @"or" = @intFromEnum(Token.Tag.pipe_pipe), + bit_and = @intFromEnum(Token.Tag.@"and"), + bit_or = @intFromEnum(Token.Tag.pipe), + bit_xor = @intFromEnum(Token.Tag.caret), + shl = @intFromEnum(Token.Tag.lt_lt), + shr = @intFromEnum(Token.Tag.gt_gt), + eq = @intFromEnum(Token.Tag.equal_equal), + ne = @intFromEnum(Token.Tag.bang_equal), + lt = @intFromEnum(Token.Tag.lt), + le = @intFromEnum(Token.Tag.lt_equal), + gt = @intFromEnum(Token.Tag.gt), + ge = @intFromEnum(Token.Tag.gt_equal), + // Following operators are assignment operators + assign = @intFromEnum(Token.Tag.equal), + add_assign = @intFromEnum(Token.Tag.plus_equal), + sub_assign = @intFromEnum(Token.Tag.minus_equal), + mul_assign = @intFromEnum(Token.Tag.star_equal), + div_assign = @intFromEnum(Token.Tag.slash_equal), + rem_assign = @intFromEnum(Token.Tag.percent_equal), + pow_assign = @intFromEnum(Token.Tag.star_star_equal), + and_assign = @intFromEnum(Token.Tag.and_and_equal), + or_assign = @intFromEnum(Token.Tag.pipe_pipe_equal), + bit_and_assign = @intFromEnum(Token.Tag.and_equal), + bit_or_assign = @intFromEnum(Token.Tag.pipe_equal), + bit_xor_assign = @intFromEnum(Token.Tag.caret_equal), + shl_assign = @intFromEnum(Token.Tag.lt_lt_equal), + shr_assign = @intFromEnum(Token.Tag.gt_gt_equal), + + const Direction = enum { ltr, rtl }; + + /// Only the binary operator levels are actually used, + /// but all are listed for reference. + const Level = enum { + /// e. e[] e() + accessor, + /// new + new, + /// ++e, --e, !e, ~e, +e, -e, typeof, void, delete + prefix, + /// ** + exponentiation, + /// *, /, % + multiplicative, + /// +, - + additive, + /// <<, >>, >>> + bitwise_shift, + /// <, <=, >, >= + relational, + /// ==, != + equality, + /// & + bit_and, + /// ^ + bit_xor, + /// | + bit_or, + /// && + logical_and, + /// ||, ?? + logical_or, + /// =, +=, -=, *=, /=, %=, / <<=, >>=, >>>=, &=, ^=, |= + assign, + }; + + pub fn direction(op: Operator) Direction { + return switch (op) { + .pow => .rtl, + + .assign, + .add_assign, + .sub_assign, + .mul_assign, + .div_assign, + .rem_assign, + .pow_assign, + .and_assign, + .or_assign, + .bit_and_assign, + .bit_or_assign, + .bit_xor_assign, + .shl_assign, + .shr_assign, + => .rtl, + + else => .ltr, + }; + } + + pub fn level(op: Operator) Level { + return switch (op) { + .pow => .exponentiation, + .mul, .div, .rem => .multiplicative, + .add, .sub => .additive, + .shl, .shr => .bitwise_shift, + .lt, .le, .gt, .ge => .relational, + .eq, .ne => .equality, + .@"and" => .logical_and, + .@"or" => .logical_or, + .bit_and => .bit_and, + .bit_xor => .bit_xor, + .bit_or => .bit_or, + .assign, + .add_assign, + .sub_assign, + .mul_assign, + .div_assign, + .rem_assign, + .pow_assign, + .and_assign, + .or_assign, + .bit_and_assign, + .bit_or_assign, + .bit_xor_assign, + .shl_assign, + .shr_assign, + => .assign, + }; + } + + const lut = brk: { + const token_tags = std.enums.values(Token.Tag); + const op_tags = std.enums.values(Operator); + + var is_operator_lut: [token_tags.len]bool = .{false} ** token_tags.len; + for (op_tags) |op| { + is_operator_lut[@intFromEnum(op)] = true; + } + + var ast_tag_lut: [token_tags.len]Node.Tag = .{.root} ** token_tags.len; + for (op_tags) |op| { + ast_tag_lut[@intFromEnum(op)] = @field(Node.Tag, @tagName(op)); + } + + break :brk struct { + const is_operator = is_operator_lut; + const ast_tag = ast_tag_lut; + }; + }; + + pub fn fromToken(tag: Token.Tag) ?Operator { + return if (lut.is_operator[@intFromEnum(tag)]) + @enumFromInt(@intFromEnum(tag)) + else + null; + } + + pub fn astTag(op: Operator) Node.Tag { + const tag = lut.ast_tag[@intFromEnum(op)]; + assert(tag != .root); // invalid operator or lut construction + return tag; + } + + pub fn isAssign(op: Operator) bool { + return switch (op) { + .assign, + .add_assign, + .sub_assign, + .mul_assign, + .div_assign, + .rem_assign, + .pow_assign, + .and_assign, + .or_assign, + .bit_and_assign, + .bit_or_assign, + .bit_xor_assign, + .shl_assign, + .shr_assign, + => true, + else => false, + }; + } +}; + +const ParseExprMode = enum { + /// Standard parsing + expression, + /// Statements allow assignment binary operators in addition to other expressions. + statement, + /// This bans struct initializations so that parsing stops at a '{' + /// Example: "if hello {" must not be parsed as a struct initialization. + preceding_block, +}; + +/// Some constructs are valid expressions as well as valid top-level +/// declarations. Top level declarations must have associated names, but the +/// anonymous versions of these must not have names. +const ExprParseContext = enum { + declaration, + expression, +}; + +const TypePresence = enum { + types_optional, + types_required, +}; + /// When error.SyntaxError is returned, `p.err` is set to this as well as `p.pos` /// When printing these, token list and Ast is not actually available, so full tokens /// are stored in the errors. pub const AstError = union(enum) { - // /// Failure during tokenization - // tokenization: struct { - // err: Tokenizer.TokenizeError, - // byte: ByteOffset, - // }, /// Unexpected data, expected a token. unexpected_token: struct { expected: Token.Tag, @@ -134,23 +335,6 @@ pub const AstError = union(enum) { pub fn build(err: AstError, ctx: *Report.MsgContext) !void { switch (err) { - // .tokenization => |data| { - // // TODO: consider making tokenizer emit AstError more directly, - // // so that it can just annotate start and ends - // try ctx.msg(switch (data.err) { - // error.UnterminatedString => .{ .codepoint_eol = data.byte }, - // else => .{ .codepoint = data.byte }, - // }, "{s}", .{switch (data.err) { - // error.OutOfMemory => unreachable, - // error.InvalidSyntax => "unexpected token", - // error.TrailingNumericSeparator => "trailing numeric separator", - // error.UnterminatedString => "unterminated string literal", - // error.InvalidEscapeSequence => "invalid escape sequence", - // error.InvalidEscapeCodepoint => "invalid escape codepoint", - // error.InvalidUtf8 => "file is not valid utf8", - // error.GitConflict => "git merge conflict encountered", - // }}); - // }, .unexpected_token => |data| { try ctx.msg(.{ .token_stored = data.got }, "expected {s}, but found {s}", .{ data.expected.description(), @@ -230,7 +414,8 @@ fn parseModuleMembers(p: *Parse) Error!SubList { .kw_pub, => try p.scratch.append(p.gpa, try p.parseModuleDecl()), - .kw_struct => try p.scratch.append(p.gpa, try p.parseStructDecl()), + .kw_struct => try p.scratch.append(p.gpa, try p.parseStructDecl(.declaration)), + .kw_tuple => try p.scratch.append(p.gpa, try p.parseStructDecl(.declaration)), .id => if (p.isContextualKeyword("test")) { try p.scratch.append(p.gpa, try p.parseTestDecl()); @@ -292,14 +477,6 @@ fn parseModuleDecl(p: *Parse) Error!NodeIndex { }; } -/// Some constructs are valid expressions as well as valid top-level -/// declarations. Top level declarations must have associated names, but the -/// anonymous versions of these must not have names. -const ExprParseContext = enum { - declaration, - expression, -}; - fn parseFnDecl(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -339,8 +516,6 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex { }); } -const TypePresence = enum { types_optional, types_required }; - fn parseParamList(p: *Parse, types: TypePresence) Error!SubList { const trace = t.trace(@src()); defer trace.end(); @@ -390,13 +565,16 @@ fn parseParamDecl(p: *Parse, types: TypePresence) Error!NodeIndex { }); } -fn parseStructDecl(p: *Parse) Error!NodeIndex { +fn parseStructDecl(p: *Parse, ctx: ExprParseContext) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); const main_token = p.assertToken(.kw_struct); - _ = try p.expect(.id); + switch (ctx) { + .declaration => _ = try p.expect(.id), + else => {}, + } _ = try p.expect(.start_brace); @@ -409,6 +587,28 @@ fn parseStructDecl(p: *Parse) Error!NodeIndex { }); } +fn parseTupleDecl(p: *Parse, ctx: ExprParseContext) Error!NodeIndex { + const trace = t.trace(@src()); + defer trace.end(); + + const main_token = p.assertToken(.kw_tuple); + + switch (ctx) { + .declaration => _ = try p.expect(.id), + else => {}, + } + + _ = try p.expect(.start_paren); + + const fields = try p.parseExpressionList(.end_paren, .expression); + + return try p.addNode(.{ + .tag = .tuple_type, + .main_token = main_token, + .data = .{ .list = fields }, + }); +} + fn parseTestDecl(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -447,9 +647,7 @@ fn parseStructFieldList(p: *Parse) Error!SubList { try p.scratch.append(p.gpa, try p.parseStructField()); switch (p.currentTag()) { - .comma => { - _ = p.nextToken(); - }, + .comma => _ = p.nextToken(), .end_brace => break, else => return p.failUnexpectedToken(.struct_field), } @@ -578,13 +776,13 @@ fn parseVarDecl(p: *Parse) Error!NodeIndex { _ = try p.expect(.id); const data_type: OptionalNodeIndex = if (p.eatToken(.colon)) |_| - optionalNodeIndex(try p.tryParseExpression(.expression)) + optionalNodeIndex(try p.parseExpressionOrFail(.expression)) else .none; _ = try p.expect(.equal); - const data_init = try p.tryParseExpression(.expression); + const data_init = try p.parseExpressionOrFail(.expression); _ = try p.expect(.semi); @@ -619,204 +817,13 @@ fn isExpressionStart(p: *Parse) bool { .string, .tilde, .kw_fn, + .kw_struct, + .kw_tuple, => true, else => false, }; } -const Operator = enum(u8) { - add = @intFromEnum(Token.Tag.plus), - sub = @intFromEnum(Token.Tag.minus), - mul = @intFromEnum(Token.Tag.star), - div = @intFromEnum(Token.Tag.slash), - rem = @intFromEnum(Token.Tag.percent), - pow = @intFromEnum(Token.Tag.star_star), - @"and" = @intFromEnum(Token.Tag.and_and), - @"or" = @intFromEnum(Token.Tag.pipe_pipe), - bit_and = @intFromEnum(Token.Tag.@"and"), - bit_or = @intFromEnum(Token.Tag.pipe), - bit_xor = @intFromEnum(Token.Tag.caret), - shl = @intFromEnum(Token.Tag.lt_lt), - shr = @intFromEnum(Token.Tag.gt_gt), - eq = @intFromEnum(Token.Tag.equal_equal), - ne = @intFromEnum(Token.Tag.bang_equal), - lt = @intFromEnum(Token.Tag.lt), - le = @intFromEnum(Token.Tag.lt_equal), - gt = @intFromEnum(Token.Tag.gt), - ge = @intFromEnum(Token.Tag.gt_equal), - // Following operators are assignment operators - assign = @intFromEnum(Token.Tag.equal), - add_assign = @intFromEnum(Token.Tag.plus_equal), - sub_assign = @intFromEnum(Token.Tag.minus_equal), - mul_assign = @intFromEnum(Token.Tag.star_equal), - div_assign = @intFromEnum(Token.Tag.slash_equal), - rem_assign = @intFromEnum(Token.Tag.percent_equal), - pow_assign = @intFromEnum(Token.Tag.star_star_equal), - and_assign = @intFromEnum(Token.Tag.and_and_equal), - or_assign = @intFromEnum(Token.Tag.pipe_pipe_equal), - bit_and_assign = @intFromEnum(Token.Tag.and_equal), - bit_or_assign = @intFromEnum(Token.Tag.pipe_equal), - bit_xor_assign = @intFromEnum(Token.Tag.caret_equal), - shl_assign = @intFromEnum(Token.Tag.lt_lt_equal), - shr_assign = @intFromEnum(Token.Tag.gt_gt_equal), - - const Direction = enum { ltr, rtl }; - - /// Only the binary operator levels are actually used, - /// but all are listed for reference. - const Level = enum { - /// e. e[] e() - accessor, - /// new - new, - /// ++e, --e, !e, ~e, +e, -e, typeof, void, delete - prefix, - /// ** - exponentiation, - /// *, /, % - multiplicative, - /// +, - - additive, - /// <<, >>, >>> - bitwise_shift, - /// <, <=, >, >= - relational, - /// ==, != - equality, - /// & - bit_and, - /// ^ - bit_xor, - /// | - bit_or, - /// && - logical_and, - /// ||, ?? - logical_or, - /// =, +=, -=, *=, /=, %=, / <<=, >>=, >>>=, &=, ^=, |= - assign, - }; - - pub fn direction(op: Operator) Direction { - return switch (op) { - .pow => .rtl, - - .assign, - .add_assign, - .sub_assign, - .mul_assign, - .div_assign, - .rem_assign, - .pow_assign, - .and_assign, - .or_assign, - .bit_and_assign, - .bit_or_assign, - .bit_xor_assign, - .shl_assign, - .shr_assign, - => .rtl, - - else => .ltr, - }; - } - - pub fn level(op: Operator) Level { - return switch (op) { - .pow => .exponentiation, - .mul, .div, .rem => .multiplicative, - .add, .sub => .additive, - .shl, .shr => .bitwise_shift, - .lt, .le, .gt, .ge => .relational, - .eq, .ne => .equality, - .@"and" => .logical_and, - .@"or" => .logical_or, - .bit_and => .bit_and, - .bit_xor => .bit_xor, - .bit_or => .bit_or, - .assign, - .add_assign, - .sub_assign, - .mul_assign, - .div_assign, - .rem_assign, - .pow_assign, - .and_assign, - .or_assign, - .bit_and_assign, - .bit_or_assign, - .bit_xor_assign, - .shl_assign, - .shr_assign, - => .assign, - }; - } - - const lut = brk: { - const token_tags = std.enums.values(Token.Tag); - const op_tags = std.enums.values(Operator); - - var is_operator_lut: [token_tags.len]bool = .{false} ** token_tags.len; - for (op_tags) |op| { - is_operator_lut[@intFromEnum(op)] = true; - } - - var ast_tag_lut: [token_tags.len]Node.Tag = .{.root} ** token_tags.len; - for (op_tags) |op| { - ast_tag_lut[@intFromEnum(op)] = @field(Node.Tag, @tagName(op)); - } - - break :brk struct { - const is_operator = is_operator_lut; - const ast_tag = ast_tag_lut; - }; - }; - - pub fn fromToken(tag: Token.Tag) ?Operator { - return if (lut.is_operator[@intFromEnum(tag)]) - @enumFromInt(@intFromEnum(tag)) - else - null; - } - - pub fn astTag(op: Operator) Node.Tag { - const tag = lut.ast_tag[@intFromEnum(op)]; - assert(tag != .root); // invalid operator or lut construction - return tag; - } - - pub fn isAssign(op: Operator) bool { - return switch (op) { - .assign, - .add_assign, - .sub_assign, - .mul_assign, - .div_assign, - .rem_assign, - .pow_assign, - .and_assign, - .or_assign, - .bit_and_assign, - .bit_or_assign, - .bit_xor_assign, - .shl_assign, - .shr_assign, - => true, - else => false, - }; - } -}; - -const ParseExprMode = enum { - /// Standard parsing - expression, - /// Statements allow assignment binary operators in addition to other expressions. - statement, - /// This bans struct initializations so that parsing stops at a '{' - /// Example: "if hello {" must not be parsed as a struct initialization. - preceding_block, -}; - fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -831,8 +838,13 @@ fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { // These unary operators are greedy, so handle them first. switch (p.currentTag()) { - .kw_return => return p.parseReturn(), - .kw_break => return p.parseBreak(), + .kw_return => return p.parseReturn(mode), + .kw_break => return p.parseBreak(mode), + + .kw_assert => return p.parseGreedyUnary(.assert, mode), + .kw_comptime => return p.parseGreedyUnary(.@"comptime", mode), + .kw_pure => return p.parseGreedyUnary(.pure, mode), + else => {}, } @@ -846,7 +858,7 @@ fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { return expr; } -fn tryParseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { +fn parseExpressionOrFail(p: *Parse, mode: ParseExprMode) Error!NodeIndex { return if (p.isExpressionStart()) p.parseExpression(mode) else @@ -858,7 +870,6 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { defer trace.end(); var expr = switch (p.currentTag()) { - // Literals .id => if (p.nextTag() == .colon) try p.parseLabelExpression() else @@ -867,24 +878,27 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { .number_float => try p.blankNode(.number_float), .string => try p.blankNode(.string), - // Complex expressions .builtin_id => try p.parseBuiltinCall(), - .dot => try p.parseAnonInstantiation(), - .kw_break => try p.parseBreak(), + .dot => try p.parseAnonStructInit(), .kw_fn => try p.parseAnonFunction(), .kw_if => try p.parseIf(), - .kw_return => try p.parseReturn(), + .kw_tuple => try p.parseTupleType(), + .start_brace => try p.parseBlock(), .start_bracket => try p.parseArrayValueOrType(mode), - .start_paren => try p.parseTupleType(), + .start_paren => try p.parseTupleInit(), + + .kw_struct => try p.parseStructDecl(.expression), + + .kw_return => return p.parseReturn(mode), + .kw_break => return p.parseBreak(mode), + .kw_assert => return p.parseGreedyUnary(.assert, mode), + .kw_comptime => return p.parseGreedyUnary(.@"comptime", mode), + .kw_pure => return p.parseGreedyUnary(.pure, mode), - // Unary operators inline .bang, .tilde, .minus, - .kw_comptime, - .kw_pure, - .kw_assert, => |tag| unary: { const main_token = p.nextToken(); const expr = try p.parseExpressionPart(mode); @@ -955,7 +969,7 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { return p.fail(.{ .array_type_flipped_hint = p.fullToken(p.pos) }); } - const index = try p.tryParseExpression(.expression); + const index = try p.parseExpressionOrFail(.expression); _ = try p.expect(.end_bracket); @@ -1020,14 +1034,14 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, mode: ParseExprMod }); } -fn parseReturn(p: *Parse) Error!NodeIndex { +fn parseReturn(p: *Parse, mode: ParseExprMode) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); const main_token = p.assertToken(.kw_return); const expr = if (p.isExpressionStart()) - try p.parseExpression(.expression) + try p.parseExpression(mode) else null; @@ -1040,7 +1054,7 @@ fn parseReturn(p: *Parse) Error!NodeIndex { }); } -fn parseBreak(p: *Parse) Error!NodeIndex { +fn parseBreak(p: *Parse, mode: ParseExprMode) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -1052,7 +1066,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex { null; const expr = if (p.isExpressionStart()) - try p.parseExpression(.expression) + try p.parseExpression(mode) else null; @@ -1066,6 +1080,23 @@ fn parseBreak(p: *Parse) Error!NodeIndex { }); } +fn parseGreedyUnary(p: *Parse, tag: Node.Tag, mode: ParseExprMode) Error!NodeIndex { + const trace = t.trace(@src()); + defer trace.end(); + + const main_token = p.nextToken(); + + const expr = try p.parseExpressionOrFail(mode); + + return try p.addNode(.{ + .tag = tag, + .main_token = main_token, + .data = .{ .un = .{ + .expr = expr, + } }, + }); +} + fn parseBuiltinCall(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -1083,11 +1114,7 @@ fn parseBuiltinCall(p: *Parse) Error!NodeIndex { }); } -fn parseExpressionList( - p: *Parse, - comptime end_token: Token.Tag, - comptime fail_context: ExpectedContext, -) Error!SubList { +fn parseExpressionList(p: *Parse, comptime end_token: Token.Tag, fail_context: ExpectedContext) Error!SubList { const trace = t.trace(@src()); defer trace.end(); @@ -1161,7 +1188,7 @@ fn parseElseValue(p: *Parse) Error!NodeIndex { }; } -fn parseAnonInstantiation(p: *Parse) Error!NodeIndex { +fn parseAnonStructInit(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -1272,9 +1299,20 @@ fn parseArrayValueOrType(p: *Parse, mode: ParseExprMode) Error!NodeIndex { }); } -fn parseTupleType(p: *Parse) Error!NodeIndex { +fn parseTupleInit(p: *Parse) Error!NodeIndex { const main_token = p.assertToken(.start_paren); const list = try p.parseExpressionList(.end_paren, .expression); + return p.addNode(.{ + .tag = .tuple_init, + .main_token = main_token, + .data = .{ .list = list }, + }); +} + +fn parseTupleType(p: *Parse) Error!NodeIndex { + const main_token = p.assertToken(.kw_tuple); + _ = try p.expect(.start_paren); + const list = try p.parseExpressionList(.end_paren, .expression); return p.addNode(.{ .tag = .tuple_type, .main_token = main_token, diff --git a/src/core/Sema.zig b/src/core/Sema.zig index cd5881ac5810b77ffc6fb5b0da7d2f552fd074e0..a3bade6fede8c25c716f12b5b9414de64c8a4117 100644 --- a/src/core/Sema.zig +++ b/src/core/Sema.zig @@ -1615,6 +1615,14 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst .value => |v| { const ty = v.typeOf(s.ip); switch (ty) { + // Allowed at runtime + .bool, + .float, + .int32, + .string, + .void, + .empty_tuple, + => {}, .numeric_literal => { const float = v.toFloat(s.ip) orelse unreachable; @@ -1649,13 +1657,6 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst .type = ty, }), .@"opaque" => @panic("TODO: represent comptime-known opaques better"), - // Allowed at runtime - .bool, - .float, - .int32, - .string, - .void, - => {}, _ => if (!ty.hasPossibleRepresentation(s.ip)) { return s.fail(.runtime_incompatible_type, .{ .src = src, diff --git a/src/core/Teg.Dump.zig b/src/core/Teg.Dump.zig index 222c7f1beed361a53fc35f22e250a0335b73a4a0..71bccf7dbebad6757c530ff9bf2a823f2b64467d 100644 --- a/src/core/Teg.Dump.zig +++ b/src/core/Teg.Dump.zig @@ -319,6 +319,24 @@ const State = struct { if (data.type_hint != .none) try writer.writeAll(")"); }, + .tuple_type => { + const data: Teg.TupleType = teg.instPayload(.tuple_type, inst); + + try writer.writeAll("("); + try s.dumpRefMarkerList(writer, data.items.slice(teg), '[', ']'); + try writer.writeAll(")"); + }, + .tuple_init => { + const data: Teg.TupleInit = teg.instPayload(.tuple_init, inst); + + try writer.writeAll("("); + if (data.type_hint.unwrap()) |type_hint| { + try s.dumpRef(writer, type_hint); + try writer.writeAll(", "); + } + try s.dumpRefMarkerList(writer, data.items.slice(teg), '[', ']'); + try writer.writeAll(")"); + }, .result_type_argument => { const data: Teg.ResultTypeArgument = teg.instData(.result_type_argument, inst); diff --git a/src/core/Teg.zig b/src/core/Teg.zig index e6e2b340e3386a9622c72ddf865abcfaa1c75c94..f54ec2cf2e3d2b5fd86f38ac2cae4811d56e3508 100644 --- a/src/core/Teg.zig +++ b/src/core/Teg.zig @@ -75,6 +75,9 @@ pub const Tag = enum(u8) { struct_init, array_init, + tuple_init, + tuple_type, + add, sub, mul, @@ -136,6 +139,8 @@ pub const Tag = enum(u8) { .struct_decl => PlNode(StructDecl), .struct_init => PlNode(StructInit), .array_init => PlNode(ArrayInit), + .tuple_type => PlNode(TupleType), + .tuple_init => PlNode(TupleInit), .func => PlNode(Func), .load_decl => StrNode, @@ -230,7 +235,7 @@ pub const Inst = struct { main_module_inst = 0, _, - pub const static_len = @intFromEnum(Ref.empty_array) + 1; + pub const static_len = @intFromEnum(Ref.empty_tuple) + 1; pub fn toRef(i: Index) Inst.Ref { return @enumFromInt(static_len + @intFromEnum(i)); @@ -269,6 +274,7 @@ pub const Inst = struct { numeric_literal_type, numeric_literal_array_type, never_array_type, + empty_tuple_type, void, bool_true, @@ -278,7 +284,7 @@ pub const Inst = struct { nan, empty_string, empty_array, - + empty_tuple, _, pub const Decoded = union(enum) { @@ -423,6 +429,20 @@ pub const ArrayInit = struct { list: RefList, }; +pub const TupleType = struct { + /// The items in the tuple + items: RefList, +}; + +pub const TupleInit = struct { + /// Optionally provide a result type for more reliable type coercion. + /// If specified, this must be an array type. Otherwise, the first + /// element's type is used. + type_hint: Inst.Ref.Optional, + /// The items in the tuple + items: RefList, +}; + pub const Decl = struct { name: NullTerminatedString, hash: Cache.BinDigest, @@ -697,6 +717,8 @@ pub fn writeToHasher( .struct_decl, .struct_init, .array_init, + .tuple_init, + .tuple_type, .func, .store_decl, .store_field, @@ -902,6 +924,19 @@ fn writePlNodeInnerToHasher( hasher.add(isolatedIndexValue(decl_index, data.body)); hasher.add(data.instruction_count); }, + TupleType => { + hasher.add(data.items.len); + for (data.items.slice(teg)) |item| { + hasher.add(isolatedRefValue(decl_index, item)); + } + }, + TupleInit => { + if (data.type_hint.unwrap()) |type_hint| + hasher.add(isolatedRefValue(decl_index, type_hint)) + else + hasher.add(@as(i32, std.math.maxInt(i32))); + writeRefListToHasher(hasher, decl_index, data.items.slice(teg)); + }, else => @compileError("hash not implemented for " ++ @typeName(T)), } } diff --git a/src/core/Tokenizer.zig b/src/core/Tokenizer.zig index 2ed112a4e62969157e0190d5fcb6f0da3e1520b3..6ddb2db422c8e5e09ee001c738797336d0539233 100644 --- a/src/core/Tokenizer.zig +++ b/src/core/Tokenizer.zig @@ -75,6 +75,7 @@ pub const Token = struct { kw_return, kw_struct, kw_trait, + kw_tuple, kw_var, kw_while, // sort-lines: end diff --git a/src/core/Transform.zig b/src/core/Transform.zig index 32509ffc2eba9d367bf18111af3fbe6ec57ee987..785be5697d6eea5a478ffbcf822c8e8fe7818190 100644 --- a/src/core/Transform.zig +++ b/src/core/Transform.zig @@ -903,6 +903,7 @@ fn transformExpression( switch (node_tag) { .root, .param_decl, + .struct_field, => unreachable, // not an expression .id => { @@ -1520,6 +1521,37 @@ fn transformExpression( }); }, + .tuple_init => { + const tuple_init = t.ast.nodeData(node).list; + + const items = try t.transformExpressionList(ctx, .none, tuple_init.slice(&t.ast)) orelse return null; + + if (items.len == 0) { + return .empty_tuple; + } + + return try t.addRef(.tuple_init, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.TupleInit, .{ + .type_hint = .none, + .items = items, + }), + }); + }, + + .tuple_type => { + const tuple_type = t.ast.nodeData(node).list; + + const items = try t.transformExpressionList(ctx, .none, tuple_type.slice(&t.ast)) orelse return null; + + return try t.addRef(.tuple_type, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.TupleType, .{ + .items = items, + }), + }); + }, + inline .fn_decl, .fn_expr => |tag| { const data = switch (tag) { .fn_decl => t.ast.fullFnDecl(node), diff --git a/src/core/js/NodeGen.zig b/src/core/js/NodeGen.zig index 5ed8f7017f896b336b79b901ef3c3c3b721e508f..14d2d9ce4b57ac5fc3c48b1da6438e19ec86c2c1 100644 --- a/src/core/js/NodeGen.zig +++ b/src/core/js/NodeGen.zig @@ -557,6 +557,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { .simple_type, .struct_type, .array_type, + .tuple_type, => unreachable, // Type values are not allowed here .int32, @@ -566,6 +567,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { .simple_value, .struct_value, .array_value, + .tuple_value, => return try c.add(.plain_value, .{ .value = v }), .external_value => { diff --git a/test/behavior/tuple b/test/behavior/tuple new file mode 100644 index 0000000000000000000000000000000000000000..b9c2bf8b2ef8391fe5d333ba7e4598df2e983995 --- /dev/null +++ b/test/behavior/tuple @@ -0,0 +1,14 @@ +let assertEqual = @extern("assertEqual"); + +fn hello() -> tuple (int, float) { + (5, 2.5) +} + +fn main { + let x = hello(); + assertEqual(x.0, 5); + assertEqual(x.1, 2.5); +} + +fn f(a: float) -> float { a } +fn i(a: int) -> int { a }