const std = @import("std"); const mem = std.mem; const Allocator = mem.Allocator; const assert = std.debug.assert; const big = std.math.big; const Compilation = @import("Compilation.zig"); const Source = @import("Source.zig"); const Tokenizer = @import("Tokenizer.zig"); const Preprocessor = @import("Preprocessor.zig"); const Tree = @import("Tree.zig"); const Token = Tree.Token; const TokenIndex = Tree.TokenIndex; const NodeIndex = Tree.NodeIndex; const Type = @import("Type.zig"); const Diagnostics = @import("Diagnostics.zig"); const NodeList = std.ArrayList(NodeIndex); const InitList = @import("InitList.zig"); const Attribute = @import("Attribute.zig"); const CharInfo = @import("CharInfo.zig"); const CharLiteral = @import("CharLiteral.zig"); const Value = @import("Value.zig"); const SymbolStack = @import("SymbolStack.zig"); const Symbol = SymbolStack.Symbol; const record_layout = @import("record_layout.zig"); const StringId = @import("StringInterner.zig").StringId; const number_affixes = @import("number_affixes.zig"); const NumberPrefix = number_affixes.Prefix; const NumberSuffix = number_affixes.Suffix; const BuiltinFunction = @import("builtins/BuiltinFunction.zig"); const target_util = @import("target.zig"); const Parser = @This(); const Switch = struct { default: ?TokenIndex = null, ranges: std.ArrayList(Range), ty: Type, const Range = struct { first: Value, last: Value, tok: TokenIndex, }; fn add( self: *Switch, comp: *Compilation, first: Value, last: Value, tok: TokenIndex, ) !?Range { for (self.ranges.items) |range| { if (last.compare(.gte, range.first, self.ty, comp) and first.compare(.lte, range.last, self.ty, comp)) { return range; // They overlap. } } try self.ranges.append(.{ .first = first, .last = last, .tok = tok, }); return null; } }; const Label = union(enum) { unresolved_goto: TokenIndex, label: TokenIndex, }; pub const Error = Compilation.Error || error{ParsingFailed}; /// An attribute that has been parsed but not yet validated in its context const TentativeAttribute = struct { attr: Attribute, tok: TokenIndex, }; /// How the parser handles const int decl references when it is expecting an integer /// constant expression. const ConstDeclFoldingMode = enum { /// fold const decls as if they were literals fold_const_decls, /// fold const decls as if they were literals and issue GNU extension diagnostic gnu_folding_extension, /// fold const decls as if they were literals and issue VLA diagnostic gnu_vla_folding_extension, /// folding const decls is prohibited; return an unavailable value no_const_decl_folding, }; // values from preprocessor pp: *Preprocessor, comp: *Compilation, gpa: mem.Allocator, tok_ids: []const Token.Id, tok_i: TokenIndex = 0, // values of the incomplete Tree arena: Allocator, nodes: Tree.Node.List = .{}, data: NodeList, retained_strings: std.ArrayList(u8), value_map: Tree.ValueMap, // buffers used during compilation syms: SymbolStack = .{}, strings: std.ArrayList(u8), labels: std.ArrayList(Label), list_buf: NodeList, decl_buf: NodeList, param_buf: std.ArrayList(Type.Func.Param), enum_buf: std.ArrayList(Type.Enum.Field), record_buf: std.ArrayList(Type.Record.Field), attr_buf: std.MultiArrayList(TentativeAttribute) = .{}, attr_application_buf: std.ArrayListUnmanaged(Attribute) = .{}, field_attr_buf: std.ArrayList([]const Attribute), /// type name -> variable name location for tentative definitions (top-level defs with thus-far-incomplete types) /// e.g. `struct Foo bar;` where `struct Foo` is not defined yet. /// The key is the StringId of `Foo` and the value is the TokenIndex of `bar` /// Items are removed if the type is subsequently completed with a definition. /// We only store the first tentative definition that uses a given type because this map is only used /// for issuing an error message, and correcting the first error for a type will fix all of them for that type. tentative_defs: std.AutoHashMapUnmanaged(StringId, TokenIndex) = .{}, // configuration and miscellaneous info no_eval: bool = false, in_macro: bool = false, extension_suppressed: bool = false, contains_address_of_label: bool = false, label_count: u32 = 0, const_decl_folding: ConstDeclFoldingMode = .fold_const_decls, /// location of first computed goto in function currently being parsed /// if a computed goto is used, the function must contain an /// address-of-label expression (tracked with contains_address_of_label) computed_goto_tok: ?TokenIndex = null, /// Various variables that are different for each function. func: struct { /// null if not in function, will always be plain func, var_args_func or old_style_func ty: ?Type = null, name: TokenIndex = 0, ident: ?Result = null, pretty_ident: ?Result = null, } = .{}, /// Various variables that are different for each record. record: struct { // invalid means we're not parsing a record kind: Token.Id = .invalid, flexible_field: ?TokenIndex = null, start: usize = 0, field_attr_start: usize = 0, fn addField(r: @This(), p: *Parser, name: StringId, tok: TokenIndex) Error!void { var i = p.record_members.items.len; while (i > r.start) { i -= 1; if (p.record_members.items[i].name == name) { try p.errStr(.duplicate_member, tok, p.tokSlice(tok)); try p.errTok(.previous_definition, p.record_members.items[i].tok); break; } } try p.record_members.append(p.gpa, .{ .name = name, .tok = tok }); } fn addFieldsFromAnonymous(r: @This(), p: *Parser, ty: Type) Error!void { for (ty.data.record.fields) |f| { if (f.isAnonymousRecord()) { try r.addFieldsFromAnonymous(p, f.ty.canonicalize(.standard)); } else if (f.name_tok != 0) { try r.addField(p, f.name, f.name_tok); } } } } = .{}, record_members: std.ArrayListUnmanaged(struct { tok: TokenIndex, name: StringId }) = .{}, @"switch": ?*Switch = null, in_loop: bool = false, pragma_pack: ?u8 = null, string_ids: struct { declspec_id: StringId, main_id: StringId, file: StringId, jmp_buf: StringId, sigjmp_buf: StringId, ucontext_t: StringId, }, /// Checks codepoint for various pedantic warnings /// Returns true if diagnostic issued fn checkIdentifierCodepointWarnings(comp: *Compilation, codepoint: u21, loc: Source.Location) Compilation.Error!bool { assert(codepoint >= 0x80); const err_start = comp.diag.list.items.len; if (!CharInfo.isC99IdChar(codepoint)) { try comp.diag.add(.{ .tag = .c99_compat, .loc = loc, }, &.{}); } if (CharInfo.isInvisible(codepoint)) { try comp.diag.add(.{ .tag = .unicode_zero_width, .loc = loc, .extra = .{ .actual_codepoint = codepoint }, }, &.{}); } if (CharInfo.homoglyph(codepoint)) |resembles| { try comp.diag.add(.{ .tag = .unicode_homoglyph, .loc = loc, .extra = .{ .codepoints = .{ .actual = codepoint, .resembles = resembles } }, }, &.{}); } return comp.diag.list.items.len != err_start; } /// Issues diagnostics for the current extended identifier token /// Return value indicates whether the token should be considered an identifier /// true means consider the token to actually be an identifier /// false means it is not fn validateExtendedIdentifier(p: *Parser) !bool { assert(p.tok_ids[p.tok_i] == .extended_identifier); const slice = p.tokSlice(p.tok_i); const view = std.unicode.Utf8View.init(slice) catch { try p.errTok(.invalid_utf8, p.tok_i); return error.FatalError; }; var it = view.iterator(); var valid_identifier = true; var warned = false; var len: usize = 0; var invalid_char: u21 = undefined; var loc = p.pp.tokens.items(.loc)[p.tok_i]; const standard = p.comp.langopts.standard; while (it.nextCodepoint()) |codepoint| { defer { len += 1; loc.byte_offset += std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable; } if (codepoint == '$') { warned = true; try p.comp.diag.add(.{ .tag = .dollar_in_identifier_extension, .loc = loc, }, &.{}); } if (codepoint <= 0x7F) continue; if (!valid_identifier) continue; const allowed = standard.codepointAllowedInIdentifier(codepoint, len == 0); if (!allowed) { invalid_char = codepoint; valid_identifier = false; continue; } if (!warned) { warned = try checkIdentifierCodepointWarnings(p.comp, codepoint, loc); } } if (!valid_identifier) { if (len == 1) { try p.errExtra(.unexpected_character, p.tok_i, .{ .actual_codepoint = invalid_char }); return false; } else { try p.errExtra(.invalid_identifier_start_char, p.tok_i, .{ .actual_codepoint = invalid_char }); } } return true; } fn eatIdentifier(p: *Parser) !?TokenIndex { switch (p.tok_ids[p.tok_i]) { .identifier => {}, .extended_identifier => { if (!try p.validateExtendedIdentifier()) { p.tok_i += 1; return null; } }, else => return null, } p.tok_i += 1; // Handle illegal '$' characters in identifiers if (!p.comp.langopts.dollars_in_identifiers) { if (p.tok_ids[p.tok_i] == .invalid and p.tokSlice(p.tok_i)[0] == '$') { try p.err(.dollars_in_identifiers); p.tok_i += 1; return error.ParsingFailed; } } return p.tok_i - 1; } fn expectIdentifier(p: *Parser) Error!TokenIndex { const actual = p.tok_ids[p.tok_i]; if (actual != .identifier and actual != .extended_identifier) { return p.errExpectedToken(.identifier, actual); } return (try p.eatIdentifier()) orelse unreachable; } fn eatToken(p: *Parser, id: Token.Id) ?TokenIndex { assert(id != .identifier and id != .extended_identifier); // use eatIdentifier if (p.tok_ids[p.tok_i] == id) { defer p.tok_i += 1; return p.tok_i; } else return null; } fn expectToken(p: *Parser, expected: Token.Id) Error!TokenIndex { assert(expected != .identifier and expected != .extended_identifier); // use expectIdentifier const actual = p.tok_ids[p.tok_i]; if (actual != expected) return p.errExpectedToken(expected, actual); defer p.tok_i += 1; return p.tok_i; } pub fn tokSlice(p: *Parser, tok: TokenIndex) []const u8 { if (p.tok_ids[tok].lexeme()) |some| return some; const loc = p.pp.tokens.items(.loc)[tok]; var tmp_tokenizer = Tokenizer{ .buf = p.comp.getSource(loc.id).buf, .comp = p.comp, .index = loc.byte_offset, .source = .generated, }; const res = tmp_tokenizer.next(); return tmp_tokenizer.buf[res.start..res.end]; } fn expectClosing(p: *Parser, opening: TokenIndex, id: Token.Id) Error!void { _ = p.expectToken(id) catch |e| { if (e == error.ParsingFailed) { try p.errTok(switch (id) { .r_paren => .to_match_paren, .r_brace => .to_match_brace, .r_bracket => .to_match_brace, else => unreachable, }, opening); } return e; }; } fn errOverflow(p: *Parser, op_tok: TokenIndex, res: Result) !void { if (res.ty.isUnsignedInt(p.comp)) { try p.errExtra(.overflow_unsigned, op_tok, .{ .unsigned = res.val.data.int }); } else { try p.errExtra(.overflow_signed, op_tok, .{ .signed = res.val.signExtend(res.ty, p.comp) }); } } fn errExpectedToken(p: *Parser, expected: Token.Id, actual: Token.Id) Error { switch (actual) { .invalid => try p.errExtra(.expected_invalid, p.tok_i, .{ .tok_id_expected = expected }), .eof => try p.errExtra(.expected_eof, p.tok_i, .{ .tok_id_expected = expected }), else => try p.errExtra(.expected_token, p.tok_i, .{ .tok_id = .{ .expected = expected, .actual = actual, } }), } return error.ParsingFailed; } pub fn errStr(p: *Parser, tag: Diagnostics.Tag, tok_i: TokenIndex, str: []const u8) Compilation.Error!void { @setCold(true); return p.errExtra(tag, tok_i, .{ .str = str }); } pub fn errExtra(p: *Parser, tag: Diagnostics.Tag, tok_i: TokenIndex, extra: Diagnostics.Message.Extra) Compilation.Error!void { @setCold(true); const tok = p.pp.tokens.get(tok_i); var loc = tok.loc; if (tok_i != 0 and tok.id == .eof) { // if the token is EOF, point at the end of the previous token instead const prev = p.pp.tokens.get(tok_i - 1); loc = prev.loc; loc.byte_offset += @intCast(p.tokSlice(tok_i - 1).len); } try p.comp.diag.add(.{ .tag = tag, .loc = loc, .extra = extra, }, tok.expansionSlice()); } pub fn errTok(p: *Parser, tag: Diagnostics.Tag, tok_i: TokenIndex) Compilation.Error!void { @setCold(true); return p.errExtra(tag, tok_i, .{ .none = {} }); } pub fn err(p: *Parser, tag: Diagnostics.Tag) Compilation.Error!void { @setCold(true); return p.errExtra(tag, p.tok_i, .{ .none = {} }); } pub fn todo(p: *Parser, msg: []const u8) Error { try p.errStr(.todo, p.tok_i, msg); return error.ParsingFailed; } pub fn typeStr(p: *Parser, ty: Type) ![]const u8 { if (Type.Builder.fromType(ty).str(p.comp.langopts)) |str| return str; const strings_top = p.strings.items.len; defer p.strings.items.len = strings_top; const mapper = p.comp.string_interner.getSlowTypeMapper(); try ty.print(mapper, p.comp.langopts, p.strings.writer()); return try p.comp.diag.arena.allocator().dupe(u8, p.strings.items[strings_top..]); } pub fn typePairStr(p: *Parser, a: Type, b: Type) ![]const u8 { return p.typePairStrExtra(a, " and ", b); } pub fn typePairStrExtra(p: *Parser, a: Type, msg: []const u8, b: Type) ![]const u8 { const strings_top = p.strings.items.len; defer p.strings.items.len = strings_top; try p.strings.append('\''); const mapper = p.comp.string_interner.getSlowTypeMapper(); try a.print(mapper, p.comp.langopts, p.strings.writer()); try p.strings.append('\''); try p.strings.appendSlice(msg); try p.strings.append('\''); try b.print(mapper, p.comp.langopts, p.strings.writer()); try p.strings.append('\''); return try p.comp.diag.arena.allocator().dupe(u8, p.strings.items[strings_top..]); } pub fn floatValueChangedStr(p: *Parser, res: *Result, old_value: f64, int_ty: Type) ![]const u8 { const strings_top = p.strings.items.len; defer p.strings.items.len = strings_top; var w = p.strings.writer(); const type_pair_str = try p.typePairStrExtra(res.ty, " to ", int_ty); try w.writeAll(type_pair_str); const is_zero = res.val.isZero(); const non_zero_str: []const u8 = if (is_zero) "non-zero " else ""; if (int_ty.is(.bool)) { try w.print(" changes {s}value from {d} to {}", .{ non_zero_str, old_value, res.val.getBool() }); } else if (int_ty.isUnsignedInt(p.comp)) { try w.print(" changes {s}value from {d} to {d}", .{ non_zero_str, old_value, res.val.getInt(u64) }); } else { try w.print(" changes {s}value from {d} to {d}", .{ non_zero_str, old_value, res.val.getInt(i64) }); } return try p.comp.diag.arena.allocator().dupe(u8, p.strings.items[strings_top..]); } fn checkDeprecatedUnavailable(p: *Parser, ty: Type, usage_tok: TokenIndex, decl_tok: TokenIndex) !void { if (ty.getAttribute(.@"error")) |@"error"| { const strings_top = p.strings.items.len; defer p.strings.items.len = strings_top; const w = p.strings.writer(); const msg_str = p.retainedString(@"error".msg); try w.print("call to '{s}' declared with attribute error: {s}", .{ p.tokSlice(@"error".__name_tok), msg_str }); const str = try p.comp.diag.arena.allocator().dupe(u8, p.strings.items[strings_top..]); try p.errStr(.error_attribute, usage_tok, str); } if (ty.getAttribute(.warning)) |warning| { const strings_top = p.strings.items.len; defer p.strings.items.len = strings_top; const w = p.strings.writer(); const msg_str = p.retainedString(warning.msg); try w.print("call to '{s}' declared with attribute warning: {s}", .{ p.tokSlice(warning.__name_tok), msg_str }); const str = try p.comp.diag.arena.allocator().dupe(u8, p.strings.items[strings_top..]); try p.errStr(.warning_attribute, usage_tok, str); } if (ty.getAttribute(.unavailable)) |unavailable| { try p.errDeprecated(.unavailable, usage_tok, unavailable.msg); try p.errStr(.unavailable_note, unavailable.__name_tok, p.tokSlice(decl_tok)); return error.ParsingFailed; } else if (ty.getAttribute(.deprecated)) |deprecated| { try p.errDeprecated(.deprecated_declarations, usage_tok, deprecated.msg); try p.errStr(.deprecated_note, deprecated.__name_tok, p.tokSlice(decl_tok)); } } /// Returned slice is invalidated if additional strings are added to p.retained_strings fn retainedString(p: *Parser, range: Value.ByteRange) []const u8 { return range.slice(p.retained_strings.items); } fn errDeprecated(p: *Parser, tag: Diagnostics.Tag, tok_i: TokenIndex, msg: ?Value.ByteRange) Compilation.Error!void { const strings_top = p.strings.items.len; defer p.strings.items.len = strings_top; const w = p.strings.writer(); try w.print("'{s}' is ", .{p.tokSlice(tok_i)}); const reason: []const u8 = switch (tag) { .unavailable => "unavailable", .deprecated_declarations => "deprecated", else => unreachable, }; try w.writeAll(reason); if (msg) |m| { const str = p.retainedString(m); try w.print(": {s}", .{str}); } const str = try p.comp.diag.arena.allocator().dupe(u8, p.strings.items[strings_top..]); return p.errStr(tag, tok_i, str); } fn addNode(p: *Parser, node: Tree.Node) Allocator.Error!NodeIndex { if (p.in_macro) return .none; const res = p.nodes.len; try p.nodes.append(p.gpa, node); return @enumFromInt(res); } fn addList(p: *Parser, nodes: []const NodeIndex) Allocator.Error!Tree.Node.Range { if (p.in_macro) return Tree.Node.Range{ .start = 0, .end = 0 }; const start: u32 = @intCast(p.data.items.len); try p.data.appendSlice(nodes); const end: u32 = @intCast(p.data.items.len); return Tree.Node.Range{ .start = start, .end = end }; } fn findLabel(p: *Parser, name: []const u8) ?TokenIndex { for (p.labels.items) |item| { switch (item) { .label => |l| if (mem.eql(u8, p.tokSlice(l), name)) return l, .unresolved_goto => {}, } } return null; } fn nodeIs(p: *Parser, node: NodeIndex, tag: Tree.Tag) bool { return p.getNode(node, tag) != null; } fn getNode(p: *Parser, node: NodeIndex, tag: Tree.Tag) ?NodeIndex { var cur = node; const tags = p.nodes.items(.tag); const data = p.nodes.items(.data); while (true) { const cur_tag = tags[@intFromEnum(cur)]; if (cur_tag == .paren_expr) { cur = data[@intFromEnum(cur)].un; } else if (cur_tag == tag) { return cur; } else { return null; } } } fn pragma(p: *Parser) Compilation.Error!bool { var found_pragma = false; while (p.eatToken(.keyword_pragma)) |_| { found_pragma = true; const name_tok = p.tok_i; const name = p.tokSlice(name_tok); const end_idx = mem.indexOfScalarPos(Token.Id, p.tok_ids, p.tok_i, .nl).?; const pragma_len = @as(TokenIndex, @intCast(end_idx)) - p.tok_i; defer p.tok_i += pragma_len + 1; // skip past .nl as well if (p.comp.getPragma(name)) |prag| { try prag.parserCB(p, p.tok_i); } } return found_pragma; } /// Issue errors for top-level definitions whose type was never completed. fn diagnoseIncompleteDefinitions(p: *Parser) !void { @setCold(true); const node_slices = p.nodes.slice(); const tags = node_slices.items(.tag); const tys = node_slices.items(.ty); const data = node_slices.items(.data); const err_start = p.comp.diag.list.items.len; for (p.decl_buf.items) |decl_node| { const idx = @intFromEnum(decl_node); switch (tags[idx]) { .struct_forward_decl, .union_forward_decl, .enum_forward_decl => {}, else => continue, } const ty = tys[idx]; const decl_type_name = if (ty.getRecord()) |rec| rec.name else if (ty.get(.@"enum")) |en| en.data.@"enum".name else unreachable; const tentative_def_tok = p.tentative_defs.get(decl_type_name) orelse continue; const type_str = try p.typeStr(ty); try p.errStr(.tentative_definition_incomplete, tentative_def_tok, type_str); try p.errStr(.forward_declaration_here, data[idx].decl_ref, type_str); } const errors_added = p.comp.diag.list.items.len - err_start; assert(errors_added == 2 * p.tentative_defs.count()); // Each tentative def should add an error + note } /// root : (decl | assembly ';' | staticAssert)* pub fn parse(pp: *Preprocessor) Compilation.Error!Tree { assert(pp.linemarkers == .none); pp.comp.pragmaEvent(.before_parse); var arena = std.heap.ArenaAllocator.init(pp.comp.gpa); errdefer arena.deinit(); var p = Parser{ .pp = pp, .comp = pp.comp, .gpa = pp.comp.gpa, .arena = arena.allocator(), .tok_ids = pp.tokens.items(.id), .strings = std.ArrayList(u8).init(pp.comp.gpa), .retained_strings = std.ArrayList(u8).init(pp.comp.gpa), .value_map = Tree.ValueMap.init(pp.comp.gpa), .data = NodeList.init(pp.comp.gpa), .labels = std.ArrayList(Label).init(pp.comp.gpa), .list_buf = NodeList.init(pp.comp.gpa), .decl_buf = NodeList.init(pp.comp.gpa), .param_buf = std.ArrayList(Type.Func.Param).init(pp.comp.gpa), .enum_buf = std.ArrayList(Type.Enum.Field).init(pp.comp.gpa), .record_buf = std.ArrayList(Type.Record.Field).init(pp.comp.gpa), .field_attr_buf = std.ArrayList([]const Attribute).init(pp.comp.gpa), .string_ids = .{ .declspec_id = try pp.comp.intern("__declspec"), .main_id = try pp.comp.intern("main"), .file = try pp.comp.intern("FILE"), .jmp_buf = try pp.comp.intern("jmp_buf"), .sigjmp_buf = try pp.comp.intern("sigjmp_buf"), .ucontext_t = try pp.comp.intern("ucontext_t"), }, }; errdefer { p.nodes.deinit(pp.comp.gpa); p.retained_strings.deinit(); p.value_map.deinit(); } defer { p.data.deinit(); p.labels.deinit(); p.strings.deinit(); p.syms.deinit(pp.comp.gpa); p.list_buf.deinit(); p.decl_buf.deinit(); p.param_buf.deinit(); p.enum_buf.deinit(); p.record_buf.deinit(); p.record_members.deinit(pp.comp.gpa); p.attr_buf.deinit(pp.comp.gpa); p.attr_application_buf.deinit(pp.comp.gpa); p.tentative_defs.deinit(pp.comp.gpa); assert(p.field_attr_buf.items.len == 0); p.field_attr_buf.deinit(); } // NodeIndex 0 must be invalid _ = try p.addNode(.{ .tag = .invalid, .ty = undefined, .data = undefined }); { if (p.comp.langopts.hasChar8_T()) { try p.syms.defineTypedef(&p, try p.comp.intern("char8_t"), .{ .specifier = .uchar }, 0, .none); } try p.syms.defineTypedef(&p, try p.comp.intern("__int128_t"), .{ .specifier = .int128 }, 0, .none); try p.syms.defineTypedef(&p, try p.comp.intern("__uint128_t"), .{ .specifier = .uint128 }, 0, .none); const elem_ty = try p.arena.create(Type); elem_ty.* = .{ .specifier = .char }; try p.syms.defineTypedef(&p, try p.comp.intern("__builtin_ms_va_list"), .{ .specifier = .pointer, .data = .{ .sub_type = elem_ty }, }, 0, .none); const ty = &pp.comp.types.va_list; try p.syms.defineTypedef(&p, try p.comp.intern("__builtin_va_list"), ty.*, 0, .none); if (ty.isArray()) ty.decayArray(); try p.syms.defineTypedef(&p, try p.comp.intern("__NSConstantString"), pp.comp.types.ns_constant_string.ty, 0, .none); } while (p.eatToken(.eof) == null) { if (try p.pragma()) continue; if (try p.parseOrNextDecl(staticAssert)) continue; if (try p.parseOrNextDecl(decl)) continue; if (p.eatToken(.keyword_extension)) |_| { const saved_extension = p.extension_suppressed; defer p.extension_suppressed = saved_extension; p.extension_suppressed = true; if (try p.parseOrNextDecl(decl)) continue; switch (p.tok_ids[p.tok_i]) { .semicolon => p.tok_i += 1, .keyword_static_assert, .keyword_c23_static_assert, .keyword_pragma, .keyword_extension, .keyword_asm, .keyword_asm1, .keyword_asm2, => {}, else => try p.err(.expected_external_decl), } continue; } if (p.assembly(.global) catch |er| switch (er) { error.ParsingFailed => { p.nextExternDecl(); continue; }, else => |e| return e, }) |node| { try p.decl_buf.append(node); continue; } if (p.eatToken(.semicolon)) |tok| { try p.errTok(.extra_semi, tok); continue; } try p.err(.expected_external_decl); p.tok_i += 1; } if (p.tentative_defs.count() > 0) { try p.diagnoseIncompleteDefinitions(); } const root_decls = try p.decl_buf.toOwnedSlice(); errdefer pp.comp.gpa.free(root_decls); if (root_decls.len == 0) { try p.errTok(.empty_translation_unit, p.tok_i - 1); } pp.comp.pragmaEvent(.after_parse); const data = try p.data.toOwnedSlice(); errdefer pp.comp.gpa.free(data); const strings = try p.retained_strings.toOwnedSlice(); errdefer pp.comp.gpa.free(strings); return Tree{ .comp = pp.comp, .tokens = pp.tokens.slice(), .arena = arena, .generated = pp.comp.generated_buf.items, .nodes = p.nodes.toOwnedSlice(), .data = data, .root_decls = root_decls, .strings = strings, .value_map = p.value_map, }; } fn skipToPragmaSentinel(p: *Parser) void { while (true) : (p.tok_i += 1) { if (p.tok_ids[p.tok_i] == .nl) return; if (p.tok_ids[p.tok_i] == .eof) { p.tok_i -= 1; return; } } } fn parseOrNextDecl(p: *Parser, comptime func: fn (*Parser) Error!bool) Compilation.Error!bool { return func(p) catch |er| switch (er) { error.ParsingFailed => { p.nextExternDecl(); return true; }, else => |e| return e, }; } fn nextExternDecl(p: *Parser) void { var parens: u32 = 0; while (true) : (p.tok_i += 1) { switch (p.tok_ids[p.tok_i]) { .l_paren, .l_brace, .l_bracket => parens += 1, .r_paren, .r_brace, .r_bracket => if (parens != 0) { parens -= 1; }, .keyword_typedef, .keyword_extern, .keyword_static, .keyword_auto, .keyword_register, .keyword_thread_local, .keyword_c23_thread_local, .keyword_inline, .keyword_inline1, .keyword_inline2, .keyword_noreturn, .keyword_void, .keyword_bool, .keyword_c23_bool, .keyword_char, .keyword_short, .keyword_int, .keyword_long, .keyword_signed, .keyword_unsigned, .keyword_float, .keyword_double, .keyword_complex, .keyword_atomic, .keyword_enum, .keyword_struct, .keyword_union, .keyword_alignas, .keyword_c23_alignas, .identifier, .extended_identifier, .keyword_typeof, .keyword_typeof1, .keyword_typeof2, .keyword_extension, .keyword_bit_int, => if (parens == 0) return, .keyword_pragma => p.skipToPragmaSentinel(), .eof => return, .semicolon => if (parens == 0) { p.tok_i += 1; return; }, else => {}, } } } fn skipTo(p: *Parser, id: Token.Id) void { var parens: u32 = 0; while (true) : (p.tok_i += 1) { if (p.tok_ids[p.tok_i] == id and parens == 0) { p.tok_i += 1; return; } switch (p.tok_ids[p.tok_i]) { .l_paren, .l_brace, .l_bracket => parens += 1, .r_paren, .r_brace, .r_bracket => if (parens != 0) { parens -= 1; }, .keyword_pragma => p.skipToPragmaSentinel(), .eof => return, else => {}, } } } /// Called after a typedef is defined fn typedefDefined(p: *Parser, name: StringId, ty: Type) void { if (name == p.string_ids.file) { p.comp.types.file = ty; } else if (name == p.string_ids.jmp_buf) { p.comp.types.jmp_buf = ty; } else if (name == p.string_ids.sigjmp_buf) { p.comp.types.sigjmp_buf = ty; } else if (name == p.string_ids.ucontext_t) { p.comp.types.ucontext_t = ty; } } // ====== declarations ====== /// decl /// : declSpec (initDeclarator ( ',' initDeclarator)*)? ';' /// | declSpec declarator decl* compoundStmt fn decl(p: *Parser) Error!bool { _ = try p.pragma(); const first_tok = p.tok_i; const attr_buf_top = p.attr_buf.len; defer p.attr_buf.len = attr_buf_top; try p.attributeSpecifier(); var decl_spec = if (try p.declSpec()) |some| some else blk: { if (p.func.ty != null) { p.tok_i = first_tok; return false; } switch (p.tok_ids[first_tok]) { .asterisk, .l_paren, .identifier, .extended_identifier => {}, else => if (p.tok_i != first_tok) { try p.err(.expected_ident_or_l_paren); return error.ParsingFailed; } else return false, } var spec: Type.Builder = .{}; break :blk DeclSpec{ .ty = try spec.finish(p) }; }; if (decl_spec.noreturn) |tok| { const attr = Attribute{ .tag = .noreturn, .args = .{ .noreturn = {} }, .syntax = .keyword }; try p.attr_buf.append(p.gpa, .{ .attr = attr, .tok = tok }); } var init_d = (try p.initDeclarator(&decl_spec, attr_buf_top)) orelse { _ = try p.expectToken(.semicolon); if (decl_spec.ty.is(.@"enum") or (decl_spec.ty.isRecord() and !decl_spec.ty.isAnonymousRecord(p.comp) and !decl_spec.ty.isTypeof())) // we follow GCC and clang's behavior here { const specifier = decl_spec.ty.canonicalize(.standard).specifier; const attrs = p.attr_buf.items(.attr)[attr_buf_top..]; const toks = p.attr_buf.items(.tok)[attr_buf_top..]; for (attrs, toks) |attr, tok| { try p.errExtra(.ignored_record_attr, tok, .{ .ignored_record_attr = .{ .tag = attr.tag, .specifier = switch (specifier) { .@"enum" => .@"enum", .@"struct" => .@"struct", .@"union" => .@"union", else => unreachable, } }, }); } return true; } try p.errTok(.missing_declaration, first_tok); return true; }; // Check for function definition. if (init_d.d.func_declarator != null and init_d.initializer.node == .none and init_d.d.ty.isFunc()) fn_def: { if (decl_spec.auto_type) |tok_i| { try p.errStr(.auto_type_not_allowed, tok_i, "function return type"); return error.ParsingFailed; } switch (p.tok_ids[p.tok_i]) { .comma, .semicolon => break :fn_def, .l_brace => {}, else => if (init_d.d.old_style_func == null) { try p.err(.expected_fn_body); return true; }, } if (p.func.ty != null) try p.err(.func_not_in_root); const node = try p.addNode(undefined); // reserve space const interned_declarator_name = try p.comp.intern(p.tokSlice(init_d.d.name)); try p.syms.defineSymbol(p, interned_declarator_name, init_d.d.ty, init_d.d.name, node, .{}, false); const func = p.func; p.func = .{ .ty = init_d.d.ty, .name = init_d.d.name, }; if (interned_declarator_name == p.string_ids.main_id and !init_d.d.ty.returnType().is(.int)) { try p.errTok(.main_return_type, init_d.d.name); } defer p.func = func; try p.syms.pushScope(p); defer p.syms.popScope(); // Collect old style parameter declarations. if (init_d.d.old_style_func != null) { const attrs = init_d.d.ty.getAttributes(); var base_ty = if (init_d.d.ty.specifier == .attributed) init_d.d.ty.elemType() else init_d.d.ty; base_ty.specifier = .func; init_d.d.ty = try base_ty.withAttributes(p.arena, attrs); const param_buf_top = p.param_buf.items.len; defer p.param_buf.items.len = param_buf_top; param_loop: while (true) { const param_decl_spec = (try p.declSpec()) orelse break; if (p.eatToken(.semicolon)) |semi| { try p.errTok(.missing_declaration, semi); continue :param_loop; } while (true) { const attr_buf_top_declarator = p.attr_buf.len; defer p.attr_buf.len = attr_buf_top_declarator; var d = (try p.declarator(param_decl_spec.ty, .normal)) orelse { try p.errTok(.missing_declaration, first_tok); _ = try p.expectToken(.semicolon); continue :param_loop; }; try p.attributeSpecifier(); if (d.ty.hasIncompleteSize() and !d.ty.is(.void)) try p.errStr(.parameter_incomplete_ty, d.name, try p.typeStr(d.ty)); if (d.ty.isFunc()) { // Params declared as functions are converted to function pointers. const elem_ty = try p.arena.create(Type); elem_ty.* = d.ty; d.ty = Type{ .specifier = .pointer, .data = .{ .sub_type = elem_ty }, }; } else if (d.ty.isArray()) { // params declared as arrays are converted to pointers d.ty.decayArray(); } else if (d.ty.is(.void)) { try p.errTok(.invalid_void_param, d.name); } // find and correct parameter types // TODO check for missing declarations and redefinitions const name_str = p.tokSlice(d.name); const interned_name = try p.comp.intern(name_str); for (init_d.d.ty.params()) |*param| { if (param.name == interned_name) { param.ty = d.ty; break; } } else { try p.errStr(.parameter_missing, d.name, name_str); } d.ty = try Attribute.applyParameterAttributes(p, d.ty, attr_buf_top_declarator, .alignas_on_param); // bypass redefinition check to avoid duplicate errors try p.syms.syms.append(p.gpa, .{ .kind = .def, .name = interned_name, .tok = d.name, .ty = d.ty, .val = .{}, }); if (p.eatToken(.comma) == null) break; } _ = try p.expectToken(.semicolon); } } else { for (init_d.d.ty.params()) |param| { if (param.ty.hasUnboundVLA()) try p.errTok(.unbound_vla, param.name_tok); if (param.ty.hasIncompleteSize() and !param.ty.is(.void) and param.ty.specifier != .invalid) try p.errStr(.parameter_incomplete_ty, param.name_tok, try p.typeStr(param.ty)); if (param.name == .empty) { try p.errTok(.omitting_parameter_name, param.name_tok); continue; } // bypass redefinition check to avoid duplicate errors try p.syms.syms.append(p.gpa, .{ .kind = .def, .name = param.name, .tok = param.name_tok, .ty = param.ty, .val = .{}, }); } } const body = (try p.compoundStmt(true, null)) orelse { assert(init_d.d.old_style_func != null); try p.err(.expected_fn_body); return true; }; p.nodes.set(@intFromEnum(node), .{ .ty = init_d.d.ty, .tag = try decl_spec.validateFnDef(p), .data = .{ .decl = .{ .name = init_d.d.name, .node = body } }, }); try p.decl_buf.append(node); // check gotos if (func.ty == null) { for (p.labels.items) |item| { if (item == .unresolved_goto) try p.errStr(.undeclared_label, item.unresolved_goto, p.tokSlice(item.unresolved_goto)); } if (p.computed_goto_tok) |goto_tok| { if (!p.contains_address_of_label) try p.errTok(.invalid_computed_goto, goto_tok); } p.labels.items.len = 0; p.label_count = 0; p.contains_address_of_label = false; p.computed_goto_tok = null; } return true; } // Declare all variable/typedef declarators. while (true) { if (init_d.d.old_style_func) |tok_i| try p.errTok(.invalid_old_style_params, tok_i); const tag = try decl_spec.validate(p, &init_d.d.ty, init_d.initializer.node != .none); const node = try p.addNode(.{ .ty = init_d.d.ty, .tag = tag, .data = .{ .decl = .{ .name = init_d.d.name, .node = init_d.initializer.node }, } }); try p.decl_buf.append(node); const interned_name = try p.comp.intern(p.tokSlice(init_d.d.name)); if (decl_spec.storage_class == .typedef) { try p.syms.defineTypedef(p, interned_name, init_d.d.ty, init_d.d.name, node); p.typedefDefined(interned_name, init_d.d.ty); } else if (init_d.initializer.node != .none or (p.func.ty != null and decl_spec.storage_class != .@"extern")) { // TODO validate global variable/constexpr initializer comptime known try p.syms.defineSymbol( p, interned_name, init_d.d.ty, init_d.d.name, node, if (init_d.d.ty.isConst() or decl_spec.constexpr != null) init_d.initializer.val else .{}, decl_spec.constexpr != null, ); } else { try p.syms.declareSymbol(p, interned_name, init_d.d.ty, init_d.d.name, node); } if (p.eatToken(.comma) == null) break; if (decl_spec.auto_type) |tok_i| try p.errTok(.auto_type_requires_single_declarator, tok_i); init_d = (try p.initDeclarator(&decl_spec, attr_buf_top)) orelse { try p.err(.expected_ident_or_l_paren); continue; }; } _ = try p.expectToken(.semicolon); return true; } fn staticAssertMessage(p: *Parser, cond_node: NodeIndex, message: Result) !?[]const u8 { const cond_tag = p.nodes.items(.tag)[@intFromEnum(cond_node)]; if (cond_tag != .builtin_types_compatible_p and message.node == .none) return null; var buf = std.ArrayList(u8).init(p.gpa); defer buf.deinit(); if (cond_tag == .builtin_types_compatible_p) { const mapper = p.comp.string_interner.getSlowTypeMapper(); const data = p.nodes.items(.data)[@intFromEnum(cond_node)].bin; try buf.appendSlice("'__builtin_types_compatible_p("); const lhs_ty = p.nodes.items(.ty)[@intFromEnum(data.lhs)]; try lhs_ty.print(mapper, p.comp.langopts, buf.writer()); try buf.appendSlice(", "); const rhs_ty = p.nodes.items(.ty)[@intFromEnum(data.rhs)]; try rhs_ty.print(mapper, p.comp.langopts, buf.writer()); try buf.appendSlice(")'"); } if (message.node != .none) { if (buf.items.len > 0) { try buf.append(' '); } const data = message.val.data.bytes; try buf.ensureUnusedCapacity(data.len()); try Tree.dumpStr( p.retained_strings.items, data, p.nodes.items(.tag)[@intFromEnum(message.node)], buf.writer(), ); } return try p.comp.diag.arena.allocator().dupe(u8, buf.items); } /// staticAssert /// : keyword_static_assert '(' integerConstExpr (',' STRING_LITERAL)? ')' ';' /// | keyword_c23_static_assert '(' integerConstExpr (',' STRING_LITERAL)? ')' ';' fn staticAssert(p: *Parser) Error!bool { const static_assert = p.eatToken(.keyword_static_assert) orelse p.eatToken(.keyword_c23_static_assert) orelse return false; const l_paren = try p.expectToken(.l_paren); const res_token = p.tok_i; var res = try p.constExpr(.gnu_folding_extension); const res_node = res.node; const str = if (p.eatToken(.comma) != null) switch (p.tok_ids[p.tok_i]) { .string_literal, .string_literal_utf_16, .string_literal_utf_8, .string_literal_utf_32, .string_literal_wide, => try p.stringLiteral(), else => { try p.err(.expected_str_literal); return error.ParsingFailed; }, } else Result{}; try p.expectClosing(l_paren, .r_paren); _ = try p.expectToken(.semicolon); if (str.node == .none) { try p.errTok(.static_assert_missing_message, static_assert); try p.errStr(.pre_c2x_compat, static_assert, "'_Static_assert' with no message"); } // Array will never be zero; a value of zero for a pointer is a null pointer constant if ((res.ty.isArray() or res.ty.isPtr()) and !res.val.isZero()) { const err_start = p.comp.diag.list.items.len; try p.errTok(.const_decl_folded, res_token); if (res.ty.isPtr() and err_start != p.comp.diag.list.items.len) { // Don't show the note if the .const_decl_folded diagnostic was not added try p.errTok(.constant_expression_conversion_not_allowed, res_token); } } try res.boolCast(p, .{ .specifier = .bool }, res_token); if (res.val.tag == .unavailable) { if (res.ty.specifier != .invalid) { try p.errTok(.static_assert_not_constant, res_token); } } else { if (!res.val.getBool()) { if (try p.staticAssertMessage(res_node, str)) |message| { try p.errStr(.static_assert_failure_message, static_assert, message); } else { try p.errTok(.static_assert_failure, static_assert); } } } const node = try p.addNode(.{ .tag = .static_assert, .data = .{ .bin = .{ .lhs = res.node, .rhs = str.node, } }, }); try p.decl_buf.append(node); return true; } pub const DeclSpec = struct { storage_class: union(enum) { auto: TokenIndex, @"extern": TokenIndex, register: TokenIndex, static: TokenIndex, typedef: TokenIndex, none, } = .none, thread_local: ?TokenIndex = null, constexpr: ?TokenIndex = null, @"inline": ?TokenIndex = null, noreturn: ?TokenIndex = null, auto_type: ?TokenIndex = null, ty: Type, fn validateParam(d: DeclSpec, p: *Parser, ty: *Type) Error!void { switch (d.storage_class) { .none => {}, .register => ty.qual.register = true, .auto, .@"extern", .static, .typedef => |tok_i| try p.errTok(.invalid_storage_on_param, tok_i), } if (d.thread_local) |tok_i| try p.errTok(.threadlocal_non_var, tok_i); if (d.@"inline") |tok_i| try p.errStr(.func_spec_non_func, tok_i, "inline"); if (d.noreturn) |tok_i| try p.errStr(.func_spec_non_func, tok_i, "_Noreturn"); if (d.constexpr) |tok_i| try p.errTok(.invalid_storage_on_param, tok_i); if (d.auto_type) |tok_i| { try p.errStr(.auto_type_not_allowed, tok_i, "function prototype"); ty.* = Type.invalid; } } fn validateFnDef(d: DeclSpec, p: *Parser) Error!Tree.Tag { switch (d.storage_class) { .none, .@"extern", .static => {}, .auto, .register, .typedef => |tok_i| try p.errTok(.illegal_storage_on_func, tok_i), } if (d.thread_local) |tok_i| try p.errTok(.threadlocal_non_var, tok_i); if (d.constexpr) |tok_i| try p.errTok(.illegal_storage_on_func, tok_i); const is_static = d.storage_class == .static; const is_inline = d.@"inline" != null; if (is_static) { if (is_inline) return .inline_static_fn_def; return .static_fn_def; } else { if (is_inline) return .inline_fn_def; return .fn_def; } } fn validate(d: DeclSpec, p: *Parser, ty: *Type, has_init: bool) Error!Tree.Tag { const is_static = d.storage_class == .static; if (ty.isFunc() and d.storage_class != .typedef) { switch (d.storage_class) { .none, .@"extern" => {}, .static => |tok_i| if (p.func.ty != null) try p.errTok(.static_func_not_global, tok_i), .typedef => unreachable, .auto, .register => |tok_i| try p.errTok(.illegal_storage_on_func, tok_i), } if (d.thread_local) |tok_i| try p.errTok(.threadlocal_non_var, tok_i); if (d.constexpr) |tok_i| try p.errTok(.illegal_storage_on_func, tok_i); const is_inline = d.@"inline" != null; if (is_static) { if (is_inline) return .inline_static_fn_proto; return .static_fn_proto; } else { if (is_inline) return .inline_fn_proto; return .fn_proto; } } else { if (d.@"inline") |tok_i| try p.errStr(.func_spec_non_func, tok_i, "inline"); // TODO move to attribute validation if (d.noreturn) |tok_i| try p.errStr(.func_spec_non_func, tok_i, "_Noreturn"); switch (d.storage_class) { .auto, .register => if (p.func.ty == null) try p.err(.illegal_storage_on_global), .typedef => return .typedef, else => {}, } ty.qual.register = d.storage_class == .register; const is_extern = d.storage_class == .@"extern" and !has_init; if (d.thread_local != null) { if (is_static) return .threadlocal_static_var; if (is_extern) return .threadlocal_extern_var; return .threadlocal_var; } else { if (is_static) return .static_var; if (is_extern) return .extern_var; return .@"var"; } } } }; /// typeof /// : keyword_typeof '(' typeName ')' /// | keyword_typeof '(' expr ')' fn typeof(p: *Parser) Error!?Type { switch (p.tok_ids[p.tok_i]) { .keyword_typeof, .keyword_typeof1, .keyword_typeof2 => p.tok_i += 1, else => return null, } const l_paren = try p.expectToken(.l_paren); if (try p.typeName()) |ty| { try p.expectClosing(l_paren, .r_paren); const typeof_ty = try p.arena.create(Type); typeof_ty.* = .{ .data = ty.data, .qual = ty.qual.inheritFromTypeof(), .specifier = ty.specifier, }; return Type{ .data = .{ .sub_type = typeof_ty }, .specifier = .typeof_type, }; } const typeof_expr = try p.parseNoEval(expr); try typeof_expr.expect(p); try p.expectClosing(l_paren, .r_paren); // Special case nullptr_t since it's defined as typeof(nullptr) if (typeof_expr.ty.is(.nullptr_t)) { return Type{ .specifier = .nullptr_t, .qual = typeof_expr.ty.qual.inheritFromTypeof() }; } const inner = try p.arena.create(Type.Expr); inner.* = .{ .node = typeof_expr.node, .ty = .{ .data = typeof_expr.ty.data, .qual = typeof_expr.ty.qual.inheritFromTypeof(), .specifier = typeof_expr.ty.specifier, }, }; return Type{ .data = .{ .expr = inner }, .specifier = .typeof_expr, }; } /// declSpec: (storageClassSpec | typeSpec | typeQual | funcSpec | alignSpec)+ /// storageClassSpec: /// : keyword_typedef /// | keyword_extern /// | keyword_static /// | keyword_threadlocal /// | keyword_auto /// | keyword_register /// funcSpec : keyword_inline | keyword_noreturn fn declSpec(p: *Parser) Error!?DeclSpec { var d: DeclSpec = .{ .ty = .{ .specifier = undefined } }; var spec: Type.Builder = .{}; const start = p.tok_i; while (true) { if (try p.typeSpec(&spec)) continue; const id = p.tok_ids[p.tok_i]; switch (id) { .keyword_typedef, .keyword_extern, .keyword_static, .keyword_auto, .keyword_register, => { if (d.storage_class != .none) { try p.errStr(.multiple_storage_class, p.tok_i, @tagName(d.storage_class)); return error.ParsingFailed; } if (d.thread_local != null) { switch (id) { .keyword_extern, .keyword_static => {}, else => try p.errStr(.cannot_combine_spec, p.tok_i, id.lexeme().?), } if (d.constexpr) |tok| try p.errStr(.cannot_combine_spec, p.tok_i, p.tok_ids[tok].lexeme().?); } if (d.constexpr != null) { switch (id) { .keyword_auto, .keyword_register, .keyword_static => {}, else => try p.errStr(.cannot_combine_spec, p.tok_i, id.lexeme().?), } if (d.thread_local) |tok| try p.errStr(.cannot_combine_spec, p.tok_i, p.tok_ids[tok].lexeme().?); } switch (id) { .keyword_typedef => d.storage_class = .{ .typedef = p.tok_i }, .keyword_extern => d.storage_class = .{ .@"extern" = p.tok_i }, .keyword_static => d.storage_class = .{ .static = p.tok_i }, .keyword_auto => d.storage_class = .{ .auto = p.tok_i }, .keyword_register => d.storage_class = .{ .register = p.tok_i }, else => unreachable, } }, .keyword_thread_local, .keyword_c23_thread_local, => { if (d.thread_local != null) { try p.errStr(.duplicate_decl_spec, p.tok_i, id.lexeme().?); } if (d.constexpr) |tok| try p.errStr(.cannot_combine_spec, p.tok_i, p.tok_ids[tok].lexeme().?); switch (d.storage_class) { .@"extern", .none, .static => {}, else => try p.errStr(.cannot_combine_spec, p.tok_i, @tagName(d.storage_class)), } d.thread_local = p.tok_i; }, .keyword_constexpr => { if (d.constexpr != null) { try p.errStr(.duplicate_decl_spec, p.tok_i, id.lexeme().?); } if (d.thread_local) |tok| try p.errStr(.cannot_combine_spec, p.tok_i, p.tok_ids[tok].lexeme().?); switch (d.storage_class) { .auto, .register, .none, .static => {}, else => try p.errStr(.cannot_combine_spec, p.tok_i, @tagName(d.storage_class)), } d.constexpr = p.tok_i; }, .keyword_inline, .keyword_inline1, .keyword_inline2 => { if (d.@"inline" != null) { try p.errStr(.duplicate_decl_spec, p.tok_i, "inline"); } d.@"inline" = p.tok_i; }, .keyword_noreturn => { if (d.noreturn != null) { try p.errStr(.duplicate_decl_spec, p.tok_i, "_Noreturn"); } d.noreturn = p.tok_i; }, else => break, } p.tok_i += 1; } if (p.tok_i == start) return null; d.ty = try spec.finish(p); d.auto_type = spec.auto_type_tok; return d; } const InitDeclarator = struct { d: Declarator, initializer: Result = .{} }; /// attribute /// : attrIdentifier /// | attrIdentifier '(' identifier ')' /// | attrIdentifier '(' identifier (',' expr)+ ')' /// | attrIdentifier '(' (expr (',' expr)*)? ')' fn attribute(p: *Parser, kind: Attribute.Kind, namespace: ?[]const u8) Error!?TentativeAttribute { const name_tok = p.tok_i; switch (p.tok_ids[p.tok_i]) { .keyword_const, .keyword_const1, .keyword_const2 => p.tok_i += 1, else => _ = try p.expectIdentifier(), } const name = p.tokSlice(name_tok); const attr = Attribute.fromString(kind, namespace, name) orelse { const tag: Diagnostics.Tag = if (kind == .declspec) .declspec_attr_not_supported else .unknown_attribute; try p.errStr(tag, name_tok, name); if (p.eatToken(.l_paren)) |_| p.skipTo(.r_paren); return null; }; const required_count = Attribute.requiredArgCount(attr); var arguments = Attribute.initArguments(attr, name_tok); var arg_idx: u32 = 0; switch (p.tok_ids[p.tok_i]) { .comma, .r_paren => {}, // will be consumed in attributeList .l_paren => blk: { p.tok_i += 1; if (p.eatToken(.r_paren)) |_| break :blk; if (Attribute.wantsIdentEnum(attr)) { if (try p.eatIdentifier()) |ident| { if (Attribute.diagnoseIdent(attr, &arguments, p.tokSlice(ident))) |msg| { try p.errExtra(msg.tag, ident, msg.extra); p.skipTo(.r_paren); return error.ParsingFailed; } } else { try p.errExtra(.attribute_requires_identifier, name_tok, .{ .str = name }); return error.ParsingFailed; } } else { const arg_start = p.tok_i; var first_expr = try p.assignExpr(); try first_expr.expect(p); if (p.diagnose(attr, &arguments, arg_idx, first_expr)) |msg| { try p.errExtra(msg.tag, arg_start, msg.extra); p.skipTo(.r_paren); return error.ParsingFailed; } } arg_idx += 1; while (p.eatToken(.r_paren) == null) : (arg_idx += 1) { _ = try p.expectToken(.comma); const arg_start = p.tok_i; var arg_expr = try p.assignExpr(); try arg_expr.expect(p); if (p.diagnose(attr, &arguments, arg_idx, arg_expr)) |msg| { try p.errExtra(msg.tag, arg_start, msg.extra); p.skipTo(.r_paren); return error.ParsingFailed; } } }, else => {}, } if (arg_idx < required_count) { try p.errExtra(.attribute_not_enough_args, name_tok, .{ .attr_arg_count = .{ .attribute = attr, .expected = required_count } }); return error.ParsingFailed; } return TentativeAttribute{ .attr = .{ .tag = attr, .args = arguments, .syntax = kind.toSyntax() }, .tok = name_tok }; } fn diagnose(p: *Parser, attr: Attribute.Tag, arguments: *Attribute.Arguments, arg_idx: u32, res: Result) ?Diagnostics.Message { if (Attribute.wantsAlignment(attr, arg_idx)) { return Attribute.diagnoseAlignment(attr, arguments, arg_idx, res.val, res.ty, p.comp); } const node = p.nodes.get(@intFromEnum(res.node)); return Attribute.diagnose(attr, arguments, arg_idx, res.val, node, p.retained_strings.items); } /// attributeList : (attribute (',' attribute)*)? fn gnuAttributeList(p: *Parser) Error!void { if (p.tok_ids[p.tok_i] == .r_paren) return; if (try p.attribute(.gnu, null)) |attr| try p.attr_buf.append(p.gpa, attr); while (p.tok_ids[p.tok_i] != .r_paren) { _ = try p.expectToken(.comma); if (try p.attribute(.gnu, null)) |attr| try p.attr_buf.append(p.gpa, attr); } } fn c2xAttributeList(p: *Parser) Error!void { while (p.tok_ids[p.tok_i] != .r_bracket) { var namespace_tok = try p.expectIdentifier(); var namespace: ?[]const u8 = null; if (p.eatToken(.colon_colon)) |_| { namespace = p.tokSlice(namespace_tok); } else { p.tok_i -= 1; } if (try p.attribute(.c2x, namespace)) |attr| try p.attr_buf.append(p.gpa, attr); _ = p.eatToken(.comma); } } fn msvcAttributeList(p: *Parser) Error!void { while (p.tok_ids[p.tok_i] != .r_paren) { if (try p.attribute(.declspec, null)) |attr| try p.attr_buf.append(p.gpa, attr); _ = p.eatToken(.comma); } } fn c2xAttribute(p: *Parser) !bool { if (!p.comp.langopts.standard.atLeast(.c2x)) return false; const bracket1 = p.eatToken(.l_bracket) orelse return false; const bracket2 = p.eatToken(.l_bracket) orelse { p.tok_i -= 1; return false; }; try p.c2xAttributeList(); _ = try p.expectClosing(bracket2, .r_bracket); _ = try p.expectClosing(bracket1, .r_bracket); return true; } fn msvcAttribute(p: *Parser) !bool { _ = p.eatToken(.keyword_declspec) orelse return false; const l_paren = try p.expectToken(.l_paren); try p.msvcAttributeList(); _ = try p.expectClosing(l_paren, .r_paren); return true; } fn gnuAttribute(p: *Parser) !bool { switch (p.tok_ids[p.tok_i]) { .keyword_attribute1, .keyword_attribute2 => p.tok_i += 1, else => return false, } const paren1 = try p.expectToken(.l_paren); const paren2 = try p.expectToken(.l_paren); try p.gnuAttributeList(); _ = try p.expectClosing(paren2, .r_paren); _ = try p.expectClosing(paren1, .r_paren); return true; } fn attributeSpecifier(p: *Parser) Error!void { return attributeSpecifierExtra(p, null); } /// attributeSpecifier : (keyword_attribute '( '(' attributeList ')' ')')* fn attributeSpecifierExtra(p: *Parser, declarator_name: ?TokenIndex) Error!void { while (true) { if (try p.gnuAttribute()) continue; if (try p.c2xAttribute()) continue; const maybe_declspec_tok = p.tok_i; const attr_buf_top = p.attr_buf.len; if (try p.msvcAttribute()) { if (declarator_name) |name_tok| { try p.errTok(.declspec_not_allowed_after_declarator, maybe_declspec_tok); try p.errTok(.declarator_name_tok, name_tok); p.attr_buf.len = attr_buf_top; } continue; } break; } } /// initDeclarator : declarator assembly? attributeSpecifier? ('=' initializer)? fn initDeclarator(p: *Parser, decl_spec: *DeclSpec, attr_buf_top: usize) Error!?InitDeclarator { const this_attr_buf_top = p.attr_buf.len; defer p.attr_buf.len = this_attr_buf_top; var init_d = InitDeclarator{ .d = (try p.declarator(decl_spec.ty, .normal)) orelse return null, }; try p.attributeSpecifierExtra(init_d.d.name); _ = try p.assembly(.decl_label); try p.attributeSpecifierExtra(init_d.d.name); var apply_var_attributes = false; if (decl_spec.storage_class == .typedef) { if (decl_spec.auto_type) |tok_i| { try p.errStr(.auto_type_not_allowed, tok_i, "typedef"); return error.ParsingFailed; } init_d.d.ty = try Attribute.applyTypeAttributes(p, init_d.d.ty, attr_buf_top, null); } else if (init_d.d.ty.isFunc()) { init_d.d.ty = try Attribute.applyFunctionAttributes(p, init_d.d.ty, attr_buf_top); } else { apply_var_attributes = true; } if (p.eatToken(.equal)) |eq| init: { if (decl_spec.storage_class == .typedef or init_d.d.func_declarator != null) { try p.errTok(.illegal_initializer, eq); } else if (init_d.d.ty.is(.variable_len_array)) { try p.errTok(.vla_init, eq); } else if (decl_spec.storage_class == .@"extern") { try p.err(.extern_initializer); decl_spec.storage_class = .none; } if (init_d.d.ty.hasIncompleteSize() and !init_d.d.ty.is(.incomplete_array)) { try p.errStr(.variable_incomplete_ty, init_d.d.name, try p.typeStr(init_d.d.ty)); return error.ParsingFailed; } try p.syms.pushScope(p); defer p.syms.popScope(); const interned_name = try p.comp.intern(p.tokSlice(init_d.d.name)); try p.syms.declareSymbol(p, interned_name, init_d.d.ty, init_d.d.name, .none); var init_list_expr = try p.initializer(init_d.d.ty); init_d.initializer = init_list_expr; if (!init_list_expr.ty.isArray()) break :init; if (init_d.d.ty.specifier == .incomplete_array) { // Modifying .data is exceptionally allowed for .incomplete_array. init_d.d.ty.data.array.len = init_list_expr.ty.arrayLen() orelse break :init; init_d.d.ty.specifier = .array; } } const name = init_d.d.name; if (init_d.d.ty.is(.auto_type)) { if (init_d.initializer.node == .none) { init_d.d.ty = Type.invalid; try p.errStr(.auto_type_requires_initializer, name, p.tokSlice(name)); return init_d; } else { init_d.d.ty.specifier = init_d.initializer.ty.specifier; init_d.d.ty.data = init_d.initializer.ty.data; } } if (apply_var_attributes) { init_d.d.ty = try Attribute.applyVariableAttributes(p, init_d.d.ty, attr_buf_top, null); } if (decl_spec.storage_class != .typedef and init_d.d.ty.hasIncompleteSize()) incomplete: { const specifier = init_d.d.ty.canonicalize(.standard).specifier; if (decl_spec.storage_class == .@"extern") switch (specifier) { .@"struct", .@"union", .@"enum" => break :incomplete, .incomplete_array => { init_d.d.ty.decayArray(); break :incomplete; }, else => {}, }; // if there was an initializer expression it must have contained an error if (init_d.initializer.node != .none) break :incomplete; if (p.func.ty == null) { if (specifier == .incomplete_array) { // TODO properly check this after finishing parsing try p.errStr(.tentative_array, name, try p.typeStr(init_d.d.ty)); break :incomplete; } else if (init_d.d.ty.getRecord()) |record| { _ = try p.tentative_defs.getOrPutValue(p.gpa, record.name, init_d.d.name); break :incomplete; } else if (init_d.d.ty.get(.@"enum")) |en| { _ = try p.tentative_defs.getOrPutValue(p.gpa, en.data.@"enum".name, init_d.d.name); break :incomplete; } } try p.errStr(.variable_incomplete_ty, name, try p.typeStr(init_d.d.ty)); } return init_d; } /// typeSpec /// : keyword_void /// | keyword_auto_type /// | keyword_char /// | keyword_short /// | keyword_int /// | keyword_long /// | keyword_float /// | keyword_double /// | keyword_signed /// | keyword_unsigned /// | keyword_bool /// | keyword_c23_bool /// | keyword_complex /// | atomicTypeSpec /// | recordSpec /// | enumSpec /// | typedef // IDENTIFIER /// | typeof /// | keyword_bit_int '(' integerConstExpr ')' /// atomicTypeSpec : keyword_atomic '(' typeName ')' /// alignSpec /// : keyword_alignas '(' typeName ')' /// | keyword_alignas '(' integerConstExpr ')' /// | keyword_c23_alignas '(' typeName ')' /// | keyword_c23_alignas '(' integerConstExpr ')' fn typeSpec(p: *Parser, ty: *Type.Builder) Error!bool { const start = p.tok_i; while (true) { try p.attributeSpecifier(); if (try p.typeof()) |inner_ty| { try ty.combineFromTypeof(p, inner_ty, start); continue; } if (try p.typeQual(&ty.qual)) continue; switch (p.tok_ids[p.tok_i]) { .keyword_void => try ty.combine(p, .void, p.tok_i), .keyword_auto_type => { try p.errTok(.auto_type_extension, p.tok_i); try ty.combine(p, .auto_type, p.tok_i); }, .keyword_bool, .keyword_c23_bool => try ty.combine(p, .bool, p.tok_i), .keyword_int8, .keyword_int8_2, .keyword_char => try ty.combine(p, .char, p.tok_i), .keyword_int16, .keyword_int16_2, .keyword_short => try ty.combine(p, .short, p.tok_i), .keyword_int32, .keyword_int32_2, .keyword_int => try ty.combine(p, .int, p.tok_i), .keyword_long => try ty.combine(p, .long, p.tok_i), .keyword_int64, .keyword_int64_2 => try ty.combine(p, .long_long, p.tok_i), .keyword_int128 => try ty.combine(p, .int128, p.tok_i), .keyword_signed => try ty.combine(p, .signed, p.tok_i), .keyword_unsigned => try ty.combine(p, .unsigned, p.tok_i), .keyword_fp16 => try ty.combine(p, .fp16, p.tok_i), .keyword_float16 => try ty.combine(p, .float16, p.tok_i), .keyword_float => try ty.combine(p, .float, p.tok_i), .keyword_double => try ty.combine(p, .double, p.tok_i), .keyword_complex => try ty.combine(p, .complex, p.tok_i), .keyword_float80 => try ty.combine(p, .float80, p.tok_i), .keyword_float128 => try ty.combine(p, .float128, p.tok_i), .keyword_atomic => { const atomic_tok = p.tok_i; p.tok_i += 1; const l_paren = p.eatToken(.l_paren) orelse { // _Atomic qualifier not _Atomic(typeName) p.tok_i = atomic_tok; break; }; const inner_ty = (try p.typeName()) orelse { try p.err(.expected_type); return error.ParsingFailed; }; try p.expectClosing(l_paren, .r_paren); const new_spec = Type.Builder.fromType(inner_ty); try ty.combine(p, new_spec, atomic_tok); if (ty.qual.atomic != null) try p.errStr(.duplicate_decl_spec, atomic_tok, "atomic") else ty.qual.atomic = atomic_tok; continue; }, .keyword_alignas, .keyword_c23_alignas, => { const align_tok = p.tok_i; p.tok_i += 1; const l_paren = try p.expectToken(.l_paren); const typename_start = p.tok_i; if (try p.typeName()) |inner_ty| { if (!inner_ty.alignable()) { try p.errStr(.invalid_alignof, typename_start, try p.typeStr(inner_ty)); } const alignment = Attribute.Alignment{ .requested = inner_ty.alignof(p.comp) }; try p.attr_buf.append(p.gpa, .{ .attr = .{ .tag = .aligned, .args = .{ .aligned = .{ .alignment = alignment, .__name_tok = align_tok }, }, .syntax = .keyword }, .tok = align_tok, }); } else { const arg_start = p.tok_i; const res = try p.integerConstExpr(.no_const_decl_folding); if (!res.val.isZero()) { var args = Attribute.initArguments(.aligned, align_tok); if (p.diagnose(.aligned, &args, 0, res)) |msg| { try p.errExtra(msg.tag, arg_start, msg.extra); p.skipTo(.r_paren); return error.ParsingFailed; } args.aligned.alignment.?.node = res.node; try p.attr_buf.append(p.gpa, .{ .attr = .{ .tag = .aligned, .args = args, .syntax = .keyword }, .tok = align_tok, }); } } try p.expectClosing(l_paren, .r_paren); continue; }, .keyword_stdcall, .keyword_stdcall2, .keyword_thiscall, .keyword_thiscall2, .keyword_vectorcall, .keyword_vectorcall2, => try p.attr_buf.append(p.gpa, .{ .attr = .{ .tag = .calling_convention, .args = .{ .calling_convention = .{ .cc = switch (p.tok_ids[p.tok_i]) { .keyword_stdcall, .keyword_stdcall2, => .stdcall, .keyword_thiscall, .keyword_thiscall2, => .thiscall, .keyword_vectorcall, .keyword_vectorcall2, => .vectorcall, else => unreachable, } }, }, .syntax = .keyword }, .tok = p.tok_i, }), .keyword_struct, .keyword_union => { const tag_tok = p.tok_i; const record_ty = try p.recordSpec(); try ty.combine(p, Type.Builder.fromType(record_ty), tag_tok); continue; }, .keyword_enum => { const tag_tok = p.tok_i; const enum_ty = try p.enumSpec(); try ty.combine(p, Type.Builder.fromType(enum_ty), tag_tok); continue; }, .identifier, .extended_identifier => { var interned_name = try p.comp.intern(p.tokSlice(p.tok_i)); var declspec_found = false; if (interned_name == p.string_ids.declspec_id) { try p.errTok(.declspec_not_enabled, p.tok_i); p.tok_i += 1; if (p.eatToken(.l_paren)) |_| { p.skipTo(.r_paren); continue; } declspec_found = true; } if (ty.typedef != null) break; if (declspec_found) { interned_name = try p.comp.intern(p.tokSlice(p.tok_i)); } const typedef = (try p.syms.findTypedef(p, interned_name, p.tok_i, ty.specifier != .none)) orelse break; if (!ty.combineTypedef(p, typedef.ty, typedef.tok)) break; }, .keyword_bit_int => { try p.err(.bit_int); const bit_int_tok = p.tok_i; p.tok_i += 1; const l_paren = try p.expectToken(.l_paren); const res = try p.integerConstExpr(.gnu_folding_extension); try p.expectClosing(l_paren, .r_paren); var bits: i16 = undefined; if (res.val.tag == .unavailable) { try p.errTok(.expected_integer_constant_expr, bit_int_tok); return error.ParsingFailed; } else if (res.val.compare(.lte, Value.int(0), res.ty, p.comp)) { bits = -1; } else if (res.val.compare(.gt, Value.int(128), res.ty, p.comp)) { bits = 129; } else { bits = res.val.getInt(i16); } try ty.combine(p, .{ .bit_int = bits }, bit_int_tok); continue; }, else => break, } // consume single token specifiers here p.tok_i += 1; } return p.tok_i != start; } fn getAnonymousName(p: *Parser, kind_tok: TokenIndex) !StringId { const loc = p.pp.tokens.items(.loc)[kind_tok]; const source = p.comp.getSource(loc.id); const line_col = source.lineCol(loc); const kind_str = switch (p.tok_ids[kind_tok]) { .keyword_struct, .keyword_union, .keyword_enum => p.tokSlice(kind_tok), else => "record field", }; const str = try std.fmt.allocPrint( p.arena, "(anonymous {s} at {s}:{d}:{d})", .{ kind_str, source.path, line_col.line_no, line_col.col }, ); return p.comp.intern(str); } /// recordSpec /// : (keyword_struct | keyword_union) IDENTIFIER? { recordDecl* } /// | (keyword_struct | keyword_union) IDENTIFIER fn recordSpec(p: *Parser) Error!Type { const starting_pragma_pack = p.pragma_pack; const kind_tok = p.tok_i; const is_struct = p.tok_ids[kind_tok] == .keyword_struct; p.tok_i += 1; const attr_buf_top = p.attr_buf.len; defer p.attr_buf.len = attr_buf_top; try p.attributeSpecifier(); const maybe_ident = try p.eatIdentifier(); const l_brace = p.eatToken(.l_brace) orelse { const ident = maybe_ident orelse { try p.err(.ident_or_l_brace); return error.ParsingFailed; }; // check if this is a reference to a previous type const interned_name = try p.comp.intern(p.tokSlice(ident)); if (try p.syms.findTag(p, interned_name, p.tok_ids[kind_tok], ident, p.tok_ids[p.tok_i])) |prev| { return prev.ty; } else { // this is a forward declaration, create a new record Type. const record_ty = try Type.Record.create(p.arena, interned_name); const ty = try Attribute.applyTypeAttributes(p, .{ .specifier = if (is_struct) .@"struct" else .@"union", .data = .{ .record = record_ty }, }, attr_buf_top, null); try p.syms.syms.append(p.gpa, .{ .kind = if (is_struct) .@"struct" else .@"union", .name = interned_name, .tok = ident, .ty = ty, .val = .{}, }); try p.decl_buf.append(try p.addNode(.{ .tag = if (is_struct) .struct_forward_decl else .union_forward_decl, .ty = ty, .data = .{ .decl_ref = ident }, })); return ty; } }; var done = false; errdefer if (!done) p.skipTo(.r_brace); // Get forward declared type or create a new one var defined = false; const record_ty: *Type.Record = if (maybe_ident) |ident| record_ty: { const ident_str = p.tokSlice(ident); const interned_name = try p.comp.intern(ident_str); if (try p.syms.defineTag(p, interned_name, p.tok_ids[kind_tok], ident)) |prev| { if (!prev.ty.hasIncompleteSize()) { // if the record isn't incomplete, this is a redefinition try p.errStr(.redefinition, ident, ident_str); try p.errTok(.previous_definition, prev.tok); } else { defined = true; break :record_ty prev.ty.get(if (is_struct) .@"struct" else .@"union").?.data.record; } } break :record_ty try Type.Record.create(p.arena, interned_name); } else try Type.Record.create(p.arena, try p.getAnonymousName(kind_tok)); // Initially create ty as a regular non-attributed type, since attributes for a record // can be specified after the closing rbrace, which we haven't encountered yet. var ty = Type{ .specifier = if (is_struct) .@"struct" else .@"union", .data = .{ .record = record_ty }, }; // declare a symbol for the type // We need to replace the symbol's type if it has attributes var symbol_index: ?usize = null; if (maybe_ident != null and !defined) { symbol_index = p.syms.syms.len; try p.syms.syms.append(p.gpa, .{ .kind = if (is_struct) .@"struct" else .@"union", .name = record_ty.name, .tok = maybe_ident.?, .ty = ty, .val = .{}, }); } // reserve space for this record try p.decl_buf.append(.none); const decl_buf_top = p.decl_buf.items.len; const record_buf_top = p.record_buf.items.len; errdefer p.decl_buf.items.len = decl_buf_top - 1; defer { p.decl_buf.items.len = decl_buf_top; p.record_buf.items.len = record_buf_top; } const old_record = p.record; const old_members = p.record_members.items.len; const old_field_attr_start = p.field_attr_buf.items.len; p.record = .{ .kind = p.tok_ids[kind_tok], .start = p.record_members.items.len, .field_attr_start = p.field_attr_buf.items.len, }; defer p.record = old_record; defer p.record_members.items.len = old_members; defer p.field_attr_buf.items.len = old_field_attr_start; try p.recordDecls(); if (p.record.flexible_field) |some| { if (p.record_buf.items[record_buf_top..].len == 1 and is_struct) { try p.errTok(.flexible_in_empty, some); } } for (p.record_buf.items[record_buf_top..]) |field| { if (field.ty.hasIncompleteSize() and !field.ty.is(.incomplete_array)) break; } else { record_ty.fields = try p.arena.dupe(Type.Record.Field, p.record_buf.items[record_buf_top..]); } if (old_field_attr_start < p.field_attr_buf.items.len) { const field_attr_slice = p.field_attr_buf.items[old_field_attr_start..]; const duped = try p.arena.dupe([]const Attribute, field_attr_slice); record_ty.field_attributes = duped.ptr; } if (p.record_buf.items.len == record_buf_top) { try p.errStr(.empty_record, kind_tok, p.tokSlice(kind_tok)); try p.errStr(.empty_record_size, kind_tok, p.tokSlice(kind_tok)); } try p.expectClosing(l_brace, .r_brace); done = true; try p.attributeSpecifier(); ty = try Attribute.applyTypeAttributes(p, .{ .specifier = if (is_struct) .@"struct" else .@"union", .data = .{ .record = record_ty }, }, attr_buf_top, null); if (ty.specifier == .attributed and symbol_index != null) { p.syms.syms.items(.ty)[symbol_index.?] = ty; } if (!ty.hasIncompleteSize()) { const pragma_pack_value = switch (p.comp.langopts.emulate) { .clang => starting_pragma_pack, .gcc => p.pragma_pack, // TODO: msvc considers `#pragma pack` on a per-field basis .msvc => p.pragma_pack, }; record_layout.compute(record_ty, ty, p.comp, pragma_pack_value); } // finish by creating a node var node: Tree.Node = .{ .tag = if (is_struct) .struct_decl_two else .union_decl_two, .ty = ty, .data = .{ .bin = .{ .lhs = .none, .rhs = .none } }, }; const record_decls = p.decl_buf.items[decl_buf_top..]; switch (record_decls.len) { 0 => {}, 1 => node.data = .{ .bin = .{ .lhs = record_decls[0], .rhs = .none } }, 2 => node.data = .{ .bin = .{ .lhs = record_decls[0], .rhs = record_decls[1] } }, else => { node.tag = if (is_struct) .struct_decl else .union_decl; node.data = .{ .range = try p.addList(record_decls) }; }, } p.decl_buf.items[decl_buf_top - 1] = try p.addNode(node); if (p.func.ty == null) { _ = p.tentative_defs.remove(record_ty.name); } return ty; } /// recordDecl /// : specQual (recordDeclarator (',' recordDeclarator)*)? ; /// | staticAssert fn recordDecls(p: *Parser) Error!void { while (true) { if (try p.pragma()) continue; if (try p.parseOrNextDecl(staticAssert)) continue; if (p.eatToken(.keyword_extension)) |_| { const saved_extension = p.extension_suppressed; defer p.extension_suppressed = saved_extension; p.extension_suppressed = true; if (try p.parseOrNextDecl(recordDeclarator)) continue; try p.err(.expected_type); p.nextExternDecl(); continue; } if (try p.parseOrNextDecl(recordDeclarator)) continue; break; } } /// recordDeclarator : keyword_extension? declarator (':' integerConstExpr)? fn recordDeclarator(p: *Parser) Error!bool { const attr_buf_top = p.attr_buf.len; defer p.attr_buf.len = attr_buf_top; const base_ty = (try p.specQual()) orelse return false; try p.attributeSpecifier(); // .record while (true) { const this_decl_top = p.attr_buf.len; defer p.attr_buf.len = this_decl_top; try p.attributeSpecifier(); // 0 means unnamed var name_tok: TokenIndex = 0; var ty = base_ty; if (ty.is(.auto_type)) { try p.errStr(.auto_type_not_allowed, p.tok_i, if (p.record.kind == .keyword_struct) "struct member" else "union member"); ty = Type.invalid; } var bits_node: NodeIndex = .none; var bits: ?u32 = null; const first_tok = p.tok_i; if (try p.declarator(ty, .record)) |d| { name_tok = d.name; ty = d.ty; } if (p.eatToken(.colon)) |_| bits: { const bits_tok = p.tok_i; const res = try p.integerConstExpr(.gnu_folding_extension); if (!ty.isInt()) { try p.errStr(.non_int_bitfield, first_tok, try p.typeStr(ty)); break :bits; } if (res.val.tag == .unavailable) { try p.errTok(.expected_integer_constant_expr, bits_tok); break :bits; } else if (res.val.compare(.lt, Value.int(0), res.ty, p.comp)) { try p.errExtra(.negative_bitwidth, first_tok, .{ .signed = res.val.signExtend(res.ty, p.comp), }); break :bits; } // incomplete size error is reported later const bit_size = ty.bitSizeof(p.comp) orelse break :bits; if (res.val.compare(.gt, Value.int(bit_size), res.ty, p.comp)) { try p.errTok(.bitfield_too_big, name_tok); break :bits; } else if (res.val.isZero() and name_tok != 0) { try p.errTok(.zero_width_named_field, name_tok); break :bits; } bits = res.val.getInt(u32); bits_node = res.node; } try p.attributeSpecifier(); // .record const to_append = try Attribute.applyFieldAttributes(p, &ty, attr_buf_top); const any_fields_have_attrs = p.field_attr_buf.items.len > p.record.field_attr_start; if (any_fields_have_attrs) { try p.field_attr_buf.append(to_append); } else { if (to_append.len > 0) { const preceding = p.record_members.items.len - p.record.start; if (preceding > 0) { try p.field_attr_buf.appendNTimes(&.{}, preceding); } try p.field_attr_buf.append(to_append); } } if (name_tok == 0 and bits_node == .none) unnamed: { if (ty.is(.@"enum") or ty.hasIncompleteSize()) break :unnamed; if (ty.isAnonymousRecord(p.comp)) { // An anonymous record appears as indirect fields on the parent try p.record_buf.append(.{ .name = try p.getAnonymousName(first_tok), .ty = ty, }); const node = try p.addNode(.{ .tag = .indirect_record_field_decl, .ty = ty, .data = undefined, }); try p.decl_buf.append(node); try p.record.addFieldsFromAnonymous(p, ty); break; // must be followed by a semicolon } try p.err(.missing_declaration); } else { const interned_name = if (name_tok != 0) try p.comp.intern(p.tokSlice(name_tok)) else try p.getAnonymousName(first_tok); try p.record_buf.append(.{ .name = interned_name, .ty = ty, .name_tok = name_tok, .bit_width = bits, }); if (name_tok != 0) try p.record.addField(p, interned_name, name_tok); const node = try p.addNode(.{ .tag = .record_field_decl, .ty = ty, .data = .{ .decl = .{ .name = name_tok, .node = bits_node } }, }); try p.decl_buf.append(node); } if (ty.isFunc()) { try p.errTok(.func_field, first_tok); } else if (ty.is(.variable_len_array)) { try p.errTok(.vla_field, first_tok); } else if (ty.is(.incomplete_array)) { if (p.record.kind == .keyword_union) { try p.errTok(.flexible_in_union, first_tok); } if (p.record.flexible_field) |some| { if (p.record.kind == .keyword_struct) { try p.errTok(.flexible_non_final, some); } } p.record.flexible_field = first_tok; } else if (ty.specifier != .invalid and ty.hasIncompleteSize()) { try p.errStr(.field_incomplete_ty, first_tok, try p.typeStr(ty)); } else if (p.record.flexible_field) |some| { if (some != first_tok and p.record.kind == .keyword_struct) try p.errTok(.flexible_non_final, some); } if (p.eatToken(.comma) == null) break; } if (p.eatToken(.semicolon) == null) { const tok_id = p.tok_ids[p.tok_i]; if (tok_id == .r_brace) { try p.err(.missing_semicolon); } else { return p.errExpectedToken(.semicolon, tok_id); } } return true; } /// specQual : (typeSpec | typeQual | alignSpec)+ fn specQual(p: *Parser) Error!?Type { var spec: Type.Builder = .{}; if (try p.typeSpec(&spec)) { return try spec.finish(p); } return null; } /// enumSpec /// : keyword_enum IDENTIFIER? (: typeName)? { enumerator (',' enumerator)? ',') } /// | keyword_enum IDENTIFIER (: typeName)? fn enumSpec(p: *Parser) Error!Type { const enum_tok = p.tok_i; p.tok_i += 1; const attr_buf_top = p.attr_buf.len; defer p.attr_buf.len = attr_buf_top; try p.attributeSpecifier(); const maybe_ident = try p.eatIdentifier(); const fixed_ty = if (p.eatToken(.colon)) |colon| fixed: { const fixed = (try p.typeName()) orelse { if (p.record.kind != .invalid) { // This is a bit field. p.tok_i -= 1; break :fixed null; } try p.err(.expected_type); try p.errTok(.enum_fixed, colon); break :fixed null; }; try p.errTok(.enum_fixed, colon); break :fixed fixed; } else null; const l_brace = p.eatToken(.l_brace) orelse { const ident = maybe_ident orelse { try p.err(.ident_or_l_brace); return error.ParsingFailed; }; // check if this is a reference to a previous type const interned_name = try p.comp.intern(p.tokSlice(ident)); if (try p.syms.findTag(p, interned_name, .keyword_enum, ident, p.tok_ids[p.tok_i])) |prev| { try p.checkEnumFixedTy(fixed_ty, ident, prev); return prev.ty; } else { // this is a forward declaration, create a new enum Type. const enum_ty = try Type.Enum.create(p.arena, interned_name, fixed_ty); const ty = try Attribute.applyTypeAttributes(p, .{ .specifier = .@"enum", .data = .{ .@"enum" = enum_ty }, }, attr_buf_top, null); try p.syms.syms.append(p.gpa, .{ .kind = .@"enum", .name = interned_name, .tok = ident, .ty = ty, .val = .{}, }); try p.decl_buf.append(try p.addNode(.{ .tag = .enum_forward_decl, .ty = ty, .data = .{ .decl_ref = ident }, })); return ty; } }; var done = false; errdefer if (!done) p.skipTo(.r_brace); // Get forward declared type or create a new one var defined = false; const enum_ty: *Type.Enum = if (maybe_ident) |ident| enum_ty: { const ident_str = p.tokSlice(ident); const interned_name = try p.comp.intern(ident_str); if (try p.syms.defineTag(p, interned_name, .keyword_enum, ident)) |prev| { const enum_ty = prev.ty.get(.@"enum").?.data.@"enum"; if (!enum_ty.isIncomplete() and !enum_ty.fixed) { // if the enum isn't incomplete, this is a redefinition try p.errStr(.redefinition, ident, ident_str); try p.errTok(.previous_definition, prev.tok); } else { try p.checkEnumFixedTy(fixed_ty, ident, prev); defined = true; break :enum_ty enum_ty; } } break :enum_ty try Type.Enum.create(p.arena, interned_name, fixed_ty); } else try Type.Enum.create(p.arena, try p.getAnonymousName(enum_tok), fixed_ty); // reserve space for this enum try p.decl_buf.append(.none); const decl_buf_top = p.decl_buf.items.len; const list_buf_top = p.list_buf.items.len; const enum_buf_top = p.enum_buf.items.len; errdefer p.decl_buf.items.len = decl_buf_top - 1; defer { p.decl_buf.items.len = decl_buf_top; p.list_buf.items.len = list_buf_top; p.enum_buf.items.len = enum_buf_top; } const sym_stack_top = p.syms.syms.len; var e = Enumerator.init(fixed_ty); while (try p.enumerator(&e)) |field_and_node| { try p.enum_buf.append(field_and_node.field); try p.list_buf.append(field_and_node.node); if (p.eatToken(.comma) == null) break; } if (p.enum_buf.items.len == enum_buf_top) try p.err(.empty_enum); try p.expectClosing(l_brace, .r_brace); done = true; try p.attributeSpecifier(); const ty = try Attribute.applyTypeAttributes(p, .{ .specifier = .@"enum", .data = .{ .@"enum" = enum_ty }, }, attr_buf_top, null); if (!enum_ty.fixed) { const tag_specifier = try e.getTypeSpecifier(p, ty.enumIsPacked(p.comp), maybe_ident orelse enum_tok); enum_ty.tag_ty = .{ .specifier = tag_specifier }; } const enum_fields = p.enum_buf.items[enum_buf_top..]; const field_nodes = p.list_buf.items[list_buf_top..]; if (fixed_ty == null) { const vals = p.syms.syms.items(.val)[sym_stack_top..]; const types = p.syms.syms.items(.ty)[sym_stack_top..]; for (enum_fields, 0..) |*field, i| { if (field.ty.eql(Type.int, p.comp, false)) continue; var res = Result{ .node = field.node, .ty = field.ty, .val = vals[i] }; const dest_ty = if (p.comp.fixedEnumTagSpecifier()) |some| Type{ .specifier = some } else if (res.intFitsInType(p, Type.int)) Type.int else if (!res.ty.eql(enum_ty.tag_ty, p.comp, false)) enum_ty.tag_ty else continue; vals[i].intCast(field.ty, dest_ty, p.comp); types[i] = dest_ty; p.nodes.items(.ty)[@intFromEnum(field_nodes[i])] = dest_ty; field.ty = dest_ty; res.ty = dest_ty; if (res.node != .none) { try res.implicitCast(p, .int_cast); field.node = res.node; p.nodes.items(.data)[@intFromEnum(field_nodes[i])].decl.node = res.node; } } } enum_ty.fields = try p.arena.dupe(Type.Enum.Field, enum_fields); // declare a symbol for the type if (maybe_ident != null and !defined) { try p.syms.syms.append(p.gpa, .{ .kind = .@"enum", .name = enum_ty.name, .ty = ty, .tok = maybe_ident.?, .val = .{}, }); } // finish by creating a node var node: Tree.Node = .{ .tag = .enum_decl_two, .ty = ty, .data = .{ .bin = .{ .lhs = .none, .rhs = .none }, } }; switch (field_nodes.len) { 0 => {}, 1 => node.data = .{ .bin = .{ .lhs = field_nodes[0], .rhs = .none } }, 2 => node.data = .{ .bin = .{ .lhs = field_nodes[0], .rhs = field_nodes[1] } }, else => { node.tag = .enum_decl; node.data = .{ .range = try p.addList(field_nodes) }; }, } p.decl_buf.items[decl_buf_top - 1] = try p.addNode(node); if (p.func.ty == null) { _ = p.tentative_defs.remove(enum_ty.name); } return ty; } fn checkEnumFixedTy(p: *Parser, fixed_ty: ?Type, ident_tok: TokenIndex, prev: Symbol) !void { const enum_ty = prev.ty.get(.@"enum").?.data.@"enum"; if (fixed_ty) |some| { if (!enum_ty.fixed) { try p.errTok(.enum_prev_nonfixed, ident_tok); try p.errTok(.previous_definition, prev.tok); return error.ParsingFailed; } if (!enum_ty.tag_ty.eql(some, p.comp, false)) { const str = try p.typePairStrExtra(some, " (was ", enum_ty.tag_ty); try p.errStr(.enum_different_explicit_ty, ident_tok, str); try p.errTok(.previous_definition, prev.tok); return error.ParsingFailed; } } else if (enum_ty.fixed) { try p.errTok(.enum_prev_fixed, ident_tok); try p.errTok(.previous_definition, prev.tok); return error.ParsingFailed; } } const Enumerator = struct { res: Result, num_positive_bits: usize = 0, num_negative_bits: usize = 0, fixed: bool, fn init(fixed_ty: ?Type) Enumerator { return .{ .res = .{ .ty = fixed_ty orelse .{ .specifier = .int }, .val = .{ .tag = .unavailable }, }, .fixed = fixed_ty != null, }; } /// Increment enumerator value adjusting type if needed. fn incr(e: *Enumerator, p: *Parser, tok: TokenIndex) !void { e.res.node = .none; const old_val = e.res.val; if (old_val.tag == .unavailable) { // First enumerator, set to 0 fits in all types. e.res.val = Value.int(0); return; } if (e.res.val.add(e.res.val, Value.int(1), e.res.ty, p.comp)) { const byte_size = e.res.ty.sizeof(p.comp).?; const bit_size: u8 = @intCast(if (e.res.ty.isUnsignedInt(p.comp)) byte_size * 8 else byte_size * 8 - 1); if (e.fixed) { try p.errStr(.enum_not_representable_fixed, tok, try p.typeStr(e.res.ty)); return; } const new_ty = if (p.comp.nextLargestIntSameSign(e.res.ty)) |larger| blk: { try p.errTok(.enumerator_overflow, tok); break :blk larger; } else blk: { try p.errExtra(.enum_not_representable, tok, .{ .pow_2_as_string = bit_size }); break :blk Type{ .specifier = .ulong_long }; }; e.res.ty = new_ty; _ = e.res.val.add(old_val, Value.int(1), e.res.ty, p.comp); } } /// Set enumerator value to specified value. fn set(e: *Enumerator, p: *Parser, res: Result, tok: TokenIndex) !void { if (res.ty.specifier == .invalid) return; if (e.fixed and !res.ty.eql(e.res.ty, p.comp, false)) { if (!res.intFitsInType(p, e.res.ty)) { try p.errStr(.enum_not_representable_fixed, tok, try p.typeStr(e.res.ty)); return error.ParsingFailed; } var copy = res; copy.ty = e.res.ty; try copy.implicitCast(p, .int_cast); e.res = copy; } else { e.res = res; try e.res.intCast(p, e.res.ty.integerPromotion(p.comp), tok); } } fn getTypeSpecifier(e: *const Enumerator, p: *Parser, is_packed: bool, tok: TokenIndex) !Type.Specifier { if (p.comp.fixedEnumTagSpecifier()) |tag_specifier| return tag_specifier; const char_width = (Type{ .specifier = .schar }).sizeof(p.comp).? * 8; const short_width = (Type{ .specifier = .short }).sizeof(p.comp).? * 8; const int_width = (Type{ .specifier = .int }).sizeof(p.comp).? * 8; if (e.num_negative_bits > 0) { if (is_packed and e.num_negative_bits <= char_width and e.num_positive_bits < char_width) { return .schar; } else if (is_packed and e.num_negative_bits <= short_width and e.num_positive_bits < short_width) { return .short; } else if (e.num_negative_bits <= int_width and e.num_positive_bits < int_width) { return .int; } const long_width = (Type{ .specifier = .long }).sizeof(p.comp).? * 8; if (e.num_negative_bits <= long_width and e.num_positive_bits < long_width) { return .long; } const long_long_width = (Type{ .specifier = .long_long }).sizeof(p.comp).? * 8; if (e.num_negative_bits > long_long_width or e.num_positive_bits >= long_long_width) { try p.errTok(.enum_too_large, tok); } return .long_long; } if (is_packed and e.num_positive_bits <= char_width) { return .uchar; } else if (is_packed and e.num_positive_bits <= short_width) { return .ushort; } else if (e.num_positive_bits <= int_width) { return .uint; } else if (e.num_positive_bits <= (Type{ .specifier = .long }).sizeof(p.comp).? * 8) { return .ulong; } return .ulong_long; } }; const EnumFieldAndNode = struct { field: Type.Enum.Field, node: NodeIndex }; /// enumerator : IDENTIFIER ('=' integerConstExpr) fn enumerator(p: *Parser, e: *Enumerator) Error!?EnumFieldAndNode { _ = try p.pragma(); const name_tok = (try p.eatIdentifier()) orelse { if (p.tok_ids[p.tok_i] == .r_brace) return null; try p.err(.expected_identifier); p.skipTo(.r_brace); return error.ParsingFailed; }; const attr_buf_top = p.attr_buf.len; defer p.attr_buf.len = attr_buf_top; try p.attributeSpecifier(); const err_start = p.comp.diag.list.items.len; if (p.eatToken(.equal)) |_| { const specified = try p.integerConstExpr(.gnu_folding_extension); if (specified.val.tag == .unavailable) { try p.errTok(.enum_val_unavailable, name_tok + 2); try e.incr(p, name_tok); } else { try e.set(p, specified, name_tok); } } else { try e.incr(p, name_tok); } var res = e.res; res.ty = try Attribute.applyEnumeratorAttributes(p, res.ty, attr_buf_top); if (res.ty.isUnsignedInt(p.comp) or res.val.compare(.gte, Value.int(0), res.ty, p.comp)) { e.num_positive_bits = @max(e.num_positive_bits, res.val.minUnsignedBits(res.ty, p.comp)); } else { e.num_negative_bits = @max(e.num_negative_bits, res.val.minSignedBits(res.ty, p.comp)); } if (err_start == p.comp.diag.list.items.len) { // only do these warnings if we didn't already warn about overflow or non-representable values if (e.res.val.compare(.lt, Value.int(0), e.res.ty, p.comp)) { const val = e.res.val.getInt(i64); if (val < (Type{ .specifier = .int }).minInt(p.comp)) { try p.errExtra(.enumerator_too_small, name_tok, .{ .signed = val, }); } } else { const val = e.res.val.getInt(u64); if (val > (Type{ .specifier = .int }).maxInt(p.comp)) { try p.errExtra(.enumerator_too_large, name_tok, .{ .unsigned = val, }); } } } const interned_name = try p.comp.intern(p.tokSlice(name_tok)); try p.syms.defineEnumeration(p, interned_name, res.ty, name_tok, e.res.val); const node = try p.addNode(.{ .tag = .enum_field_decl, .ty = res.ty, .data = .{ .decl = .{ .name = name_tok, .node = res.node, } }, }); try p.value_map.put(node, e.res.val); return EnumFieldAndNode{ .field = .{ .name = interned_name, .ty = res.ty, .name_tok = name_tok, .node = res.node, }, .node = node }; } /// typeQual : keyword_const | keyword_restrict | keyword_volatile | keyword_atomic fn typeQual(p: *Parser, b: *Type.Qualifiers.Builder) Error!bool { var any = false; while (true) { switch (p.tok_ids[p.tok_i]) { .keyword_restrict, .keyword_restrict1, .keyword_restrict2 => { if (b.restrict != null) try p.errStr(.duplicate_decl_spec, p.tok_i, "restrict") else b.restrict = p.tok_i; }, .keyword_const, .keyword_const1, .keyword_const2 => { if (b.@"const" != null) try p.errStr(.duplicate_decl_spec, p.tok_i, "const") else b.@"const" = p.tok_i; }, .keyword_volatile, .keyword_volatile1, .keyword_volatile2 => { if (b.@"volatile" != null) try p.errStr(.duplicate_decl_spec, p.tok_i, "volatile") else b.@"volatile" = p.tok_i; }, .keyword_atomic => { // _Atomic(typeName) instead of just _Atomic if (p.tok_ids[p.tok_i + 1] == .l_paren) break; if (b.atomic != null) try p.errStr(.duplicate_decl_spec, p.tok_i, "atomic") else b.atomic = p.tok_i; }, else => break, } p.tok_i += 1; any = true; } return any; } const Declarator = struct { name: TokenIndex, ty: Type, func_declarator: ?TokenIndex = null, old_style_func: ?TokenIndex = null, }; const DeclaratorKind = enum { normal, abstract, param, record }; /// declarator : pointer? (IDENTIFIER | '(' declarator ')') directDeclarator* /// abstractDeclarator /// : pointer? ('(' abstractDeclarator ')')? directAbstractDeclarator* fn declarator( p: *Parser, base_type: Type, kind: DeclaratorKind, ) Error!?Declarator { const start = p.tok_i; var d = Declarator{ .name = 0, .ty = try p.pointer(base_type) }; if (base_type.is(.auto_type) and !d.ty.is(.auto_type)) { try p.errTok(.auto_type_requires_plain_declarator, start); return error.ParsingFailed; } const maybe_ident = p.tok_i; if (kind != .abstract and (try p.eatIdentifier()) != null) { d.name = maybe_ident; const combine_tok = p.tok_i; d.ty = try p.directDeclarator(d.ty, &d, kind); try d.ty.validateCombinedType(p, combine_tok); return d; } else if (p.eatToken(.l_paren)) |l_paren| blk: { var res = (try p.declarator(.{ .specifier = .void }, kind)) orelse { p.tok_i = l_paren; break :blk; }; try p.expectClosing(l_paren, .r_paren); const suffix_start = p.tok_i; const outer = try p.directDeclarator(d.ty, &d, kind); try res.ty.combine(outer); try res.ty.validateCombinedType(p, suffix_start); res.old_style_func = d.old_style_func; return res; } const expected_ident = p.tok_i; d.ty = try p.directDeclarator(d.ty, &d, kind); if (kind == .normal and !d.ty.isEnumOrRecord()) { try p.errTok(.expected_ident_or_l_paren, expected_ident); return error.ParsingFailed; } try d.ty.validateCombinedType(p, expected_ident); if (start == p.tok_i) return null; return d; } /// directDeclarator /// : '[' typeQual* assignExpr? ']' directDeclarator? /// | '[' keyword_static typeQual* assignExpr ']' directDeclarator? /// | '[' typeQual+ keyword_static assignExpr ']' directDeclarator? /// | '[' typeQual* '*' ']' directDeclarator? /// | '(' paramDecls ')' directDeclarator? /// | '(' (IDENTIFIER (',' IDENTIFIER))? ')' directDeclarator? /// directAbstractDeclarator /// : '[' typeQual* assignExpr? ']' /// | '[' keyword_static typeQual* assignExpr ']' /// | '[' typeQual+ keyword_static assignExpr ']' /// | '[' '*' ']' /// | '(' paramDecls? ')' fn directDeclarator(p: *Parser, base_type: Type, d: *Declarator, kind: DeclaratorKind) Error!Type { if (p.eatToken(.l_bracket)) |l_bracket| { if (p.tok_ids[p.tok_i] == .l_bracket) { switch (kind) { .normal, .record => if (p.comp.langopts.standard.atLeast(.c2x)) { p.tok_i -= 1; return base_type; }, .param, .abstract => {}, } try p.err(.expected_expr); return error.ParsingFailed; } var res_ty = Type{ // so that we can get any restrict type that might be present .specifier = .pointer, }; var quals = Type.Qualifiers.Builder{}; var got_quals = try p.typeQual(&quals); var static = p.eatToken(.keyword_static); if (static != null and !got_quals) got_quals = try p.typeQual(&quals); var star = p.eatToken(.asterisk); const size_tok = p.tok_i; const const_decl_folding = p.const_decl_folding; p.const_decl_folding = .gnu_vla_folding_extension; const size = if (star) |_| Result{} else try p.assignExpr(); p.const_decl_folding = const_decl_folding; try p.expectClosing(l_bracket, .r_bracket); if (star != null and static != null) { try p.errTok(.invalid_static_star, static.?); static = null; } if (kind != .param) { if (static != null) try p.errTok(.static_non_param, l_bracket) else if (got_quals) try p.errTok(.array_qualifiers, l_bracket); if (star) |some| try p.errTok(.star_non_param, some); static = null; quals = .{}; star = null; } else { try quals.finish(p, &res_ty); } if (static) |_| try size.expect(p); if (base_type.is(.auto_type)) { try p.errStr(.array_of_auto_type, d.name, p.tokSlice(d.name)); return error.ParsingFailed; } const outer = try p.directDeclarator(base_type, d, kind); var max_bits = p.comp.target.ptrBitWidth(); if (max_bits > 61) max_bits = 61; const max_bytes = (@as(u64, 1) << @truncate(max_bits)) - 1; // `outer` is validated later so it may be invalid here const outer_size = outer.sizeof(p.comp); const max_elems = max_bytes / @max(1, outer_size orelse 1); if (!size.ty.isInt()) { try p.errStr(.array_size_non_int, size_tok, try p.typeStr(size.ty)); return error.ParsingFailed; } if (size.val.tag == .unavailable) { if (size.node != .none) { try p.errTok(.vla, size_tok); if (p.func.ty == null and kind != .param and p.record.kind == .invalid) { try p.errTok(.variable_len_array_file_scope, d.name); } const expr_ty = try p.arena.create(Type.Expr); expr_ty.ty = .{ .specifier = .void }; expr_ty.node = size.node; res_ty.data = .{ .expr = expr_ty }; res_ty.specifier = .variable_len_array; if (static) |some| try p.errTok(.useless_static, some); } else if (star) |_| { const elem_ty = try p.arena.create(Type); elem_ty.* = .{ .specifier = .void }; res_ty.data = .{ .sub_type = elem_ty }; res_ty.specifier = .unspecified_variable_len_array; } else { const arr_ty = try p.arena.create(Type.Array); arr_ty.elem = .{ .specifier = .void }; arr_ty.len = 0; res_ty.data = .{ .array = arr_ty }; res_ty.specifier = .incomplete_array; } } else { var size_val = size.val; const size_t = p.comp.types.size; if (size_val.isZero()) { try p.errTok(.zero_length_array, l_bracket); } else if (size_val.compare(.lt, Value.int(0), size_t, p.comp)) { try p.errTok(.negative_array_size, l_bracket); return error.ParsingFailed; } const arr_ty = try p.arena.create(Type.Array); arr_ty.elem = .{ .specifier = .void }; if (size_val.compare(.gt, Value.int(max_elems), size_t, p.comp)) { try p.errTok(.array_too_large, l_bracket); arr_ty.len = max_elems; } else { arr_ty.len = size_val.getInt(u64); } res_ty.data = .{ .array = arr_ty }; res_ty.specifier = .array; } try res_ty.combine(outer); return res_ty; } else if (p.eatToken(.l_paren)) |l_paren| { d.func_declarator = l_paren; const func_ty = try p.arena.create(Type.Func); func_ty.params = &.{}; func_ty.return_type.specifier = .void; var specifier: Type.Specifier = .func; if (p.eatToken(.ellipsis)) |_| { try p.err(.param_before_var_args); try p.expectClosing(l_paren, .r_paren); var res_ty = Type{ .specifier = .func, .data = .{ .func = func_ty } }; const outer = try p.directDeclarator(base_type, d, kind); try res_ty.combine(outer); return res_ty; } if (try p.paramDecls()) |params| { func_ty.params = params; if (p.eatToken(.ellipsis)) |_| specifier = .var_args_func; } else if (p.tok_ids[p.tok_i] == .r_paren) { specifier = .var_args_func; } else if (p.tok_ids[p.tok_i] == .identifier or p.tok_ids[p.tok_i] == .extended_identifier) { d.old_style_func = p.tok_i; const param_buf_top = p.param_buf.items.len; try p.syms.pushScope(p); defer { p.param_buf.items.len = param_buf_top; p.syms.popScope(); } specifier = .old_style_func; while (true) { const name_tok = try p.expectIdentifier(); const interned_name = try p.comp.intern(p.tokSlice(name_tok)); try p.syms.defineParam(p, interned_name, undefined, name_tok); try p.param_buf.append(.{ .name = interned_name, .name_tok = name_tok, .ty = .{ .specifier = .int }, }); if (p.eatToken(.comma) == null) break; } func_ty.params = try p.arena.dupe(Type.Func.Param, p.param_buf.items[param_buf_top..]); } else { try p.err(.expected_param_decl); } try p.expectClosing(l_paren, .r_paren); var res_ty = Type{ .specifier = specifier, .data = .{ .func = func_ty }, }; const outer = try p.directDeclarator(base_type, d, kind); try res_ty.combine(outer); return res_ty; } else return base_type; } /// pointer : '*' typeQual* pointer? fn pointer(p: *Parser, base_ty: Type) Error!Type { var ty = base_ty; while (p.eatToken(.asterisk)) |_| { const elem_ty = try p.arena.create(Type); elem_ty.* = ty; ty = Type{ .specifier = .pointer, .data = .{ .sub_type = elem_ty }, }; var quals = Type.Qualifiers.Builder{}; _ = try p.typeQual(&quals); try quals.finish(p, &ty); } return ty; } /// paramDecls : paramDecl (',' paramDecl)* (',' '...') /// paramDecl : declSpec (declarator | abstractDeclarator) fn paramDecls(p: *Parser) Error!?[]Type.Func.Param { // TODO warn about visibility of types declared here const param_buf_top = p.param_buf.items.len; defer p.param_buf.items.len = param_buf_top; try p.syms.pushScope(p); defer p.syms.popScope(); while (true) { const attr_buf_top = p.attr_buf.len; defer p.attr_buf.len = attr_buf_top; const param_decl_spec = if (try p.declSpec()) |some| some else if (p.param_buf.items.len == param_buf_top) return null else blk: { var spec: Type.Builder = .{}; break :blk DeclSpec{ .ty = try spec.finish(p) }; }; var name_tok: TokenIndex = 0; const first_tok = p.tok_i; var param_ty = param_decl_spec.ty; if (try p.declarator(param_decl_spec.ty, .param)) |some| { if (some.old_style_func) |tok_i| try p.errTok(.invalid_old_style_params, tok_i); try p.attributeSpecifier(); name_tok = some.name; param_ty = some.ty; if (some.name != 0) { const interned_name = try p.comp.intern(p.tokSlice(name_tok)); try p.syms.defineParam(p, interned_name, param_ty, name_tok); } } param_ty = try Attribute.applyParameterAttributes(p, param_ty, attr_buf_top, .alignas_on_param); if (param_ty.isFunc()) { // params declared as functions are converted to function pointers const elem_ty = try p.arena.create(Type); elem_ty.* = param_ty; param_ty = Type{ .specifier = .pointer, .data = .{ .sub_type = elem_ty }, }; } else if (param_ty.isArray()) { // params declared as arrays are converted to pointers param_ty.decayArray(); } else if (param_ty.is(.void)) { // validate void parameters if (p.param_buf.items.len == param_buf_top) { if (p.tok_ids[p.tok_i] != .r_paren) { try p.err(.void_only_param); if (param_ty.anyQual()) try p.err(.void_param_qualified); return error.ParsingFailed; } return &[0]Type.Func.Param{}; } try p.err(.void_must_be_first_param); return error.ParsingFailed; } try param_decl_spec.validateParam(p, ¶m_ty); try p.param_buf.append(.{ .name = if (name_tok == 0) .empty else try p.comp.intern(p.tokSlice(name_tok)), .name_tok = if (name_tok == 0) first_tok else name_tok, .ty = param_ty, }); if (p.eatToken(.comma) == null) break; if (p.tok_ids[p.tok_i] == .ellipsis) break; } return try p.arena.dupe(Type.Func.Param, p.param_buf.items[param_buf_top..]); } /// typeName : specQual abstractDeclarator fn typeName(p: *Parser) Error!?Type { const attr_buf_top = p.attr_buf.len; defer p.attr_buf.len = attr_buf_top; var ty = (try p.specQual()) orelse return null; if (try p.declarator(ty, .abstract)) |some| { if (some.old_style_func) |tok_i| try p.errTok(.invalid_old_style_params, tok_i); return try Attribute.applyTypeAttributes(p, some.ty, attr_buf_top, .align_ignored); } return try Attribute.applyTypeAttributes(p, ty, attr_buf_top, .align_ignored); } /// initializer /// : assignExpr /// | '{' initializerItems '}' fn initializer(p: *Parser, init_ty: Type) Error!Result { // fast path for non-braced initializers if (p.tok_ids[p.tok_i] != .l_brace) { const tok = p.tok_i; var res = try p.assignExpr(); try res.expect(p); if (try p.coerceArrayInit(&res, tok, init_ty)) return res; try p.coerceInit(&res, tok, init_ty); return res; } if (init_ty.is(.auto_type)) { try p.err(.auto_type_with_init_list); return error.ParsingFailed; } var il: InitList = .{}; defer il.deinit(p.gpa); _ = try p.initializerItem(&il, init_ty); const res = try p.convertInitList(il, init_ty); var res_ty = p.nodes.items(.ty)[@intFromEnum(res)]; res_ty.qual = init_ty.qual; return Result{ .ty = res_ty, .node = res }; } /// initializerItems : designation? initializer (',' designation? initializer)* ','? /// designation : designator+ '=' /// designator /// : '[' integerConstExpr ']' /// | '.' identifier fn initializerItem(p: *Parser, il: *InitList, init_ty: Type) Error!bool { const l_brace = p.eatToken(.l_brace) orelse { const tok = p.tok_i; var res = try p.assignExpr(); if (res.empty(p)) return false; const arr = try p.coerceArrayInit(&res, tok, init_ty); if (!arr) try p.coerceInit(&res, tok, init_ty); if (il.tok != 0) { try p.errTok(.initializer_overrides, tok); try p.errTok(.previous_initializer, il.tok); } il.node = res.node; il.tok = tok; return true; }; const is_scalar = init_ty.isScalar(); const is_complex = init_ty.isComplex(); const scalar_inits_needed: usize = if (is_complex) 2 else 1; if (p.eatToken(.r_brace)) |_| { if (is_scalar) try p.errTok(.empty_scalar_init, l_brace); if (il.tok != 0) { try p.errTok(.initializer_overrides, l_brace); try p.errTok(.previous_initializer, il.tok); } il.node = .none; il.tok = l_brace; return true; } var count: u64 = 0; var warned_excess = false; var is_str_init = false; var index_hint: ?u64 = null; while (true) : (count += 1) { errdefer p.skipTo(.r_brace); var first_tok = p.tok_i; var cur_ty = init_ty; var cur_il = il; var designation = false; var cur_index_hint: ?u64 = null; while (true) { if (p.eatToken(.l_bracket)) |l_bracket| { if (!cur_ty.isArray()) { try p.errStr(.invalid_array_designator, l_bracket, try p.typeStr(cur_ty)); return error.ParsingFailed; } const expr_tok = p.tok_i; const index_res = try p.integerConstExpr(.gnu_folding_extension); try p.expectClosing(l_bracket, .r_bracket); if (index_res.val.tag == .unavailable) { try p.errTok(.expected_integer_constant_expr, expr_tok); return error.ParsingFailed; } else if (index_res.val.compare(.lt, index_res.val.zero(), index_res.ty, p.comp)) { try p.errExtra(.negative_array_designator, l_bracket + 1, .{ .signed = index_res.val.signExtend(index_res.ty, p.comp), }); return error.ParsingFailed; } const max_len = cur_ty.arrayLen() orelse std.math.maxInt(usize); if (index_res.val.data.int >= max_len) { try p.errExtra(.oob_array_designator, l_bracket + 1, .{ .unsigned = index_res.val.data.int }); return error.ParsingFailed; } const checked = index_res.val.getInt(u64); cur_index_hint = cur_index_hint orelse checked; cur_il = try cur_il.find(p.gpa, checked); cur_ty = cur_ty.elemType(); designation = true; } else if (p.eatToken(.period)) |period| { const field_tok = try p.expectIdentifier(); const field_str = p.tokSlice(field_tok); const field_name = try p.comp.intern(field_str); cur_ty = cur_ty.canonicalize(.standard); if (!cur_ty.isRecord()) { try p.errStr(.invalid_field_designator, period, try p.typeStr(cur_ty)); return error.ParsingFailed; } else if (!cur_ty.hasField(field_name)) { try p.errStr(.no_such_field_designator, period, field_str); return error.ParsingFailed; } // TODO check if union already has field set outer: while (true) { for (cur_ty.data.record.fields, 0..) |f, i| { if (f.isAnonymousRecord()) { // Recurse into anonymous field if it has a field by the name. if (!f.ty.hasField(field_name)) continue; cur_ty = f.ty.canonicalize(.standard); cur_il = try il.find(p.gpa, i); cur_index_hint = cur_index_hint orelse i; continue :outer; } if (field_name == f.name) { cur_il = try cur_il.find(p.gpa, i); cur_ty = f.ty; cur_index_hint = cur_index_hint orelse i; break :outer; } } unreachable; // we already checked that the starting type has this field } designation = true; } else break; } if (designation) index_hint = null; defer index_hint = cur_index_hint orelse null; if (designation) _ = try p.expectToken(.equal); if (!designation and cur_ty.hasAttribute(.designated_init)) { try p.err(.designated_init_needed); } var saw = false; if (is_str_init and p.isStringInit(init_ty)) { // discard further strings var tmp_il = InitList{}; defer tmp_il.deinit(p.gpa); saw = try p.initializerItem(&tmp_il, .{ .specifier = .void }); } else if (count == 0 and p.isStringInit(init_ty)) { is_str_init = true; saw = try p.initializerItem(il, init_ty); } else if (is_scalar and count >= scalar_inits_needed) { // discard further scalars var tmp_il = InitList{}; defer tmp_il.deinit(p.gpa); saw = try p.initializerItem(&tmp_il, .{ .specifier = .void }); } else if (p.tok_ids[p.tok_i] == .l_brace) { if (designation) { // designation overrides previous value, let existing mechanism handle it saw = try p.initializerItem(cur_il, cur_ty); } else if (try p.findAggregateInitializer(&cur_il, &cur_ty, &index_hint)) { saw = try p.initializerItem(cur_il, cur_ty); } else { // discard further values var tmp_il = InitList{}; defer tmp_il.deinit(p.gpa); saw = try p.initializerItem(&tmp_il, .{ .specifier = .void }); if (!warned_excess) try p.errTok(if (init_ty.isArray()) .excess_array_init else .excess_struct_init, first_tok); warned_excess = true; } } else single_item: { first_tok = p.tok_i; var res = try p.assignExpr(); saw = !res.empty(p); if (!saw) break :single_item; excess: { if (index_hint) |*hint| { if (try p.findScalarInitializerAt(&cur_il, &cur_ty, res.ty, first_tok, hint)) break :excess; } else if (try p.findScalarInitializer(&cur_il, &cur_ty, res.ty, first_tok)) break :excess; if (designation) break :excess; if (!warned_excess) try p.errTok(if (init_ty.isArray()) .excess_array_init else .excess_struct_init, first_tok); warned_excess = true; break :single_item; } const arr = try p.coerceArrayInit(&res, first_tok, cur_ty); if (!arr) try p.coerceInit(&res, first_tok, cur_ty); if (cur_il.tok != 0) { try p.errTok(.initializer_overrides, first_tok); try p.errTok(.previous_initializer, cur_il.tok); } cur_il.node = res.node; cur_il.tok = first_tok; } if (!saw) { if (designation) { try p.err(.expected_expr); return error.ParsingFailed; } break; } else if (count == 1) { if (is_str_init) try p.errTok(.excess_str_init, first_tok); if (is_scalar and !is_complex) try p.errTok(.excess_scalar_init, first_tok); } else if (count == 2) { if (is_scalar and is_complex) try p.errTok(.excess_scalar_init, first_tok); } if (p.eatToken(.comma) == null) break; } try p.expectClosing(l_brace, .r_brace); if (is_complex and count == 1) { // count of 1 means we saw exactly 2 items in the initializer list try p.errTok(.complex_component_init, l_brace); } if (is_scalar or is_str_init) return true; if (il.tok != 0) { try p.errTok(.initializer_overrides, l_brace); try p.errTok(.previous_initializer, il.tok); } il.node = .none; il.tok = l_brace; return true; } /// Returns true if the value is unused. fn findScalarInitializerAt(p: *Parser, il: **InitList, ty: *Type, actual_ty: Type, first_tok: TokenIndex, start_index: *u64) Error!bool { if (ty.isArray()) { if (il.*.node != .none) return false; start_index.* += 1; const arr_ty = ty.*; const elem_count = arr_ty.arrayLen() orelse std.math.maxInt(u64); if (elem_count == 0) { try p.errTok(.empty_aggregate_init_braces, first_tok); return error.ParsingFailed; } const elem_ty = arr_ty.elemType(); const arr_il = il.*; if (start_index.* < elem_count) { ty.* = elem_ty; il.* = try arr_il.find(p.gpa, start_index.*); _ = try p.findScalarInitializer(il, ty, actual_ty, first_tok); return true; } return false; } else if (ty.get(.@"struct")) |struct_ty| { if (il.*.node != .none) return false; start_index.* += 1; const fields = struct_ty.data.record.fields; if (fields.len == 0) { try p.errTok(.empty_aggregate_init_braces, first_tok); return error.ParsingFailed; } const struct_il = il.*; if (start_index.* < fields.len) { const field = fields[@intCast(start_index.*)]; ty.* = field.ty; il.* = try struct_il.find(p.gpa, start_index.*); _ = try p.findScalarInitializer(il, ty, actual_ty, first_tok); return true; } return false; } else if (ty.get(.@"union")) |_| { return false; } return il.*.node == .none; } /// Returns true if the value is unused. fn findScalarInitializer(p: *Parser, il: **InitList, ty: *Type, actual_ty: Type, first_tok: TokenIndex) Error!bool { if (ty.isArray() or ty.isComplex()) { if (il.*.node != .none) return false; const start_index = il.*.list.items.len; var index = if (start_index != 0) il.*.list.items[start_index - 1].index else start_index; const arr_ty = ty.*; const elem_count: u64 = arr_ty.expectedInitListSize() orelse std.math.maxInt(u64); if (elem_count == 0) { try p.errTok(.empty_aggregate_init_braces, first_tok); return error.ParsingFailed; } const elem_ty = arr_ty.elemType(); const arr_il = il.*; while (index < elem_count) : (index += 1) { ty.* = elem_ty; il.* = try arr_il.find(p.gpa, index); if (il.*.node == .none and actual_ty.eql(elem_ty, p.comp, false)) return true; if (try p.findScalarInitializer(il, ty, actual_ty, first_tok)) return true; } return false; } else if (ty.get(.@"struct")) |struct_ty| { if (il.*.node != .none) return false; if (actual_ty.eql(ty.*, p.comp, false)) return true; const start_index = il.*.list.items.len; var index = if (start_index != 0) il.*.list.items[start_index - 1].index + 1 else start_index; const fields = struct_ty.data.record.fields; if (fields.len == 0) { try p.errTok(.empty_aggregate_init_braces, first_tok); return error.ParsingFailed; } const struct_il = il.*; while (index < fields.len) : (index += 1) { const field = fields[@intCast(index)]; ty.* = field.ty; il.* = try struct_il.find(p.gpa, index); if (il.*.node == .none and actual_ty.eql(field.ty, p.comp, false)) return true; if (try p.findScalarInitializer(il, ty, actual_ty, first_tok)) return true; } return false; } else if (ty.get(.@"union")) |union_ty| { if (il.*.node != .none) return false; if (actual_ty.eql(ty.*, p.comp, false)) return true; if (union_ty.data.record.fields.len == 0) { try p.errTok(.empty_aggregate_init_braces, first_tok); return error.ParsingFailed; } ty.* = union_ty.data.record.fields[0].ty; il.* = try il.*.find(p.gpa, 0); // if (il.*.node == .none and actual_ty.eql(ty, p.comp, false)) return true; if (try p.findScalarInitializer(il, ty, actual_ty, first_tok)) return true; return false; } return il.*.node == .none; } fn findAggregateInitializer(p: *Parser, il: **InitList, ty: *Type, start_index: *?u64) Error!bool { if (ty.isArray()) { if (il.*.node != .none) return false; const list_index = il.*.list.items.len; const index = if (start_index.*) |*some| blk: { some.* += 1; break :blk some.*; } else if (list_index != 0) il.*.list.items[list_index - 1].index + 1 else list_index; const arr_ty = ty.*; const elem_count = arr_ty.arrayLen() orelse std.math.maxInt(u64); const elem_ty = arr_ty.elemType(); if (index < elem_count) { ty.* = elem_ty; il.* = try il.*.find(p.gpa, index); return true; } return false; } else if (ty.get(.@"struct")) |struct_ty| { if (il.*.node != .none) return false; const list_index = il.*.list.items.len; const index = if (start_index.*) |*some| blk: { some.* += 1; break :blk some.*; } else if (list_index != 0) il.*.list.items[list_index - 1].index + 1 else list_index; const field_count = struct_ty.data.record.fields.len; if (index < field_count) { ty.* = struct_ty.data.record.fields[@intCast(index)].ty; il.* = try il.*.find(p.gpa, index); return true; } return false; } else if (ty.get(.@"union")) |union_ty| { if (il.*.node != .none) return false; if (start_index.*) |_| return false; // overrides if (union_ty.data.record.fields.len == 0) return false; ty.* = union_ty.data.record.fields[0].ty; il.* = try il.*.find(p.gpa, 0); return true; } else { try p.err(.too_many_scalar_init_braces); return il.*.node == .none; } } fn coerceArrayInit(p: *Parser, item: *Result, tok: TokenIndex, target: Type) !bool { if (!target.isArray()) return false; const is_str_lit = p.nodeIs(item.node, .string_literal_expr); if (!is_str_lit and !p.nodeIs(item.node, .compound_literal_expr) or !item.ty.isArray()) { try p.errTok(.array_init_str, tok); return true; // do not do further coercion } const target_spec = target.elemType().canonicalize(.standard).specifier; const item_spec = item.ty.elemType().canonicalize(.standard).specifier; const compatible = target.elemType().eql(item.ty.elemType(), p.comp, false) or (is_str_lit and item_spec == .char and (target_spec == .uchar or target_spec == .schar)) or (is_str_lit and item_spec == .uchar and (target_spec == .uchar or target_spec == .schar or target_spec == .char)); if (!compatible) { const e_msg = " with array of type "; try p.errStr(.incompatible_array_init, tok, try p.typePairStrExtra(target, e_msg, item.ty)); return true; // do not do further coercion } if (target.get(.array)) |arr_ty| { assert(item.ty.specifier == .array); var len = item.ty.arrayLen().?; const array_len = arr_ty.arrayLen().?; if (is_str_lit) { // the null byte of a string can be dropped if (len - 1 > array_len) try p.errTok(.str_init_too_long, tok); } else if (len > array_len) { try p.errStr( .arr_init_too_long, tok, try p.typePairStrExtra(target, " with array of type ", item.ty), ); } } return true; } fn coerceInit(p: *Parser, item: *Result, tok: TokenIndex, target: Type) !void { if (target.is(.void)) return; // Do not do type coercion on excess items const node = item.node; try item.lvalConversion(p); if (target.is(.auto_type)) { if (p.getNode(node, .member_access_expr) orelse p.getNode(node, .member_access_ptr_expr)) |member_node| { if (Tree.isBitfield(p.nodes.slice(), member_node)) try p.errTok(.auto_type_from_bitfield, tok); } return; } try item.coerce(p, target, tok, .init); } fn isStringInit(p: *Parser, ty: Type) bool { if (!ty.isArray() or !ty.elemType().isInt()) return false; var i = p.tok_i; while (true) : (i += 1) { switch (p.tok_ids[i]) { .l_paren => {}, .string_literal, .string_literal_utf_16, .string_literal_utf_8, .string_literal_utf_32, .string_literal_wide, => return true, else => return false, } } } /// Convert InitList into an AST fn convertInitList(p: *Parser, il: InitList, init_ty: Type) Error!NodeIndex { const is_complex = init_ty.isComplex(); if (init_ty.isScalar() and !is_complex) { if (il.node == .none) { return p.addNode(.{ .tag = .default_init_expr, .ty = init_ty, .data = undefined }); } return il.node; } else if (init_ty.is(.variable_len_array)) { return error.ParsingFailed; // vla invalid, reported earlier } else if (init_ty.isArray() or is_complex) { if (il.node != .none) { return il.node; } const list_buf_top = p.list_buf.items.len; defer p.list_buf.items.len = list_buf_top; const elem_ty = init_ty.elemType(); const max_items: u64 = init_ty.expectedInitListSize() orelse std.math.maxInt(usize); var start: u64 = 0; for (il.list.items) |*init| { if (init.index > start) { const elem = try p.addNode(.{ .tag = .array_filler_expr, .ty = elem_ty, .data = .{ .int = init.index - start }, }); try p.list_buf.append(elem); } start = init.index + 1; const elem = try p.convertInitList(init.list, elem_ty); try p.list_buf.append(elem); } var arr_init_node: Tree.Node = .{ .tag = .array_init_expr_two, .ty = init_ty, .data = .{ .bin = .{ .lhs = .none, .rhs = .none } }, }; if (init_ty.specifier == .incomplete_array) { arr_init_node.ty.specifier = .array; arr_init_node.ty.data.array.len = start; } else if (init_ty.is(.incomplete_array)) { const arr_ty = try p.arena.create(Type.Array); arr_ty.* = .{ .elem = init_ty.elemType(), .len = start }; arr_init_node.ty = .{ .specifier = .array, .data = .{ .array = arr_ty }, }; const attrs = init_ty.getAttributes(); arr_init_node.ty = try arr_init_node.ty.withAttributes(p.arena, attrs); } else if (start < max_items) { const elem = try p.addNode(.{ .tag = .array_filler_expr, .ty = elem_ty, .data = .{ .int = max_items - start }, }); try p.list_buf.append(elem); } const items = p.list_buf.items[list_buf_top..]; switch (items.len) { 0 => {}, 1 => arr_init_node.data.bin.lhs = items[0], 2 => arr_init_node.data.bin = .{ .lhs = items[0], .rhs = items[1] }, else => { arr_init_node.tag = .array_init_expr; arr_init_node.data = .{ .range = try p.addList(items) }; }, } return try p.addNode(arr_init_node); } else if (init_ty.get(.@"struct")) |struct_ty| { assert(!struct_ty.hasIncompleteSize()); if (il.node != .none) { return il.node; } const list_buf_top = p.list_buf.items.len; defer p.list_buf.items.len = list_buf_top; var init_index: usize = 0; for (struct_ty.data.record.fields, 0..) |f, i| { if (init_index < il.list.items.len and il.list.items[init_index].index == i) { const item = try p.convertInitList(il.list.items[init_index].list, f.ty); try p.list_buf.append(item); init_index += 1; } else { const item = try p.addNode(.{ .tag = .default_init_expr, .ty = f.ty, .data = undefined }); try p.list_buf.append(item); } } var struct_init_node: Tree.Node = .{ .tag = .struct_init_expr_two, .ty = init_ty, .data = .{ .bin = .{ .lhs = .none, .rhs = .none } }, }; const items = p.list_buf.items[list_buf_top..]; switch (items.len) { 0 => {}, 1 => struct_init_node.data.bin.lhs = items[0], 2 => struct_init_node.data.bin = .{ .lhs = items[0], .rhs = items[1] }, else => { struct_init_node.tag = .struct_init_expr; struct_init_node.data = .{ .range = try p.addList(items) }; }, } return try p.addNode(struct_init_node); } else if (init_ty.get(.@"union")) |union_ty| { if (il.node != .none) { return il.node; } var union_init_node: Tree.Node = .{ .tag = .union_init_expr, .ty = init_ty, .data = .{ .union_init = .{ .field_index = 0, .node = .none } }, }; if (union_ty.data.record.fields.len == 0) { // do nothing for empty unions } else if (il.list.items.len == 0) { union_init_node.data.union_init.node = try p.addNode(.{ .tag = .default_init_expr, .ty = init_ty, .data = undefined, }); } else { const init = il.list.items[0]; const index: u32 = @truncate(init.index); const field_ty = union_ty.data.record.fields[index].ty; union_init_node.data.union_init = .{ .field_index = index, .node = try p.convertInitList(init.list, field_ty), }; } return try p.addNode(union_init_node); } else { return error.ParsingFailed; // initializer target is invalid, reported earlier } } fn msvcAsmStmt(p: *Parser) Error!?NodeIndex { return p.todo("MSVC assembly statements"); } /// asmOperand : ('[' IDENTIFIER ']')? asmStr '(' expr ')' fn asmOperand(p: *Parser, names: *std.ArrayList(?TokenIndex), constraints: *NodeList, exprs: *NodeList) Error!void { if (p.eatToken(.l_bracket)) |l_bracket| { const ident = (try p.eatIdentifier()) orelse { try p.err(.expected_identifier); return error.ParsingFailed; }; try names.append(ident); try p.expectClosing(l_bracket, .r_bracket); } else { try names.append(null); } const constraint = try p.asmStr(); try constraints.append(constraint.node); const l_paren = p.eatToken(.l_paren) orelse { try p.errExtra(.expected_token, p.tok_i, .{ .tok_id = .{ .actual = p.tok_ids[p.tok_i], .expected = .l_paren } }); return error.ParsingFailed; }; const res = try p.expr(); try p.expectClosing(l_paren, .r_paren); try res.expect(p); try exprs.append(res.node); } /// gnuAsmStmt /// : asmStr /// | asmStr ':' asmOperand* /// | asmStr ':' asmOperand* ':' asmOperand* /// | asmStr ':' asmOperand* ':' asmOperand* : asmStr? (',' asmStr)* /// | asmStr ':' asmOperand* ':' asmOperand* : asmStr? (',' asmStr)* : IDENTIFIER (',' IDENTIFIER)* fn gnuAsmStmt(p: *Parser, quals: Tree.GNUAssemblyQualifiers, l_paren: TokenIndex) Error!NodeIndex { const asm_str = try p.asmStr(); try p.checkAsmStr(asm_str.val, l_paren); if (p.tok_ids[p.tok_i] == .r_paren) { return p.addNode(.{ .tag = .gnu_asm_simple, .ty = .{ .specifier = .void }, .data = .{ .un = asm_str.node }, }); } const expected_items = 8; // arbitrarily chosen, most assembly will have fewer than 8 inputs/outputs/constraints/names const bytes_needed = expected_items * @sizeOf(?TokenIndex) + expected_items * 3 * @sizeOf(NodeIndex); var stack_fallback = std.heap.stackFallback(bytes_needed, p.gpa); const allocator = stack_fallback.get(); // TODO: Consider using a TokenIndex of 0 instead of null if we need to store the names in the tree var names = std.ArrayList(?TokenIndex).initCapacity(allocator, expected_items) catch unreachable; // stack allocation already succeeded defer names.deinit(); var constraints = NodeList.initCapacity(allocator, expected_items) catch unreachable; // stack allocation already succeeded defer constraints.deinit(); var exprs = NodeList.initCapacity(allocator, expected_items) catch unreachable; //stack allocation already succeeded defer exprs.deinit(); var clobbers = NodeList.initCapacity(allocator, expected_items) catch unreachable; //stack allocation already succeeded defer clobbers.deinit(); // Outputs var ate_extra_colon = false; if (p.eatToken(.colon) orelse p.eatToken(.colon_colon)) |tok_i| { ate_extra_colon = p.tok_ids[tok_i] == .colon_colon; if (!ate_extra_colon) { if (p.tok_ids[p.tok_i].isStringLiteral() or p.tok_ids[p.tok_i] == .l_bracket) { while (true) { try p.asmOperand(&names, &constraints, &exprs); if (p.eatToken(.comma) == null) break; } } } } const num_outputs = names.items.len; // Inputs if (ate_extra_colon or p.tok_ids[p.tok_i] == .colon or p.tok_ids[p.tok_i] == .colon_colon) { if (ate_extra_colon) { ate_extra_colon = false; } else { ate_extra_colon = p.tok_ids[p.tok_i] == .colon_colon; p.tok_i += 1; } if (!ate_extra_colon) { if (p.tok_ids[p.tok_i].isStringLiteral() or p.tok_ids[p.tok_i] == .l_bracket) { while (true) { try p.asmOperand(&names, &constraints, &exprs); if (p.eatToken(.comma) == null) break; } } } } std.debug.assert(names.items.len == constraints.items.len and constraints.items.len == exprs.items.len); const num_inputs = names.items.len - num_outputs; _ = num_inputs; // Clobbers if (ate_extra_colon or p.tok_ids[p.tok_i] == .colon or p.tok_ids[p.tok_i] == .colon_colon) { if (ate_extra_colon) { ate_extra_colon = false; } else { ate_extra_colon = p.tok_ids[p.tok_i] == .colon_colon; p.tok_i += 1; } if (!ate_extra_colon and p.tok_ids[p.tok_i].isStringLiteral()) { while (true) { const clobber = try p.asmStr(); try clobbers.append(clobber.node); if (p.eatToken(.comma) == null) break; } } } if (!quals.goto and (p.tok_ids[p.tok_i] != .r_paren or ate_extra_colon)) { try p.errExtra(.expected_token, p.tok_i, .{ .tok_id = .{ .actual = p.tok_ids[p.tok_i], .expected = .r_paren } }); return error.ParsingFailed; } // Goto labels var num_labels: u32 = 0; if (ate_extra_colon or p.tok_ids[p.tok_i] == .colon) { if (!ate_extra_colon) { p.tok_i += 1; } while (true) { const ident = (try p.eatIdentifier()) orelse { try p.err(.expected_identifier); return error.ParsingFailed; }; const ident_str = p.tokSlice(ident); const label = p.findLabel(ident_str) orelse blk: { try p.labels.append(.{ .unresolved_goto = ident }); break :blk ident; }; try names.append(ident); const elem_ty = try p.arena.create(Type); elem_ty.* = .{ .specifier = .void }; const result_ty = Type{ .specifier = .pointer, .data = .{ .sub_type = elem_ty } }; const label_addr_node = try p.addNode(.{ .tag = .addr_of_label, .data = .{ .decl_ref = label }, .ty = result_ty, }); try exprs.append(label_addr_node); num_labels += 1; if (p.eatToken(.comma) == null) break; } } else if (quals.goto) { try p.errExtra(.expected_token, p.tok_i, .{ .tok_id = .{ .actual = p.tok_ids[p.tok_i], .expected = .colon } }); return error.ParsingFailed; } // TODO: validate and insert into AST return .none; } fn checkAsmStr(p: *Parser, asm_str: Value, tok: TokenIndex) !void { if (!p.comp.langopts.gnu_asm) { const str = asm_str.data.bytes; if (str.len() > 1) { // Empty string (just a NUL byte) is ok because it does not emit any assembly try p.errTok(.gnu_asm_disabled, tok); } } } /// assembly /// : keyword_asm asmQual* '(' asmStr ')' /// | keyword_asm asmQual* '(' gnuAsmStmt ')' /// | keyword_asm msvcAsmStmt fn assembly(p: *Parser, kind: enum { global, decl_label, stmt }) Error!?NodeIndex { const asm_tok = p.tok_i; switch (p.tok_ids[p.tok_i]) { .keyword_asm => { try p.err(.extension_token_used); p.tok_i += 1; }, .keyword_asm1, .keyword_asm2 => p.tok_i += 1, else => return null, } if (!p.tok_ids[p.tok_i].canOpenGCCAsmStmt()) { return p.msvcAsmStmt(); } var quals: Tree.GNUAssemblyQualifiers = .{}; while (true) : (p.tok_i += 1) switch (p.tok_ids[p.tok_i]) { .keyword_volatile, .keyword_volatile1, .keyword_volatile2 => { if (kind != .stmt) try p.errStr(.meaningless_asm_qual, p.tok_i, "volatile"); if (quals.@"volatile") try p.errStr(.duplicate_asm_qual, p.tok_i, "volatile"); quals.@"volatile" = true; }, .keyword_inline, .keyword_inline1, .keyword_inline2 => { if (kind != .stmt) try p.errStr(.meaningless_asm_qual, p.tok_i, "inline"); if (quals.@"inline") try p.errStr(.duplicate_asm_qual, p.tok_i, "inline"); quals.@"inline" = true; }, .keyword_goto => { if (kind != .stmt) try p.errStr(.meaningless_asm_qual, p.tok_i, "goto"); if (quals.goto) try p.errStr(.duplicate_asm_qual, p.tok_i, "goto"); quals.goto = true; }, else => break, }; const l_paren = try p.expectToken(.l_paren); var result_node: NodeIndex = .none; switch (kind) { .decl_label => { const asm_str = try p.asmStr(); const str = asm_str.val.data.bytes.trim(1); // remove null-terminator const attr = Attribute{ .tag = .asm_label, .args = .{ .asm_label = .{ .name = str } }, .syntax = .keyword }; try p.attr_buf.append(p.gpa, .{ .attr = attr, .tok = asm_tok }); }, .global => { const asm_str = try p.asmStr(); try p.checkAsmStr(asm_str.val, l_paren); result_node = try p.addNode(.{ .tag = .file_scope_asm, .ty = .{ .specifier = .void }, .data = .{ .decl = .{ .name = asm_tok, .node = asm_str.node } }, }); }, .stmt => result_node = try p.gnuAsmStmt(quals, l_paren), } try p.expectClosing(l_paren, .r_paren); if (kind != .decl_label) _ = try p.expectToken(.semicolon); return result_node; } /// Same as stringLiteral but errors on unicode and wide string literals fn asmStr(p: *Parser) Error!Result { var i = p.tok_i; while (true) : (i += 1) switch (p.tok_ids[i]) { .string_literal => {}, .string_literal_utf_16, .string_literal_utf_8, .string_literal_utf_32 => { try p.errStr(.invalid_asm_str, p.tok_i, "unicode"); return error.ParsingFailed; }, .string_literal_wide => { try p.errStr(.invalid_asm_str, p.tok_i, "wide"); return error.ParsingFailed; }, else => break, }; return try p.stringLiteral(); } // ====== statements ====== /// stmt /// : labeledStmt /// | compoundStmt /// | keyword_if '(' expr ')' stmt (keyword_else stmt)? /// | keyword_switch '(' expr ')' stmt /// | keyword_while '(' expr ')' stmt /// | keyword_do stmt while '(' expr ')' ';' /// | keyword_for '(' (decl | expr? ';') expr? ';' expr? ')' stmt /// | keyword_goto (IDENTIFIER | ('*' expr)) ';' /// | keyword_continue ';' /// | keyword_break ';' /// | keyword_return expr? ';' /// | assembly ';' /// | expr? ';' fn stmt(p: *Parser) Error!NodeIndex { if (try p.labeledStmt()) |some| return some; if (try p.compoundStmt(false, null)) |some| return some; if (p.eatToken(.keyword_if)) |_| { const l_paren = try p.expectToken(.l_paren); const cond_tok = p.tok_i; var cond = try p.expr(); try cond.expect(p); try cond.lvalConversion(p); try cond.usualUnaryConversion(p, cond_tok); if (!cond.ty.isScalar()) try p.errStr(.statement_scalar, l_paren + 1, try p.typeStr(cond.ty)); try cond.saveValue(p); try p.expectClosing(l_paren, .r_paren); const then = try p.stmt(); const @"else" = if (p.eatToken(.keyword_else)) |_| try p.stmt() else .none; if (then != .none and @"else" != .none) return try p.addNode(.{ .tag = .if_then_else_stmt, .data = .{ .if3 = .{ .cond = cond.node, .body = (try p.addList(&.{ then, @"else" })).start } }, }) else return try p.addNode(.{ .tag = .if_then_stmt, .data = .{ .bin = .{ .lhs = cond.node, .rhs = then } }, }); } if (p.eatToken(.keyword_switch)) |_| { const l_paren = try p.expectToken(.l_paren); const cond_tok = p.tok_i; var cond = try p.expr(); try cond.expect(p); try cond.lvalConversion(p); try cond.usualUnaryConversion(p, cond_tok); if (!cond.ty.isInt()) try p.errStr(.statement_int, l_paren + 1, try p.typeStr(cond.ty)); try cond.saveValue(p); try p.expectClosing(l_paren, .r_paren); const old_switch = p.@"switch"; var @"switch" = Switch{ .ranges = std.ArrayList(Switch.Range).init(p.gpa), .ty = cond.ty, }; p.@"switch" = &@"switch"; defer { @"switch".ranges.deinit(); p.@"switch" = old_switch; } const body = try p.stmt(); return try p.addNode(.{ .tag = .switch_stmt, .data = .{ .bin = .{ .lhs = cond.node, .rhs = body } }, }); } if (p.eatToken(.keyword_while)) |_| { const l_paren = try p.expectToken(.l_paren); const cond_tok = p.tok_i; var cond = try p.expr(); try cond.expect(p); try cond.lvalConversion(p); try cond.usualUnaryConversion(p, cond_tok); if (!cond.ty.isScalar()) try p.errStr(.statement_scalar, l_paren + 1, try p.typeStr(cond.ty)); try cond.saveValue(p); try p.expectClosing(l_paren, .r_paren); const body = body: { const old_loop = p.in_loop; p.in_loop = true; defer p.in_loop = old_loop; break :body try p.stmt(); }; return try p.addNode(.{ .tag = .while_stmt, .data = .{ .bin = .{ .lhs = cond.node, .rhs = body } }, }); } if (p.eatToken(.keyword_do)) |_| { const body = body: { const old_loop = p.in_loop; p.in_loop = true; defer p.in_loop = old_loop; break :body try p.stmt(); }; _ = try p.expectToken(.keyword_while); const l_paren = try p.expectToken(.l_paren); const cond_tok = p.tok_i; var cond = try p.expr(); try cond.expect(p); try cond.lvalConversion(p); try cond.usualUnaryConversion(p, cond_tok); if (!cond.ty.isScalar()) try p.errStr(.statement_scalar, l_paren + 1, try p.typeStr(cond.ty)); try cond.saveValue(p); try p.expectClosing(l_paren, .r_paren); _ = try p.expectToken(.semicolon); return try p.addNode(.{ .tag = .do_while_stmt, .data = .{ .bin = .{ .lhs = cond.node, .rhs = body } }, }); } if (p.eatToken(.keyword_for)) |_| { try p.syms.pushScope(p); defer p.syms.popScope(); const decl_buf_top = p.decl_buf.items.len; defer p.decl_buf.items.len = decl_buf_top; const l_paren = try p.expectToken(.l_paren); const got_decl = try p.decl(); // for (init const init_start = p.tok_i; var err_start = p.comp.diag.list.items.len; var init = if (!got_decl) try p.expr() else Result{}; try init.saveValue(p); try init.maybeWarnUnused(p, init_start, err_start); if (!got_decl) _ = try p.expectToken(.semicolon); // for (init; cond const cond_tok = p.tok_i; var cond = try p.expr(); if (cond.node != .none) { try cond.lvalConversion(p); try cond.usualUnaryConversion(p, cond_tok); if (!cond.ty.isScalar()) try p.errStr(.statement_scalar, l_paren + 1, try p.typeStr(cond.ty)); } try cond.saveValue(p); _ = try p.expectToken(.semicolon); // for (init; cond; incr const incr_start = p.tok_i; err_start = p.comp.diag.list.items.len; var incr = try p.expr(); try incr.maybeWarnUnused(p, incr_start, err_start); try incr.saveValue(p); try p.expectClosing(l_paren, .r_paren); const body = body: { const old_loop = p.in_loop; p.in_loop = true; defer p.in_loop = old_loop; break :body try p.stmt(); }; if (got_decl) { const start = (try p.addList(p.decl_buf.items[decl_buf_top..])).start; const end = (try p.addList(&.{ cond.node, incr.node, body })).end; return try p.addNode(.{ .tag = .for_decl_stmt, .data = .{ .range = .{ .start = start, .end = end } }, }); } else if (init.node == .none and cond.node == .none and incr.node == .none) { return try p.addNode(.{ .tag = .forever_stmt, .data = .{ .un = body }, }); } else return try p.addNode(.{ .tag = .for_stmt, .data = .{ .if3 = .{ .cond = body, .body = (try p.addList(&.{ init.node, cond.node, incr.node })).start, } } }); } if (p.eatToken(.keyword_goto)) |goto_tok| { if (p.eatToken(.asterisk)) |_| { const expr_tok = p.tok_i; var e = try p.expr(); try e.expect(p); try e.lvalConversion(p); p.computed_goto_tok = p.computed_goto_tok orelse goto_tok; if (!e.ty.isPtr()) { const elem_ty = try p.arena.create(Type); elem_ty.* = .{ .specifier = .void, .qual = .{ .@"const" = true } }; const result_ty = Type{ .specifier = .pointer, .data = .{ .sub_type = elem_ty }, }; if (!e.ty.isInt()) { try p.errStr(.incompatible_arg, expr_tok, try p.typePairStrExtra(e.ty, " to parameter of incompatible type ", result_ty)); return error.ParsingFailed; } if (e.val.isZero()) { try e.nullCast(p, result_ty); } else { try p.errStr(.implicit_int_to_ptr, expr_tok, try p.typePairStrExtra(e.ty, " to ", result_ty)); try e.ptrCast(p, result_ty); } } try e.un(p, .computed_goto_stmt); _ = try p.expectToken(.semicolon); return e.node; } const name_tok = try p.expectIdentifier(); const str = p.tokSlice(name_tok); if (p.findLabel(str) == null) { try p.labels.append(.{ .unresolved_goto = name_tok }); } _ = try p.expectToken(.semicolon); return try p.addNode(.{ .tag = .goto_stmt, .data = .{ .decl_ref = name_tok }, }); } if (p.eatToken(.keyword_continue)) |cont| { if (!p.in_loop) try p.errTok(.continue_not_in_loop, cont); _ = try p.expectToken(.semicolon); return try p.addNode(.{ .tag = .continue_stmt, .data = undefined }); } if (p.eatToken(.keyword_break)) |br| { if (!p.in_loop and p.@"switch" == null) try p.errTok(.break_not_in_loop_or_switch, br); _ = try p.expectToken(.semicolon); return try p.addNode(.{ .tag = .break_stmt, .data = undefined }); } if (try p.returnStmt()) |some| return some; if (try p.assembly(.stmt)) |some| return some; const expr_start = p.tok_i; const err_start = p.comp.diag.list.items.len; const e = try p.expr(); if (e.node != .none) { _ = try p.expectToken(.semicolon); try e.maybeWarnUnused(p, expr_start, err_start); return e.node; } const attr_buf_top = p.attr_buf.len; defer p.attr_buf.len = attr_buf_top; try p.attributeSpecifier(); if (p.eatToken(.semicolon)) |_| { var null_node: Tree.Node = .{ .tag = .null_stmt, .data = undefined }; null_node.ty = try Attribute.applyStatementAttributes(p, null_node.ty, expr_start, attr_buf_top); return p.addNode(null_node); } try p.err(.expected_stmt); return error.ParsingFailed; } /// labeledStmt /// : IDENTIFIER ':' stmt /// | keyword_case integerConstExpr ':' stmt /// | keyword_default ':' stmt fn labeledStmt(p: *Parser) Error!?NodeIndex { if ((p.tok_ids[p.tok_i] == .identifier or p.tok_ids[p.tok_i] == .extended_identifier) and p.tok_ids[p.tok_i + 1] == .colon) { const name_tok = p.expectIdentifier() catch unreachable; const str = p.tokSlice(name_tok); if (p.findLabel(str)) |some| { try p.errStr(.duplicate_label, name_tok, str); try p.errStr(.previous_label, some, str); } else { p.label_count += 1; try p.labels.append(.{ .label = name_tok }); var i: usize = 0; while (i < p.labels.items.len) { if (p.labels.items[i] == .unresolved_goto and mem.eql(u8, p.tokSlice(p.labels.items[i].unresolved_goto), str)) { _ = p.labels.swapRemove(i); } else i += 1; } } p.tok_i += 1; const attr_buf_top = p.attr_buf.len; defer p.attr_buf.len = attr_buf_top; try p.attributeSpecifier(); var labeled_stmt = Tree.Node{ .tag = .labeled_stmt, .data = .{ .decl = .{ .name = name_tok, .node = try p.stmt() } }, }; labeled_stmt.ty = try Attribute.applyLabelAttributes(p, labeled_stmt.ty, attr_buf_top); return try p.addNode(labeled_stmt); } else if (p.eatToken(.keyword_case)) |case| { const first_item = try p.integerConstExpr(.gnu_folding_extension); const ellipsis = p.tok_i; const second_item = if (p.eatToken(.ellipsis) != null) blk: { try p.errTok(.gnu_switch_range, ellipsis); break :blk try p.integerConstExpr(.gnu_folding_extension); } else null; _ = try p.expectToken(.colon); if (p.@"switch") |some| check: { if (some.ty.hasIncompleteSize()) break :check; // error already reported for incomplete size const first = first_item.val; const last = if (second_item) |second| second.val else first; if (first.tag == .unavailable) { try p.errTok(.case_val_unavailable, case + 1); break :check; } else if (last.tag == .unavailable) { try p.errTok(.case_val_unavailable, ellipsis + 1); break :check; } else if (last.compare(.lt, first, some.ty, p.comp)) { try p.errTok(.empty_case_range, case + 1); break :check; } // TODO cast to target type const prev = (try some.add(p.comp, first, last, case + 1)) orelse break :check; // TODO check which value was already handled if (some.ty.isUnsignedInt(p.comp)) { try p.errExtra(.duplicate_switch_case_unsigned, case + 1, .{ .unsigned = first.data.int, }); } else { try p.errExtra(.duplicate_switch_case_signed, case + 1, .{ .signed = first.signExtend(some.ty, p.comp), }); } try p.errTok(.previous_case, prev.tok); } else { try p.errStr(.case_not_in_switch, case, "case"); } const s = try p.stmt(); if (second_item) |some| return try p.addNode(.{ .tag = .case_range_stmt, .data = .{ .if3 = .{ .cond = s, .body = (try p.addList(&.{ first_item.node, some.node })).start } }, }) else return try p.addNode(.{ .tag = .case_stmt, .data = .{ .bin = .{ .lhs = first_item.node, .rhs = s } }, }); } else if (p.eatToken(.keyword_default)) |default| { _ = try p.expectToken(.colon); const s = try p.stmt(); const node = try p.addNode(.{ .tag = .default_stmt, .data = .{ .un = s }, }); const @"switch" = p.@"switch" orelse { try p.errStr(.case_not_in_switch, default, "default"); return node; }; if (@"switch".default) |previous| { try p.errTok(.multiple_default, default); try p.errTok(.previous_case, previous); } else { @"switch".default = default; } return node; } else return null; } const StmtExprState = struct { last_expr_tok: TokenIndex = 0, last_expr_res: Result = .{ .ty = .{ .specifier = .void } }, }; /// compoundStmt : '{' ( decl | keyword_extension decl | staticAssert | stmt)* '}' fn compoundStmt(p: *Parser, is_fn_body: bool, stmt_expr_state: ?*StmtExprState) Error!?NodeIndex { const l_brace = p.eatToken(.l_brace) orelse return null; const decl_buf_top = p.decl_buf.items.len; defer p.decl_buf.items.len = decl_buf_top; // the parameters of a function are in the same scope as the body if (!is_fn_body) try p.syms.pushScope(p); defer if (!is_fn_body) p.syms.popScope(); var noreturn_index: ?TokenIndex = null; var noreturn_label_count: u32 = 0; while (p.eatToken(.r_brace) == null) : (_ = try p.pragma()) { if (stmt_expr_state) |state| state.* = .{}; if (try p.parseOrNextStmt(staticAssert, l_brace)) continue; if (try p.parseOrNextStmt(decl, l_brace)) continue; if (p.eatToken(.keyword_extension)) |ext| { const saved_extension = p.extension_suppressed; defer p.extension_suppressed = saved_extension; p.extension_suppressed = true; if (try p.parseOrNextStmt(decl, l_brace)) continue; p.tok_i = ext; } const stmt_tok = p.tok_i; const s = p.stmt() catch |er| switch (er) { error.ParsingFailed => { try p.nextStmt(l_brace); continue; }, else => |e| return e, }; if (s == .none) continue; if (stmt_expr_state) |state| { state.* = .{ .last_expr_tok = stmt_tok, .last_expr_res = .{ .node = s, .ty = p.nodes.items(.ty)[@intFromEnum(s)], }, }; } try p.decl_buf.append(s); if (noreturn_index == null and p.nodeIsNoreturn(s) == .yes) { noreturn_index = p.tok_i; noreturn_label_count = p.label_count; } switch (p.nodes.items(.tag)[@intFromEnum(s)]) { .case_stmt, .default_stmt, .labeled_stmt => noreturn_index = null, else => {}, } } if (noreturn_index) |some| { // if new labels were defined we cannot be certain that the code is unreachable if (some != p.tok_i - 1 and noreturn_label_count == p.label_count) try p.errTok(.unreachable_code, some); } if (is_fn_body) { const last_noreturn = if (p.decl_buf.items.len == decl_buf_top) .no else p.nodeIsNoreturn(p.decl_buf.items[p.decl_buf.items.len - 1]); if (last_noreturn != .yes) { const ret_ty = p.func.ty.?.returnType(); var return_zero = false; if (last_noreturn == .no and !ret_ty.is(.void) and !ret_ty.isFunc() and !ret_ty.isArray()) { const func_name = p.tokSlice(p.func.name); const interned_name = try p.comp.intern(func_name); if (interned_name == p.string_ids.main_id and ret_ty.is(.int)) { return_zero = true; } else { try p.errStr(.func_does_not_return, p.tok_i - 1, func_name); } } try p.decl_buf.append(try p.addNode(.{ .tag = .implicit_return, .ty = p.func.ty.?.returnType(), .data = .{ .return_zero = return_zero } })); } if (p.func.ident) |some| try p.decl_buf.insert(decl_buf_top, some.node); if (p.func.pretty_ident) |some| try p.decl_buf.insert(decl_buf_top, some.node); } var node: Tree.Node = .{ .tag = .compound_stmt_two, .data = .{ .bin = .{ .lhs = .none, .rhs = .none } }, }; const statements = p.decl_buf.items[decl_buf_top..]; switch (statements.len) { 0 => {}, 1 => node.data = .{ .bin = .{ .lhs = statements[0], .rhs = .none } }, 2 => node.data = .{ .bin = .{ .lhs = statements[0], .rhs = statements[1] } }, else => { node.tag = .compound_stmt; node.data = .{ .range = try p.addList(statements) }; }, } return try p.addNode(node); } const NoreturnKind = enum { no, yes, complex }; fn nodeIsNoreturn(p: *Parser, node: NodeIndex) NoreturnKind { switch (p.nodes.items(.tag)[@intFromEnum(node)]) { .break_stmt, .continue_stmt, .return_stmt => return .yes, .if_then_else_stmt => { const data = p.data.items[p.nodes.items(.data)[@intFromEnum(node)].if3.body..]; const then_type = p.nodeIsNoreturn(data[0]); const else_type = p.nodeIsNoreturn(data[1]); if (then_type == .complex or else_type == .complex) return .complex; if (then_type == .yes and else_type == .yes) return .yes; return .no; }, .compound_stmt_two => { const data = p.nodes.items(.data)[@intFromEnum(node)]; if (data.bin.rhs != .none) return p.nodeIsNoreturn(data.bin.rhs); if (data.bin.lhs != .none) return p.nodeIsNoreturn(data.bin.lhs); return .no; }, .compound_stmt => { const data = p.nodes.items(.data)[@intFromEnum(node)]; return p.nodeIsNoreturn(p.data.items[data.range.end - 1]); }, .labeled_stmt => { const data = p.nodes.items(.data)[@intFromEnum(node)]; return p.nodeIsNoreturn(data.decl.node); }, .switch_stmt => { const data = p.nodes.items(.data)[@intFromEnum(node)]; if (data.bin.rhs == .none) return .complex; if (p.nodeIsNoreturn(data.bin.rhs) == .yes) return .yes; return .complex; }, else => return .no, } } fn parseOrNextStmt(p: *Parser, comptime func: fn (*Parser) Error!bool, l_brace: TokenIndex) !bool { return func(p) catch |er| switch (er) { error.ParsingFailed => { try p.nextStmt(l_brace); return true; }, else => |e| return e, }; } fn nextStmt(p: *Parser, l_brace: TokenIndex) !void { var parens: u32 = 0; while (p.tok_i < p.tok_ids.len) : (p.tok_i += 1) { switch (p.tok_ids[p.tok_i]) { .l_paren, .l_brace, .l_bracket => parens += 1, .r_paren, .r_bracket => if (parens != 0) { parens -= 1; }, .r_brace => if (parens == 0) return else { parens -= 1; }, .semicolon, .keyword_for, .keyword_while, .keyword_do, .keyword_if, .keyword_goto, .keyword_switch, .keyword_case, .keyword_default, .keyword_continue, .keyword_break, .keyword_return, .keyword_typedef, .keyword_extern, .keyword_static, .keyword_auto, .keyword_register, .keyword_thread_local, .keyword_c23_thread_local, .keyword_inline, .keyword_inline1, .keyword_inline2, .keyword_noreturn, .keyword_void, .keyword_bool, .keyword_c23_bool, .keyword_char, .keyword_short, .keyword_int, .keyword_long, .keyword_signed, .keyword_unsigned, .keyword_float, .keyword_double, .keyword_complex, .keyword_atomic, .keyword_enum, .keyword_struct, .keyword_union, .keyword_alignas, .keyword_c23_alignas, .keyword_typeof, .keyword_typeof1, .keyword_typeof2, .keyword_extension, => if (parens == 0) return, .keyword_pragma => p.skipToPragmaSentinel(), else => {}, } } p.tok_i -= 1; // So we can consume EOF try p.expectClosing(l_brace, .r_brace); unreachable; } fn returnStmt(p: *Parser) Error!?NodeIndex { const ret_tok = p.eatToken(.keyword_return) orelse return null; const e_tok = p.tok_i; var e = try p.expr(); _ = try p.expectToken(.semicolon); const ret_ty = p.func.ty.?.returnType(); if (p.func.ty.?.hasAttribute(.noreturn)) { try p.errStr(.invalid_noreturn, e_tok, p.tokSlice(p.func.name)); } if (e.node == .none) { if (!ret_ty.is(.void)) try p.errStr(.func_should_return, ret_tok, p.tokSlice(p.func.name)); return try p.addNode(.{ .tag = .return_stmt, .data = .{ .un = e.node } }); } else if (ret_ty.is(.void)) { try p.errStr(.void_func_returns_value, e_tok, p.tokSlice(p.func.name)); return try p.addNode(.{ .tag = .return_stmt, .data = .{ .un = e.node } }); } try e.lvalConversion(p); try e.coerce(p, ret_ty, e_tok, .ret); try e.saveValue(p); return try p.addNode(.{ .tag = .return_stmt, .data = .{ .un = e.node } }); } // ====== expressions ====== pub fn macroExpr(p: *Parser) Compilation.Error!bool { const res = p.condExpr() catch |e| switch (e) { error.OutOfMemory => return error.OutOfMemory, error.FatalError => return error.FatalError, error.ParsingFailed => return false, }; if (res.val.tag == .unavailable) { try p.errTok(.expected_expr, p.tok_i); return false; } return res.val.getBool(); } const CallExpr = union(enum) { standard: NodeIndex, builtin: struct { node: NodeIndex, tag: BuiltinFunction.Tag, }, fn init(p: *Parser, call_node: NodeIndex, func_node: NodeIndex) CallExpr { if (p.getNode(call_node, .builtin_call_expr_one)) |node| { const data = p.nodes.items(.data)[@intFromEnum(node)]; const name = p.tokSlice(data.decl.name); const builtin_ty = p.comp.builtins.lookup(name); return .{ .builtin = .{ .node = node, .tag = builtin_ty.builtin.tag } }; } return .{ .standard = func_node }; } fn shouldPerformLvalConversion(self: CallExpr, arg_idx: u32) bool { return switch (self) { .standard => true, .builtin => |builtin| switch (builtin.tag) { BuiltinFunction.tagFromName("__builtin_va_start").?, BuiltinFunction.tagFromName("__va_start").?, BuiltinFunction.tagFromName("va_start").?, => arg_idx != 1, else => true, }, }; } fn shouldPromoteVarArg(self: CallExpr, arg_idx: u32) bool { return switch (self) { .standard => true, .builtin => |builtin| switch (builtin.tag) { BuiltinFunction.tagFromName("__builtin_va_start").?, BuiltinFunction.tagFromName("__va_start").?, BuiltinFunction.tagFromName("va_start").?, => arg_idx != 1, BuiltinFunction.tagFromName("__builtin_complex").? => false, else => true, }, }; } fn shouldCoerceArg(self: CallExpr, arg_idx: u32) bool { _ = self; _ = arg_idx; return true; } fn checkVarArg(self: CallExpr, p: *Parser, first_after: TokenIndex, param_tok: TokenIndex, arg: *Result, arg_idx: u32) !void { if (self == .standard) return; const builtin_tok = p.nodes.items(.data)[@intFromEnum(self.builtin.node)].decl.name; switch (self.builtin.tag) { BuiltinFunction.tagFromName("__builtin_va_start").?, BuiltinFunction.tagFromName("__va_start").?, BuiltinFunction.tagFromName("va_start").?, => return p.checkVaStartArg(builtin_tok, first_after, param_tok, arg, arg_idx), BuiltinFunction.tagFromName("__builtin_complex").? => return p.checkComplexArg(builtin_tok, first_after, param_tok, arg, arg_idx), else => {}, } } /// Some functions cannot be expressed as standard C prototypes. For example `__builtin_complex` requires /// two arguments of the same real floating point type (e.g. two doubles or two floats). These functions are /// encoded as varargs functions with custom typechecking. Since varargs functions do not have a fixed number /// of arguments, `paramCountOverride` is used to tell us how many arguments we should actually expect to see for /// these custom-typechecked functions. fn paramCountOverride(self: CallExpr) ?u32 { return switch (self) { .standard => null, .builtin => |builtin| switch (builtin.tag) { BuiltinFunction.tagFromName("__builtin_complex").? => 2, else => null, }, }; } fn returnType(self: CallExpr, p: *Parser, callable_ty: Type) Type { return switch (self) { .standard => callable_ty.returnType(), .builtin => |builtin| switch (builtin.tag) { BuiltinFunction.tagFromName("__builtin_complex").? => { const last_param = p.list_buf.items[p.list_buf.items.len - 1]; return p.nodes.items(.ty)[@intFromEnum(last_param)].makeComplex(); }, else => callable_ty.returnType(), }, }; } fn finish(self: CallExpr, p: *Parser, ty: Type, list_buf_top: usize, arg_count: u32) Error!Result { const ret_ty = self.returnType(p, ty); switch (self) { .standard => |func_node| { var call_node: Tree.Node = .{ .tag = .call_expr_one, .ty = ret_ty, .data = .{ .bin = .{ .lhs = func_node, .rhs = .none } }, }; const args = p.list_buf.items[list_buf_top..]; switch (arg_count) { 0 => {}, 1 => call_node.data.bin.rhs = args[1], // args[0] == func.node else => { call_node.tag = .call_expr; call_node.data = .{ .range = try p.addList(args) }; }, } return Result{ .node = try p.addNode(call_node), .ty = ret_ty }; }, .builtin => |builtin| { const index = @intFromEnum(builtin.node); var call_node = p.nodes.get(index); defer p.nodes.set(index, call_node); call_node.ty = ret_ty; const args = p.list_buf.items[list_buf_top..]; switch (arg_count) { 0 => {}, 1 => call_node.data.decl.node = args[1], // args[0] == func.node else => { call_node.tag = .builtin_call_expr; args[0] = @enumFromInt(call_node.data.decl.name); call_node.data = .{ .range = try p.addList(args) }; }, } return Result{ .node = builtin.node, .ty = ret_ty }; }, } } }; const Result = struct { node: NodeIndex = .none, ty: Type = .{ .specifier = .int }, val: Value = .{}, fn expect(res: Result, p: *Parser) Error!void { if (p.in_macro) { if (res.val.tag == .unavailable) { try p.errTok(.expected_expr, p.tok_i); return error.ParsingFailed; } return; } if (res.node == .none) { try p.errTok(.expected_expr, p.tok_i); return error.ParsingFailed; } } fn empty(res: Result, p: *Parser) bool { if (p.in_macro) return res.val.tag == .unavailable; return res.node == .none; } fn maybeWarnUnused(res: Result, p: *Parser, expr_start: TokenIndex, err_start: usize) Error!void { if (res.ty.is(.void) or res.node == .none) return; // don't warn about unused result if the expression contained errors besides other unused results for (p.comp.diag.list.items[err_start..]) |err_item| { if (err_item.tag != .unused_value) return; } var cur_node = res.node; while (true) switch (p.nodes.items(.tag)[@intFromEnum(cur_node)]) { .invalid, // So that we don't need to check for node == 0 .assign_expr, .mul_assign_expr, .div_assign_expr, .mod_assign_expr, .add_assign_expr, .sub_assign_expr, .shl_assign_expr, .shr_assign_expr, .bit_and_assign_expr, .bit_xor_assign_expr, .bit_or_assign_expr, .pre_inc_expr, .pre_dec_expr, .post_inc_expr, .post_dec_expr, => return, .call_expr_one => { const fn_ptr = p.nodes.items(.data)[@intFromEnum(cur_node)].bin.lhs; const fn_ty = p.nodes.items(.ty)[@intFromEnum(fn_ptr)].elemType(); if (fn_ty.hasAttribute(.nodiscard)) try p.errStr(.nodiscard_unused, expr_start, "TODO get name"); if (fn_ty.hasAttribute(.warn_unused_result)) try p.errStr(.warn_unused_result, expr_start, "TODO get name"); return; }, .call_expr => { const fn_ptr = p.data.items[p.nodes.items(.data)[@intFromEnum(cur_node)].range.start]; const fn_ty = p.nodes.items(.ty)[@intFromEnum(fn_ptr)].elemType(); if (fn_ty.hasAttribute(.nodiscard)) try p.errStr(.nodiscard_unused, expr_start, "TODO get name"); if (fn_ty.hasAttribute(.warn_unused_result)) try p.errStr(.warn_unused_result, expr_start, "TODO get name"); return; }, .stmt_expr => { const body = p.nodes.items(.data)[@intFromEnum(cur_node)].un; switch (p.nodes.items(.tag)[@intFromEnum(body)]) { .compound_stmt_two => { const body_stmt = p.nodes.items(.data)[@intFromEnum(body)].bin; cur_node = if (body_stmt.rhs != .none) body_stmt.rhs else body_stmt.lhs; }, .compound_stmt => { const data = p.nodes.items(.data)[@intFromEnum(body)]; cur_node = p.data.items[data.range.end - 1]; }, else => unreachable, } }, .comma_expr => cur_node = p.nodes.items(.data)[@intFromEnum(cur_node)].bin.rhs, .paren_expr => cur_node = p.nodes.items(.data)[@intFromEnum(cur_node)].un, else => break, }; try p.errTok(.unused_value, expr_start); } fn boolRes(lhs: *Result, p: *Parser, tag: Tree.Tag, rhs: Result) !void { if (lhs.val.tag == .nullptr_t) { lhs.val = Value.int(0); } if (lhs.ty.specifier != .invalid) { lhs.ty = Type.int; } return lhs.bin(p, tag, rhs); } fn bin(lhs: *Result, p: *Parser, tag: Tree.Tag, rhs: Result) !void { lhs.node = try p.addNode(.{ .tag = tag, .ty = lhs.ty, .data = .{ .bin = .{ .lhs = lhs.node, .rhs = rhs.node } }, }); } fn un(operand: *Result, p: *Parser, tag: Tree.Tag) Error!void { operand.node = try p.addNode(.{ .tag = tag, .ty = operand.ty, .data = .{ .un = operand.node }, }); } fn implicitCast(operand: *Result, p: *Parser, kind: Tree.CastKind) Error!void { operand.node = try p.addNode(.{ .tag = .implicit_cast, .ty = operand.ty, .data = .{ .cast = .{ .operand = operand.node, .kind = kind } }, }); } fn adjustCondExprPtrs(a: *Result, tok: TokenIndex, b: *Result, p: *Parser) !bool { assert(a.ty.isPtr() and b.ty.isPtr()); const a_elem = a.ty.elemType(); const b_elem = b.ty.elemType(); if (a_elem.eql(b_elem, p.comp, true)) return true; var adjusted_elem_ty = try p.arena.create(Type); adjusted_elem_ty.* = a_elem; const has_void_star_branch = a.ty.isVoidStar() or b.ty.isVoidStar(); const only_quals_differ = a_elem.eql(b_elem, p.comp, false); const pointers_compatible = only_quals_differ or has_void_star_branch; if (!pointers_compatible or has_void_star_branch) { if (!pointers_compatible) { try p.errStr(.pointer_mismatch, tok, try p.typePairStrExtra(a.ty, " and ", b.ty)); } adjusted_elem_ty.* = .{ .specifier = .void }; } if (pointers_compatible) { adjusted_elem_ty.qual = a_elem.qual.mergeCV(b_elem.qual); } if (!adjusted_elem_ty.eql(a_elem, p.comp, true)) { a.ty = .{ .data = .{ .sub_type = adjusted_elem_ty }, .specifier = .pointer, }; try a.implicitCast(p, .bitcast); } if (!adjusted_elem_ty.eql(b_elem, p.comp, true)) { b.ty = .{ .data = .{ .sub_type = adjusted_elem_ty }, .specifier = .pointer, }; try b.implicitCast(p, .bitcast); } return true; } /// Adjust types for binary operation, returns true if the result can and should be evaluated. fn adjustTypes(a: *Result, tok: TokenIndex, b: *Result, p: *Parser, kind: enum { integer, arithmetic, boolean_logic, relational, equality, conditional, add, sub, }) !bool { if (b.ty.specifier == .invalid) { try a.saveValue(p); a.ty = Type.invalid; } if (a.ty.specifier == .invalid) { return false; } try a.lvalConversion(p); try b.lvalConversion(p); const a_vec = a.ty.is(.vector); const b_vec = b.ty.is(.vector); if (a_vec and b_vec) { if (a.ty.eql(b.ty, p.comp, false)) { return a.shouldEval(b, p); } return a.invalidBinTy(tok, b, p); } else if (a_vec) { if (b.coerceExtra(p, a.ty.elemType(), tok, .test_coerce)) { try b.saveValue(p); try b.implicitCast(p, .vector_splat); return a.shouldEval(b, p); } else |er| switch (er) { error.CoercionFailed => return a.invalidBinTy(tok, b, p), else => |e| return e, } } else if (b_vec) { if (a.coerceExtra(p, b.ty.elemType(), tok, .test_coerce)) { try a.saveValue(p); try a.implicitCast(p, .vector_splat); return a.shouldEval(b, p); } else |er| switch (er) { error.CoercionFailed => return a.invalidBinTy(tok, b, p), else => |e| return e, } } const a_int = a.ty.isInt(); const b_int = b.ty.isInt(); if (a_int and b_int) { try a.usualArithmeticConversion(b, p, tok); return a.shouldEval(b, p); } if (kind == .integer) return a.invalidBinTy(tok, b, p); const a_float = a.ty.isFloat(); const b_float = b.ty.isFloat(); const a_arithmetic = a_int or a_float; const b_arithmetic = b_int or b_float; if (a_arithmetic and b_arithmetic) { // <, <=, >, >= only work on real types if (kind == .relational and (!a.ty.isReal() or !b.ty.isReal())) return a.invalidBinTy(tok, b, p); try a.usualArithmeticConversion(b, p, tok); return a.shouldEval(b, p); } if (kind == .arithmetic) return a.invalidBinTy(tok, b, p); const a_nullptr = a.ty.is(.nullptr_t); const b_nullptr = b.ty.is(.nullptr_t); const a_ptr = a.ty.isPtr(); const b_ptr = b.ty.isPtr(); const a_scalar = a_arithmetic or a_ptr; const b_scalar = b_arithmetic or b_ptr; switch (kind) { .boolean_logic => { if (!(a_scalar or a_nullptr) or !(b_scalar or b_nullptr)) return a.invalidBinTy(tok, b, p); // Do integer promotions but nothing else if (a_int) try a.intCast(p, a.ty.integerPromotion(p.comp), tok); if (b_int) try b.intCast(p, b.ty.integerPromotion(p.comp), tok); return a.shouldEval(b, p); }, .relational, .equality => { if (kind == .equality and (a_nullptr or b_nullptr)) { if (a_nullptr and b_nullptr) return a.shouldEval(b, p); const nullptr_res = if (a_nullptr) a else b; const other_res = if (a_nullptr) b else a; if (other_res.ty.isPtr()) { try nullptr_res.nullCast(p, other_res.ty); return other_res.shouldEval(nullptr_res, p); } else if (other_res.val.isZero()) { other_res.val = .{ .tag = .nullptr_t }; try other_res.nullCast(p, nullptr_res.ty); return other_res.shouldEval(nullptr_res, p); } return a.invalidBinTy(tok, b, p); } // comparisons between floats and pointes not allowed if (!a_scalar or !b_scalar or (a_float and b_ptr) or (b_float and a_ptr)) return a.invalidBinTy(tok, b, p); if ((a_int or b_int) and !(a.val.isZero() or b.val.isZero())) { try p.errStr(.comparison_ptr_int, tok, try p.typePairStr(a.ty, b.ty)); } else if (a_ptr and b_ptr) { if (!a.ty.isVoidStar() and !b.ty.isVoidStar() and !a.ty.eql(b.ty, p.comp, false)) try p.errStr(.comparison_distinct_ptr, tok, try p.typePairStr(a.ty, b.ty)); } else if (a_ptr) { try b.ptrCast(p, a.ty); } else { assert(b_ptr); try a.ptrCast(p, b.ty); } return a.shouldEval(b, p); }, .conditional => { // doesn't matter what we return here, as the result is ignored if (a.ty.is(.void) or b.ty.is(.void)) { try a.toVoid(p); try b.toVoid(p); return true; } if (a_nullptr and b_nullptr) return true; if ((a_ptr and b_int) or (a_int and b_ptr)) { if (a.val.isZero() or b.val.isZero()) { try a.nullCast(p, b.ty); try b.nullCast(p, a.ty); return true; } const int_ty = if (a_int) a else b; const ptr_ty = if (a_ptr) a else b; try p.errStr(.implicit_int_to_ptr, tok, try p.typePairStrExtra(int_ty.ty, " to ", ptr_ty.ty)); try int_ty.ptrCast(p, ptr_ty.ty); return true; } if (a_ptr and b_ptr) return a.adjustCondExprPtrs(tok, b, p); if ((a_ptr and b_nullptr) or (a_nullptr and b_ptr)) { const nullptr_res = if (a_nullptr) a else b; const ptr_res = if (a_nullptr) b else a; try nullptr_res.nullCast(p, ptr_res.ty); return true; } if (a.ty.isRecord() and b.ty.isRecord() and a.ty.eql(b.ty, p.comp, false)) { return true; } return a.invalidBinTy(tok, b, p); }, .add => { // if both aren't arithmetic one should be pointer and the other an integer if (a_ptr == b_ptr or a_int == b_int) return a.invalidBinTy(tok, b, p); // Do integer promotions but nothing else if (a_int) try a.intCast(p, a.ty.integerPromotion(p.comp), tok); if (b_int) try b.intCast(p, b.ty.integerPromotion(p.comp), tok); // The result type is the type of the pointer operand if (a_int) a.ty = b.ty else b.ty = a.ty; return a.shouldEval(b, p); }, .sub => { // if both aren't arithmetic then either both should be pointers or just a if (!a_ptr or !(b_ptr or b_int)) return a.invalidBinTy(tok, b, p); if (a_ptr and b_ptr) { if (!a.ty.eql(b.ty, p.comp, false)) try p.errStr(.incompatible_pointers, tok, try p.typePairStr(a.ty, b.ty)); a.ty = p.comp.types.ptrdiff; } // Do integer promotion on b if needed if (b_int) try b.intCast(p, b.ty.integerPromotion(p.comp), tok); return a.shouldEval(b, p); }, else => return a.invalidBinTy(tok, b, p), } } fn lvalConversion(res: *Result, p: *Parser) Error!void { if (res.ty.isFunc()) { var elem_ty = try p.arena.create(Type); elem_ty.* = res.ty; res.ty.specifier = .pointer; res.ty.data = .{ .sub_type = elem_ty }; try res.implicitCast(p, .function_to_pointer); } else if (res.ty.isArray()) { res.val.tag = .unavailable; res.ty.decayArray(); try res.implicitCast(p, .array_to_pointer); } else if (!p.in_macro and Tree.isLval(p.nodes.slice(), p.data.items, p.value_map, res.node)) { res.ty.qual = .{}; try res.implicitCast(p, .lval_to_rval); } } fn boolCast(res: *Result, p: *Parser, bool_ty: Type, tok: TokenIndex) Error!void { if (res.ty.isArray()) { if (res.val.tag == .bytes) { try p.errStr(.string_literal_to_bool, tok, try p.typePairStrExtra(res.ty, " to ", bool_ty)); } else { try p.errStr(.array_address_to_bool, tok, p.tokSlice(tok)); } try res.lvalConversion(p); res.val = Value.int(1); res.ty = bool_ty; try res.implicitCast(p, .pointer_to_bool); } else if (res.ty.isPtr()) { res.val.toBool(); res.ty = bool_ty; try res.implicitCast(p, .pointer_to_bool); } else if (res.ty.isInt() and !res.ty.is(.bool)) { res.val.toBool(); res.ty = bool_ty; try res.implicitCast(p, .int_to_bool); } else if (res.ty.isFloat()) { const old_value = res.val; const value_change_kind = res.val.floatToInt(res.ty, bool_ty, p.comp); try res.floatToIntWarning(p, bool_ty, old_value, value_change_kind, tok); if (!res.ty.isReal()) { res.ty = res.ty.makeReal(); try res.implicitCast(p, .complex_float_to_real); } res.ty = bool_ty; try res.implicitCast(p, .float_to_bool); } } fn intCast(res: *Result, p: *Parser, int_ty: Type, tok: TokenIndex) Error!void { if (int_ty.hasIncompleteSize()) return error.ParsingFailed; // Diagnostic already issued if (res.ty.is(.bool)) { res.ty = int_ty.makeReal(); try res.implicitCast(p, .bool_to_int); if (!int_ty.isReal()) { res.ty = int_ty; try res.implicitCast(p, .real_to_complex_int); } } else if (res.ty.isPtr()) { res.ty = int_ty.makeReal(); try res.implicitCast(p, .pointer_to_int); if (!int_ty.isReal()) { res.ty = int_ty; try res.implicitCast(p, .real_to_complex_int); } } else if (res.ty.isFloat()) { const old_value = res.val; const value_change_kind = res.val.floatToInt(res.ty, int_ty, p.comp); try res.floatToIntWarning(p, int_ty, old_value, value_change_kind, tok); const old_real = res.ty.isReal(); const new_real = int_ty.isReal(); if (old_real and new_real) { res.ty = int_ty; try res.implicitCast(p, .float_to_int); } else if (old_real) { res.ty = int_ty.makeReal(); try res.implicitCast(p, .float_to_int); res.ty = int_ty; try res.implicitCast(p, .real_to_complex_int); } else if (new_real) { res.ty = res.ty.makeReal(); try res.implicitCast(p, .complex_float_to_real); res.ty = int_ty; try res.implicitCast(p, .float_to_int); } else { res.ty = int_ty; try res.implicitCast(p, .complex_float_to_complex_int); } } else if (!res.ty.eql(int_ty, p.comp, true)) { res.val.intCast(res.ty, int_ty, p.comp); const old_real = res.ty.isReal(); const new_real = int_ty.isReal(); if (old_real and new_real) { res.ty = int_ty; try res.implicitCast(p, .int_cast); } else if (old_real) { const real_int_ty = int_ty.makeReal(); if (!res.ty.eql(real_int_ty, p.comp, false)) { res.ty = real_int_ty; try res.implicitCast(p, .int_cast); } res.ty = int_ty; try res.implicitCast(p, .real_to_complex_int); } else if (new_real) { res.ty = res.ty.makeReal(); try res.implicitCast(p, .complex_int_to_real); res.ty = int_ty; try res.implicitCast(p, .int_cast); } else { res.ty = int_ty; try res.implicitCast(p, .complex_int_cast); } } } fn floatToIntWarning(res: *Result, p: *Parser, int_ty: Type, old_value: Value, change_kind: Value.FloatToIntChangeKind, tok: TokenIndex) !void { switch (change_kind) { .none => return p.errStr(.float_to_int, tok, try p.typePairStrExtra(res.ty, " to ", int_ty)), .out_of_range => return p.errStr(.float_out_of_range, tok, try p.typePairStrExtra(res.ty, " to ", int_ty)), .overflow => return p.errStr(.float_overflow_conversion, tok, try p.typePairStrExtra(res.ty, " to ", int_ty)), .nonzero_to_zero => return p.errStr(.float_zero_conversion, tok, try p.floatValueChangedStr(res, old_value.getFloat(f64), int_ty)), .value_changed => return p.errStr(.float_value_changed, tok, try p.floatValueChangedStr(res, old_value.getFloat(f64), int_ty)), } } fn floatCast(res: *Result, p: *Parser, float_ty: Type) Error!void { if (res.ty.is(.bool)) { res.val.intToFloat(res.ty, float_ty, p.comp); res.ty = float_ty.makeReal(); try res.implicitCast(p, .bool_to_float); if (!float_ty.isReal()) { res.ty = float_ty; try res.implicitCast(p, .real_to_complex_float); } } else if (res.ty.isInt()) { res.val.intToFloat(res.ty, float_ty, p.comp); const old_real = res.ty.isReal(); const new_real = float_ty.isReal(); if (old_real and new_real) { res.ty = float_ty; try res.implicitCast(p, .int_to_float); } else if (old_real) { res.ty = float_ty.makeReal(); try res.implicitCast(p, .int_to_float); res.ty = float_ty; try res.implicitCast(p, .real_to_complex_float); } else if (new_real) { res.ty = res.ty.makeReal(); try res.implicitCast(p, .complex_int_to_real); res.ty = float_ty; try res.implicitCast(p, .int_to_float); } else { res.ty = float_ty; try res.implicitCast(p, .complex_int_to_complex_float); } } else if (!res.ty.eql(float_ty, p.comp, true)) { res.val.floatCast(res.ty, float_ty, p.comp); const old_real = res.ty.isReal(); const new_real = float_ty.isReal(); if (old_real and new_real) { res.ty = float_ty; try res.implicitCast(p, .float_cast); } else if (old_real) { if (res.ty.floatRank() != float_ty.floatRank()) { res.ty = float_ty.makeReal(); try res.implicitCast(p, .float_cast); } res.ty = float_ty; try res.implicitCast(p, .real_to_complex_float); } else if (new_real) { res.ty = res.ty.makeReal(); try res.implicitCast(p, .complex_float_to_real); if (res.ty.floatRank() != float_ty.floatRank()) { res.ty = float_ty; try res.implicitCast(p, .float_cast); } } else { res.ty = float_ty; try res.implicitCast(p, .complex_float_cast); } } } /// Converts a bool or integer to a pointer fn ptrCast(res: *Result, p: *Parser, ptr_ty: Type) Error!void { if (res.ty.is(.bool)) { res.ty = ptr_ty; try res.implicitCast(p, .bool_to_pointer); } else if (res.ty.isInt()) { res.val.intCast(res.ty, ptr_ty, p.comp); res.ty = ptr_ty; try res.implicitCast(p, .int_to_pointer); } } /// Convert pointer to one with a different child type fn ptrChildTypeCast(res: *Result, p: *Parser, ptr_ty: Type) Error!void { res.ty = ptr_ty; return res.implicitCast(p, .bitcast); } fn toVoid(res: *Result, p: *Parser) Error!void { if (!res.ty.is(.void)) { res.ty = .{ .specifier = .void }; try res.implicitCast(p, .to_void); } } fn nullCast(res: *Result, p: *Parser, ptr_ty: Type) Error!void { if (!res.ty.is(.nullptr_t) and !res.val.isZero()) return; res.ty = ptr_ty; try res.implicitCast(p, .null_to_pointer); } fn usualUnaryConversion(res: *Result, p: *Parser, tok: TokenIndex) Error!void { if (res.ty.isFloat()) fp_eval: { const eval_method = p.comp.langopts.fp_eval_method orelse break :fp_eval; switch (eval_method) { .source => {}, .indeterminate => unreachable, .double => { if (res.ty.floatRank() < (Type{ .specifier = .double }).floatRank()) { const spec: Type.Specifier = if (res.ty.isReal()) .double else .complex_double; return res.floatCast(p, .{ .specifier = spec }); } }, .extended => { if (res.ty.floatRank() < (Type{ .specifier = .long_double }).floatRank()) { const spec: Type.Specifier = if (res.ty.isReal()) .long_double else .complex_long_double; return res.floatCast(p, .{ .specifier = spec }); } }, } } if (res.ty.is(.fp16) and !p.comp.langopts.use_native_half_type) { return res.floatCast(p, .{ .specifier = .float }); } if (res.ty.isInt()) { const slice = p.nodes.slice(); if (Tree.bitfieldWidth(slice, res.node, true)) |width| { if (res.ty.bitfieldPromotion(p.comp, width)) |promotion_ty| { return res.intCast(p, promotion_ty, tok); } } return res.intCast(p, res.ty.integerPromotion(p.comp), tok); } } fn usualArithmeticConversion(a: *Result, b: *Result, p: *Parser, tok: TokenIndex) Error!void { try a.usualUnaryConversion(p, tok); try b.usualUnaryConversion(p, tok); // if either is a float cast to that type if (a.ty.isFloat() or b.ty.isFloat()) { const float_types = [7][2]Type.Specifier{ .{ .complex_long_double, .long_double }, .{ .complex_float128, .float128 }, .{ .complex_float80, .float80 }, .{ .complex_double, .double }, .{ .complex_float, .float }, // No `_Complex __fp16` type .{ .invalid, .fp16 }, // No `_Complex _Float16` .{ .invalid, .float16 }, }; const a_spec = a.ty.canonicalize(.standard).specifier; const b_spec = b.ty.canonicalize(.standard).specifier; if (p.comp.target.c_type_bit_size(.longdouble) == 128) { if (try a.floatConversion(b, a_spec, b_spec, p, float_types[0])) return; } if (try a.floatConversion(b, a_spec, b_spec, p, float_types[1])) return; if (p.comp.target.c_type_bit_size(.longdouble) == 80) { if (try a.floatConversion(b, a_spec, b_spec, p, float_types[0])) return; } if (try a.floatConversion(b, a_spec, b_spec, p, float_types[2])) return; if (p.comp.target.c_type_bit_size(.longdouble) == 64) { if (try a.floatConversion(b, a_spec, b_spec, p, float_types[0])) return; } if (try a.floatConversion(b, a_spec, b_spec, p, float_types[3])) return; if (try a.floatConversion(b, a_spec, b_spec, p, float_types[4])) return; if (try a.floatConversion(b, a_spec, b_spec, p, float_types[5])) return; if (try a.floatConversion(b, a_spec, b_spec, p, float_types[6])) return; } if (a.ty.eql(b.ty, p.comp, true)) { // cast to promoted type try a.intCast(p, a.ty, tok); try b.intCast(p, b.ty, tok); return; } const target = a.ty.integerConversion(b.ty, p.comp); if (!target.isReal()) { try a.saveValue(p); try b.saveValue(p); } try a.intCast(p, target, tok); try b.intCast(p, target, tok); } fn floatConversion(a: *Result, b: *Result, a_spec: Type.Specifier, b_spec: Type.Specifier, p: *Parser, pair: [2]Type.Specifier) !bool { if (a_spec == pair[0] or a_spec == pair[1] or b_spec == pair[0] or b_spec == pair[1]) { const both_real = a.ty.isReal() and b.ty.isReal(); const res_spec = pair[@intFromBool(both_real)]; const ty = Type{ .specifier = res_spec }; try a.floatCast(p, ty); try b.floatCast(p, ty); return true; } return false; } fn invalidBinTy(a: *Result, tok: TokenIndex, b: *Result, p: *Parser) Error!bool { try p.errStr(.invalid_bin_types, tok, try p.typePairStr(a.ty, b.ty)); a.val.tag = .unavailable; b.val.tag = .unavailable; a.ty = Type.invalid; return false; } fn shouldEval(a: *Result, b: *Result, p: *Parser) Error!bool { if (p.no_eval) return false; if (a.val.tag != .unavailable and b.val.tag != .unavailable) return true; try a.saveValue(p); try b.saveValue(p); return p.no_eval; } /// Saves value and replaces it with `.unavailable`. fn saveValue(res: *Result, p: *Parser) !void { assert(!p.in_macro); if (res.val.tag == .unavailable or res.val.tag == .nullptr_t) return; if (!p.in_macro) try p.value_map.put(res.node, res.val); res.val.tag = .unavailable; } fn castType(res: *Result, p: *Parser, to: Type, tok: TokenIndex) !void { var cast_kind: Tree.CastKind = undefined; if (to.is(.void)) { // everything can cast to void cast_kind = .to_void; res.val.tag = .unavailable; } else if (to.is(.nullptr_t)) { if (res.ty.is(.nullptr_t)) { cast_kind = .no_op; } else { try p.errStr(.invalid_object_cast, tok, try p.typePairStrExtra(res.ty, " to ", to)); return error.ParsingFailed; } } else if (res.ty.is(.nullptr_t)) { if (to.is(.bool)) { try res.nullCast(p, res.ty); res.val.toBool(); res.ty = .{ .specifier = .bool }; try res.implicitCast(p, .pointer_to_bool); try res.saveValue(p); } else if (to.isPtr()) { try res.nullCast(p, to); } else { try p.errStr(.invalid_object_cast, tok, try p.typePairStrExtra(res.ty, " to ", to)); return error.ParsingFailed; } cast_kind = .no_op; } else if (res.val.isZero() and to.isPtr()) { cast_kind = .null_to_pointer; } else if (to.isScalar()) cast: { const old_float = res.ty.isFloat(); const new_float = to.isFloat(); if (new_float and res.ty.isPtr()) { try p.errStr(.invalid_cast_to_float, tok, try p.typeStr(to)); return error.ParsingFailed; } else if (old_float and to.isPtr()) { try p.errStr(.invalid_cast_to_pointer, tok, try p.typeStr(res.ty)); return error.ParsingFailed; } const old_real = res.ty.isReal(); const new_real = to.isReal(); if (to.eql(res.ty, p.comp, false)) { cast_kind = .no_op; } else if (to.is(.bool)) { if (res.ty.isPtr()) { cast_kind = .pointer_to_bool; } else if (res.ty.isInt()) { if (!old_real) { res.ty = res.ty.makeReal(); try res.implicitCast(p, .complex_int_to_real); } cast_kind = .int_to_bool; } else if (old_float) { if (!old_real) { res.ty = res.ty.makeReal(); try res.implicitCast(p, .complex_float_to_real); } cast_kind = .float_to_bool; } } else if (to.isInt()) { if (res.ty.is(.bool)) { if (!new_real) { res.ty = to.makeReal(); try res.implicitCast(p, .bool_to_int); cast_kind = .real_to_complex_int; } else { cast_kind = .bool_to_int; } } else if (res.ty.isInt()) { if (old_real and new_real) { cast_kind = .int_cast; } else if (old_real) { res.ty = to.makeReal(); try res.implicitCast(p, .int_cast); cast_kind = .real_to_complex_int; } else if (new_real) { res.ty = res.ty.makeReal(); try res.implicitCast(p, .complex_int_to_real); cast_kind = .int_cast; } else { cast_kind = .complex_int_cast; } } else if (res.ty.isPtr()) { if (!new_real) { res.ty = to.makeReal(); try res.implicitCast(p, .pointer_to_int); cast_kind = .real_to_complex_int; } else { cast_kind = .pointer_to_int; } } else if (old_real and new_real) { cast_kind = .float_to_int; } else if (old_real) { res.ty = to.makeReal(); try res.implicitCast(p, .float_to_int); cast_kind = .real_to_complex_int; } else if (new_real) { res.ty = res.ty.makeReal(); try res.implicitCast(p, .complex_float_to_real); cast_kind = .float_to_int; } else { cast_kind = .complex_float_to_complex_int; } } else if (to.isPtr()) { if (res.ty.isArray()) cast_kind = .array_to_pointer else if (res.ty.isPtr()) cast_kind = .bitcast else if (res.ty.isFunc()) cast_kind = .function_to_pointer else if (res.ty.is(.bool)) cast_kind = .bool_to_pointer else if (res.ty.isInt()) { if (!old_real) { res.ty = res.ty.makeReal(); try res.implicitCast(p, .complex_int_to_real); } cast_kind = .int_to_pointer; } } else if (new_float) { if (res.ty.is(.bool)) { if (!new_real) { res.ty = to.makeReal(); try res.implicitCast(p, .bool_to_float); cast_kind = .real_to_complex_float; } else { cast_kind = .bool_to_float; } } else if (res.ty.isInt()) { if (old_real and new_real) { cast_kind = .int_to_float; } else if (old_real) { res.ty = to.makeReal(); try res.implicitCast(p, .int_to_float); cast_kind = .real_to_complex_float; } else if (new_real) { res.ty = res.ty.makeReal(); try res.implicitCast(p, .complex_int_to_real); cast_kind = .int_to_float; } else { cast_kind = .complex_int_to_complex_float; } } else if (old_real and new_real) { cast_kind = .float_cast; } else if (old_real) { res.ty = to.makeReal(); try res.implicitCast(p, .float_cast); cast_kind = .real_to_complex_float; } else if (new_real) { res.ty = res.ty.makeReal(); try res.implicitCast(p, .complex_float_to_real); cast_kind = .float_cast; } else { cast_kind = .complex_float_cast; } } if (res.val.tag == .unavailable) break :cast; const old_int = res.ty.isInt() or res.ty.isPtr(); const new_int = to.isInt() or to.isPtr(); if (to.is(.bool)) { res.val.toBool(); } else if (old_float and new_int) { // Explicit cast, no conversion warning _ = res.val.floatToInt(res.ty, to, p.comp); } else if (new_float and old_int) { res.val.intToFloat(res.ty, to, p.comp); } else if (new_float and old_float) { res.val.floatCast(res.ty, to, p.comp); } else if (old_int and new_int) { if (to.hasIncompleteSize()) { try p.errStr(.cast_to_incomplete_type, tok, try p.typeStr(to)); return error.ParsingFailed; } res.val.intCast(res.ty, to, p.comp); } } else if (to.get(.@"union")) |union_ty| { if (union_ty.data.record.hasFieldOfType(res.ty, p.comp)) { cast_kind = .union_cast; try p.errTok(.gnu_union_cast, tok); } else { if (union_ty.data.record.isIncomplete()) { try p.errStr(.cast_to_incomplete_type, tok, try p.typeStr(to)); } else { try p.errStr(.invalid_union_cast, tok, try p.typeStr(res.ty)); } return error.ParsingFailed; } } else { if (to.is(.auto_type)) { try p.errTok(.invalid_cast_to_auto_type, tok); } else { try p.errStr(.invalid_cast_type, tok, try p.typeStr(to)); } return error.ParsingFailed; } if (to.anyQual()) try p.errStr(.qual_cast, tok, try p.typeStr(to)); if (to.isInt() and res.ty.isPtr() and to.sizeCompare(res.ty, p.comp) == .lt) { try p.errStr(.cast_to_smaller_int, tok, try p.typePairStrExtra(to, " from ", res.ty)); } res.ty = to; res.ty.qual = .{}; res.node = try p.addNode(.{ .tag = .explicit_cast, .ty = res.ty, .data = .{ .cast = .{ .operand = res.node, .kind = cast_kind } }, }); } fn intFitsInType(res: Result, p: *Parser, ty: Type) bool { const max_int = Value.int(ty.maxInt(p.comp)); const min_int = Value.int(ty.minInt(p.comp)); return res.val.compare(.lte, max_int, res.ty, p.comp) and (res.ty.isUnsignedInt(p.comp) or res.val.compare(.gte, min_int, res.ty, p.comp)); } const CoerceContext = union(enum) { assign, init, ret, arg: TokenIndex, test_coerce, fn note(ctx: CoerceContext, p: *Parser) !void { switch (ctx) { .arg => |tok| try p.errTok(.parameter_here, tok), .test_coerce => unreachable, else => {}, } } fn typePairStr(ctx: CoerceContext, p: *Parser, dest_ty: Type, src_ty: Type) ![]const u8 { switch (ctx) { .assign, .init => return p.typePairStrExtra(dest_ty, " from incompatible type ", src_ty), .ret => return p.typePairStrExtra(src_ty, " from a function with incompatible result type ", dest_ty), .arg => return p.typePairStrExtra(src_ty, " to parameter of incompatible type ", dest_ty), .test_coerce => unreachable, } } }; /// Perform assignment-like coercion to `dest_ty`. fn coerce(res: *Result, p: *Parser, dest_ty: Type, tok: TokenIndex, ctx: CoerceContext) Error!void { if (res.ty.specifier == .invalid or dest_ty.specifier == .invalid) { res.ty = Type.invalid; return; } return res.coerceExtra(p, dest_ty, tok, ctx) catch |er| switch (er) { error.CoercionFailed => unreachable, else => |e| return e, }; } const Stage1Limitation = Error || error{CoercionFailed}; fn coerceExtra(res: *Result, p: *Parser, dest_ty: Type, tok: TokenIndex, ctx: CoerceContext) Stage1Limitation!void { // Subject of the coercion does not need to be qualified. var unqual_ty = dest_ty.canonicalize(.standard); unqual_ty.qual = .{}; if (unqual_ty.is(.nullptr_t)) { if (res.ty.is(.nullptr_t)) return; } else if (unqual_ty.is(.bool)) { if (res.ty.isScalar() and !res.ty.is(.nullptr_t)) { // this is ridiculous but it's what clang does try res.boolCast(p, unqual_ty, tok); return; } } else if (unqual_ty.isInt()) { if (res.ty.isInt() or res.ty.isFloat()) { try res.intCast(p, unqual_ty, tok); return; } else if (res.ty.isPtr()) { if (ctx == .test_coerce) return error.CoercionFailed; try p.errStr(.implicit_ptr_to_int, tok, try p.typePairStrExtra(res.ty, " to ", dest_ty)); try ctx.note(p); try res.intCast(p, unqual_ty, tok); return; } } else if (unqual_ty.isFloat()) { if (res.ty.isInt() or res.ty.isFloat()) { try res.floatCast(p, unqual_ty); return; } } else if (unqual_ty.isPtr()) { if (res.ty.is(.nullptr_t) or res.val.isZero()) { try res.nullCast(p, dest_ty); return; } else if (res.ty.isInt() and res.ty.isReal()) { if (ctx == .test_coerce) return error.CoercionFailed; try p.errStr(.implicit_int_to_ptr, tok, try p.typePairStrExtra(res.ty, " to ", dest_ty)); try ctx.note(p); try res.ptrCast(p, unqual_ty); return; } else if (res.ty.isVoidStar() or unqual_ty.eql(res.ty, p.comp, true)) { return; // ok } else if (unqual_ty.isVoidStar() and res.ty.isPtr() or (res.ty.isInt() and res.ty.isReal())) { return; // ok } else if (unqual_ty.eql(res.ty, p.comp, false)) { if (!unqual_ty.elemType().qual.hasQuals(res.ty.elemType().qual)) { try p.errStr(switch (ctx) { .assign => .ptr_assign_discards_quals, .init => .ptr_init_discards_quals, .ret => .ptr_ret_discards_quals, .arg => .ptr_arg_discards_quals, .test_coerce => return error.CoercionFailed, }, tok, try ctx.typePairStr(p, dest_ty, res.ty)); } try res.ptrCast(p, unqual_ty); return; } else if (res.ty.isPtr()) { const different_sign_only = unqual_ty.elemType().sameRankDifferentSign(res.ty.elemType(), p.comp); try p.errStr(switch (ctx) { .assign => ([2]Diagnostics.Tag{ .incompatible_ptr_assign, .incompatible_ptr_assign_sign })[@intFromBool(different_sign_only)], .init => ([2]Diagnostics.Tag{ .incompatible_ptr_init, .incompatible_ptr_init_sign })[@intFromBool(different_sign_only)], .ret => ([2]Diagnostics.Tag{ .incompatible_return, .incompatible_return_sign })[@intFromBool(different_sign_only)], .arg => ([2]Diagnostics.Tag{ .incompatible_ptr_arg, .incompatible_ptr_arg_sign })[@intFromBool(different_sign_only)], .test_coerce => return error.CoercionFailed, }, tok, try ctx.typePairStr(p, dest_ty, res.ty)); try ctx.note(p); try res.ptrChildTypeCast(p, unqual_ty); return; } } else if (unqual_ty.isRecord()) { if (unqual_ty.eql(res.ty, p.comp, false)) { return; // ok } if (ctx == .arg) if (unqual_ty.get(.@"union")) |union_ty| { if (dest_ty.hasAttribute(.transparent_union)) transparent_union: { res.coerceExtra(p, union_ty.data.record.fields[0].ty, tok, .test_coerce) catch |er| switch (er) { error.CoercionFailed => break :transparent_union, else => |e| return e, }; res.node = try p.addNode(.{ .tag = .union_init_expr, .ty = dest_ty, .data = .{ .union_init = .{ .field_index = 0, .node = res.node } }, }); res.ty = dest_ty; return; } }; } else if (unqual_ty.is(.vector)) { if (unqual_ty.eql(res.ty, p.comp, false)) { return; // ok } } else { if (ctx == .assign and (unqual_ty.isArray() or unqual_ty.isFunc())) { try p.errTok(.not_assignable, tok); return; } else if (ctx == .test_coerce) { return error.CoercionFailed; } // This case should not be possible and an error should have already been emitted but we // might still have attempted to parse further so return error.ParsingFailed here to stop. return error.ParsingFailed; } try p.errStr(switch (ctx) { .assign => .incompatible_assign, .init => .incompatible_init, .ret => .incompatible_return, .arg => .incompatible_arg, .test_coerce => return error.CoercionFailed, }, tok, try ctx.typePairStr(p, dest_ty, res.ty)); try ctx.note(p); } }; /// expr : assignExpr (',' assignExpr)* fn expr(p: *Parser) Error!Result { var expr_start = p.tok_i; var err_start = p.comp.diag.list.items.len; var lhs = try p.assignExpr(); if (p.tok_ids[p.tok_i] == .comma) try lhs.expect(p); while (p.eatToken(.comma)) |_| { try lhs.maybeWarnUnused(p, expr_start, err_start); expr_start = p.tok_i; err_start = p.comp.diag.list.items.len; var rhs = try p.assignExpr(); try rhs.expect(p); try rhs.lvalConversion(p); lhs.val = rhs.val; lhs.ty = rhs.ty; try lhs.bin(p, .comma_expr, rhs); } return lhs; } fn tokToTag(p: *Parser, tok: TokenIndex) Tree.Tag { return switch (p.tok_ids[tok]) { .equal => .assign_expr, .asterisk_equal => .mul_assign_expr, .slash_equal => .div_assign_expr, .percent_equal => .mod_assign_expr, .plus_equal => .add_assign_expr, .minus_equal => .sub_assign_expr, .angle_bracket_angle_bracket_left_equal => .shl_assign_expr, .angle_bracket_angle_bracket_right_equal => .shr_assign_expr, .ampersand_equal => .bit_and_assign_expr, .caret_equal => .bit_xor_assign_expr, .pipe_equal => .bit_or_assign_expr, .equal_equal => .equal_expr, .bang_equal => .not_equal_expr, .angle_bracket_left => .less_than_expr, .angle_bracket_left_equal => .less_than_equal_expr, .angle_bracket_right => .greater_than_expr, .angle_bracket_right_equal => .greater_than_equal_expr, .angle_bracket_angle_bracket_left => .shl_expr, .angle_bracket_angle_bracket_right => .shr_expr, .plus => .add_expr, .minus => .sub_expr, .asterisk => .mul_expr, .slash => .div_expr, .percent => .mod_expr, else => unreachable, }; } /// assignExpr /// : condExpr /// | unExpr ('=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=') assignExpr fn assignExpr(p: *Parser) Error!Result { var lhs = try p.condExpr(); if (lhs.empty(p)) return lhs; const tok = p.tok_i; const eq = p.eatToken(.equal); const mul = eq orelse p.eatToken(.asterisk_equal); const div = mul orelse p.eatToken(.slash_equal); const mod = div orelse p.eatToken(.percent_equal); const add = mod orelse p.eatToken(.plus_equal); const sub = add orelse p.eatToken(.minus_equal); const shl = sub orelse p.eatToken(.angle_bracket_angle_bracket_left_equal); const shr = shl orelse p.eatToken(.angle_bracket_angle_bracket_right_equal); const bit_and = shr orelse p.eatToken(.ampersand_equal); const bit_xor = bit_and orelse p.eatToken(.caret_equal); const bit_or = bit_xor orelse p.eatToken(.pipe_equal); const tag = p.tokToTag(bit_or orelse return lhs); var rhs = try p.assignExpr(); try rhs.expect(p); try rhs.lvalConversion(p); var is_const: bool = undefined; if (!Tree.isLvalExtra(p.nodes.slice(), p.data.items, p.value_map, lhs.node, &is_const) or is_const) { try p.errTok(.not_assignable, tok); return error.ParsingFailed; } // adjustTypes will do do lvalue conversion but we do not want that var lhs_copy = lhs; switch (tag) { .assign_expr => {}, // handle plain assignment separately .mul_assign_expr, .div_assign_expr, .mod_assign_expr, => { if (rhs.val.isZero() and lhs.ty.isInt() and rhs.ty.isInt()) { switch (tag) { .div_assign_expr => try p.errStr(.division_by_zero, div.?, "division"), .mod_assign_expr => try p.errStr(.division_by_zero, mod.?, "remainder"), else => {}, } } _ = try lhs_copy.adjustTypes(tok, &rhs, p, if (tag == .mod_assign_expr) .integer else .arithmetic); try lhs.bin(p, tag, rhs); return lhs; }, .sub_assign_expr, .add_assign_expr, => { if (lhs.ty.isPtr() and rhs.ty.isInt()) { try rhs.ptrCast(p, lhs.ty); } else { _ = try lhs_copy.adjustTypes(tok, &rhs, p, .arithmetic); } try lhs.bin(p, tag, rhs); return lhs; }, .shl_assign_expr, .shr_assign_expr, .bit_and_assign_expr, .bit_xor_assign_expr, .bit_or_assign_expr, => { _ = try lhs_copy.adjustTypes(tok, &rhs, p, .integer); try lhs.bin(p, tag, rhs); return lhs; }, else => unreachable, } try rhs.coerce(p, lhs.ty, tok, .assign); try lhs.bin(p, tag, rhs); return lhs; } /// Returns a parse error if the expression is not an integer constant /// integerConstExpr : constExpr fn integerConstExpr(p: *Parser, decl_folding: ConstDeclFoldingMode) Error!Result { const start = p.tok_i; const res = try p.constExpr(decl_folding); if (!res.ty.isInt() and res.ty.specifier != .invalid) { try p.errTok(.expected_integer_constant_expr, start); return error.ParsingFailed; } return res; } /// Caller is responsible for issuing a diagnostic if result is invalid/unavailable /// constExpr : condExpr fn constExpr(p: *Parser, decl_folding: ConstDeclFoldingMode) Error!Result { const const_decl_folding = p.const_decl_folding; defer p.const_decl_folding = const_decl_folding; p.const_decl_folding = decl_folding; const res = try p.condExpr(); try res.expect(p); if (res.ty.specifier == .invalid or res.val.tag == .unavailable) return res; // saveValue sets val to unavailable var copy = res; try copy.saveValue(p); return res; } /// condExpr : lorExpr ('?' expression? ':' condExpr)? fn condExpr(p: *Parser) Error!Result { const cond_tok = p.tok_i; var cond = try p.lorExpr(); if (cond.empty(p) or p.eatToken(.question_mark) == null) return cond; try cond.lvalConversion(p); const saved_eval = p.no_eval; if (!cond.ty.isScalar()) { try p.errStr(.cond_expr_type, cond_tok, try p.typeStr(cond.ty)); return error.ParsingFailed; } // Prepare for possible binary conditional expression. var maybe_colon = p.eatToken(.colon); // Depending on the value of the condition, avoid evaluating unreachable branches. var then_expr = blk: { defer p.no_eval = saved_eval; if (cond.val.tag != .unavailable and !cond.val.getBool()) p.no_eval = true; break :blk try p.expr(); }; try then_expr.expect(p); // If we saw a colon then this is a binary conditional expression. if (maybe_colon) |colon| { var cond_then = cond; cond_then.node = try p.addNode(.{ .tag = .cond_dummy_expr, .ty = cond.ty, .data = .{ .un = cond.node } }); _ = try cond_then.adjustTypes(colon, &then_expr, p, .conditional); cond.ty = then_expr.ty; cond.node = try p.addNode(.{ .tag = .binary_cond_expr, .ty = cond.ty, .data = .{ .if3 = .{ .cond = cond.node, .body = (try p.addList(&.{ cond_then.node, then_expr.node })).start } }, }); return cond; } const colon = try p.expectToken(.colon); var else_expr = blk: { defer p.no_eval = saved_eval; if (cond.val.tag != .unavailable and cond.val.getBool()) p.no_eval = true; break :blk try p.condExpr(); }; try else_expr.expect(p); _ = try then_expr.adjustTypes(colon, &else_expr, p, .conditional); if (cond.val.tag != .unavailable) { cond.val = if (cond.val.getBool()) then_expr.val else else_expr.val; } else { try then_expr.saveValue(p); try else_expr.saveValue(p); } cond.ty = then_expr.ty; cond.node = try p.addNode(.{ .tag = .cond_expr, .ty = cond.ty, .data = .{ .if3 = .{ .cond = cond.node, .body = (try p.addList(&.{ then_expr.node, else_expr.node })).start } }, }); return cond; } /// lorExpr : landExpr ('||' landExpr)* fn lorExpr(p: *Parser) Error!Result { var lhs = try p.landExpr(); if (lhs.empty(p)) return lhs; const saved_eval = p.no_eval; defer p.no_eval = saved_eval; while (p.eatToken(.pipe_pipe)) |tok| { if (lhs.val.tag != .unavailable and lhs.val.getBool()) p.no_eval = true; var rhs = try p.landExpr(); try rhs.expect(p); if (try lhs.adjustTypes(tok, &rhs, p, .boolean_logic)) { const res = @intFromBool(lhs.val.getBool() or rhs.val.getBool()); lhs.val = Value.int(res); } try lhs.boolRes(p, .bool_or_expr, rhs); } return lhs; } /// landExpr : orExpr ('&&' orExpr)* fn landExpr(p: *Parser) Error!Result { var lhs = try p.orExpr(); if (lhs.empty(p)) return lhs; const saved_eval = p.no_eval; defer p.no_eval = saved_eval; while (p.eatToken(.ampersand_ampersand)) |tok| { if (lhs.val.tag != .unavailable and !lhs.val.getBool()) p.no_eval = true; var rhs = try p.orExpr(); try rhs.expect(p); if (try lhs.adjustTypes(tok, &rhs, p, .boolean_logic)) { const res = @intFromBool(lhs.val.getBool() and rhs.val.getBool()); lhs.val = Value.int(res); } try lhs.boolRes(p, .bool_and_expr, rhs); } return lhs; } /// orExpr : xorExpr ('|' xorExpr)* fn orExpr(p: *Parser) Error!Result { var lhs = try p.xorExpr(); if (lhs.empty(p)) return lhs; while (p.eatToken(.pipe)) |tok| { var rhs = try p.xorExpr(); try rhs.expect(p); if (try lhs.adjustTypes(tok, &rhs, p, .integer)) { lhs.val = lhs.val.bitOr(rhs.val, lhs.ty, p.comp); } try lhs.bin(p, .bit_or_expr, rhs); } return lhs; } /// xorExpr : andExpr ('^' andExpr)* fn xorExpr(p: *Parser) Error!Result { var lhs = try p.andExpr(); if (lhs.empty(p)) return lhs; while (p.eatToken(.caret)) |tok| { var rhs = try p.andExpr(); try rhs.expect(p); if (try lhs.adjustTypes(tok, &rhs, p, .integer)) { lhs.val = lhs.val.bitXor(rhs.val, lhs.ty, p.comp); } try lhs.bin(p, .bit_xor_expr, rhs); } return lhs; } /// andExpr : eqExpr ('&' eqExpr)* fn andExpr(p: *Parser) Error!Result { var lhs = try p.eqExpr(); if (lhs.empty(p)) return lhs; while (p.eatToken(.ampersand)) |tok| { var rhs = try p.eqExpr(); try rhs.expect(p); if (try lhs.adjustTypes(tok, &rhs, p, .integer)) { lhs.val = lhs.val.bitAnd(rhs.val, lhs.ty, p.comp); } try lhs.bin(p, .bit_and_expr, rhs); } return lhs; } /// eqExpr : compExpr (('==' | '!=') compExpr)* fn eqExpr(p: *Parser) Error!Result { var lhs = try p.compExpr(); if (lhs.empty(p)) return lhs; while (true) { const eq = p.eatToken(.equal_equal); const ne = eq orelse p.eatToken(.bang_equal); const tag = p.tokToTag(ne orelse break); var rhs = try p.compExpr(); try rhs.expect(p); if (try lhs.adjustTypes(ne.?, &rhs, p, .equality)) { const op: std.math.CompareOperator = if (tag == .equal_expr) .eq else .neq; const res = lhs.val.compare(op, rhs.val, lhs.ty, p.comp); lhs.val = Value.int(@intFromBool(res)); } try lhs.boolRes(p, tag, rhs); } return lhs; } /// compExpr : shiftExpr (('<' | '<=' | '>' | '>=') shiftExpr)* fn compExpr(p: *Parser) Error!Result { var lhs = try p.shiftExpr(); if (lhs.empty(p)) return lhs; while (true) { const lt = p.eatToken(.angle_bracket_left); const le = lt orelse p.eatToken(.angle_bracket_left_equal); const gt = le orelse p.eatToken(.angle_bracket_right); const ge = gt orelse p.eatToken(.angle_bracket_right_equal); const tag = p.tokToTag(ge orelse break); var rhs = try p.shiftExpr(); try rhs.expect(p); if (try lhs.adjustTypes(ge.?, &rhs, p, .relational)) { const op: std.math.CompareOperator = switch (tag) { .less_than_expr => .lt, .less_than_equal_expr => .lte, .greater_than_expr => .gt, .greater_than_equal_expr => .gte, else => unreachable, }; const res = lhs.val.compare(op, rhs.val, lhs.ty, p.comp); lhs.val = Value.int(@intFromBool(res)); } try lhs.boolRes(p, tag, rhs); } return lhs; } /// shiftExpr : addExpr (('<<' | '>>') addExpr)* fn shiftExpr(p: *Parser) Error!Result { var lhs = try p.addExpr(); if (lhs.empty(p)) return lhs; while (true) { const shl = p.eatToken(.angle_bracket_angle_bracket_left); const shr = shl orelse p.eatToken(.angle_bracket_angle_bracket_right); const tag = p.tokToTag(shr orelse break); var rhs = try p.addExpr(); try rhs.expect(p); if (try lhs.adjustTypes(shr.?, &rhs, p, .integer)) { if (shl != null) { lhs.val = lhs.val.shl(rhs.val, lhs.ty, p.comp); } else { lhs.val = lhs.val.shr(rhs.val, lhs.ty, p.comp); } } try lhs.bin(p, tag, rhs); } return lhs; } /// addExpr : mulExpr (('+' | '-') mulExpr)* fn addExpr(p: *Parser) Error!Result { var lhs = try p.mulExpr(); if (lhs.empty(p)) return lhs; while (true) { const plus = p.eatToken(.plus); const minus = plus orelse p.eatToken(.minus); const tag = p.tokToTag(minus orelse break); var rhs = try p.mulExpr(); try rhs.expect(p); const lhs_ty = lhs.ty; if (try lhs.adjustTypes(minus.?, &rhs, p, if (plus != null) .add else .sub)) { if (plus != null) { if (lhs.val.add(lhs.val, rhs.val, lhs.ty, p.comp)) try p.errOverflow(plus.?, lhs); } else { if (lhs.val.sub(lhs.val, rhs.val, lhs.ty, p.comp)) try p.errOverflow(minus.?, lhs); } } if (lhs.ty.specifier != .invalid and lhs_ty.isPtr() and !lhs_ty.isVoidStar() and lhs_ty.elemType().hasIncompleteSize()) { try p.errStr(.ptr_arithmetic_incomplete, minus.?, try p.typeStr(lhs_ty.elemType())); lhs.ty = Type.invalid; } try lhs.bin(p, tag, rhs); } return lhs; } /// mulExpr : castExpr (('*' | '/' | '%') castExpr)*´ fn mulExpr(p: *Parser) Error!Result { var lhs = try p.castExpr(); if (lhs.empty(p)) return lhs; while (true) { const mul = p.eatToken(.asterisk); const div = mul orelse p.eatToken(.slash); const percent = div orelse p.eatToken(.percent); const tag = p.tokToTag(percent orelse break); var rhs = try p.castExpr(); try rhs.expect(p); if (rhs.val.isZero() and mul == null and !p.no_eval and lhs.ty.isInt() and rhs.ty.isInt()) { const err_tag: Diagnostics.Tag = if (p.in_macro) .division_by_zero_macro else .division_by_zero; lhs.val.tag = .unavailable; if (div != null) { try p.errStr(err_tag, div.?, "division"); } else { try p.errStr(err_tag, percent.?, "remainder"); } if (p.in_macro) return error.ParsingFailed; } if (try lhs.adjustTypes(percent.?, &rhs, p, if (tag == .mod_expr) .integer else .arithmetic)) { if (mul != null) { if (lhs.val.mul(lhs.val, rhs.val, lhs.ty, p.comp)) try p.errOverflow(mul.?, lhs); } else if (div != null) { lhs.val = Value.div(lhs.val, rhs.val, lhs.ty, p.comp); } else { var res = Value.rem(lhs.val, rhs.val, lhs.ty, p.comp); if (res.tag == .unavailable) { if (p.in_macro) { // match clang behavior by defining invalid remainder to be zero in macros res = Value.int(0); } else { try lhs.saveValue(p); try rhs.saveValue(p); } } lhs.val = res; } } try lhs.bin(p, tag, rhs); } return lhs; } /// This will always be the last message, if present fn removeUnusedWarningForTok(p: *Parser, last_expr_tok: TokenIndex) void { if (last_expr_tok == 0) return; if (p.comp.diag.list.items.len == 0) return; const last_expr_loc = p.pp.tokens.items(.loc)[last_expr_tok]; const last_msg = p.comp.diag.list.items[p.comp.diag.list.items.len - 1]; if (last_msg.tag == .unused_value and last_msg.loc.eql(last_expr_loc)) { p.comp.diag.list.items.len = p.comp.diag.list.items.len - 1; } } /// castExpr /// : '(' compoundStmt ')' /// | '(' typeName ')' castExpr /// | '(' typeName ')' '{' initializerItems '}' /// | __builtin_choose_expr '(' integerConstExpr ',' assignExpr ',' assignExpr ')' /// | __builtin_va_arg '(' assignExpr ',' typeName ')' /// | __builtin_offsetof '(' typeName ',' offsetofMemberDesignator ')' /// | __builtin_bitoffsetof '(' typeName ',' offsetofMemberDesignator ')' /// | unExpr fn castExpr(p: *Parser) Error!Result { if (p.eatToken(.l_paren)) |l_paren| cast_expr: { if (p.tok_ids[p.tok_i] == .l_brace) { try p.err(.gnu_statement_expression); if (p.func.ty == null) { try p.err(.stmt_expr_not_allowed_file_scope); return error.ParsingFailed; } var stmt_expr_state: StmtExprState = .{}; const body_node = (try p.compoundStmt(false, &stmt_expr_state)).?; // compoundStmt only returns null if .l_brace isn't the first token p.removeUnusedWarningForTok(stmt_expr_state.last_expr_tok); var res = Result{ .node = body_node, .ty = stmt_expr_state.last_expr_res.ty, .val = stmt_expr_state.last_expr_res.val, }; try p.expectClosing(l_paren, .r_paren); try res.un(p, .stmt_expr); return res; } const ty = (try p.typeName()) orelse { p.tok_i -= 1; break :cast_expr; }; try p.expectClosing(l_paren, .r_paren); if (p.tok_ids[p.tok_i] == .l_brace) { // Compound literal; handled in unExpr p.tok_i = l_paren; break :cast_expr; } var operand = try p.castExpr(); try operand.expect(p); try operand.lvalConversion(p); try operand.castType(p, ty, l_paren); return operand; } switch (p.tok_ids[p.tok_i]) { .builtin_choose_expr => return p.builtinChooseExpr(), .builtin_va_arg => return p.builtinVaArg(), .builtin_offsetof => return p.builtinOffsetof(false), .builtin_bitoffsetof => return p.builtinOffsetof(true), .builtin_types_compatible_p => return p.typesCompatible(), // TODO: other special-cased builtins else => {}, } return p.unExpr(); } fn typesCompatible(p: *Parser) Error!Result { p.tok_i += 1; const l_paren = try p.expectToken(.l_paren); const first = (try p.typeName()) orelse { try p.err(.expected_type); p.skipTo(.r_paren); return error.ParsingFailed; }; const lhs = try p.addNode(.{ .tag = .invalid, .ty = first, .data = undefined }); _ = try p.expectToken(.comma); const second = (try p.typeName()) orelse { try p.err(.expected_type); p.skipTo(.r_paren); return error.ParsingFailed; }; const rhs = try p.addNode(.{ .tag = .invalid, .ty = second, .data = undefined }); try p.expectClosing(l_paren, .r_paren); const compatible = first.compatible(second, p.comp); var res = Result{ .val = Value.int(@intFromBool(compatible)), .node = try p.addNode(.{ .tag = .builtin_types_compatible_p, .ty = Type.int, .data = .{ .bin = .{ .lhs = lhs, .rhs = rhs, } } }), }; try p.value_map.put(res.node, res.val); return res; } fn builtinChooseExpr(p: *Parser) Error!Result { p.tok_i += 1; const l_paren = try p.expectToken(.l_paren); const cond_tok = p.tok_i; var cond = try p.integerConstExpr(.no_const_decl_folding); if (cond.val.tag == .unavailable) { try p.errTok(.builtin_choose_cond, cond_tok); return error.ParsingFailed; } _ = try p.expectToken(.comma); var then_expr = if (cond.val.getBool()) try p.assignExpr() else try p.parseNoEval(assignExpr); try then_expr.expect(p); _ = try p.expectToken(.comma); var else_expr = if (!cond.val.getBool()) try p.assignExpr() else try p.parseNoEval(assignExpr); try else_expr.expect(p); try p.expectClosing(l_paren, .r_paren); if (cond.val.getBool()) { cond.val = then_expr.val; cond.ty = then_expr.ty; } else { cond.val = else_expr.val; cond.ty = else_expr.ty; } cond.node = try p.addNode(.{ .tag = .builtin_choose_expr, .ty = cond.ty, .data = .{ .if3 = .{ .cond = cond.node, .body = (try p.addList(&.{ then_expr.node, else_expr.node })).start } }, }); return cond; } fn builtinVaArg(p: *Parser) Error!Result { const builtin_tok = p.tok_i; p.tok_i += 1; const l_paren = try p.expectToken(.l_paren); const va_list_tok = p.tok_i; var va_list = try p.assignExpr(); try va_list.expect(p); try va_list.lvalConversion(p); _ = try p.expectToken(.comma); const ty = (try p.typeName()) orelse { try p.err(.expected_type); return error.ParsingFailed; }; try p.expectClosing(l_paren, .r_paren); if (!va_list.ty.eql(p.comp.types.va_list, p.comp, true)) { try p.errStr(.incompatible_va_arg, va_list_tok, try p.typeStr(va_list.ty)); return error.ParsingFailed; } return Result{ .ty = ty, .node = try p.addNode(.{ .tag = .special_builtin_call_one, .ty = ty, .data = .{ .decl = .{ .name = builtin_tok, .node = va_list.node } }, }) }; } fn builtinOffsetof(p: *Parser, want_bits: bool) Error!Result { const builtin_tok = p.tok_i; p.tok_i += 1; const l_paren = try p.expectToken(.l_paren); const ty_tok = p.tok_i; const ty = (try p.typeName()) orelse { try p.err(.expected_type); p.skipTo(.r_paren); return error.ParsingFailed; }; if (!ty.isRecord()) { try p.errStr(.offsetof_ty, ty_tok, try p.typeStr(ty)); p.skipTo(.r_paren); return error.ParsingFailed; } else if (ty.hasIncompleteSize()) { try p.errStr(.offsetof_incomplete, ty_tok, try p.typeStr(ty)); p.skipTo(.r_paren); return error.ParsingFailed; } _ = try p.expectToken(.comma); const offsetof_expr = try p.offsetofMemberDesignator(ty); try p.expectClosing(l_paren, .r_paren); return Result{ .ty = p.comp.types.size, .val = if (offsetof_expr.val.tag == .int and !want_bits) Value.int(offsetof_expr.val.data.int / 8) else offsetof_expr.val, .node = try p.addNode(.{ .tag = .special_builtin_call_one, .ty = p.comp.types.size, .data = .{ .decl = .{ .name = builtin_tok, .node = offsetof_expr.node } }, }), }; } /// offsetofMemberDesignator: IDENTIFIER ('.' IDENTIFIER | '[' expr ']' )* fn offsetofMemberDesignator(p: *Parser, base_ty: Type) Error!Result { errdefer p.skipTo(.r_paren); const base_field_name_tok = try p.expectIdentifier(); const base_field_name = try p.comp.intern(p.tokSlice(base_field_name_tok)); try p.validateFieldAccess(base_ty, base_ty, base_field_name_tok, base_field_name); const base_node = try p.addNode(.{ .tag = .default_init_expr, .ty = base_ty, .data = undefined }); var offset_num: u64 = 0; const base_record_ty = base_ty.canonicalize(.standard); var lhs = try p.fieldAccessExtra(base_node, base_record_ty, base_field_name, false, &offset_num); var bit_offset = Value.int(offset_num); while (true) switch (p.tok_ids[p.tok_i]) { .period => { p.tok_i += 1; const field_name_tok = try p.expectIdentifier(); const field_name = try p.comp.intern(p.tokSlice(field_name_tok)); if (!lhs.ty.isRecord()) { try p.errStr(.offsetof_ty, field_name_tok, try p.typeStr(lhs.ty)); return error.ParsingFailed; } try p.validateFieldAccess(lhs.ty, lhs.ty, field_name_tok, field_name); const record_ty = lhs.ty.canonicalize(.standard); lhs = try p.fieldAccessExtra(lhs.node, record_ty, field_name, false, &offset_num); if (bit_offset.tag != .unavailable) { bit_offset = Value.int(offset_num + bit_offset.getInt(u64)); } }, .l_bracket => { const l_bracket_tok = p.tok_i; p.tok_i += 1; var index = try p.expr(); try index.expect(p); _ = try p.expectClosing(l_bracket_tok, .r_bracket); if (!lhs.ty.isArray()) { try p.errStr(.offsetof_array, l_bracket_tok, try p.typeStr(lhs.ty)); return error.ParsingFailed; } var ptr = lhs; try ptr.lvalConversion(p); try index.lvalConversion(p); if (!index.ty.isInt()) try p.errTok(.invalid_index, l_bracket_tok); try p.checkArrayBounds(index, lhs, l_bracket_tok); try index.saveValue(p); try ptr.bin(p, .array_access_expr, index); lhs = ptr; }, else => break, }; return Result{ .ty = base_ty, .val = bit_offset, .node = lhs.node }; } /// unExpr /// : (compoundLiteral | primaryExpr) suffixExpr* /// | '&&' IDENTIFIER /// | ('&' | '*' | '+' | '-' | '~' | '!' | '++' | '--' | keyword_extension | keyword_imag | keyword_real) castExpr /// | keyword_sizeof unExpr /// | keyword_sizeof '(' typeName ')' /// | keyword_alignof '(' typeName ')' /// | keyword_c23_alignof '(' typeName ')' fn unExpr(p: *Parser) Error!Result { const tok = p.tok_i; switch (p.tok_ids[tok]) { .ampersand_ampersand => { const address_tok = p.tok_i; p.tok_i += 1; const name_tok = try p.expectIdentifier(); try p.errTok(.gnu_label_as_value, address_tok); p.contains_address_of_label = true; const str = p.tokSlice(name_tok); if (p.findLabel(str) == null) { try p.labels.append(.{ .unresolved_goto = name_tok }); } const elem_ty = try p.arena.create(Type); elem_ty.* = .{ .specifier = .void }; const result_ty = Type{ .specifier = .pointer, .data = .{ .sub_type = elem_ty } }; return Result{ .node = try p.addNode(.{ .tag = .addr_of_label, .data = .{ .decl_ref = name_tok }, .ty = result_ty, }), .ty = result_ty, }; }, .ampersand => { if (p.in_macro) { try p.err(.invalid_preproc_operator); return error.ParsingFailed; } p.tok_i += 1; var operand = try p.castExpr(); try operand.expect(p); const slice = p.nodes.slice(); if (p.getNode(operand.node, .member_access_expr) orelse p.getNode(operand.node, .member_access_ptr_expr)) |member_node| { if (Tree.isBitfield(slice, member_node)) try p.errTok(.addr_of_bitfield, tok); } if (!Tree.isLval(slice, p.data.items, p.value_map, operand.node)) { try p.errTok(.addr_of_rvalue, tok); } if (operand.ty.qual.register) try p.errTok(.addr_of_register, tok); const elem_ty = try p.arena.create(Type); elem_ty.* = operand.ty; operand.ty = Type{ .specifier = .pointer, .data = .{ .sub_type = elem_ty }, }; try operand.saveValue(p); try operand.un(p, .addr_of_expr); return operand; }, .asterisk => { const asterisk_loc = p.tok_i; p.tok_i += 1; var operand = try p.castExpr(); try operand.expect(p); if (operand.ty.isArray() or operand.ty.isPtr() or operand.ty.isFunc()) { try operand.lvalConversion(p); operand.ty = operand.ty.elemType(); } else { try p.errTok(.indirection_ptr, tok); } if (operand.ty.hasIncompleteSize() and !operand.ty.is(.void)) { try p.errStr(.deref_incomplete_ty_ptr, asterisk_loc, try p.typeStr(operand.ty)); } operand.ty.qual = .{}; try operand.un(p, .deref_expr); return operand; }, .plus => { p.tok_i += 1; var operand = try p.castExpr(); try operand.expect(p); try operand.lvalConversion(p); if (!operand.ty.isInt() and !operand.ty.isFloat()) try p.errStr(.invalid_argument_un, tok, try p.typeStr(operand.ty)); try operand.usualUnaryConversion(p, tok); return operand; }, .minus => { p.tok_i += 1; var operand = try p.castExpr(); try operand.expect(p); try operand.lvalConversion(p); if (!operand.ty.isInt() and !operand.ty.isFloat()) try p.errStr(.invalid_argument_un, tok, try p.typeStr(operand.ty)); try operand.usualUnaryConversion(p, tok); if (operand.val.tag == .int or operand.val.tag == .float) { _ = operand.val.sub(operand.val.zero(), operand.val, operand.ty, p.comp); } else { operand.val.tag = .unavailable; } try operand.un(p, .negate_expr); return operand; }, .plus_plus => { p.tok_i += 1; var operand = try p.castExpr(); try operand.expect(p); if (!operand.ty.isScalar()) try p.errStr(.invalid_argument_un, tok, try p.typeStr(operand.ty)); if (operand.ty.isComplex()) try p.errStr(.complex_prefix_postfix_op, p.tok_i, try p.typeStr(operand.ty)); if (!Tree.isLval(p.nodes.slice(), p.data.items, p.value_map, operand.node) or operand.ty.isConst()) { try p.errTok(.not_assignable, tok); return error.ParsingFailed; } try operand.usualUnaryConversion(p, tok); if (operand.val.tag == .int or operand.val.tag == .float) { if (operand.val.add(operand.val, operand.val.one(), operand.ty, p.comp)) try p.errOverflow(tok, operand); } else { operand.val.tag = .unavailable; } try operand.un(p, .pre_inc_expr); return operand; }, .minus_minus => { p.tok_i += 1; var operand = try p.castExpr(); try operand.expect(p); if (!operand.ty.isScalar()) try p.errStr(.invalid_argument_un, tok, try p.typeStr(operand.ty)); if (operand.ty.isComplex()) try p.errStr(.complex_prefix_postfix_op, p.tok_i, try p.typeStr(operand.ty)); if (!Tree.isLval(p.nodes.slice(), p.data.items, p.value_map, operand.node) or operand.ty.isConst()) { try p.errTok(.not_assignable, tok); return error.ParsingFailed; } try operand.usualUnaryConversion(p, tok); if (operand.val.tag == .int or operand.val.tag == .float) { if (operand.val.sub(operand.val, operand.val.one(), operand.ty, p.comp)) try p.errOverflow(tok, operand); } else { operand.val.tag = .unavailable; } try operand.un(p, .pre_dec_expr); return operand; }, .tilde => { p.tok_i += 1; var operand = try p.castExpr(); try operand.expect(p); try operand.lvalConversion(p); try operand.usualUnaryConversion(p, tok); if (operand.ty.isInt()) { if (operand.val.tag == .int) { operand.val = operand.val.bitNot(operand.ty, p.comp); } } else { try p.errStr(.invalid_argument_un, tok, try p.typeStr(operand.ty)); operand.val.tag = .unavailable; } try operand.un(p, .bit_not_expr); return operand; }, .bang => { p.tok_i += 1; var operand = try p.castExpr(); try operand.expect(p); try operand.lvalConversion(p); if (!operand.ty.isScalar()) try p.errStr(.invalid_argument_un, tok, try p.typeStr(operand.ty)); try operand.usualUnaryConversion(p, tok); if (operand.val.tag == .int) { const res = Value.int(@intFromBool(!operand.val.getBool())); operand.val = res; } else if (operand.val.tag == .nullptr_t) { operand.val = Value.int(1); } else { if (operand.ty.isDecayed()) { operand.val = Value.int(0); } else { operand.val.tag = .unavailable; } } operand.ty = .{ .specifier = .int }; try operand.un(p, .bool_not_expr); return operand; }, .keyword_sizeof => { p.tok_i += 1; const expected_paren = p.tok_i; var res = Result{}; if (try p.typeName()) |ty| { res.ty = ty; try p.errTok(.expected_parens_around_typename, expected_paren); } else if (p.eatToken(.l_paren)) |l_paren| { if (try p.typeName()) |ty| { res.ty = ty; try p.expectClosing(l_paren, .r_paren); } else { p.tok_i = expected_paren; res = try p.parseNoEval(unExpr); } } else { res = try p.parseNoEval(unExpr); } if (res.ty.is(.void)) { try p.errStr(.pointer_arith_void, tok, "sizeof"); } else if (res.ty.isDecayed()) { const array_ty = res.ty.originalTypeOfDecayedArray(); const err_str = try p.typePairStrExtra(res.ty, " instead of ", array_ty); try p.errStr(.sizeof_array_arg, tok, err_str); } if (res.ty.sizeof(p.comp)) |size| { if (size == 0) { try p.errTok(.sizeof_returns_zero, tok); } res.val = Value.int(size); res.ty = p.comp.types.size; } else { res.val.tag = .unavailable; if (res.ty.hasIncompleteSize()) { try p.errStr(.invalid_sizeof, expected_paren - 1, try p.typeStr(res.ty)); res.ty = Type.invalid; } else { res.ty = p.comp.types.size; } } try res.un(p, .sizeof_expr); return res; }, .keyword_alignof, .keyword_alignof1, .keyword_alignof2, .keyword_c23_alignof, => { p.tok_i += 1; const expected_paren = p.tok_i; var res = Result{}; if (try p.typeName()) |ty| { res.ty = ty; try p.errTok(.expected_parens_around_typename, expected_paren); } else if (p.eatToken(.l_paren)) |l_paren| { if (try p.typeName()) |ty| { res.ty = ty; try p.expectClosing(l_paren, .r_paren); } else { p.tok_i = expected_paren; res = try p.parseNoEval(unExpr); try p.errTok(.alignof_expr, expected_paren); } } else { res = try p.parseNoEval(unExpr); try p.errTok(.alignof_expr, expected_paren); } if (res.ty.is(.void)) { try p.errStr(.pointer_arith_void, tok, "alignof"); } if (res.ty.alignable()) { res.val = Value.int(res.ty.alignof(p.comp)); res.ty = p.comp.types.size; } else { try p.errStr(.invalid_alignof, expected_paren, try p.typeStr(res.ty)); res.ty = Type.invalid; } try res.un(p, .alignof_expr); return res; }, .keyword_extension => { p.tok_i += 1; const saved_extension = p.extension_suppressed; defer p.extension_suppressed = saved_extension; p.extension_suppressed = true; var child = try p.castExpr(); try child.expect(p); return child; }, .keyword_imag1, .keyword_imag2 => { const imag_tok = p.tok_i; p.tok_i += 1; var operand = try p.castExpr(); try operand.expect(p); try operand.lvalConversion(p); if (!operand.ty.isInt() and !operand.ty.isFloat()) { try p.errStr(.invalid_imag, imag_tok, try p.typeStr(operand.ty)); } if (operand.ty.isReal()) { switch (p.comp.langopts.emulate) { .msvc => {}, // Doesn't support `_Complex` or `__imag` in the first place .gcc => { if (operand.ty.isInt()) { operand.val = Value.int(0); } else if (operand.ty.isFloat()) { operand.val = Value.float(0); } }, .clang => { if (operand.val.tag == .int) { operand.val = Value.int(0); } else { operand.val.tag = .unavailable; } }, } } // convert _Complex T to T operand.ty = operand.ty.makeReal(); try operand.un(p, .imag_expr); return operand; }, .keyword_real1, .keyword_real2 => { const real_tok = p.tok_i; p.tok_i += 1; var operand = try p.castExpr(); try operand.expect(p); try operand.lvalConversion(p); if (!operand.ty.isInt() and !operand.ty.isFloat()) { try p.errStr(.invalid_real, real_tok, try p.typeStr(operand.ty)); } // convert _Complex T to T operand.ty = operand.ty.makeReal(); try operand.un(p, .real_expr); return operand; }, else => { var lhs = try p.compoundLiteral(); if (lhs.empty(p)) { lhs = try p.primaryExpr(); if (lhs.empty(p)) return lhs; } while (true) { const suffix = try p.suffixExpr(lhs); if (suffix.empty(p)) break; lhs = suffix; } return lhs; }, } } /// compoundLiteral /// : '(' type_name ')' '{' initializer_list '}' /// | '(' type_name ')' '{' initializer_list ',' '}' fn compoundLiteral(p: *Parser) Error!Result { const l_paren = p.eatToken(.l_paren) orelse return Result{}; const ty = (try p.typeName()) orelse { p.tok_i = l_paren; return Result{}; }; try p.expectClosing(l_paren, .r_paren); if (ty.isFunc()) { try p.err(.func_init); } else if (ty.is(.variable_len_array)) { try p.err(.vla_init); } else if (ty.hasIncompleteSize() and !ty.is(.incomplete_array)) { try p.errStr(.variable_incomplete_ty, p.tok_i, try p.typeStr(ty)); return error.ParsingFailed; } var init_list_expr = try p.initializer(ty); try init_list_expr.un(p, .compound_literal_expr); return init_list_expr; } /// suffixExpr /// : '[' expr ']' /// | '(' argumentExprList? ')' /// | '.' IDENTIFIER /// | '->' IDENTIFIER /// | '++' /// | '--' /// argumentExprList : assignExpr (',' assignExpr)* fn suffixExpr(p: *Parser, lhs: Result) Error!Result { assert(!lhs.empty(p)); switch (p.tok_ids[p.tok_i]) { .l_paren => return p.callExpr(lhs), .plus_plus => { defer p.tok_i += 1; var operand = lhs; if (!operand.ty.isScalar()) try p.errStr(.invalid_argument_un, p.tok_i, try p.typeStr(operand.ty)); if (operand.ty.isComplex()) try p.errStr(.complex_prefix_postfix_op, p.tok_i, try p.typeStr(operand.ty)); if (!Tree.isLval(p.nodes.slice(), p.data.items, p.value_map, operand.node) or operand.ty.isConst()) { try p.err(.not_assignable); return error.ParsingFailed; } try operand.usualUnaryConversion(p, p.tok_i); try operand.un(p, .post_inc_expr); return operand; }, .minus_minus => { defer p.tok_i += 1; var operand = lhs; if (!operand.ty.isScalar()) try p.errStr(.invalid_argument_un, p.tok_i, try p.typeStr(operand.ty)); if (operand.ty.isComplex()) try p.errStr(.complex_prefix_postfix_op, p.tok_i, try p.typeStr(operand.ty)); if (!Tree.isLval(p.nodes.slice(), p.data.items, p.value_map, operand.node) or operand.ty.isConst()) { try p.err(.not_assignable); return error.ParsingFailed; } try operand.usualUnaryConversion(p, p.tok_i); try operand.un(p, .post_dec_expr); return operand; }, .l_bracket => { const l_bracket = p.tok_i; p.tok_i += 1; var index = try p.expr(); try index.expect(p); try p.expectClosing(l_bracket, .r_bracket); const array_before_conversion = lhs; const index_before_conversion = index; var ptr = lhs; try ptr.lvalConversion(p); try index.lvalConversion(p); if (ptr.ty.isPtr()) { ptr.ty = ptr.ty.elemType(); if (!index.ty.isInt()) try p.errTok(.invalid_index, l_bracket); try p.checkArrayBounds(index_before_conversion, array_before_conversion, l_bracket); } else if (index.ty.isPtr()) { index.ty = index.ty.elemType(); if (!ptr.ty.isInt()) try p.errTok(.invalid_index, l_bracket); try p.checkArrayBounds(array_before_conversion, index_before_conversion, l_bracket); std.mem.swap(Result, &ptr, &index); } else { try p.errTok(.invalid_subscript, l_bracket); } try ptr.saveValue(p); try index.saveValue(p); try ptr.bin(p, .array_access_expr, index); return ptr; }, .period => { p.tok_i += 1; const name = try p.expectIdentifier(); return p.fieldAccess(lhs, name, false); }, .arrow => { p.tok_i += 1; const name = try p.expectIdentifier(); if (lhs.ty.isArray()) { var copy = lhs; copy.ty.decayArray(); try copy.implicitCast(p, .array_to_pointer); return p.fieldAccess(copy, name, true); } return p.fieldAccess(lhs, name, true); }, else => return Result{}, } } fn fieldAccess( p: *Parser, lhs: Result, field_name_tok: TokenIndex, is_arrow: bool, ) !Result { const expr_ty = lhs.ty; const is_ptr = expr_ty.isPtr(); const expr_base_ty = if (is_ptr) expr_ty.elemType() else expr_ty; const record_ty = expr_base_ty.canonicalize(.standard); switch (record_ty.specifier) { .@"struct", .@"union" => {}, else => { try p.errStr(.expected_record_ty, field_name_tok, try p.typeStr(expr_ty)); return error.ParsingFailed; }, } if (record_ty.hasIncompleteSize()) { try p.errStr(.deref_incomplete_ty_ptr, field_name_tok - 2, try p.typeStr(expr_base_ty)); return error.ParsingFailed; } if (is_arrow and !is_ptr) try p.errStr(.member_expr_not_ptr, field_name_tok, try p.typeStr(expr_ty)); if (!is_arrow and is_ptr) try p.errStr(.member_expr_ptr, field_name_tok, try p.typeStr(expr_ty)); const field_name = try p.comp.intern(p.tokSlice(field_name_tok)); try p.validateFieldAccess(record_ty, expr_ty, field_name_tok, field_name); var discard: u64 = 0; return p.fieldAccessExtra(lhs.node, record_ty, field_name, is_arrow, &discard); } fn validateFieldAccess(p: *Parser, record_ty: Type, expr_ty: Type, field_name_tok: TokenIndex, field_name: StringId) Error!void { if (record_ty.hasField(field_name)) return; p.strings.items.len = 0; try p.strings.writer().print("'{s}' in '", .{p.tokSlice(field_name_tok)}); const mapper = p.comp.string_interner.getSlowTypeMapper(); try expr_ty.print(mapper, p.comp.langopts, p.strings.writer()); try p.strings.append('\''); const duped = try p.comp.diag.arena.allocator().dupe(u8, p.strings.items); try p.errStr(.no_such_member, field_name_tok, duped); return error.ParsingFailed; } fn fieldAccessExtra(p: *Parser, lhs: NodeIndex, record_ty: Type, field_name: StringId, is_arrow: bool, offset_bits: *u64) Error!Result { for (record_ty.data.record.fields, 0..) |f, i| { if (f.isAnonymousRecord()) { if (!f.ty.hasField(field_name)) continue; const inner = try p.addNode(.{ .tag = if (is_arrow) .member_access_ptr_expr else .member_access_expr, .ty = f.ty, .data = .{ .member = .{ .lhs = lhs, .index = @intCast(i) } }, }); const ret = p.fieldAccessExtra(inner, f.ty, field_name, false, offset_bits); offset_bits.* += f.layout.offset_bits; return ret; } if (field_name == f.name) { offset_bits.* = f.layout.offset_bits; return Result{ .ty = f.ty, .node = try p.addNode(.{ .tag = if (is_arrow) .member_access_ptr_expr else .member_access_expr, .ty = f.ty, .data = .{ .member = .{ .lhs = lhs, .index = @intCast(i) } }, }), }; } } // We already checked that this container has a field by the name. unreachable; } fn checkVaStartArg(p: *Parser, builtin_tok: TokenIndex, first_after: TokenIndex, param_tok: TokenIndex, arg: *Result, idx: u32) !void { assert(idx != 0); if (idx > 1) { try p.errTok(.closing_paren, first_after); return error.ParsingFailed; } var func_ty = p.func.ty orelse { try p.errTok(.va_start_not_in_func, builtin_tok); return; }; const func_params = func_ty.params(); if (func_ty.specifier != .var_args_func or func_params.len == 0) { return p.errTok(.va_start_fixed_args, builtin_tok); } const last_param_name = func_params[func_params.len - 1].name; const decl_ref = p.getNode(arg.node, .decl_ref_expr); if (decl_ref == null or last_param_name != try p.comp.intern(p.tokSlice(p.nodes.items(.data)[@intFromEnum(decl_ref.?)].decl_ref))) { try p.errTok(.va_start_not_last_param, param_tok); } } fn checkComplexArg(p: *Parser, builtin_tok: TokenIndex, first_after: TokenIndex, param_tok: TokenIndex, arg: *Result, idx: u32) !void { _ = builtin_tok; _ = first_after; if (idx <= 1 and !arg.ty.isFloat()) { try p.errStr(.not_floating_type, param_tok, try p.typeStr(arg.ty)); } else if (idx == 1) { const prev_idx = p.list_buf.items[p.list_buf.items.len - 1]; const prev_ty = p.nodes.items(.ty)[@intFromEnum(prev_idx)]; if (!prev_ty.eql(arg.ty, p.comp, false)) { try p.errStr(.argument_types_differ, param_tok, try p.typePairStrExtra(prev_ty, " vs ", arg.ty)); } } } fn checkVariableBuiltinArgument(p: *Parser, builtin_tok: TokenIndex, first_after: TokenIndex, param_tok: TokenIndex, arg: *Result, arg_idx: u32, tag: BuiltinFunction.Tag) !void { switch (tag) { .__builtin_va_start, .__va_start, .va_start => return p.checkVaStartArg(builtin_tok, first_after, param_tok, arg, arg_idx), else => {}, } } fn callExpr(p: *Parser, lhs: Result) Error!Result { const l_paren = p.tok_i; p.tok_i += 1; const ty = lhs.ty.isCallable() orelse { try p.errStr(.not_callable, l_paren, try p.typeStr(lhs.ty)); return error.ParsingFailed; }; const params = ty.params(); var func = lhs; try func.lvalConversion(p); const list_buf_top = p.list_buf.items.len; defer p.list_buf.items.len = list_buf_top; try p.list_buf.append(func.node); var arg_count: u32 = 0; var first_after = l_paren; const call_expr = CallExpr.init(p, lhs.node, func.node); while (p.eatToken(.r_paren) == null) { const param_tok = p.tok_i; if (arg_count == params.len) first_after = p.tok_i; var arg = try p.assignExpr(); try arg.expect(p); if (call_expr.shouldPerformLvalConversion(arg_count)) { try arg.lvalConversion(p); } if (arg.ty.hasIncompleteSize() and !arg.ty.is(.void)) return error.ParsingFailed; if (arg_count >= params.len) { if (call_expr.shouldPromoteVarArg(arg_count)) { if (arg.ty.isInt()) try arg.intCast(p, arg.ty.integerPromotion(p.comp), param_tok); if (arg.ty.is(.float)) try arg.floatCast(p, .{ .specifier = .double }); } try call_expr.checkVarArg(p, first_after, param_tok, &arg, arg_count); try arg.saveValue(p); try p.list_buf.append(arg.node); arg_count += 1; _ = p.eatToken(.comma) orelse { try p.expectClosing(l_paren, .r_paren); break; }; continue; } const p_ty = params[arg_count].ty; if (call_expr.shouldCoerceArg(arg_count)) { try arg.coerce(p, p_ty, param_tok, .{ .arg = params[arg_count].name_tok }); } try arg.saveValue(p); try p.list_buf.append(arg.node); arg_count += 1; _ = p.eatToken(.comma) orelse { try p.expectClosing(l_paren, .r_paren); break; }; } const actual: u32 = @intCast(arg_count); const extra = Diagnostics.Message.Extra{ .arguments = .{ .expected = @intCast(params.len), .actual = actual, } }; if (call_expr.paramCountOverride()) |expected| { if (expected != actual) { try p.errExtra(.expected_arguments, first_after, .{ .arguments = .{ .expected = expected, .actual = actual } }); } } else if (ty.is(.func) and params.len != arg_count) { try p.errExtra(.expected_arguments, first_after, extra); } else if (ty.is(.old_style_func) and params.len != arg_count) { try p.errExtra(.expected_arguments_old, first_after, extra); } else if (ty.is(.var_args_func) and arg_count < params.len) { try p.errExtra(.expected_at_least_arguments, first_after, extra); } return call_expr.finish(p, ty, list_buf_top, arg_count); } fn checkArrayBounds(p: *Parser, index: Result, array: Result, tok: TokenIndex) !void { if (index.val.tag == .unavailable) return; const array_len = array.ty.arrayLen() orelse return; if (array_len == 0) return; if (array_len == 1) { if (p.getNode(array.node, .member_access_expr) orelse p.getNode(array.node, .member_access_ptr_expr)) |node| { const data = p.nodes.items(.data)[@intFromEnum(node)]; var lhs = p.nodes.items(.ty)[@intFromEnum(data.member.lhs)]; if (lhs.get(.pointer)) |ptr| { lhs = ptr.data.sub_type.*; } if (lhs.is(.@"struct")) { const record = lhs.getRecord().?; if (data.member.index + 1 == record.fields.len) { if (!index.val.isZero()) { try p.errExtra(.old_style_flexible_struct, tok, .{ .unsigned = index.val.data.int, }); } return; } } } } const len = Value.int(array_len); if (index.ty.isUnsignedInt(p.comp)) { if (index.val.compare(.gte, len, p.comp.types.size, p.comp)) try p.errExtra(.array_after, tok, .{ .unsigned = index.val.data.int }); } else { if (index.val.compare(.lt, Value.int(0), index.ty, p.comp)) { try p.errExtra(.array_before, tok, .{ .signed = index.val.signExtend(index.ty, p.comp), }); } else if (index.val.compare(.gte, len, p.comp.types.size, p.comp)) { try p.errExtra(.array_after, tok, .{ .unsigned = index.val.data.int }); } } } /// primaryExpr /// : IDENTIFIER /// | keyword_true /// | keyword_false /// | keyword_nullptr /// | INTEGER_LITERAL /// | FLOAT_LITERAL /// | IMAGINARY_LITERAL /// | CHAR_LITERAL /// | STRING_LITERAL /// | '(' expr ')' /// | genericSelection fn primaryExpr(p: *Parser) Error!Result { if (p.eatToken(.l_paren)) |l_paren| { var e = try p.expr(); try e.expect(p); try p.expectClosing(l_paren, .r_paren); try e.un(p, .paren_expr); return e; } switch (p.tok_ids[p.tok_i]) { .identifier, .extended_identifier => { const name_tok = p.expectIdentifier() catch unreachable; const name = p.tokSlice(name_tok); const interned_name = try p.comp.intern(name); if (p.syms.findSymbol(interned_name)) |sym| { try p.checkDeprecatedUnavailable(sym.ty, name_tok, sym.tok); if (sym.kind == .constexpr) { return Result{ .val = sym.val, .ty = sym.ty, .node = try p.addNode(.{ .tag = .decl_ref_expr, .ty = sym.ty, .data = .{ .decl_ref = name_tok }, }), }; } if (sym.val.tag == .int) { switch (p.const_decl_folding) { .gnu_folding_extension => try p.errTok(.const_decl_folded, name_tok), .gnu_vla_folding_extension => try p.errTok(.const_decl_folded_vla, name_tok), else => {}, } } return Result{ .val = if (p.const_decl_folding == .no_const_decl_folding and sym.kind != .enumeration) Value{} else sym.val, .ty = sym.ty, .node = try p.addNode(.{ .tag = if (sym.kind == .enumeration) .enumeration_ref else .decl_ref_expr, .ty = sym.ty, .data = .{ .decl_ref = name_tok }, }), }; } if (try p.comp.builtins.getOrCreate(p.comp, name, p.arena)) |some| { for (p.tok_ids[p.tok_i..]) |id| switch (id) { .r_paren => {}, // closing grouped expr .l_paren => break, // beginning of a call else => { try p.errTok(.builtin_must_be_called, name_tok); return error.ParsingFailed; }, }; if (some.builtin.properties.header != .none) { try p.errStr(.implicit_builtin, name_tok, name); try p.errExtra(.implicit_builtin_header_note, name_tok, .{ .builtin_with_header = .{ .builtin = some.builtin.tag, .header = some.builtin.properties.header, } }); } return Result{ .ty = some.ty, .node = try p.addNode(.{ .tag = .builtin_call_expr_one, .ty = some.ty, .data = .{ .decl = .{ .name = name_tok, .node = .none } }, }), }; } if (p.tok_ids[p.tok_i] == .l_paren) { // allow implicitly declaring functions before C99 like `puts("foo")` if (mem.startsWith(u8, name, "__builtin_")) try p.errStr(.unknown_builtin, name_tok, name) else try p.errStr(.implicit_func_decl, name_tok, name); const func_ty = try p.arena.create(Type.Func); func_ty.* = .{ .return_type = .{ .specifier = .int }, .params = &.{} }; const ty: Type = .{ .specifier = .old_style_func, .data = .{ .func = func_ty } }; const node = try p.addNode(.{ .ty = ty, .tag = .fn_proto, .data = .{ .decl = .{ .name = name_tok } }, }); try p.decl_buf.append(node); try p.syms.declareSymbol(p, interned_name, ty, name_tok, node); return Result{ .ty = ty, .node = try p.addNode(.{ .tag = .decl_ref_expr, .ty = ty, .data = .{ .decl_ref = name_tok }, }), }; } try p.errStr(.undeclared_identifier, name_tok, p.tokSlice(name_tok)); return error.ParsingFailed; }, .keyword_true, .keyword_false => |id| { p.tok_i += 1; const numeric_value = @intFromBool(id == .keyword_true); const res = Result{ .val = Value.int(numeric_value), .ty = .{ .specifier = .bool }, .node = try p.addNode(.{ .tag = .bool_literal, .ty = .{ .specifier = .bool }, .data = .{ .int = numeric_value }, }), }; std.debug.assert(!p.in_macro); // Should have been replaced with .one / .zero try p.value_map.put(res.node, res.val); return res; }, .keyword_nullptr => { defer p.tok_i += 1; try p.errStr(.pre_c2x_compat, p.tok_i, "'nullptr'"); return Result{ .val = .{ .tag = .nullptr_t }, .ty = .{ .specifier = .nullptr_t }, .node = try p.addNode(.{ .tag = .nullptr_literal, .ty = .{ .specifier = .nullptr_t }, .data = undefined, }), }; }, .macro_func, .macro_function => { defer p.tok_i += 1; var ty: Type = undefined; var tok = p.tok_i; if (p.func.ident) |some| { ty = some.ty; tok = p.nodes.items(.data)[@intFromEnum(some.node)].decl.name; } else if (p.func.ty) |_| { const start: u32 = @intCast(p.retained_strings.items.len); try p.retained_strings.appendSlice(p.tokSlice(p.func.name)); try p.retained_strings.append(0); const predef = try p.makePredefinedIdentifier(start); ty = predef.ty; p.func.ident = predef; } else { const start: u32 = @intCast(p.retained_strings.items.len); try p.retained_strings.append(0); const predef = try p.makePredefinedIdentifier(start); ty = predef.ty; p.func.ident = predef; try p.decl_buf.append(predef.node); } if (p.func.ty == null) try p.err(.predefined_top_level); return Result{ .ty = ty, .node = try p.addNode(.{ .tag = .decl_ref_expr, .ty = ty, .data = .{ .decl_ref = tok }, }), }; }, .macro_pretty_func => { defer p.tok_i += 1; var ty: Type = undefined; if (p.func.pretty_ident) |some| { ty = some.ty; } else if (p.func.ty) |func_ty| { const mapper = p.comp.string_interner.getSlowTypeMapper(); const start: u32 = @intCast(p.retained_strings.items.len); try Type.printNamed(func_ty, p.tokSlice(p.func.name), mapper, p.comp.langopts, p.retained_strings.writer()); try p.retained_strings.append(0); const predef = try p.makePredefinedIdentifier(start); ty = predef.ty; p.func.pretty_ident = predef; } else { const start: u32 = @intCast(p.retained_strings.items.len); try p.retained_strings.appendSlice("top level\x00"); const predef = try p.makePredefinedIdentifier(start); ty = predef.ty; p.func.pretty_ident = predef; try p.decl_buf.append(predef.node); } if (p.func.ty == null) try p.err(.predefined_top_level); return Result{ .ty = ty, .node = try p.addNode(.{ .tag = .decl_ref_expr, .ty = ty, .data = .{ .decl_ref = p.tok_i }, }), }; }, .string_literal, .string_literal_utf_16, .string_literal_utf_8, .string_literal_utf_32, .string_literal_wide, => return p.stringLiteral(), .char_literal, .char_literal_utf_8, .char_literal_utf_16, .char_literal_utf_32, .char_literal_wide, => return p.charLiteral(), .zero => { p.tok_i += 1; var res: Result = .{ .val = Value.int(0), .ty = if (p.in_macro) p.comp.types.intmax else Type.int }; res.node = try p.addNode(.{ .tag = .int_literal, .ty = res.ty, .data = undefined }); if (!p.in_macro) try p.value_map.put(res.node, res.val); return res; }, .one => { p.tok_i += 1; var res: Result = .{ .val = Value.int(1), .ty = if (p.in_macro) p.comp.types.intmax else Type.int }; res.node = try p.addNode(.{ .tag = .int_literal, .ty = res.ty, .data = undefined }); if (!p.in_macro) try p.value_map.put(res.node, res.val); return res; }, .pp_num => return p.ppNum(), .embed_byte => { assert(!p.in_macro); const loc = p.pp.tokens.items(.loc)[p.tok_i]; p.tok_i += 1; const buf = p.comp.getSource(.generated).buf[loc.byte_offset..]; var byte: u8 = buf[0] - '0'; for (buf[1..]) |c| { if (!std.ascii.isDigit(c)) break; byte *= 10; byte += c - '0'; } var res: Result = .{ .val = Value.int(byte) }; res.node = try p.addNode(.{ .tag = .int_literal, .ty = res.ty, .data = undefined }); try p.value_map.put(res.node, res.val); return res; }, .keyword_generic => return p.genericSelection(), else => return Result{}, } } fn makePredefinedIdentifier(p: *Parser, start: u32) !Result { const end: u32 = @intCast(p.retained_strings.items.len); const elem_ty = .{ .specifier = .char, .qual = .{ .@"const" = true } }; const arr_ty = try p.arena.create(Type.Array); arr_ty.* = .{ .elem = elem_ty, .len = end - start }; const ty: Type = .{ .specifier = .array, .data = .{ .array = arr_ty } }; const val = Value.bytes(start, end); const str_lit = try p.addNode(.{ .tag = .string_literal_expr, .ty = ty, .data = undefined }); if (!p.in_macro) try p.value_map.put(str_lit, val); return Result{ .ty = ty, .node = try p.addNode(.{ .tag = .implicit_static_var, .ty = ty, .data = .{ .decl = .{ .name = p.tok_i, .node = str_lit } }, }) }; } fn stringLiteral(p: *Parser) Error!Result { var start = p.tok_i; // use 1 for wchar_t var width: ?u8 = null; var is_u8_literal = false; while (true) { switch (p.tok_ids[p.tok_i]) { .string_literal => {}, .string_literal_utf_16 => if (width) |some| { if (some != 16) try p.err(.unsupported_str_cat); } else { width = 16; }, .string_literal_utf_8 => { is_u8_literal = true; if (width) |some| { if (some != 8) try p.err(.unsupported_str_cat); } else { width = 8; } }, .string_literal_utf_32 => if (width) |some| { if (some != 32) try p.err(.unsupported_str_cat); } else { width = 32; }, .string_literal_wide => if (width) |some| { if (some != 1) try p.err(.unsupported_str_cat); } else { width = 1; }, else => break, } p.tok_i += 1; } if (width == null) width = 8; if (width.? != 8) return p.todo("unicode string literals"); const string_start = p.retained_strings.items.len; while (start < p.tok_i) : (start += 1) { var slice = p.tokSlice(start); slice = slice[0 .. slice.len - 1]; var i = mem.indexOf(u8, slice, "\"").? + 1; try p.retained_strings.ensureUnusedCapacity(slice.len); while (i < slice.len) : (i += 1) { switch (slice[i]) { '\\' => { i += 1; switch (slice[i]) { '\n' => i += 1, '\r' => i += 2, '\'', '\"', '\\', '?' => |c| p.retained_strings.appendAssumeCapacity(c), 'n' => p.retained_strings.appendAssumeCapacity('\n'), 'r' => p.retained_strings.appendAssumeCapacity('\r'), 't' => p.retained_strings.appendAssumeCapacity('\t'), 'a' => p.retained_strings.appendAssumeCapacity(0x07), 'b' => p.retained_strings.appendAssumeCapacity(0x08), 'e' => { try p.errExtra(.non_standard_escape_char, start, .{ .invalid_escape = .{ .char = 'e', .offset = @intCast(i) } }); p.retained_strings.appendAssumeCapacity(0x1B); }, 'f' => p.retained_strings.appendAssumeCapacity(0x0C), 'v' => p.retained_strings.appendAssumeCapacity(0x0B), 'x' => p.retained_strings.appendAssumeCapacity(try p.parseNumberEscape(start, 16, slice, &i)), '0'...'7' => p.retained_strings.appendAssumeCapacity(try p.parseNumberEscape(start, 8, slice, &i)), 'u' => try p.parseUnicodeEscape(start, 4, slice, &i), 'U' => try p.parseUnicodeEscape(start, 8, slice, &i), else => unreachable, } }, else => |c| p.retained_strings.appendAssumeCapacity(c), } } } try p.retained_strings.append(0); const slice = p.retained_strings.items[string_start..]; const arr_ty = try p.arena.create(Type.Array); const specifier: Type.Specifier = if (is_u8_literal and p.comp.langopts.hasChar8_T()) .uchar else .char; arr_ty.* = .{ .elem = .{ .specifier = specifier }, .len = slice.len }; var res: Result = .{ .ty = .{ .specifier = .array, .data = .{ .array = arr_ty }, }, .val = Value.bytes(@intCast(string_start), @intCast(p.retained_strings.items.len)), }; res.node = try p.addNode(.{ .tag = .string_literal_expr, .ty = res.ty, .data = undefined }); if (!p.in_macro) try p.value_map.put(res.node, res.val); return res; } fn parseNumberEscape(p: *Parser, tok: TokenIndex, base: u8, slice: []const u8, i: *usize) !u8 { if (base == 16) i.* += 1; // skip x var char: u8 = 0; var reported = false; while (i.* < slice.len) : (i.* += 1) { const val = std.fmt.charToDigit(slice[i.*], base) catch break; // validated by Tokenizer const product, const overflowed = @mulWithOverflow(char, base); if (overflowed != 0 and !reported) { try p.errExtra(.escape_sequence_overflow, tok, .{ .unsigned = i.* }); reported = true; } char = product + val; } i.* -= 1; return char; } fn parseUnicodeEscape(p: *Parser, tok: TokenIndex, count: u8, slice: []const u8, i: *usize) !void { const c = std.fmt.parseInt(u21, slice[i.* + 1 ..][0..count], 16) catch 0x110000; // count validated by tokenizer i.* += count + 1; if (!std.unicode.utf8ValidCodepoint(c) or (c < 0xa0 and c != '$' and c != '@' and c != '`')) { try p.errExtra(.invalid_universal_character, tok, .{ .unsigned = i.* - count - 2 }); return; } var buf: [4]u8 = undefined; const to_write = std.unicode.utf8Encode(c, &buf) catch unreachable; // validated above p.retained_strings.appendSliceAssumeCapacity(buf[0..to_write]); } fn charLiteral(p: *Parser) Error!Result { defer p.tok_i += 1; const tok_id = p.tok_ids[p.tok_i]; const char_kind = CharLiteral.Kind.classify(tok_id); var val: u32 = 0; const slice = char_kind.contentSlice(p.tokSlice(p.tok_i)); if (slice.len == 1 and std.ascii.isASCII(slice[0])) { // fast path: single unescaped ASCII char val = slice[0]; } else { var char_literal_parser = CharLiteral.Parser.init(slice, char_kind, p.comp); const max_chars_expected = 4; var stack_fallback = std.heap.stackFallback(max_chars_expected * @sizeOf(u32), p.comp.gpa); var chars = std.ArrayList(u32).initCapacity(stack_fallback.get(), max_chars_expected) catch unreachable; // stack allocation already succeeded defer chars.deinit(); while (char_literal_parser.next()) |item| switch (item) { .value => |c| try chars.append(c), .improperly_encoded => |s| { try chars.ensureUnusedCapacity(s.len); for (s) |c| chars.appendAssumeCapacity(c); }, .utf8_text => |view| { var it = view.iterator(); var max_codepoint: u21 = 0; try chars.ensureUnusedCapacity(view.bytes.len); while (it.nextCodepoint()) |c| { max_codepoint = @max(max_codepoint, c); chars.appendAssumeCapacity(c); } if (max_codepoint > char_kind.maxCodepoint(p.comp)) { char_literal_parser.err(.char_too_large, .{ .none = {} }); } }, }; const is_multichar = chars.items.len > 1; if (is_multichar) { if (char_kind == .char and chars.items.len == 4) { char_literal_parser.warn(.four_char_char_literal, .{ .none = {} }); } else if (char_kind == .char) { char_literal_parser.warn(.multichar_literal_warning, .{ .none = {} }); } else { const kind = switch (char_kind) { .wide => "wide", .utf_8, .utf_16, .utf_32 => "Unicode", else => unreachable, }; char_literal_parser.err(.invalid_multichar_literal, .{ .str = kind }); } } var multichar_overflow = false; if (char_kind == .char and is_multichar) { for (chars.items) |item| { val, const overflowed = @shlWithOverflow(val, 8); multichar_overflow = multichar_overflow or overflowed != 0; val += @as(u8, @truncate(item)); } } else if (chars.items.len > 0) { val = chars.items[chars.items.len - 1]; } if (multichar_overflow) { char_literal_parser.err(.char_lit_too_wide, .{ .none = {} }); } for (char_literal_parser.errors.constSlice()) |item| { try p.errExtra(item.tag, p.tok_i, item.extra); } } const ty = char_kind.charLiteralType(p.comp); // This is the type the literal will have if we're in a macro; macros always operate on intmax_t/uintmax_t values const macro_ty = if (ty.isUnsignedInt(p.comp) or (char_kind == .char and p.comp.getCharSignedness() == .unsigned)) p.comp.types.intmax.makeIntegerUnsigned() else p.comp.types.intmax; var res = Result{ .ty = if (p.in_macro) macro_ty else ty, .val = Value.int(val), .node = try p.addNode(.{ .tag = .char_literal, .ty = ty, .data = undefined }), }; if (!p.in_macro) try p.value_map.put(res.node, res.val); return res; } fn parseFloat(p: *Parser, buf: []const u8, suffix: NumberSuffix) !Result { switch (suffix) { .L => return p.todo("long double literals"), .IL => { try p.err(.gnu_imaginary_constant); return p.todo("long double imaginary literals"); }, .None, .I, .F, .IF, .F16 => { const ty = Type{ .specifier = switch (suffix) { .None, .I => .double, .F, .IF => .float, .F16 => .float16, else => unreachable, } }; const d_val = std.fmt.parseFloat(f64, buf) catch |er| switch (er) { error.InvalidCharacter => return p.todo("c2x digit separators in floats"), else => unreachable, }; const tag: Tree.Tag = switch (suffix) { .None, .I => .double_literal, .F, .IF => .float_literal, .F16 => .float16_literal, else => unreachable, }; var res = Result{ .ty = ty, .node = try p.addNode(.{ .tag = tag, .ty = ty, .data = undefined }), .val = Value.float(d_val), }; if (suffix.isImaginary()) { try p.err(.gnu_imaginary_constant); res.ty = .{ .specifier = switch (suffix) { .I => .complex_double, .IF => .complex_float, else => unreachable, } }; res.val.tag = .unavailable; try res.un(p, .imaginary_literal); } return res; }, else => unreachable, } } fn getIntegerPart(p: *Parser, buf: []const u8, prefix: NumberPrefix, tok_i: TokenIndex) ![]const u8 { if (buf[0] == '.') return ""; if (!prefix.digitAllowed(buf[0])) { switch (prefix) { .binary => try p.errExtra(.invalid_binary_digit, tok_i, .{ .ascii = @intCast(buf[0]) }), .octal => try p.errExtra(.invalid_octal_digit, tok_i, .{ .ascii = @intCast(buf[0]) }), .hex => try p.errStr(.invalid_int_suffix, tok_i, buf), .decimal => unreachable, } return error.ParsingFailed; } for (buf, 0..) |c, idx| { if (idx == 0) continue; switch (c) { '.' => return buf[0..idx], 'p', 'P' => return if (prefix == .hex) buf[0..idx] else { try p.errStr(.invalid_int_suffix, tok_i, buf[idx..]); return error.ParsingFailed; }, 'e', 'E' => { switch (prefix) { .hex => continue, .decimal => return buf[0..idx], .binary => try p.errExtra(.invalid_binary_digit, tok_i, .{ .ascii = @intCast(c) }), .octal => try p.errExtra(.invalid_octal_digit, tok_i, .{ .ascii = @intCast(c) }), } return error.ParsingFailed; }, '0'...'9', 'a'...'d', 'A'...'D', 'f', 'F' => { if (!prefix.digitAllowed(c)) { switch (prefix) { .binary => try p.errExtra(.invalid_binary_digit, tok_i, .{ .ascii = @intCast(c) }), .octal => try p.errExtra(.invalid_octal_digit, tok_i, .{ .ascii = @intCast(c) }), .decimal, .hex => try p.errStr(.invalid_int_suffix, tok_i, buf[idx..]), } return error.ParsingFailed; } }, '\'' => {}, else => return buf[0..idx], } } return buf; } fn fixedSizeInt(p: *Parser, base: u8, buf: []const u8, suffix: NumberSuffix, tok_i: TokenIndex) !Result { var val: u64 = 0; var overflow = false; for (buf) |c| { const digit: u64 = switch (c) { '0'...'9' => c - '0', 'A'...'Z' => c - 'A' + 10, 'a'...'z' => c - 'a' + 10, '\'' => continue, else => unreachable, }; if (val != 0) { const product, const overflowed = @mulWithOverflow(val, base); if (overflowed != 0) { overflow = true; } val = product; } const sum, const overflowed = @addWithOverflow(val, digit); if (overflowed != 0) overflow = true; val = sum; } if (overflow) { try p.errTok(.int_literal_too_big, tok_i); var res: Result = .{ .ty = .{ .specifier = .ulong_long }, .val = Value.int(val) }; res.node = try p.addNode(.{ .tag = .int_literal, .ty = res.ty, .data = undefined }); if (!p.in_macro) try p.value_map.put(res.node, res.val); return res; } if (suffix.isSignedInteger()) { if (val > p.comp.types.intmax.maxInt(p.comp)) { try p.errTok(.implicitly_unsigned_literal, tok_i); } } return if (base == 10) switch (suffix) { .None, .I => p.castInt(val, &.{ .int, .long, .long_long }), .U, .IU => p.castInt(val, &.{ .uint, .ulong, .ulong_long }), .L, .IL => p.castInt(val, &.{ .long, .long_long }), .UL, .IUL => p.castInt(val, &.{ .ulong, .ulong_long }), .LL, .ILL => p.castInt(val, &.{.long_long}), .ULL, .IULL => p.castInt(val, &.{.ulong_long}), else => unreachable, } else switch (suffix) { .None, .I => p.castInt(val, &.{ .int, .uint, .long, .ulong, .long_long, .ulong_long }), .U, .IU => p.castInt(val, &.{ .uint, .ulong, .ulong_long }), .L, .IL => p.castInt(val, &.{ .long, .ulong, .long_long, .ulong_long }), .UL, .IUL => p.castInt(val, &.{ .ulong, .ulong_long }), .LL, .ILL => p.castInt(val, &.{ .long_long, .ulong_long }), .ULL, .IULL => p.castInt(val, &.{.ulong_long}), else => unreachable, }; } fn parseInt(p: *Parser, prefix: NumberPrefix, buf: []const u8, suffix: NumberSuffix, tok_i: TokenIndex) !Result { if (prefix == .binary) { try p.errTok(.binary_integer_literal, tok_i); } const base = @intFromEnum(prefix); var res = if (suffix.isBitInt()) try p.bitInt(base, buf, suffix, tok_i) else try p.fixedSizeInt(base, buf, suffix, tok_i); if (suffix.isImaginary()) { try p.errTok(.gnu_imaginary_constant, tok_i); res.ty = res.ty.makeComplex(); res.val.tag = .unavailable; try res.un(p, .imaginary_literal); } return res; } fn bitInt(p: *Parser, base: u8, buf: []const u8, suffix: NumberSuffix, tok_i: TokenIndex) Error!Result { try p.errStr(.pre_c2x_compat, tok_i, "'_BitInt' suffix for literals"); try p.errTok(.bitint_suffix, tok_i); var managed = try big.int.Managed.init(p.gpa); defer managed.deinit(); managed.setString(base, buf) catch |e| switch (e) { error.InvalidBase => unreachable, // `base` is one of 2, 8, 10, 16 error.InvalidCharacter => unreachable, // digits validated by Tokenizer else => |er| return er, }; const c = managed.toConst(); const bits_needed: std.math.IntFittingRange(0, Compilation.bit_int_max_bits) = blk: { // Literal `0` requires at least 1 bit const count = @max(1, c.bitCountTwosComp()); // The wb suffix results in a _BitInt that includes space for the sign bit even if the // value of the constant is positive or was specified in hexadecimal or octal notation. const sign_bits = @intFromBool(suffix.isSignedInteger()); const bits_needed = count + sign_bits; if (bits_needed > Compilation.bit_int_max_bits) { const specifier: Type.Builder.Specifier = switch (suffix) { .WB => .{ .bit_int = 0 }, .UWB => .{ .ubit_int = 0 }, .IWB => .{ .complex_bit_int = 0 }, .IUWB => .{ .complex_ubit_int = 0 }, else => unreachable, }; try p.errStr(.bit_int_too_big, tok_i, specifier.str(p.comp.langopts).?); return error.ParsingFailed; } if (bits_needed > 64) { return p.todo("_BitInt constants > 64 bits"); } break :blk @intCast(bits_needed); }; const val = c.to(u64) catch |e| switch (e) { error.NegativeIntoUnsigned => unreachable, // unary minus parsed elsewhere; we only see positive integers error.TargetTooSmall => unreachable, // Validated above but Todo: handle larger _BitInt }; var res: Result = .{ .val = Value.int(val), .ty = .{ .specifier = .bit_int, .data = .{ .int = .{ .bits = bits_needed, .signedness = suffix.signedness() } }, }, }; res.node = try p.addNode(.{ .tag = .int_literal, .ty = res.ty, .data = .{ .int = val } }); if (!p.in_macro) try p.value_map.put(res.node, res.val); return res; } fn getFracPart(p: *Parser, buf: []const u8, prefix: NumberPrefix, tok_i: TokenIndex) ![]const u8 { if (buf.len == 0 or buf[0] != '.') return ""; assert(prefix != .octal); if (prefix == .binary) { try p.errStr(.invalid_int_suffix, tok_i, buf); return error.ParsingFailed; } for (buf, 0..) |c, idx| { if (idx == 0) continue; if (c == '\'') continue; if (!prefix.digitAllowed(c)) return buf[0..idx]; } return buf; } fn getExponent(p: *Parser, buf: []const u8, prefix: NumberPrefix, tok_i: TokenIndex) ![]const u8 { if (buf.len == 0) return ""; switch (buf[0]) { 'e', 'E' => assert(prefix == .decimal), 'p', 'P' => if (prefix != .hex) { try p.errStr(.invalid_float_suffix, tok_i, buf); return error.ParsingFailed; }, else => return "", } const end = for (buf, 0..) |c, idx| { if (idx == 0) continue; if (idx == 1 and (c == '+' or c == '-')) continue; switch (c) { '0'...'9' => {}, '\'' => continue, else => break idx, } } else buf.len; const exponent = buf[0..end]; if (std.mem.indexOfAny(u8, exponent, "0123456789") == null) { try p.errTok(.exponent_has_no_digits, tok_i); return error.ParsingFailed; } return exponent; } /// Using an explicit `tok_i` parameter instead of `p.tok_i` makes it easier /// to parse numbers in pragma handlers. pub fn parseNumberToken(p: *Parser, tok_i: TokenIndex) !Result { const buf = p.tokSlice(tok_i); const prefix = NumberPrefix.fromString(buf); const after_prefix = buf[prefix.stringLen()..]; const int_part = try p.getIntegerPart(after_prefix, prefix, tok_i); const after_int = after_prefix[int_part.len..]; const frac = try p.getFracPart(after_int, prefix, tok_i); const after_frac = after_int[frac.len..]; const exponent = try p.getExponent(after_frac, prefix, tok_i); const suffix_str = after_frac[exponent.len..]; const is_float = (exponent.len > 0 or frac.len > 0); const suffix = NumberSuffix.fromString(suffix_str, if (is_float) .float else .int) orelse { if (is_float) { try p.errStr(.invalid_float_suffix, tok_i, suffix_str); } else { try p.errStr(.invalid_int_suffix, tok_i, suffix_str); } return error.ParsingFailed; }; if (is_float) { assert(prefix == .hex or prefix == .decimal); if (prefix == .hex and exponent.len == 0) { try p.errTok(.hex_floating_constant_requires_exponent, tok_i); return error.ParsingFailed; } const number = buf[0 .. buf.len - suffix_str.len]; return p.parseFloat(number, suffix); } else { return p.parseInt(prefix, int_part, suffix, tok_i); } } fn ppNum(p: *Parser) Error!Result { defer p.tok_i += 1; var res = try p.parseNumberToken(p.tok_i); if (p.in_macro) { if (res.ty.isFloat() or !res.ty.isReal()) { try p.errTok(.float_literal_in_pp_expr, p.tok_i); return error.ParsingFailed; } res.ty = if (res.ty.isUnsignedInt(p.comp)) p.comp.types.intmax.makeIntegerUnsigned() else p.comp.types.intmax; } else { try p.value_map.put(res.node, res.val); } return res; } fn castInt(p: *Parser, val: u64, specs: []const Type.Specifier) Error!Result { var res: Result = .{ .val = Value.int(val) }; for (specs) |spec| { const ty = Type{ .specifier = spec }; const unsigned = ty.isUnsignedInt(p.comp); const size = ty.sizeof(p.comp).?; res.ty = ty; if (unsigned) { switch (size) { 2 => if (val <= std.math.maxInt(u16)) break, 4 => if (val <= std.math.maxInt(u32)) break, 8 => if (val <= std.math.maxInt(u64)) break, else => unreachable, } } else { switch (size) { 2 => if (val <= std.math.maxInt(i16)) break, 4 => if (val <= std.math.maxInt(i32)) break, 8 => if (val <= std.math.maxInt(i64)) break, else => unreachable, } } } else { res.ty = .{ .specifier = .ulong_long }; } res.node = try p.addNode(.{ .tag = .int_literal, .ty = res.ty, .data = .{ .int = val } }); if (!p.in_macro) try p.value_map.put(res.node, res.val); return res; } /// Run a parser function but do not evaluate the result fn parseNoEval(p: *Parser, comptime func: fn (*Parser) Error!Result) Error!Result { const no_eval = p.no_eval; defer p.no_eval = no_eval; p.no_eval = true; const parsed = try func(p); try parsed.expect(p); return parsed; } /// genericSelection : keyword_generic '(' assignExpr ',' genericAssoc (',' genericAssoc)* ')' /// genericAssoc /// : typeName ':' assignExpr /// | keyword_default ':' assignExpr fn genericSelection(p: *Parser) Error!Result { p.tok_i += 1; const l_paren = try p.expectToken(.l_paren); const controlling_tok = p.tok_i; const controlling = try p.parseNoEval(assignExpr); _ = try p.expectToken(.comma); var controlling_ty = controlling.ty; if (controlling_ty.isArray()) controlling_ty.decayArray(); const list_buf_top = p.list_buf.items.len; defer p.list_buf.items.len = list_buf_top; try p.list_buf.append(controlling.node); // Use decl_buf to store the token indexes of previous cases const decl_buf_top = p.decl_buf.items.len; defer p.decl_buf.items.len = decl_buf_top; var default_tok: ?TokenIndex = null; var default: Result = undefined; var chosen_tok: TokenIndex = undefined; var chosen: Result = .{}; while (true) { const start = p.tok_i; if (try p.typeName()) |ty| blk: { if (ty.isArray()) { try p.errTok(.generic_array_type, start); } else if (ty.isFunc()) { try p.errTok(.generic_func_type, start); } else if (ty.anyQual()) { try p.errTok(.generic_qual_type, start); } _ = try p.expectToken(.colon); const node = try p.assignExpr(); try node.expect(p); if (ty.eql(controlling_ty, p.comp, false)) { if (chosen.node == .none) { chosen = node; chosen_tok = start; break :blk; } try p.errStr(.generic_duplicate, start, try p.typeStr(ty)); try p.errStr(.generic_duplicate_here, chosen_tok, try p.typeStr(ty)); } for (p.list_buf.items[list_buf_top + 1 ..], p.decl_buf.items[decl_buf_top..]) |item, prev_tok| { const prev_ty = p.nodes.items(.ty)[@intFromEnum(item)]; if (prev_ty.eql(ty, p.comp, true)) { try p.errStr(.generic_duplicate, start, try p.typeStr(ty)); try p.errStr(.generic_duplicate_here, @intFromEnum(prev_tok), try p.typeStr(ty)); } } try p.list_buf.append(try p.addNode(.{ .tag = .generic_association_expr, .ty = ty, .data = .{ .un = node.node }, })); try p.decl_buf.append(@enumFromInt(start)); } else if (p.eatToken(.keyword_default)) |tok| { if (default_tok) |prev| { try p.errTok(.generic_duplicate_default, tok); try p.errTok(.previous_case, prev); } default_tok = tok; _ = try p.expectToken(.colon); default = try p.assignExpr(); try default.expect(p); } else { if (p.list_buf.items.len == list_buf_top + 1) { try p.err(.expected_type); return error.ParsingFailed; } break; } if (p.eatToken(.comma) == null) break; } try p.expectClosing(l_paren, .r_paren); if (chosen.node == .none) { if (default_tok != null) { try p.list_buf.insert(list_buf_top + 1, try p.addNode(.{ .tag = .generic_default_expr, .data = .{ .un = default.node }, })); chosen = default; } else { try p.errStr(.generic_no_match, controlling_tok, try p.typeStr(controlling_ty)); return error.ParsingFailed; } } else { try p.list_buf.insert(list_buf_top + 1, try p.addNode(.{ .tag = .generic_association_expr, .data = .{ .un = chosen.node }, })); if (default_tok != null) { try p.list_buf.append(try p.addNode(.{ .tag = .generic_default_expr, .data = .{ .un = chosen.node }, })); } } var generic_node: Tree.Node = .{ .tag = .generic_expr_one, .ty = chosen.ty, .data = .{ .bin = .{ .lhs = controlling.node, .rhs = chosen.node } }, }; const associations = p.list_buf.items[list_buf_top..]; if (associations.len > 2) { // associations[0] == controlling.node generic_node.tag = .generic_expr; generic_node.data = .{ .range = try p.addList(associations) }; } chosen.node = try p.addNode(generic_node); return chosen; }