From 2929100c15bc3c1df34f03ff5cf6dd381132e5bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Wed, 11 Feb 2026 14:24:37 +0000 Subject: [PATCH] parser: reorder function definitions to match upstream Parse.zig Reorder function definitions so they follow the same order as upstream zig/lib/std/zig/Parse.zig, making cross-referencing easier. Move OperInfo and NodeContainerField typedefs to the header section, and add forward declarations for parseParamDeclList and operTable that are now needed due to the new ordering. Co-Authored-By: Claude Opus 4.6 --- parser.c | 4678 +++++++++++++++++++++++++++--------------------------- 1 file changed, 2340 insertions(+), 2338 deletions(-) diff --git a/parser.c b/parser.c index 3284335c81..7ade9c77e2 100644 --- a/parser.c +++ b/parser.c @@ -48,6 +48,19 @@ typedef struct { AstNodeIndex bit_range_end; } PtrModifiers; +typedef struct { + int8_t prec; + AstNodeTag tag; + enum { + ASSOC_LEFT, + ASSOC_NONE, + } assoc; +} OperInfo; + +typedef struct { + AstNodeIndex align_expr, value_expr; +} NodeContainerField; + static AstNodeIndex addExtra(Parser*, const AstNodeIndex*, uint32_t); static AstNodeIndex addNode(AstNodeList*, AstNodeItem); static AstNodeTag assignOpNode(TokenizerTag); @@ -76,6 +89,7 @@ static AstNodeIndex makePtrTypeNode( Parser*, AstTokenIndex, AstNodeIndex, PtrModifiers, AstNodeIndex); static AstSubRange membersToSpan(const Members, Parser*); static AstTokenIndex nextToken(Parser*); +static OperInfo operTable(TokenizerTag); static AstNodeIndex parseAddrSpace(Parser*); static AstNodeIndex parseAsmExpr(Parser*); static AstNodeIndex parseAsmInputItem(Parser*); @@ -104,6 +118,7 @@ static AstNodeIndex parseInitList(Parser*, AstNodeIndex, AstTokenIndex); static AstNodeIndex parseLabeledStatement(Parser*); static AstNodeIndex parseLinkSection(Parser*); static AstNodeIndex parseLoopStatement(Parser*); +static SmallSpan parseParamDeclList(Parser*); static void parsePayload(Parser*); static AstNodeIndex parsePrefixExpr(Parser*); static AstNodeIndex parsePrimaryExpr(Parser*); @@ -136,6 +151,15 @@ static CleanupScratch initCleanupScratch(Parser* p) { static void cleanupScratch(CleanupScratch* c) { c->scratch->len = c->old_len; } +static AstSubRange membersToSpan(const Members self, Parser* p) { + if (self.len <= 2) { + const AstNodeIndex nodes[] = { self.lhs, self.rhs }; + return listToSpan(p, nodes, self.len); + } else { + return (AstSubRange) { .start = self.lhs, .end = self.rhs }; + } +} + static AstSubRange listToSpan( Parser* p, const AstNodeIndex* list, uint32_t count) { SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, count); @@ -148,56 +172,12 @@ static AstSubRange listToSpan( }; } -static AstSubRange membersToSpan(const Members self, Parser* p) { - if (self.len <= 2) { - const AstNodeIndex nodes[] = { self.lhs, self.rhs }; - return listToSpan(p, nodes, self.len); - } else { - return (AstSubRange) { .start = self.lhs, .end = self.rhs }; - } -} - -static AstTokenIndex nextToken(Parser* p) { return p->tok_i++; } - -static AstTokenIndex eatToken(Parser* p, TokenizerTag tag) { - if (p->token_tags[p->tok_i] == tag) { - return nextToken(p); - } else { - return null_token; - } -} - -static AstTokenIndex assertToken(Parser* p, TokenizerTag tag) { - const AstTokenIndex token = nextToken(p); - if (p->token_tags[token] != tag) { - fail(p, "unexpected token"); - } - return token; -} - -static bool tokensOnSameLine( - Parser* p, AstTokenIndex tok1, AstTokenIndex tok2) { - const uint32_t start1 = p->token_starts[tok1]; - const uint32_t start2 = p->token_starts[tok2]; - for (uint32_t i = start1; i < start2; i++) { - if (p->source[i] == '\n') - return false; - } - return true; -} - -static AstTokenIndex eatDocComments(Parser* p) { - AstTokenIndex first = null_token; - AstTokenIndex tok; - while ((tok = eatToken(p, TOKEN_DOC_COMMENT)) != null_token) { - if (first == null_token) { - if (tok > 0 && tokensOnSameLine(p, tok - 1, tok)) { - fail(p, "same_line_doc_comment"); - } - first = tok; - } - } - return first; +static AstNodeIndex addNode(AstNodeList* nodes, AstNodeItem item) { + astNodeListEnsureCapacity(nodes, 1); + nodes->tags[nodes->len] = item.tag; + nodes->main_tokens[nodes->len] = item.main_token; + nodes->datas[nodes->len] = item.data; + return nodes->len++; } static AstNodeIndex setNode(Parser* p, uint32_t i, AstNodeItem item) { @@ -207,6 +187,23 @@ static AstNodeIndex setNode(Parser* p, uint32_t i, AstNodeItem item) { return i; } +static uint32_t reserveNode(Parser* p, AstNodeTag tag) { + astNodeListEnsureCapacity(&p->nodes, 1); + p->nodes.len++; + p->nodes.tags[p->nodes.len - 1] = tag; + return p->nodes.len - 1; +} + +static AstNodeIndex addExtra( + Parser* p, const AstNodeIndex* extra, uint32_t count) { + const AstNodeIndex result = p->extra_data.len; + SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, count); + memcpy(p->extra_data.arr + p->extra_data.len, extra, + count * sizeof(AstNodeIndex)); + p->extra_data.len += count; + return result; +} + static void astNodeListEnsureCapacity(AstNodeList* list, uint32_t additional) { const uint32_t new_len = list->len + additional; if (new_len <= list->cap) { @@ -223,64 +220,448 @@ static void astNodeListEnsureCapacity(AstNodeList* list, uint32_t additional) { list->cap = new_cap; } -static AstNodeIndex addNode(AstNodeList* nodes, AstNodeItem item) { - astNodeListEnsureCapacity(nodes, 1); - nodes->tags[nodes->len] = item.tag; - nodes->main_tokens[nodes->len] = item.main_token; - nodes->datas[nodes->len] = item.data; - return nodes->len++; +void parseRoot(Parser* p) { + addNode( + &p->nodes, (AstNodeItem) { .tag = AST_NODE_ROOT, .main_token = 0 }); + + Members root_members = parseContainerMembers(p); + AstSubRange root_decls = membersToSpan(root_members, p); + + if (p->token_tags[p->tok_i] != TOKEN_EOF) { + fail(p, "expected EOF"); + } + + p->nodes.datas[0].lhs = root_decls.start; + p->nodes.datas[0].rhs = root_decls.end; } -static AstNodeIndex addExtra( - Parser* p, const AstNodeIndex* extra, uint32_t count) { - const AstNodeIndex result = p->extra_data.len; - SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, count); - memcpy(p->extra_data.arr + p->extra_data.len, extra, - count * sizeof(AstNodeIndex)); - p->extra_data.len += count; - return result; +static Members parseContainerMembers(Parser* p) { + CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch))) + = initCleanupScratch(p); + while (eatToken(p, TOKEN_CONTAINER_DOC_COMMENT) != null_token) + ; + + FieldState field_state = { .tag = FIELD_STATE_NONE }; + + bool trailing = false; + while (1) { + const AstTokenIndex doc_comment = eatDocComments(p); + switch (p->token_tags[p->tok_i]) { + case TOKEN_KEYWORD_TEST: { + if (doc_comment != null_token) + fail(p, "test_doc_comment"); + const AstNodeIndex test_decl = expectTestDecl(p); + SLICE_APPEND(AstNodeIndex, &p->scratch, test_decl); + trailing = p->token_tags[p->tok_i - 1] == TOKEN_R_BRACE; + break; + } + case TOKEN_KEYWORD_USINGNAMESPACE:; + fail(p, "not implemented in parseContainerMembers"); + case TOKEN_KEYWORD_COMPTIME: + // comptime can be a container field modifier or a comptime + // block/decl. Check if it's followed by a block (comptime { ... + // }). + if (p->token_tags[p->tok_i + 1] == TOKEN_L_BRACE) { + if (doc_comment != null_token) { + fail(p, "comptime_doc_comment"); + } + const AstTokenIndex comptime_token = nextToken(p); + const AstNodeIndex block_node = parseBlock(p); + SLICE_APPEND(AstNodeIndex, &p->scratch, + addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_COMPTIME, + .main_token = comptime_token, + .data = { .lhs = block_node, .rhs = 0 }, + })); + trailing = false; + break; + } + // Otherwise it's a container field with comptime modifier + goto container_field; + case TOKEN_KEYWORD_PUB: { + p->tok_i++; + AstNodeIndex top_level_decl = expectTopLevelDecl(p); + if (top_level_decl != 0) { + if (field_state.tag == FIELD_STATE_SEEN) { + field_state.tag = FIELD_STATE_END; + field_state.payload.end = top_level_decl; + } + SLICE_APPEND(AstNodeIndex, &p->scratch, top_level_decl); + } + trailing = p->token_tags[p->tok_i - 1] == TOKEN_SEMICOLON; + break; + } + case TOKEN_KEYWORD_CONST: + case TOKEN_KEYWORD_VAR: + case TOKEN_KEYWORD_THREADLOCAL: + case TOKEN_KEYWORD_EXPORT: + case TOKEN_KEYWORD_EXTERN: + case TOKEN_KEYWORD_INLINE: + case TOKEN_KEYWORD_NOINLINE: + case TOKEN_KEYWORD_FN: { + const AstNodeIndex top_level_decl = expectTopLevelDecl(p); + if (top_level_decl != 0) { + if (field_state.tag == FIELD_STATE_SEEN) { + field_state.tag = FIELD_STATE_END; + field_state.payload.end = top_level_decl; + } + SLICE_APPEND(AstNodeIndex, &p->scratch, top_level_decl); + } + trailing = (p->token_tags[p->tok_i - 1] == TOKEN_SEMICOLON); + break; + } + case TOKEN_EOF: + case TOKEN_R_BRACE: + goto break_loop; + container_field: + default:; + // skip parseCStyleContainer + const AstNodeIndex field_node = expectContainerField(p); + switch (field_state.tag) { + case FIELD_STATE_NONE: + field_state.tag = FIELD_STATE_SEEN; + break; + case FIELD_STATE_SEEN: + break; + case FIELD_STATE_END: + fail(p, "parseContainerMembers error condition"); + } + SLICE_APPEND(AstNodeIndex, &p->scratch, field_node); + switch (p->token_tags[p->tok_i]) { + case TOKEN_COMMA: + p->tok_i++; + trailing = true; + continue; + case TOKEN_R_BRACE: + case TOKEN_EOF: + trailing = false; + goto break_loop; + default: + fail(p, "expected comma after field"); + } + } + } + +break_loop:; + + const uint32_t items_len = p->scratch.len - scratch_top.old_len; + switch (items_len) { + case 0: + return (Members) { + .len = 0, + .lhs = 0, + .rhs = 0, + .trailing = trailing, + }; + case 1: + return (Members) { + .len = 1, + .lhs = p->scratch.arr[scratch_top.old_len], + .rhs = 0, + .trailing = trailing, + }; + case 2: + return (Members) { + .len = 2, + .lhs = p->scratch.arr[scratch_top.old_len], + .rhs = p->scratch.arr[scratch_top.old_len + 1], + .trailing = trailing, + }; + default:; + const AstSubRange span + = listToSpan(p, &p->scratch.arr[scratch_top.old_len], items_len); + return (Members) { + .len = items_len, + .lhs = span.start, + .rhs = span.end, + .trailing = trailing, + }; + } } -static AstNodeIndex parseByteAlign(Parser* p) { - if (eatToken(p, TOKEN_KEYWORD_ALIGN) == null_token) +static void findNextContainerMember(Parser* p) { + uint32_t level = 0; + + while (true) { + AstTokenIndex tok = nextToken(p); + + switch (p->token_tags[tok]) { + // Any of these can start a new top level declaration + case TOKEN_KEYWORD_TEST: + case TOKEN_KEYWORD_COMPTIME: + case TOKEN_KEYWORD_PUB: + case TOKEN_KEYWORD_EXPORT: + case TOKEN_KEYWORD_EXTERN: + case TOKEN_KEYWORD_INLINE: + case TOKEN_KEYWORD_NOINLINE: + case TOKEN_KEYWORD_USINGNAMESPACE: + case TOKEN_KEYWORD_THREADLOCAL: + case TOKEN_KEYWORD_CONST: + case TOKEN_KEYWORD_VAR: + case TOKEN_KEYWORD_FN: + if (level == 0) { + p->tok_i--; + return; + } + break; + case TOKEN_IDENTIFIER: + if (p->token_tags[tok + 1] == TOKEN_COMMA && level == 0) { + p->tok_i--; + return; + } + break; + case TOKEN_COMMA: + case TOKEN_SEMICOLON: + // This decl was likely meant to end here + if (level == 0) + return; + break; + case TOKEN_L_PAREN: + case TOKEN_L_BRACKET: + case TOKEN_L_BRACE: + level++; + break; + case TOKEN_R_PAREN: + case TOKEN_R_BRACKET: + if (level != 0) + level--; + break; + case TOKEN_R_BRACE: + if (level == 0) { + // end of container, exit + p->tok_i--; + return; + } + level--; + break; + case TOKEN_EOF: + p->tok_i--; + return; + default: + break; + } + } +} + +static AstNodeIndex expectTestDecl(Parser* p) { + const AstTokenIndex test_token = assertToken(p, TOKEN_KEYWORD_TEST); + const AstTokenIndex test_name + = (p->token_tags[p->tok_i] == TOKEN_STRING_LITERAL + || p->token_tags[p->tok_i] == TOKEN_IDENTIFIER) + ? nextToken(p) + : null_token; + const AstNodeIndex body = parseBlock(p); + if (body == 0) + fail(p, "expected block after test"); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_TEST_DECL, + .main_token = test_token, + .data = { .lhs = test_name, .rhs = body }, + }); +} + +static AstNodeIndex expectTopLevelDecl(Parser* p) { + AstTokenIndex extern_export_inline_token = nextToken(p); + + switch (p->token_tags[extern_export_inline_token]) { + case TOKEN_KEYWORD_EXTERN: + eatToken(p, TOKEN_STRING_LITERAL); + break; + case TOKEN_KEYWORD_EXPORT: + case TOKEN_KEYWORD_INLINE: + case TOKEN_KEYWORD_NOINLINE: + break; + default: + p->tok_i--; + } + + AstNodeIndex fn_proto = parseFnProto(p); + if (fn_proto != 0) { + switch (p->token_tags[p->tok_i]) { + case TOKEN_SEMICOLON: + p->tok_i++; + return fn_proto; + case TOKEN_L_BRACE:; + AstNodeIndex fn_decl_index = reserveNode(p, AST_NODE_FN_DECL); + AstNodeIndex body_block = parseBlock(p); + return setNode(p, fn_decl_index, + (AstNodeItem) { + .tag = AST_NODE_FN_DECL, + .main_token = p->nodes.main_tokens[fn_proto], + .data = { .lhs = fn_proto, .rhs = body_block }, + }); + default: + fail(p, "expected semicolon or lbrace"); + } + } + + eatToken(p, TOKEN_KEYWORD_THREADLOCAL); + AstNodeIndex var_decl = parseGlobalVarDecl(p); + if (var_decl != 0) { + return var_decl; + } + + // assuming the program is correct... + fail(p, "the next token should be usingnamespace, which is not supported"); + return 0; // make tcc happy +} + +static AstNodeIndex parseFnProto(Parser* p) { + AstTokenIndex fn_token = eatToken(p, TOKEN_KEYWORD_FN); + if (fn_token == null_token) return null_node; - expectToken(p, TOKEN_L_PAREN); - const AstNodeIndex expr = expectExpr(p); - expectToken(p, TOKEN_R_PAREN); - return expr; + + AstNodeIndex fn_proto_index = reserveNode(p, AST_NODE_FN_PROTO); + + eatToken(p, TOKEN_IDENTIFIER); + + SmallSpan params = parseParamDeclList(p); + const AstNodeIndex align_expr = parseByteAlign(p); + const AstNodeIndex addrspace_expr = parseAddrSpace(p); + const AstNodeIndex section_expr = parseLinkSection(p); + const AstNodeIndex callconv_expr = parseCallconv(p); + eatToken(p, TOKEN_BANG); + + const AstNodeIndex return_type_expr = parseTypeExpr(p); + + if (align_expr == 0 && section_expr == 0 && callconv_expr == 0 + && addrspace_expr == 0) { + switch (params.tag) { + case SMALL_SPAN_ZERO_OR_ONE: + return setNode(p, fn_proto_index, + (AstNodeItem) { + .tag = AST_NODE_FN_PROTO_SIMPLE, + .main_token = fn_token, + .data = { + .lhs = params.payload.zero_or_one, + .rhs = return_type_expr, + }, + }); + case SMALL_SPAN_MULTI: + return setNode(p, fn_proto_index, + (AstNodeItem) { + .tag = AST_NODE_FN_PROTO_MULTI, + .main_token = fn_token, + .data = { + .lhs = addExtra(p, + (AstNodeIndex[]) { + params.payload.multi.start, + params.payload.multi.end }, + 2), + .rhs = return_type_expr, + }, + }); + } + } + + // Complex fn proto with align/section/callconv/addrspace + switch (params.tag) { + case SMALL_SPAN_ZERO_OR_ONE: + return setNode(p, fn_proto_index, + (AstNodeItem) { + .tag = AST_NODE_FN_PROTO_ONE, + .main_token = fn_token, + .data = { + .lhs = addExtra(p, + (AstNodeIndex[]) { + OPT(params.payload.zero_or_one), + OPT(align_expr), OPT(addrspace_expr), + OPT(section_expr), OPT(callconv_expr) }, + 5), + .rhs = return_type_expr, + }, + }); + case SMALL_SPAN_MULTI: + return setNode(p, fn_proto_index, + (AstNodeItem) { + .tag = AST_NODE_FN_PROTO, + .main_token = fn_token, + .data = { + .lhs = addExtra(p, + (AstNodeIndex[]) { + params.payload.multi.start, + params.payload.multi.end, + OPT(align_expr), OPT(addrspace_expr), + OPT(section_expr), OPT(callconv_expr) }, + 6), + .rhs = return_type_expr, + }, + }); + } + return 0; // tcc } -static AstNodeIndex parseAddrSpace(Parser* p) { - if (eatToken(p, TOKEN_KEYWORD_ADDRSPACE) == null_token) +static AstNodeIndex parseVarDeclProto(Parser* p) { + AstTokenIndex mut_token; + if ((mut_token = eatToken(p, TOKEN_KEYWORD_CONST)) == null_token) + if ((mut_token = eatToken(p, TOKEN_KEYWORD_VAR)) == null_token) + return null_node; + + expectToken(p, TOKEN_IDENTIFIER); + const AstNodeIndex type_node + = eatToken(p, TOKEN_COLON) == null_token ? 0 : parseTypeExpr(p); + const AstNodeIndex align_node = parseByteAlign(p); + const AstNodeIndex addrspace_node = parseAddrSpace(p); + const AstNodeIndex section_node = parseLinkSection(p); + + if (section_node == 0 && addrspace_node == 0) { + if (align_node == 0) { + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_SIMPLE_VAR_DECL, + .main_token = mut_token, + .data = { .lhs = type_node, .rhs = 0 }, + }); + } + if (type_node == 0) { + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_ALIGNED_VAR_DECL, + .main_token = mut_token, + .data = { .lhs = align_node, .rhs = 0 }, + }); + } + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_LOCAL_VAR_DECL, + .main_token = mut_token, + .data = { + .lhs = addExtra(p, + (AstNodeIndex[]) { type_node, align_node }, 2), + .rhs = 0, + }, + }); + } + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_GLOBAL_VAR_DECL, + .main_token = mut_token, + .data = { + .lhs = addExtra(p, + (AstNodeIndex[]) { OPT(type_node), OPT(align_node), + OPT(addrspace_node), OPT(section_node) }, + 4), + .rhs = 0, + }, + }); +} + +static AstNodeIndex parseGlobalVarDecl(Parser* p) { + const AstNodeIndex var_decl = parseVarDeclProto(p); + if (var_decl == 0) { return null_node; - expectToken(p, TOKEN_L_PAREN); - const AstNodeIndex expr = expectExpr(p); - expectToken(p, TOKEN_R_PAREN); - return expr; -} + } -static AstNodeIndex parseLinkSection(Parser* p) { - if (eatToken(p, TOKEN_KEYWORD_LINKSECTION) == null_token) - return null_node; - expectToken(p, TOKEN_L_PAREN); - const AstNodeIndex expr = expectExpr(p); - expectToken(p, TOKEN_R_PAREN); - return expr; + if (eatToken(p, TOKEN_EQUAL) != null_token) { + const AstNodeIndex init_expr = expectExpr(p); + p->nodes.datas[var_decl].rhs = init_expr; + } + expectToken(p, TOKEN_SEMICOLON); + return var_decl; } -static AstNodeIndex parseCallconv(Parser* p) { - if (eatToken(p, TOKEN_KEYWORD_CALLCONV) == null_token) - return null_node; - expectToken(p, TOKEN_L_PAREN); - const AstNodeIndex expr = expectExpr(p); - expectToken(p, TOKEN_R_PAREN); - return expr; -} - -typedef struct { - AstNodeIndex align_expr, value_expr; -} NodeContainerField; - static AstNodeIndex expectContainerField(Parser* p) { eatToken(p, TOKEN_KEYWORD_COMPTIME); const AstTokenIndex main_token = p->tok_i; @@ -332,689 +713,828 @@ static AstNodeIndex expectContainerField(Parser* p) { } } -static AstNodeIndex parseBuiltinCall(Parser* p) { - const AstTokenIndex builtin_token = assertToken(p, TOKEN_BUILTIN); - assertToken(p, TOKEN_L_PAREN); +static AstNodeIndex expectStatement(Parser* p, bool allow_defer_var) { + const AstTokenIndex comptime_token = eatToken(p, TOKEN_KEYWORD_COMPTIME); + if (comptime_token != null_token) { + // comptime followed by block => comptime block statement + const AstNodeIndex block = parseBlock(p); + if (block != 0) { + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_COMPTIME, + .main_token = comptime_token, + .data = { .lhs = block, .rhs = 0 }, + }); + } + // comptime var decl or expression + if (allow_defer_var) + return expectVarDeclExprStatement(p, comptime_token); + { + const AstNodeIndex assign = parseAssignExpr(p); + if (assign == 0) { + fail(p, "expected expression"); + } + expectSemicolon(p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_COMPTIME, + .main_token = comptime_token, + .data = { .lhs = assign, .rhs = 0 }, + }); + } + } + const AstNodeIndex tok = p->token_tags[p->tok_i]; + switch (tok) { + case TOKEN_KEYWORD_DEFER: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_DEFER, + .main_token = nextToken(p), + .data = { + .lhs = expectBlockExprStatement(p), + .rhs = 0, + }, + }); + case TOKEN_KEYWORD_ERRDEFER: { + const AstTokenIndex errdefer_token = nextToken(p); + AstTokenIndex payload = null_token; + if (p->token_tags[p->tok_i] == TOKEN_PIPE) { + p->tok_i++; + payload = expectToken(p, TOKEN_IDENTIFIER); + expectToken(p, TOKEN_PIPE); + } + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_ERRDEFER, + .main_token = errdefer_token, + .data = { + .lhs = payload, + .rhs = expectBlockExprStatement(p), + }, + }); + } + case TOKEN_KEYWORD_NOSUSPEND: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_NOSUSPEND, + .main_token = nextToken(p), + .data = { + .lhs = expectBlockExprStatement(p), + .rhs = 0, + }, + }); + case TOKEN_KEYWORD_SUSPEND: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_SUSPEND, + .main_token = nextToken(p), + .data = { + .lhs = expectBlockExprStatement(p), + .rhs = 0, + }, + }); + case TOKEN_KEYWORD_IF: + return expectIfStatement(p); + case TOKEN_KEYWORD_ENUM: + case TOKEN_KEYWORD_STRUCT: + case TOKEN_KEYWORD_UNION:; + fail(p, "unsupported statement keyword"); + default:; + } + + const AstNodeIndex labeled_statement = parseLabeledStatement(p); + if (labeled_statement != 0) + return labeled_statement; + + if (allow_defer_var) { + return expectVarDeclExprStatement(p, null_token); + } else { + const AstNodeIndex assign_expr = parseAssignExpr(p); + expectSemicolon(p); + return assign_expr; + } +} + +static AstNodeIndex expectVarDeclExprStatement( + Parser* p, AstTokenIndex comptime_token) { CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch))) = initCleanupScratch(p); while (true) { - if (eatToken(p, TOKEN_R_PAREN) != null_token) + const AstNodeIndex var_decl_proto = parseVarDeclProto(p); + if (var_decl_proto != 0) { + SLICE_APPEND(AstNodeIndex, &p->scratch, var_decl_proto); + } else { + const AstNodeIndex expr = parseExpr(p); + SLICE_APPEND(AstNodeIndex, &p->scratch, expr); + } + if (eatToken(p, TOKEN_COMMA) == null_token) break; - - const AstNodeIndex param = expectExpr(p); - SLICE_APPEND(AstNodeIndex, &p->scratch, param); - switch (p->token_tags[p->tok_i]) { - case TOKEN_COMMA: - p->tok_i++; - break; - case TOKEN_R_PAREN: - p->tok_i++; - goto end_loop; - default: - fail(p, "expected comma after arg"); - } - } -end_loop:; - - const bool comma = (p->token_tags[p->tok_i - 2] == TOKEN_COMMA); - const uint32_t params_len = p->scratch.len - scratch_top.old_len; - switch (params_len) { - case 0: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_BUILTIN_CALL_TWO, - .main_token = builtin_token, - .data = { - .lhs = 0, - .rhs = 0, - }, - }); - case 1: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = comma ? - AST_NODE_BUILTIN_CALL_TWO_COMMA : - AST_NODE_BUILTIN_CALL_TWO, - .main_token = builtin_token, - .data = { - .lhs = p->scratch.arr[scratch_top.old_len], - .rhs = 0, - }, - }); - case 2: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = comma ? - AST_NODE_BUILTIN_CALL_TWO_COMMA : - AST_NODE_BUILTIN_CALL_TWO, - .main_token = builtin_token, - .data = { - .lhs = p->scratch.arr[scratch_top.old_len], - .rhs = p->scratch.arr[scratch_top.old_len+1], - }, - }); - default:; - const AstSubRange span - = listToSpan(p, &p->scratch.arr[scratch_top.old_len], params_len); - return addNode( - &p->nodes, - (AstNodeItem) { - .tag = comma ? - AST_NODE_BUILTIN_CALL_COMMA : - AST_NODE_BUILTIN_CALL, - .main_token = builtin_token, - .data = { - .lhs = span.start, - .rhs = span.end, - }, - }); - } -} - -static AstNodeIndex parseContainerDeclAuto(Parser* p) { - const AstTokenIndex main_token = nextToken(p); - AstNodeIndex arg_expr = null_node; - switch (p->token_tags[main_token]) { - case TOKEN_KEYWORD_OPAQUE: - break; - case TOKEN_KEYWORD_STRUCT: - case TOKEN_KEYWORD_ENUM: - if (eatToken(p, TOKEN_L_PAREN) != null_token) { - arg_expr = expectExpr(p); - expectToken(p, TOKEN_R_PAREN); - } - break; - case TOKEN_KEYWORD_UNION: - if (eatToken(p, TOKEN_L_PAREN) != null_token) { - if (eatToken(p, TOKEN_KEYWORD_ENUM) != null_token) { - if (eatToken(p, TOKEN_L_PAREN) != null_token) { - const AstNodeIndex enum_tag_expr = expectExpr(p); - expectToken(p, TOKEN_R_PAREN); - expectToken(p, TOKEN_R_PAREN); - expectToken(p, TOKEN_L_BRACE); - const Members members = parseContainerMembers(p); - const AstSubRange members_span = membersToSpan(members, p); - expectToken(p, TOKEN_R_BRACE); - return addNode( - &p->nodes, - (AstNodeItem) { - .tag = members.trailing - ? AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING - : AST_NODE_TAGGED_UNION_ENUM_TAG, - .main_token = main_token, - .data = { - .lhs = enum_tag_expr, - .rhs = addExtra(p, - (AstNodeIndex[]) { - members_span.start, - members_span.end }, - 2), - }, - }); - } - expectToken(p, TOKEN_R_PAREN); - expectToken(p, TOKEN_L_BRACE); - const Members members = parseContainerMembers(p); - expectToken(p, TOKEN_R_BRACE); - if (members.len <= 2) { - return addNode(&p->nodes, - (AstNodeItem) { - .tag = members.trailing - ? AST_NODE_TAGGED_UNION_TWO_TRAILING - : AST_NODE_TAGGED_UNION_TWO, - .main_token = main_token, - .data = { .lhs = members.lhs, .rhs = members.rhs }, - }); - } - const AstSubRange span = membersToSpan(members, p); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = members.trailing - ? AST_NODE_TAGGED_UNION_TRAILING - : AST_NODE_TAGGED_UNION, - .main_token = main_token, - .data = { .lhs = span.start, .rhs = span.end }, - }); - } - arg_expr = expectExpr(p); - expectToken(p, TOKEN_R_PAREN); - } - break; - default: - fail(p, "parseContainerDeclAuto: unexpected token"); } - expectToken(p, TOKEN_L_BRACE); - const Members members = parseContainerMembers(p); - expectToken(p, TOKEN_R_BRACE); + const uint32_t lhs_count = p->scratch.len - scratch_top.old_len; + assert(lhs_count > 0); - if (arg_expr == null_node) { - if (members.len <= 2) { + // Try to eat '=' for assignment/initialization + // (matches upstream: `const equal_token = p.eatToken(.equal) orelse eql:`) + AstTokenIndex equal_token = eatToken(p, TOKEN_EQUAL); + if (equal_token == null_token) { + if (lhs_count > 1) { + // Destructure requires '=' + fail(p, "expected '='"); + } + const AstNodeIndex lhs = p->scratch.arr[scratch_top.old_len]; + const AstNodeTag lhs_tag = p->nodes.tags[lhs]; + if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL + || lhs_tag == AST_NODE_ALIGNED_VAR_DECL + || lhs_tag == AST_NODE_LOCAL_VAR_DECL + || lhs_tag == AST_NODE_GLOBAL_VAR_DECL) { + // var decl without init requires '=' + fail(p, "expected '='"); + } + // Expression statement: finish with assignment operators or semicolon + const AstNodeIndex expr = finishAssignExpr(p, lhs); + // Semicolon is optional for block-terminated expressions + eatToken(p, TOKEN_SEMICOLON); + if (comptime_token != null_token) { return addNode(&p->nodes, (AstNodeItem) { - .tag = members.trailing - ? AST_NODE_CONTAINER_DECL_TWO_TRAILING - : AST_NODE_CONTAINER_DECL_TWO, - .main_token = main_token, - .data = { .lhs = members.lhs, .rhs = members.rhs }, + .tag = AST_NODE_COMPTIME, + .main_token = comptime_token, + .data = { .lhs = expr, .rhs = 0 }, }); } - const AstSubRange span = membersToSpan(members, p); + return expr; + } + + // Have '=', parse RHS and semicolon + const AstNodeIndex rhs = expectExpr(p); + expectSemicolon(p); + + if (lhs_count == 1) { + const AstNodeIndex lhs = p->scratch.arr[scratch_top.old_len]; + const AstNodeTag lhs_tag = p->nodes.tags[lhs]; + if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL + || lhs_tag == AST_NODE_ALIGNED_VAR_DECL + || lhs_tag == AST_NODE_LOCAL_VAR_DECL + || lhs_tag == AST_NODE_GLOBAL_VAR_DECL) { + // var decl initialization: const x = val; + p->nodes.datas[lhs].rhs = rhs; + return lhs; + } + // Simple assignment: x = val; return addNode(&p->nodes, (AstNodeItem) { - .tag = members.trailing ? AST_NODE_CONTAINER_DECL_TRAILING - : AST_NODE_CONTAINER_DECL, - .main_token = main_token, - .data = { .lhs = span.start, .rhs = span.end }, + .tag = AST_NODE_ASSIGN, + .main_token = equal_token, + .data = { .lhs = lhs, .rhs = rhs }, }); } - const AstSubRange span = membersToSpan(members, p); - return addNode( - &p->nodes, + // Destructure: a, b, c = rhs + // rhs and semicolon already parsed above + + // Store count + lhs nodes in extra_data + const AstNodeIndex extra_start = p->extra_data.len; + SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, lhs_count + 1); + p->extra_data.arr[p->extra_data.len++] = lhs_count; + memcpy(p->extra_data.arr + p->extra_data.len, + &p->scratch.arr[scratch_top.old_len], + lhs_count * sizeof(AstNodeIndex)); + p->extra_data.len += lhs_count; + + return addNode(&p->nodes, (AstNodeItem) { - .tag = members.trailing - ? AST_NODE_CONTAINER_DECL_ARG_TRAILING - : AST_NODE_CONTAINER_DECL_ARG, - .main_token = main_token, + .tag = AST_NODE_ASSIGN_DESTRUCTURE, + .main_token = equal_token, + .data = { .lhs = extra_start, .rhs = rhs }, + }); +} + +static AstNodeIndex expectIfStatement(Parser* p) { + const AstTokenIndex if_token = assertToken(p, TOKEN_KEYWORD_IF); + expectToken(p, TOKEN_L_PAREN); + const AstNodeIndex condition = expectExpr(p); + expectToken(p, TOKEN_R_PAREN); + parsePtrPayload(p); + bool else_required = false; + AstNodeIndex then_body; + const AstNodeIndex block2 = parseBlockExpr(p); + if (block2 != 0) { + then_body = block2; + } else { + then_body = parseAssignExpr(p); + if (then_body == 0) + fail(p, "expected block or assignment"); + if (eatToken(p, TOKEN_SEMICOLON) != null_token) + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_IF_SIMPLE, + .main_token = if_token, + .data = { .lhs = condition, .rhs = then_body }, + }); + else_required = true; + } + if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { + if (else_required) + fail(p, "expected_semi_or_else"); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_IF_SIMPLE, + .main_token = if_token, + .data = { .lhs = condition, .rhs = then_body }, + }); + } + parsePayload(p); + const AstNodeIndex else_body = expectStatement(p, false); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_IF, + .main_token = if_token, .data = { - .lhs = arg_expr, + .lhs = condition, .rhs = addExtra(p, - (AstNodeIndex[]) { span.start, span.end }, 2), + (AstNodeIndex[]) { then_body, else_body }, 2), }, }); } -static AstNodeIndex parsePrimaryTypeExpr(Parser* p) { - const TokenizerTag tok = p->token_tags[p->tok_i]; - switch (tok) { - case TOKEN_CHAR_LITERAL: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_CHAR_LITERAL, - .main_token = nextToken(p), - .data = {}, - }); - case TOKEN_NUMBER_LITERAL: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_NUMBER_LITERAL, - .main_token = nextToken(p), - .data = {}, - }); - case TOKEN_KEYWORD_UNREACHABLE: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_UNREACHABLE_LITERAL, - .main_token = nextToken(p), - .data = {}, - }); - case TOKEN_KEYWORD_ANYFRAME: - fail(p, "unsupported primary type expression"); - case TOKEN_STRING_LITERAL: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_STRING_LITERAL, - .main_token = nextToken(p), - .data = {}, - }); - case TOKEN_BUILTIN: - return parseBuiltinCall(p); - case TOKEN_KEYWORD_FN: - return parseFnProto(p); - case TOKEN_KEYWORD_IF: - return parseIfExpr(p); - case TOKEN_KEYWORD_SWITCH: - return parseSwitchExpr(p); - case TOKEN_KEYWORD_EXTERN: - case TOKEN_KEYWORD_PACKED: - // extern/packed can precede struct/union/enum - switch (p->token_tags[p->tok_i + 1]) { - case TOKEN_KEYWORD_STRUCT: - case TOKEN_KEYWORD_UNION: - case TOKEN_KEYWORD_ENUM: - p->tok_i++; // consume extern/packed - return parseContainerDeclAuto(p); - default: - fail(p, "unsupported primary type expression"); - } - case TOKEN_KEYWORD_STRUCT: - case TOKEN_KEYWORD_OPAQUE: - case TOKEN_KEYWORD_ENUM: - case TOKEN_KEYWORD_UNION: - return parseContainerDeclAuto(p); - case TOKEN_KEYWORD_COMPTIME: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_COMPTIME, - .main_token = nextToken(p), - .data = { .lhs = parseTypeExpr(p), .rhs = 0 }, - }); - case TOKEN_MULTILINE_STRING_LITERAL_LINE: { - const AstTokenIndex first = nextToken(p); - AstTokenIndex last = first; - while (p->token_tags[p->tok_i] == TOKEN_MULTILINE_STRING_LITERAL_LINE) - last = nextToken(p); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_MULTILINE_STRING_LITERAL, - .main_token = first, - .data = { .lhs = first, .rhs = last }, - }); +static AstNodeIndex parseLabeledStatement(Parser* p) { + const AstNodeIndex label_token = parseBlockLabel(p); + const AstNodeIndex block = parseBlock(p); + if (block != 0) + return block; + + const AstNodeIndex loop_stmt = parseLoopStatement(p); + if (loop_stmt != 0) + return loop_stmt; + + if (label_token != 0) { + fail(p, "parseLabeledStatement does not support labels"); } - case TOKEN_IDENTIFIER: - if (p->token_tags[p->tok_i + 1] == TOKEN_COLON) { - switch (p->token_tags[p->tok_i + 2]) { - case TOKEN_L_BRACE: { - // Labeled block: label: { ... } - nextToken(p); // consume label - nextToken(p); // consume ':' - return parseBlock(p); - } - case TOKEN_KEYWORD_WHILE: - return parseLabeledStatement(p); - case TOKEN_KEYWORD_FOR: - return parseLabeledStatement(p); - default: - break; - } - } - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_IDENTIFIER, - .main_token = nextToken(p), - .data = {}, - }); - case TOKEN_KEYWORD_FOR: - return parseForExpr(p); - case TOKEN_KEYWORD_WHILE: - return parseWhileExpr(p); - case TOKEN_KEYWORD_INLINE: - case TOKEN_PERIOD: - switch (p->token_tags[p->tok_i + 1]) { - case TOKEN_IDENTIFIER: { - const AstTokenIndex dot = nextToken(p); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_ENUM_LITERAL, - .main_token = nextToken(p), - .data = { .lhs = dot, .rhs = 0 }, - }); - } - case TOKEN_L_BRACE: { - // Anonymous struct/array init: .{ ... } - const AstTokenIndex lbrace = p->tok_i + 1; - p->tok_i = lbrace + 1; - return parseInitList(p, null_node, lbrace); - } - default: - fail(p, "unsupported period suffix"); - } - return 0; // tcc - case TOKEN_KEYWORD_ERROR: - switch (p->token_tags[p->tok_i + 1]) { - case TOKEN_PERIOD: { - const AstTokenIndex error_token = nextToken(p); - const AstTokenIndex dot = nextToken(p); - const AstTokenIndex value = expectToken(p, TOKEN_IDENTIFIER); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_ERROR_VALUE, - .main_token = error_token, - .data = { .lhs = dot, .rhs = value }, - }); - } - case TOKEN_L_BRACE: { - const AstTokenIndex error_token = nextToken(p); - const AstTokenIndex lbrace = nextToken(p); - while (p->token_tags[p->tok_i] != TOKEN_R_BRACE) - p->tok_i++; - const AstTokenIndex rbrace = nextToken(p); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_ERROR_SET_DECL, - .main_token = error_token, - .data = { .lhs = lbrace, .rhs = rbrace }, - }); - } - default: { - const AstTokenIndex main_token = nextToken(p); - const AstTokenIndex period = eatToken(p, TOKEN_PERIOD); - if (period == null_token) { - fail(p, "expected '.'"); - } - const AstTokenIndex identifier = eatToken(p, TOKEN_IDENTIFIER); - if (identifier == null_token) { - fail(p, "expected identifier"); - } - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_ERROR_VALUE, - .main_token = main_token, - .data = { .lhs = period, .rhs = identifier }, - }); - } - } - case TOKEN_L_PAREN: { - const AstTokenIndex lparen = nextToken(p); - const AstNodeIndex inner = expectExpr(p); - const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_GROUPED_EXPRESSION, - .main_token = lparen, - .data = { .lhs = inner, .rhs = rparen }, - }); - } - default: + + return null_node; +} + +static AstNodeIndex parseLoopStatement(Parser* p) { + const AstTokenIndex inline_token = eatToken(p, TOKEN_KEYWORD_INLINE); + + const AstNodeIndex for_statement = parseForStatement(p); + if (for_statement != 0) + return for_statement; + + const AstNodeIndex while_statement = parseWhileStatement(p); + if (while_statement != 0) + return while_statement; + + if (inline_token == null_token) return null_node; - } -} -static AstNodeIndex parseSuffixOp(Parser* p, AstNodeIndex lhs) { - const TokenizerTag tok = p->token_tags[p->tok_i]; - switch (tok) { - case TOKEN_L_BRACKET: { - const AstTokenIndex lbracket = nextToken(p); - const AstNodeIndex index_expr = expectExpr(p); - switch (p->token_tags[p->tok_i]) { - case TOKEN_R_BRACKET: - p->tok_i++; - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_ARRAY_ACCESS, - .main_token = lbracket, - .data = { .lhs = lhs, .rhs = index_expr }, - }); - case TOKEN_ELLIPSIS2: { - p->tok_i++; // consume .. - const AstNodeIndex end_expr = parseExpr(p); - if (eatToken(p, TOKEN_COLON) != null_token) { - const AstNodeIndex sentinel = expectExpr(p); - expectToken(p, TOKEN_R_BRACKET); - // end_expr 0 means "no end" — encode as ~0 for - // OptionalIndex.none - const AstNodeIndex opt_end - = end_expr == 0 ? ~(AstNodeIndex)0 : end_expr; - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_SLICE_SENTINEL, - .main_token = lbracket, - .data = { - .lhs = lhs, - .rhs = addExtra(p, - (AstNodeIndex[]) { - index_expr, opt_end, sentinel }, - 3), - }, - }); - } - expectToken(p, TOKEN_R_BRACKET); - if (end_expr == 0) { - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_SLICE_OPEN, - .main_token = lbracket, - .data = { .lhs = lhs, .rhs = index_expr }, - }); - } - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_SLICE, - .main_token = lbracket, - .data = { - .lhs = lhs, - .rhs = addExtra(p, - (AstNodeIndex[]) { index_expr, end_expr }, 2), - }, - }); - } - default: - fail(p, "parseSuffixOp: expected ] or .. after index expr"); - } - return 0; // tcc - } - case TOKEN_PERIOD_ASTERISK: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_DEREF, - .main_token = nextToken(p), - .data = { .lhs = lhs, .rhs = 0 }, - }); - case TOKEN_INVALID_PERIODASTERISKS: - fail(p, "unsupported suffix op"); - case TOKEN_PERIOD: - if (p->token_tags[p->tok_i + 1] == TOKEN_IDENTIFIER) { - const AstTokenIndex dot = nextToken(p); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_FIELD_ACCESS, - .main_token = dot, - .data = { .lhs = lhs, .rhs = nextToken(p) }, - }); - } - if (p->token_tags[p->tok_i + 1] == TOKEN_ASTERISK) { - const AstTokenIndex dot = nextToken(p); - nextToken(p); // consume the * - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_DEREF, - .main_token = dot, - .data = { .lhs = lhs, .rhs = 0 }, - }); - } - if (p->token_tags[p->tok_i + 1] == TOKEN_QUESTION_MARK) { - const AstTokenIndex dot = nextToken(p); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_UNWRAP_OPTIONAL, - .main_token = dot, - .data = { .lhs = lhs, .rhs = nextToken(p) }, - }); - } - fail(p, "parseSuffixOp: unsupported period suffix"); - return 0; // tcc - default: - return null_node; - } -} - -static AstNodeIndex parseSuffixExpr(Parser* p) { - if (eatToken(p, TOKEN_KEYWORD_ASYNC) != null_token) { - fail(p, "async not supported"); - } - - AstNodeIndex res = parsePrimaryTypeExpr(p); - if (res == 0) - return res; - - while (true) { - const AstNodeIndex suffix_op = parseSuffixOp(p, res); - if (suffix_op != 0) { - res = suffix_op; - continue; - } - const AstTokenIndex lparen = eatToken(p, TOKEN_L_PAREN); - if (lparen == null_token) - return res; - - CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch))) - = initCleanupScratch(p); - while (true) { - if (eatToken(p, TOKEN_R_PAREN) != null_token) - break; - const AstNodeIndex arg = expectExpr(p); - SLICE_APPEND(AstNodeIndex, &p->scratch, arg); - if (p->token_tags[p->tok_i] == TOKEN_COMMA) { - p->tok_i++; - continue; - } - expectToken(p, TOKEN_R_PAREN); - break; - } - - const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA; - const uint32_t params_len = p->scratch.len - scratch_top.old_len; - switch (params_len) { - case 0: - res = addNode( - &p->nodes, - (AstNodeItem) { - .tag = comma ? AST_NODE_CALL_ONE_COMMA : AST_NODE_CALL_ONE, - .main_token = lparen, - .data = { - .lhs = res, - .rhs = 0, - }, - }); - break; - case 1: - res = addNode( - &p->nodes, - (AstNodeItem) { - .tag = comma ? AST_NODE_CALL_ONE_COMMA : AST_NODE_CALL_ONE, - .main_token = lparen, - .data = { - .lhs = res, - .rhs = p->scratch.arr[scratch_top.old_len], - }, - }); - break; - default:; - const AstSubRange span = listToSpan( - p, &p->scratch.arr[scratch_top.old_len], params_len); - res = addNode( - &p->nodes, - (AstNodeItem) { - .tag = comma ? AST_NODE_CALL_COMMA : AST_NODE_CALL, - .main_token = lparen, - .data = { - .lhs = res, - .rhs = addExtra(p, (AstNodeIndex[]) { - span.start, - span.end, - }, 2), - }, - }); - break; - } - } -} - -static AstTokenIndex expectToken(Parser* p, TokenizerTag tag) { - if (p->token_tags[p->tok_i] == tag) { - return nextToken(p); - } else { - fail(p, "unexpected token"); - } + fail(p, "seen 'inline', there should have been a 'for' or 'while'"); return 0; // tcc } -static AstNodeIndex expectSemicolon(Parser* p) { - return expectToken(p, TOKEN_SEMICOLON); -} - -static AstNodeIndex parseErrorUnionExpr(Parser* p) { - const AstNodeIndex suffix_expr = parseSuffixExpr(p); - if (suffix_expr == 0) +static AstNodeIndex parseForStatement(Parser* p) { + const AstTokenIndex for_token = eatToken(p, TOKEN_KEYWORD_FOR); + if (for_token == null_token) return null_node; - const AstNodeIndex bang = eatToken(p, TOKEN_BANG); - if (bang == null_token) - return suffix_expr; + const uint32_t scratch_top = p->scratch.len; + const uint32_t inputs = forPrefix(p); - return addNode( - &p->nodes, + // Statement body: block or assign expr + AstNodeIndex then_body; + bool seen_semicolon = false; + const AstNodeIndex block = parseBlock(p); + if (block != 0) { + then_body = block; + } else { + then_body = parseAssignExpr(p); + if (then_body == 0) { + fail(p, "expected expression"); + } + if (eatToken(p, TOKEN_SEMICOLON) != null_token) + seen_semicolon = true; + } + + if (!seen_semicolon && eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) { + parsePayload(p); + SLICE_APPEND(AstNodeIndex, &p->scratch, then_body); + const AstNodeIndex else_body = expectBlockExprStatement(p); + SLICE_APPEND(AstNodeIndex, &p->scratch, else_body); + const uint32_t total = p->scratch.len - scratch_top; + const AstSubRange span + = listToSpan(p, &p->scratch.arr[scratch_top], total); + p->scratch.len = scratch_top; + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_FOR, + .main_token = for_token, + .data = { + .lhs = span.start, + .rhs = ((uint32_t)inputs & 0x7FFFFFFF) | (1u << 31), + }, + }); + } + + if (!seen_semicolon && block == 0) { + fail(p, "expected_semi_or_else"); + } + + if (inputs == 1) { + const AstNodeIndex input = p->scratch.arr[scratch_top]; + p->scratch.len = scratch_top; + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_FOR_SIMPLE, + .main_token = for_token, + .data = { .lhs = input, .rhs = then_body }, + }); + } + + SLICE_APPEND(AstNodeIndex, &p->scratch, then_body); + const uint32_t total = p->scratch.len - scratch_top; + const AstSubRange span + = listToSpan(p, &p->scratch.arr[scratch_top], total); + p->scratch.len = scratch_top; + return addNode(&p->nodes, (AstNodeItem) { - .tag = AST_NODE_ERROR_UNION, - .main_token = bang, + .tag = AST_NODE_FOR, + .main_token = for_token, .data = { - .lhs = suffix_expr, - .rhs = parseTypeExpr(p), + .lhs = span.start, + .rhs = (uint32_t)inputs & 0x7FFFFFFF, }, }); } -static PtrModifiers parsePtrModifiers(Parser* p) { - PtrModifiers mods = {}; +static AstNodeIndex parseForExpr(Parser* p) { + const AstTokenIndex for_token = eatToken(p, TOKEN_KEYWORD_FOR); + if (for_token == null_token) + return null_node; - while (true) { - switch (p->token_tags[p->tok_i]) { - case TOKEN_KEYWORD_CONST: - case TOKEN_KEYWORD_VOLATILE: - case TOKEN_KEYWORD_ALLOWZERO: - p->tok_i++; - continue; - case TOKEN_KEYWORD_ALIGN: - p->tok_i++; - expectToken(p, TOKEN_L_PAREN); - mods.align_node = expectExpr(p); - if (eatToken(p, TOKEN_COLON) != null_token) { - mods.bit_range_start = expectExpr(p); - expectToken(p, TOKEN_COLON); - mods.bit_range_end = expectExpr(p); - } - expectToken(p, TOKEN_R_PAREN); - continue; - case TOKEN_KEYWORD_ADDRSPACE: - p->tok_i++; - expectToken(p, TOKEN_L_PAREN); - mods.addrspace_node = expectExpr(p); - expectToken(p, TOKEN_R_PAREN); - continue; - default: - return mods; + const uint32_t scratch_top = p->scratch.len; + const uint32_t inputs = forPrefix(p); + + const AstNodeIndex then_expr = expectExpr(p); + + if (eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) { + parsePayload(p); + SLICE_APPEND(AstNodeIndex, &p->scratch, then_expr); + const AstNodeIndex else_expr = expectExpr(p); + SLICE_APPEND(AstNodeIndex, &p->scratch, else_expr); + const uint32_t total = p->scratch.len - scratch_top; + const AstSubRange span + = listToSpan(p, &p->scratch.arr[scratch_top], total); + p->scratch.len = scratch_top; + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_FOR, + .main_token = for_token, + .data = { + .lhs = span.start, + .rhs = ((uint32_t)inputs & 0x7FFFFFFF) | (1u << 31), + }, + }); + } + + if (inputs == 1) { + const AstNodeIndex input = p->scratch.arr[scratch_top]; + p->scratch.len = scratch_top; + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_FOR_SIMPLE, + .main_token = for_token, + .data = { .lhs = input, .rhs = then_expr }, + }); + } + + SLICE_APPEND(AstNodeIndex, &p->scratch, then_expr); + const uint32_t total = p->scratch.len - scratch_top; + const AstSubRange span + = listToSpan(p, &p->scratch.arr[scratch_top], total); + p->scratch.len = scratch_top; + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_FOR, + .main_token = for_token, + .data = { + .lhs = span.start, + .rhs = (uint32_t)inputs & 0x7FFFFFFF, + }, + }); +} + +static AstNodeIndex parseWhileStatement(Parser* p) { + const AstTokenIndex while_token = eatToken(p, TOKEN_KEYWORD_WHILE); + if (while_token == null_token) + return null_node; + + expectToken(p, TOKEN_L_PAREN); + const AstNodeIndex condition = expectExpr(p); + expectToken(p, TOKEN_R_PAREN); + parsePtrPayload(p); + + const AstNodeIndex cont_expr = parseWhileContinueExpr(p); + + // Statement body: block, or assign expr + AstNodeIndex body; + bool seen_semicolon = false; + const AstNodeIndex block = parseBlock(p); + if (block != 0) { + body = block; + } else { + body = parseAssignExpr(p); + if (body == 0) { + fail(p, "expected expression"); } + if (eatToken(p, TOKEN_SEMICOLON) != null_token) + seen_semicolon = true; + } + + if (seen_semicolon || eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { + if (!seen_semicolon && block == 0) { + fail(p, "expected_semi_or_else"); + } + if (cont_expr != 0) { + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_WHILE_CONT, + .main_token = while_token, + .data = { + .lhs = condition, + .rhs = addExtra(p, + (AstNodeIndex[]) { cont_expr, body }, 2), + }, + }); + } + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_WHILE_SIMPLE, + .main_token = while_token, + .data = { .lhs = condition, .rhs = body }, + }); + } + + parsePayload(p); + const AstNodeIndex else_body = expectBlockExprStatement(p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_WHILE, + .main_token = while_token, + .data = { + .lhs = condition, + .rhs = addExtra(p, + (AstNodeIndex[]) { OPT(cont_expr), body, else_body }, + 3), + }, + }); +} + +static AstNodeIndex expectBlockExprStatement(Parser* p) { + const AstNodeIndex block_expr = parseBlockExpr(p); + if (block_expr != 0) + return block_expr; + // Assign expr + semicolon + const AstNodeIndex expr = parseAssignExpr(p); + if (expr != 0) { + expectSemicolon(p); + return expr; + } + fail(p, "expectBlockExprStatement: expected block or expr"); + return 0; // tcc +} + +static AstNodeIndex parseBlockExpr(Parser* p) { + if (p->token_tags[p->tok_i] == TOKEN_L_BRACE) + return parseBlock(p); + if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER + && p->token_tags[p->tok_i + 1] == TOKEN_COLON + && p->token_tags[p->tok_i + 2] == TOKEN_L_BRACE) { + p->tok_i += 2; + return parseBlock(p); + } + return null_node; +} + +static AstNodeIndex parseAssignExpr(Parser* p) { + const AstNodeIndex expr = parseExpr(p); + if (expr == 0) + return null_node; + return finishAssignExpr(p, expr); +} + +static AstNodeIndex parseSingleAssignExpr(Parser* p) { + const AstNodeIndex expr = parseExpr(p); + if (expr == 0) + return null_node; + const AstNodeTag tag = assignOpNode(p->token_tags[p->tok_i]); + if (tag == AST_NODE_ROOT) + return expr; + const AstTokenIndex op_token = nextToken(p); + const AstNodeIndex rhs = expectExpr(p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = tag, + .main_token = op_token, + .data = { .lhs = expr, .rhs = rhs }, + }); +} + +static AstNodeIndex finishAssignExpr(Parser* p, AstNodeIndex lhs) { + const AstNodeTag assign_tag = assignOpNode(p->token_tags[p->tok_i]); + if (assign_tag == AST_NODE_ROOT) + return lhs; + + const AstTokenIndex op_token = nextToken(p); + const AstNodeIndex rhs = expectExpr(p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = assign_tag, + .main_token = op_token, + .data = { .lhs = lhs, .rhs = rhs }, + }); +} + +static AstNodeTag assignOpNode(TokenizerTag tok) { + switch (tok) { + case TOKEN_EQUAL: + return AST_NODE_ASSIGN; + case TOKEN_PLUS_EQUAL: + return AST_NODE_ASSIGN_ADD; + case TOKEN_MINUS_EQUAL: + return AST_NODE_ASSIGN_SUB; + case TOKEN_ASTERISK_EQUAL: + return AST_NODE_ASSIGN_MUL; + case TOKEN_SLASH_EQUAL: + return AST_NODE_ASSIGN_DIV; + case TOKEN_PERCENT_EQUAL: + return AST_NODE_ASSIGN_MOD; + case TOKEN_AMPERSAND_EQUAL: + return AST_NODE_ASSIGN_BIT_AND; + case TOKEN_PIPE_EQUAL: + return AST_NODE_ASSIGN_BIT_OR; + case TOKEN_CARET_EQUAL: + return AST_NODE_ASSIGN_BIT_XOR; + case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_EQUAL: + return AST_NODE_ASSIGN_SHL; + case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT_EQUAL: + return AST_NODE_ASSIGN_SHR; + case TOKEN_PLUS_PERCENT_EQUAL: + return AST_NODE_ASSIGN_ADD_WRAP; + case TOKEN_MINUS_PERCENT_EQUAL: + return AST_NODE_ASSIGN_SUB_WRAP; + case TOKEN_ASTERISK_PERCENT_EQUAL: + return AST_NODE_ASSIGN_MUL_WRAP; + case TOKEN_PLUS_PIPE_EQUAL: + return AST_NODE_ASSIGN_ADD_SAT; + case TOKEN_MINUS_PIPE_EQUAL: + return AST_NODE_ASSIGN_SUB_SAT; + case TOKEN_ASTERISK_PIPE_EQUAL: + return AST_NODE_ASSIGN_MUL_SAT; + case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE_EQUAL: + return AST_NODE_ASSIGN_SHL_SAT; + default: + return AST_NODE_ROOT; // not an assignment op } } -static AstNodeIndex makePtrTypeNode(Parser* p, AstTokenIndex main_token, - AstNodeIndex sentinel, PtrModifiers mods, AstNodeIndex elem_type) { - if (mods.bit_range_start != 0) { - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_PTR_TYPE_BIT_RANGE, - .main_token = main_token, - .data = { - .lhs = addExtra(p, - (AstNodeIndex[]) { OPT(sentinel), mods.align_node, - OPT(mods.addrspace_node), mods.bit_range_start, - mods.bit_range_end }, - 5), - .rhs = elem_type, - }, - }); +static AstNodeIndex parseExpr(Parser* p) { return parseExprPrecedence(p, 0); } + +static AstNodeIndex expectExpr(Parser* p) { + const AstNodeIndex node = parseExpr(p); + if (node == 0) { + fail(p, "expected expression"); } - if (mods.addrspace_node != 0 || (sentinel != 0 && mods.align_node != 0)) { - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_PTR_TYPE, - .main_token = main_token, - .data = { - .lhs = addExtra(p, - (AstNodeIndex[]) { OPT(sentinel), - OPT(mods.align_node), - OPT(mods.addrspace_node) }, - 3), - .rhs = elem_type, - }, - }); + return node; +} + +static AstNodeIndex parseExprPrecedence(Parser* p, int32_t min_prec) { + assert(min_prec >= 0); + + AstNodeIndex node = parsePrefixExpr(p); + if (node == 0) + return null_node; + + int8_t banned_prec = -1; + + while (true) { + const TokenizerTag tok_tag = p->token_tags[p->tok_i]; + const OperInfo info = operTable(tok_tag); + if (info.prec < min_prec) + break; + + if (info.prec == banned_prec) { + fail(p, "chained comparison operators"); + } + + const AstTokenIndex oper_token = nextToken(p); + if (tok_tag == TOKEN_KEYWORD_CATCH) + parsePayload(p); + const AstNodeIndex rhs = parseExprPrecedence(p, info.prec + 1); + if (rhs == 0) { + fail(p, "expected expression"); + } + + { + const uint32_t tok_len = tokenTagLexemeLen(tok_tag); + if (tok_len > 0) { + const uint32_t tok_start = p->token_starts[oper_token]; + const char char_before = p->source[tok_start - 1]; + const char char_after = p->source[tok_start + tok_len]; + if (tok_tag == TOKEN_AMPERSAND && char_after == '&') { + fail(p, "invalid ampersand ampersand"); + } else if (isspace((unsigned char)char_before) + != isspace((unsigned char)char_after)) { + fail(p, "mismatched binary op whitespace"); + } + } + } + + node = addNode( + &p->nodes, + (AstNodeItem) { + .tag = info.tag, + .main_token = oper_token, + .data = { + .lhs = node, + .rhs = rhs, + }, + }); + + if (info.assoc == ASSOC_NONE) + banned_prec = info.prec; } - if (sentinel != 0) { - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_PTR_TYPE_SENTINEL, - .main_token = main_token, - .data = { .lhs = sentinel, .rhs = elem_type }, - }); + + return node; +} + +static uint32_t tokenTagLexemeLen(TokenizerTag tag) { + switch (tag) { + case TOKEN_PLUS: + case TOKEN_MINUS: + case TOKEN_ASTERISK: + case TOKEN_SLASH: + case TOKEN_PERCENT: + case TOKEN_AMPERSAND: + case TOKEN_CARET: + case TOKEN_PIPE: + case TOKEN_ANGLE_BRACKET_LEFT: + case TOKEN_ANGLE_BRACKET_RIGHT: + return 1; + case TOKEN_PLUS_PLUS: + case TOKEN_MINUS_PERCENT: + case TOKEN_PLUS_PERCENT: + case TOKEN_MINUS_PIPE: + case TOKEN_PLUS_PIPE: + case TOKEN_ASTERISK_ASTERISK: + case TOKEN_ASTERISK_PERCENT: + case TOKEN_ASTERISK_PIPE: + case TOKEN_PIPE_PIPE: + case TOKEN_EQUAL_EQUAL: + case TOKEN_BANG_EQUAL: + case TOKEN_ANGLE_BRACKET_LEFT_EQUAL: + case TOKEN_ANGLE_BRACKET_RIGHT_EQUAL: + case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT: + case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT: + return 2; + case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE: + return 3; + case TOKEN_KEYWORD_OR: + return 2; + case TOKEN_KEYWORD_AND: + return 3; + case TOKEN_KEYWORD_ORELSE: + return 6; + case TOKEN_KEYWORD_CATCH: + return 5; + default: + return 0; } - return addNode(&p->nodes, +} + +static OperInfo operTable(TokenizerTag tok_tag) { + switch (tok_tag) { + case TOKEN_KEYWORD_OR: + return (OperInfo) { .prec = 10, .tag = AST_NODE_BOOL_OR }; + case TOKEN_KEYWORD_AND: + return (OperInfo) { .prec = 20, .tag = AST_NODE_BOOL_AND }; + + case TOKEN_EQUAL_EQUAL: + return (OperInfo) { + .prec = 30, .tag = AST_NODE_EQUAL_EQUAL, .assoc = ASSOC_NONE + }; + case TOKEN_BANG_EQUAL: + return (OperInfo) { + .prec = 30, .tag = AST_NODE_BANG_EQUAL, .assoc = ASSOC_NONE + }; + case TOKEN_ANGLE_BRACKET_LEFT: + return (OperInfo) { + .prec = 30, .tag = AST_NODE_LESS_THAN, .assoc = ASSOC_NONE + }; + case TOKEN_ANGLE_BRACKET_RIGHT: + return (OperInfo) { + .prec = 30, .tag = AST_NODE_GREATER_THAN, .assoc = ASSOC_NONE + }; + case TOKEN_ANGLE_BRACKET_LEFT_EQUAL: + return (OperInfo) { + .prec = 30, .tag = AST_NODE_LESS_OR_EQUAL, .assoc = ASSOC_NONE + }; + case TOKEN_ANGLE_BRACKET_RIGHT_EQUAL: + return (OperInfo) { + .prec = 30, .tag = AST_NODE_GREATER_OR_EQUAL, .assoc = ASSOC_NONE + }; + + case TOKEN_AMPERSAND: + return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_AND }; + case TOKEN_CARET: + return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_XOR }; + case TOKEN_PIPE: + return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_OR }; + case TOKEN_KEYWORD_ORELSE: + return (OperInfo) { .prec = 40, .tag = AST_NODE_ORELSE }; + case TOKEN_KEYWORD_CATCH: + return (OperInfo) { .prec = 40, .tag = AST_NODE_CATCH }; + + case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT: + return (OperInfo) { .prec = 50, .tag = AST_NODE_SHL }; + case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE: + return (OperInfo) { .prec = 50, .tag = AST_NODE_SHL_SAT }; + case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT: + return (OperInfo) { .prec = 50, .tag = AST_NODE_SHR }; + + case TOKEN_PLUS: + return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD }; + case TOKEN_MINUS: + return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB }; + case TOKEN_PLUS_PLUS: + return (OperInfo) { .prec = 60, .tag = AST_NODE_ARRAY_CAT }; + case TOKEN_PLUS_PERCENT: + return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD_WRAP }; + case TOKEN_MINUS_PERCENT: + return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB_WRAP }; + case TOKEN_PLUS_PIPE: + return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD_SAT }; + case TOKEN_MINUS_PIPE: + return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB_SAT }; + + case TOKEN_PIPE_PIPE: + return (OperInfo) { .prec = 70, .tag = AST_NODE_MERGE_ERROR_SETS }; + case TOKEN_ASTERISK: + return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL }; + case TOKEN_SLASH: + return (OperInfo) { .prec = 70, .tag = AST_NODE_DIV }; + case TOKEN_PERCENT: + return (OperInfo) { .prec = 70, .tag = AST_NODE_MOD }; + case TOKEN_ASTERISK_ASTERISK: + return (OperInfo) { .prec = 70, .tag = AST_NODE_ARRAY_MULT }; + case TOKEN_ASTERISK_PERCENT: + return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL_WRAP }; + case TOKEN_ASTERISK_PIPE: + return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL_SAT }; + + default: + return (OperInfo) { .prec = -1, .tag = AST_NODE_ROOT }; + } +} + +static AstNodeIndex parsePrefixExpr(Parser* p) { + AstNodeTag tag; + switch (p->token_tags[p->tok_i]) { + case TOKEN_BANG: + tag = AST_NODE_BOOL_NOT; + break; + case TOKEN_MINUS: + tag = AST_NODE_NEGATION; + break; + case TOKEN_TILDE: + tag = AST_NODE_BIT_NOT; + break; + case TOKEN_MINUS_PERCENT: + tag = AST_NODE_NEGATION_WRAP; + break; + case TOKEN_AMPERSAND: + tag = AST_NODE_ADDRESS_OF; + break; + case TOKEN_KEYWORD_TRY: + tag = AST_NODE_TRY; + break; + case TOKEN_KEYWORD_AWAIT: + tag = AST_NODE_AWAIT; + break; + default: + return parsePrimaryExpr(p); + } + return addNode( + &p->nodes, (AstNodeItem) { - .tag = AST_NODE_PTR_TYPE_ALIGNED, - .main_token = main_token, - .data = { .lhs = mods.align_node, .rhs = elem_type }, + .tag = tag, + .main_token = nextToken(p), + .data = { + .lhs = parsePrefixExpr(p), + .rhs = 0, + }, }); } @@ -1225,181 +1745,265 @@ static AstNodeIndex parseTypeExpr(Parser* p) { return 0; // tcc } -static AstNodeIndex expectParamDecl(Parser* p) { - eatDocComments(p); - eatToken(p, TOKEN_KEYWORD_COMPTIME); - eatToken(p, TOKEN_KEYWORD_NOALIAS); - if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER - && p->token_tags[p->tok_i + 1] == TOKEN_COLON) - p->tok_i += 2; - if (eatToken(p, TOKEN_KEYWORD_ANYTYPE) != null_token) - return 0; - return parseTypeExpr(p); +static AstNodeIndex makePtrTypeNode(Parser* p, AstTokenIndex main_token, + AstNodeIndex sentinel, PtrModifiers mods, AstNodeIndex elem_type) { + if (mods.bit_range_start != 0) { + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_PTR_TYPE_BIT_RANGE, + .main_token = main_token, + .data = { + .lhs = addExtra(p, + (AstNodeIndex[]) { OPT(sentinel), mods.align_node, + OPT(mods.addrspace_node), mods.bit_range_start, + mods.bit_range_end }, + 5), + .rhs = elem_type, + }, + }); + } + if (mods.addrspace_node != 0 || (sentinel != 0 && mods.align_node != 0)) { + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_PTR_TYPE, + .main_token = main_token, + .data = { + .lhs = addExtra(p, + (AstNodeIndex[]) { OPT(sentinel), + OPT(mods.align_node), + OPT(mods.addrspace_node) }, + 3), + .rhs = elem_type, + }, + }); + } + if (sentinel != 0) { + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_PTR_TYPE_SENTINEL, + .main_token = main_token, + .data = { .lhs = sentinel, .rhs = elem_type }, + }); + } + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_PTR_TYPE_ALIGNED, + .main_token = main_token, + .data = { .lhs = mods.align_node, .rhs = elem_type }, + }); } -static SmallSpan parseParamDeclList(Parser* p) { +static AstNodeIndex parsePrimaryExpr(Parser* p) { + switch (p->token_tags[p->tok_i]) { + case TOKEN_KEYWORD_ASM: + return parseAsmExpr(p); + case TOKEN_KEYWORD_IF: + return parseIfExpr(p); + case TOKEN_KEYWORD_BREAK: + return addNode( + &p->nodes, + (AstNodeItem) { + .tag = AST_NODE_BREAK, + .main_token = nextToken(p), + .data = { + .lhs = parseBreakLabel(p), + .rhs = parseExpr(p), + }, + }); + case TOKEN_KEYWORD_CONTINUE: + return addNode( + &p->nodes, + (AstNodeItem) { + .tag = AST_NODE_CONTINUE, + .main_token = nextToken(p), + .data = { + .lhs = parseBreakLabel(p), + .rhs = parseExpr(p), + }, + }); + case TOKEN_KEYWORD_COMPTIME: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_COMPTIME, + .main_token = nextToken(p), + .data = { .lhs = expectExpr(p), .rhs = 0 }, + }); + case TOKEN_KEYWORD_NOSUSPEND: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_NOSUSPEND, + .main_token = nextToken(p), + .data = { .lhs = expectExpr(p), .rhs = 0 }, + }); + case TOKEN_KEYWORD_RESUME: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_RESUME, + .main_token = nextToken(p), + .data = { .lhs = expectExpr(p), .rhs = 0 }, + }); + case TOKEN_KEYWORD_RETURN: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_RETURN, + .main_token = nextToken(p), + .data = { .lhs = parseExpr(p), .rhs = 0 }, + }); + case TOKEN_IDENTIFIER: + if (p->token_tags[p->tok_i + 1] == TOKEN_COLON) { + switch (p->token_tags[p->tok_i + 2]) { + case TOKEN_KEYWORD_INLINE: + p->tok_i += 3; + if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_FOR) + return parseForExpr(p); + if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_WHILE) + return parseWhileExpr(p); + fail(p, "expected for or while after inline"); + return 0; // tcc + case TOKEN_KEYWORD_FOR: + p->tok_i += 2; + return parseForExpr(p); + case TOKEN_KEYWORD_WHILE: + p->tok_i += 2; + return parseWhileExpr(p); + case TOKEN_L_BRACE: + p->tok_i += 2; + return parseBlock(p); + default: + return parseCurlySuffixExpr(p); + } + } else { + return parseCurlySuffixExpr(p); + } + case TOKEN_KEYWORD_WHILE: + return parseWhileExpr(p); + case TOKEN_KEYWORD_FOR: + return parseForExpr(p); + case TOKEN_KEYWORD_INLINE: + p->tok_i++; + if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_FOR) + return parseForExpr(p); + if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_WHILE) + return parseWhileExpr(p); + fail(p, "parsePrimaryExpr: inline without for/while"); + return 0; // tcc + case TOKEN_L_BRACE: + return parseBlock(p); + default: + return parseCurlySuffixExpr(p); + } + + return 0; // tcc +} + +static AstNodeIndex parseIfExpr(Parser* p) { + const AstTokenIndex if_token = eatToken(p, TOKEN_KEYWORD_IF); + if (if_token == null_token) + return null_node; + expectToken(p, TOKEN_L_PAREN); + const AstNodeIndex condition = expectExpr(p); + expectToken(p, TOKEN_R_PAREN); + parsePtrPayload(p); + + const AstNodeIndex then_expr = expectExpr(p); + + if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_IF_SIMPLE, + .main_token = if_token, + .data = { .lhs = condition, .rhs = then_expr }, + }); + } + + parsePayload(p); + const AstNodeIndex else_expr = expectExpr(p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_IF, + .main_token = if_token, + .data = { + .lhs = condition, + .rhs = addExtra(p, + (AstNodeIndex[]) { then_expr, else_expr }, 2), + }, + }); +} + +static AstNodeIndex parseBlock(Parser* p) { + const AstNodeIndex lbrace = eatToken(p, TOKEN_L_BRACE); + if (lbrace == null_token) + return null_node; CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch))) = initCleanupScratch(p); - // 0 = none, 1 = seen, 2 = nonfinal - int varargs = 0; - - while (true) { - if (eatToken(p, TOKEN_R_PAREN) != null_token) + while (1) { + if (p->token_tags[p->tok_i] == TOKEN_R_BRACE) break; - if (varargs == 1) - varargs = 2; - if (p->token_tags[p->tok_i] == TOKEN_ELLIPSIS3) { - p->tok_i++; - if (varargs == 0) - varargs = 1; - if (eatToken(p, TOKEN_R_PAREN) != null_token) - break; - expectToken(p, TOKEN_COMMA); - continue; - } - - const AstNodeIndex type_expr = expectParamDecl(p); - if (type_expr != 0) - SLICE_APPEND(AstNodeIndex, &p->scratch, type_expr); - - if (p->token_tags[p->tok_i] == TOKEN_COMMA) { - p->tok_i++; - continue; - } - expectToken(p, TOKEN_R_PAREN); - break; + // "const AstNodeIndex statement" once tinycc supports typeof_unqual + // (C23) + AstNodeIndex statement = expectStatement(p, true); + if (statement == 0) + break; + SLICE_APPEND(AstNodeIndex, &p->scratch, statement); } + expectToken(p, TOKEN_R_BRACE); + const bool semicolon = (p->token_tags[p->tok_i - 2] == TOKEN_SEMICOLON); - if (varargs == 2) { - fail(p, "varargs_nonfinal"); - } - - const uint32_t params_len = p->scratch.len - scratch_top.old_len; - switch (params_len) { + const uint32_t statements_len = p->scratch.len - scratch_top.old_len; + switch (statements_len) { case 0: - return (SmallSpan) { - .tag = SMALL_SPAN_ZERO_OR_ONE, - .payload = { .zero_or_one = 0 }, - }; + return addNode( + &p->nodes, + (AstNodeItem) { + .tag = AST_NODE_BLOCK_TWO, + .main_token = lbrace, + .data = { + .lhs = 0, + .rhs = 0, + }, + }); case 1: - return (SmallSpan) { - .tag = SMALL_SPAN_ZERO_OR_ONE, - .payload = { .zero_or_one = p->scratch.arr[scratch_top.old_len] }, - }; + return addNode( + &p->nodes, + (AstNodeItem) { + .tag = semicolon ? AST_NODE_BLOCK_TWO_SEMICOLON : AST_NODE_BLOCK_TWO, + .main_token = lbrace, + .data = { + .lhs = p->scratch.arr[scratch_top.old_len], + .rhs = 0, + }, + }); + case 2: + return addNode( + &p->nodes, + (AstNodeItem) { + .tag = semicolon ? AST_NODE_BLOCK_TWO_SEMICOLON : AST_NODE_BLOCK_TWO, + .main_token = lbrace, + .data = { + .lhs = p->scratch.arr[scratch_top.old_len], + .rhs = p->scratch.arr[scratch_top.old_len + 1], + }, + }); default:; - const AstSubRange span - = listToSpan(p, &p->scratch.arr[scratch_top.old_len], params_len); - return (SmallSpan) { - .tag = SMALL_SPAN_MULTI, - .payload = { .multi = span }, - }; - } -} - -static uint32_t reserveNode(Parser* p, AstNodeTag tag) { - astNodeListEnsureCapacity(&p->nodes, 1); - p->nodes.len++; - p->nodes.tags[p->nodes.len - 1] = tag; - return p->nodes.len - 1; -} - -static AstNodeIndex parseFnProto(Parser* p) { - AstTokenIndex fn_token = eatToken(p, TOKEN_KEYWORD_FN); - if (fn_token == null_token) - return null_node; - - AstNodeIndex fn_proto_index = reserveNode(p, AST_NODE_FN_PROTO); - - eatToken(p, TOKEN_IDENTIFIER); - - SmallSpan params = parseParamDeclList(p); - const AstNodeIndex align_expr = parseByteAlign(p); - const AstNodeIndex addrspace_expr = parseAddrSpace(p); - const AstNodeIndex section_expr = parseLinkSection(p); - const AstNodeIndex callconv_expr = parseCallconv(p); - eatToken(p, TOKEN_BANG); - - const AstNodeIndex return_type_expr = parseTypeExpr(p); - - if (align_expr == 0 && section_expr == 0 && callconv_expr == 0 - && addrspace_expr == 0) { - switch (params.tag) { - case SMALL_SPAN_ZERO_OR_ONE: - return setNode(p, fn_proto_index, - (AstNodeItem) { - .tag = AST_NODE_FN_PROTO_SIMPLE, - .main_token = fn_token, - .data = { - .lhs = params.payload.zero_or_one, - .rhs = return_type_expr, - }, - }); - case SMALL_SPAN_MULTI: - return setNode(p, fn_proto_index, - (AstNodeItem) { - .tag = AST_NODE_FN_PROTO_MULTI, - .main_token = fn_token, - .data = { - .lhs = addExtra(p, - (AstNodeIndex[]) { - params.payload.multi.start, - params.payload.multi.end }, - 2), - .rhs = return_type_expr, - }, - }); - } - } - - // Complex fn proto with align/section/callconv/addrspace - switch (params.tag) { - case SMALL_SPAN_ZERO_OR_ONE: - return setNode(p, fn_proto_index, + const AstSubRange span = listToSpan( + p, &p->scratch.arr[scratch_top.old_len], statements_len); + return addNode( + &p->nodes, (AstNodeItem) { - .tag = AST_NODE_FN_PROTO_ONE, - .main_token = fn_token, + .tag = semicolon ? AST_NODE_BLOCK_SEMICOLON : AST_NODE_BLOCK, + .main_token = lbrace, .data = { - .lhs = addExtra(p, - (AstNodeIndex[]) { - OPT(params.payload.zero_or_one), - OPT(align_expr), OPT(addrspace_expr), - OPT(section_expr), OPT(callconv_expr) }, - 5), - .rhs = return_type_expr, - }, - }); - case SMALL_SPAN_MULTI: - return setNode(p, fn_proto_index, - (AstNodeItem) { - .tag = AST_NODE_FN_PROTO, - .main_token = fn_token, - .data = { - .lhs = addExtra(p, - (AstNodeIndex[]) { - params.payload.multi.start, - params.payload.multi.end, - OPT(align_expr), OPT(addrspace_expr), - OPT(section_expr), OPT(callconv_expr) }, - 6), - .rhs = return_type_expr, + .lhs = span.start, + .rhs = span.end, }, }); } - return 0; // tcc -} -static AstTokenIndex parseBlockLabel(Parser* p) { - if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER - && p->token_tags[p->tok_i + 1] == TOKEN_COLON) { - const AstTokenIndex identifier = p->tok_i; - p->tok_i += 2; - return identifier; - } - return null_node; + return 0; } // forPrefix parses the for prefix: (expr, expr, ...) |captures|. @@ -1454,146 +2058,6 @@ static uint32_t forPrefix(Parser* p) { return inputs; } -static AstNodeIndex parseForExpr(Parser* p) { - const AstTokenIndex for_token = eatToken(p, TOKEN_KEYWORD_FOR); - if (for_token == null_token) - return null_node; - - const uint32_t scratch_top = p->scratch.len; - const uint32_t inputs = forPrefix(p); - - const AstNodeIndex then_expr = expectExpr(p); - - if (eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) { - parsePayload(p); - SLICE_APPEND(AstNodeIndex, &p->scratch, then_expr); - const AstNodeIndex else_expr = expectExpr(p); - SLICE_APPEND(AstNodeIndex, &p->scratch, else_expr); - const uint32_t total = p->scratch.len - scratch_top; - const AstSubRange span - = listToSpan(p, &p->scratch.arr[scratch_top], total); - p->scratch.len = scratch_top; - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_FOR, - .main_token = for_token, - .data = { - .lhs = span.start, - .rhs = ((uint32_t)inputs & 0x7FFFFFFF) | (1u << 31), - }, - }); - } - - if (inputs == 1) { - const AstNodeIndex input = p->scratch.arr[scratch_top]; - p->scratch.len = scratch_top; - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_FOR_SIMPLE, - .main_token = for_token, - .data = { .lhs = input, .rhs = then_expr }, - }); - } - - SLICE_APPEND(AstNodeIndex, &p->scratch, then_expr); - const uint32_t total = p->scratch.len - scratch_top; - const AstSubRange span - = listToSpan(p, &p->scratch.arr[scratch_top], total); - p->scratch.len = scratch_top; - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_FOR, - .main_token = for_token, - .data = { - .lhs = span.start, - .rhs = (uint32_t)inputs & 0x7FFFFFFF, - }, - }); -} - -static AstNodeIndex parseForStatement(Parser* p) { - const AstTokenIndex for_token = eatToken(p, TOKEN_KEYWORD_FOR); - if (for_token == null_token) - return null_node; - - const uint32_t scratch_top = p->scratch.len; - const uint32_t inputs = forPrefix(p); - - // Statement body: block or assign expr - AstNodeIndex then_body; - bool seen_semicolon = false; - const AstNodeIndex block = parseBlock(p); - if (block != 0) { - then_body = block; - } else { - then_body = parseAssignExpr(p); - if (then_body == 0) { - fail(p, "expected expression"); - } - if (eatToken(p, TOKEN_SEMICOLON) != null_token) - seen_semicolon = true; - } - - if (!seen_semicolon && eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) { - parsePayload(p); - SLICE_APPEND(AstNodeIndex, &p->scratch, then_body); - const AstNodeIndex else_body = expectBlockExprStatement(p); - SLICE_APPEND(AstNodeIndex, &p->scratch, else_body); - const uint32_t total = p->scratch.len - scratch_top; - const AstSubRange span - = listToSpan(p, &p->scratch.arr[scratch_top], total); - p->scratch.len = scratch_top; - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_FOR, - .main_token = for_token, - .data = { - .lhs = span.start, - .rhs = ((uint32_t)inputs & 0x7FFFFFFF) | (1u << 31), - }, - }); - } - - if (!seen_semicolon && block == 0) { - fail(p, "expected_semi_or_else"); - } - - if (inputs == 1) { - const AstNodeIndex input = p->scratch.arr[scratch_top]; - p->scratch.len = scratch_top; - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_FOR_SIMPLE, - .main_token = for_token, - .data = { .lhs = input, .rhs = then_body }, - }); - } - - SLICE_APPEND(AstNodeIndex, &p->scratch, then_body); - const uint32_t total = p->scratch.len - scratch_top; - const AstSubRange span - = listToSpan(p, &p->scratch.arr[scratch_top], total); - p->scratch.len = scratch_top; - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_FOR, - .main_token = for_token, - .data = { - .lhs = span.start, - .rhs = (uint32_t)inputs & 0x7FFFFFFF, - }, - }); -} - -static AstNodeIndex parseWhileContinueExpr(Parser* p) { - if (eatToken(p, TOKEN_COLON) == null_token) - return null_node; - expectToken(p, TOKEN_L_PAREN); - const AstNodeIndex expr = parseAssignExpr(p); - expectToken(p, TOKEN_R_PAREN); - return expr; -} - static AstNodeIndex parseWhileExpr(Parser* p) { const AstTokenIndex while_token = eatToken(p, TOKEN_KEYWORD_WHILE); if (while_token == null_token) @@ -1644,161 +2108,25 @@ static AstNodeIndex parseWhileExpr(Parser* p) { }); } -static AstNodeIndex parseWhileStatement(Parser* p) { - const AstTokenIndex while_token = eatToken(p, TOKEN_KEYWORD_WHILE); - if (while_token == null_token) - return null_node; - - expectToken(p, TOKEN_L_PAREN); - const AstNodeIndex condition = expectExpr(p); - expectToken(p, TOKEN_R_PAREN); - parsePtrPayload(p); - - const AstNodeIndex cont_expr = parseWhileContinueExpr(p); - - // Statement body: block, or assign expr - AstNodeIndex body; - bool seen_semicolon = false; - const AstNodeIndex block = parseBlock(p); - if (block != 0) { - body = block; - } else { - body = parseAssignExpr(p); - if (body == 0) { - fail(p, "expected expression"); - } - if (eatToken(p, TOKEN_SEMICOLON) != null_token) - seen_semicolon = true; - } - - if (seen_semicolon || eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { - if (!seen_semicolon && block == 0) { - fail(p, "expected_semi_or_else"); - } - if (cont_expr != 0) { - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_WHILE_CONT, - .main_token = while_token, - .data = { - .lhs = condition, - .rhs = addExtra(p, - (AstNodeIndex[]) { cont_expr, body }, 2), - }, - }); - } - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_WHILE_SIMPLE, - .main_token = while_token, - .data = { .lhs = condition, .rhs = body }, - }); - } - - parsePayload(p); - const AstNodeIndex else_body = expectBlockExprStatement(p); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_WHILE, - .main_token = while_token, - .data = { - .lhs = condition, - .rhs = addExtra(p, - (AstNodeIndex[]) { OPT(cont_expr), body, else_body }, - 3), - }, - }); -} - -static AstNodeIndex parseLoopStatement(Parser* p) { - const AstTokenIndex inline_token = eatToken(p, TOKEN_KEYWORD_INLINE); - - const AstNodeIndex for_statement = parseForStatement(p); - if (for_statement != 0) - return for_statement; - - const AstNodeIndex while_statement = parseWhileStatement(p); - if (while_statement != 0) - return while_statement; - - if (inline_token == null_token) - return null_node; - - fail(p, "seen 'inline', there should have been a 'for' or 'while'"); - return 0; // tcc -} - -static AstNodeIndex parseVarDeclProto(Parser* p) { - AstTokenIndex mut_token; - if ((mut_token = eatToken(p, TOKEN_KEYWORD_CONST)) == null_token) - if ((mut_token = eatToken(p, TOKEN_KEYWORD_VAR)) == null_token) - return null_node; - - expectToken(p, TOKEN_IDENTIFIER); - const AstNodeIndex type_node - = eatToken(p, TOKEN_COLON) == null_token ? 0 : parseTypeExpr(p); - const AstNodeIndex align_node = parseByteAlign(p); - const AstNodeIndex addrspace_node = parseAddrSpace(p); - const AstNodeIndex section_node = parseLinkSection(p); - - if (section_node == 0 && addrspace_node == 0) { - if (align_node == 0) { - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_SIMPLE_VAR_DECL, - .main_token = mut_token, - .data = { .lhs = type_node, .rhs = 0 }, - }); - } - if (type_node == 0) { - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_ALIGNED_VAR_DECL, - .main_token = mut_token, - .data = { .lhs = align_node, .rhs = 0 }, - }); - } - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_LOCAL_VAR_DECL, - .main_token = mut_token, - .data = { - .lhs = addExtra(p, - (AstNodeIndex[]) { type_node, align_node }, 2), - .rhs = 0, - }, - }); - } - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_GLOBAL_VAR_DECL, - .main_token = mut_token, - .data = { - .lhs = addExtra(p, - (AstNodeIndex[]) { OPT(type_node), OPT(align_node), - OPT(addrspace_node), OPT(section_node) }, - 4), - .rhs = 0, - }, - }); -} - -static AstTokenIndex parseBreakLabel(Parser* p) { +static AstNodeIndex parseWhileContinueExpr(Parser* p) { if (eatToken(p, TOKEN_COLON) == null_token) - return null_token; - return expectToken(p, TOKEN_IDENTIFIER); + return null_node; + expectToken(p, TOKEN_L_PAREN); + const AstNodeIndex expr = parseAssignExpr(p); + expectToken(p, TOKEN_R_PAREN); + return expr; } -// parseFieldInit tries to parse .field_name = expr; returns 0 if not a -// field init -static AstNodeIndex parseFieldInit(Parser* p) { - if (p->token_tags[p->tok_i] == TOKEN_PERIOD - && p->token_tags[p->tok_i + 1] == TOKEN_IDENTIFIER - && p->token_tags[p->tok_i + 2] == TOKEN_EQUAL) { - p->tok_i += 3; - return expectExpr(p); - } - return null_node; +static AstNodeIndex parseCurlySuffixExpr(Parser* p) { + const AstNodeIndex lhs = parseTypeExpr(p); + if (lhs == 0) + return null_node; + + const AstTokenIndex lbrace = eatToken(p, TOKEN_L_BRACE); + if (lbrace == null_token) + return lhs; + + return parseInitList(p, lhs, lbrace); } // parseInitList parses the contents of { ... } for struct/array init. @@ -1984,267 +2312,317 @@ static AstNodeIndex parseInitList( } } -static AstNodeIndex parseCurlySuffixExpr(Parser* p) { - const AstNodeIndex lhs = parseTypeExpr(p); - if (lhs == 0) +static AstNodeIndex parseErrorUnionExpr(Parser* p) { + const AstNodeIndex suffix_expr = parseSuffixExpr(p); + if (suffix_expr == 0) return null_node; - const AstTokenIndex lbrace = eatToken(p, TOKEN_L_BRACE); - if (lbrace == null_token) - return lhs; + const AstNodeIndex bang = eatToken(p, TOKEN_BANG); + if (bang == null_token) + return suffix_expr; - return parseInitList(p, lhs, lbrace); + return addNode( + &p->nodes, + (AstNodeItem) { + .tag = AST_NODE_ERROR_UNION, + .main_token = bang, + .data = { + .lhs = suffix_expr, + .rhs = parseTypeExpr(p), + }, + }); } -typedef struct { - int8_t prec; - AstNodeTag tag; - enum { - ASSOC_LEFT, - ASSOC_NONE, - } assoc; -} OperInfo; - -static uint32_t tokenTagLexemeLen(TokenizerTag tag) { - switch (tag) { - case TOKEN_PLUS: - case TOKEN_MINUS: - case TOKEN_ASTERISK: - case TOKEN_SLASH: - case TOKEN_PERCENT: - case TOKEN_AMPERSAND: - case TOKEN_CARET: - case TOKEN_PIPE: - case TOKEN_ANGLE_BRACKET_LEFT: - case TOKEN_ANGLE_BRACKET_RIGHT: - return 1; - case TOKEN_PLUS_PLUS: - case TOKEN_MINUS_PERCENT: - case TOKEN_PLUS_PERCENT: - case TOKEN_MINUS_PIPE: - case TOKEN_PLUS_PIPE: - case TOKEN_ASTERISK_ASTERISK: - case TOKEN_ASTERISK_PERCENT: - case TOKEN_ASTERISK_PIPE: - case TOKEN_PIPE_PIPE: - case TOKEN_EQUAL_EQUAL: - case TOKEN_BANG_EQUAL: - case TOKEN_ANGLE_BRACKET_LEFT_EQUAL: - case TOKEN_ANGLE_BRACKET_RIGHT_EQUAL: - case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT: - case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT: - return 2; - case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE: - return 3; - case TOKEN_KEYWORD_OR: - return 2; - case TOKEN_KEYWORD_AND: - return 3; - case TOKEN_KEYWORD_ORELSE: - return 6; - case TOKEN_KEYWORD_CATCH: - return 5; - default: - return 0; +static AstNodeIndex parseSuffixExpr(Parser* p) { + if (eatToken(p, TOKEN_KEYWORD_ASYNC) != null_token) { + fail(p, "async not supported"); } -} -static OperInfo operTable(TokenizerTag tok_tag) { - switch (tok_tag) { - case TOKEN_KEYWORD_OR: - return (OperInfo) { .prec = 10, .tag = AST_NODE_BOOL_OR }; - case TOKEN_KEYWORD_AND: - return (OperInfo) { .prec = 20, .tag = AST_NODE_BOOL_AND }; - - case TOKEN_EQUAL_EQUAL: - return (OperInfo) { - .prec = 30, .tag = AST_NODE_EQUAL_EQUAL, .assoc = ASSOC_NONE - }; - case TOKEN_BANG_EQUAL: - return (OperInfo) { - .prec = 30, .tag = AST_NODE_BANG_EQUAL, .assoc = ASSOC_NONE - }; - case TOKEN_ANGLE_BRACKET_LEFT: - return (OperInfo) { - .prec = 30, .tag = AST_NODE_LESS_THAN, .assoc = ASSOC_NONE - }; - case TOKEN_ANGLE_BRACKET_RIGHT: - return (OperInfo) { - .prec = 30, .tag = AST_NODE_GREATER_THAN, .assoc = ASSOC_NONE - }; - case TOKEN_ANGLE_BRACKET_LEFT_EQUAL: - return (OperInfo) { - .prec = 30, .tag = AST_NODE_LESS_OR_EQUAL, .assoc = ASSOC_NONE - }; - case TOKEN_ANGLE_BRACKET_RIGHT_EQUAL: - return (OperInfo) { - .prec = 30, .tag = AST_NODE_GREATER_OR_EQUAL, .assoc = ASSOC_NONE - }; - - case TOKEN_AMPERSAND: - return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_AND }; - case TOKEN_CARET: - return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_XOR }; - case TOKEN_PIPE: - return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_OR }; - case TOKEN_KEYWORD_ORELSE: - return (OperInfo) { .prec = 40, .tag = AST_NODE_ORELSE }; - case TOKEN_KEYWORD_CATCH: - return (OperInfo) { .prec = 40, .tag = AST_NODE_CATCH }; - - case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT: - return (OperInfo) { .prec = 50, .tag = AST_NODE_SHL }; - case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE: - return (OperInfo) { .prec = 50, .tag = AST_NODE_SHL_SAT }; - case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT: - return (OperInfo) { .prec = 50, .tag = AST_NODE_SHR }; - - case TOKEN_PLUS: - return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD }; - case TOKEN_MINUS: - return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB }; - case TOKEN_PLUS_PLUS: - return (OperInfo) { .prec = 60, .tag = AST_NODE_ARRAY_CAT }; - case TOKEN_PLUS_PERCENT: - return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD_WRAP }; - case TOKEN_MINUS_PERCENT: - return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB_WRAP }; - case TOKEN_PLUS_PIPE: - return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD_SAT }; - case TOKEN_MINUS_PIPE: - return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB_SAT }; - - case TOKEN_PIPE_PIPE: - return (OperInfo) { .prec = 70, .tag = AST_NODE_MERGE_ERROR_SETS }; - case TOKEN_ASTERISK: - return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL }; - case TOKEN_SLASH: - return (OperInfo) { .prec = 70, .tag = AST_NODE_DIV }; - case TOKEN_PERCENT: - return (OperInfo) { .prec = 70, .tag = AST_NODE_MOD }; - case TOKEN_ASTERISK_ASTERISK: - return (OperInfo) { .prec = 70, .tag = AST_NODE_ARRAY_MULT }; - case TOKEN_ASTERISK_PERCENT: - return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL_WRAP }; - case TOKEN_ASTERISK_PIPE: - return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL_SAT }; - - default: - return (OperInfo) { .prec = -1, .tag = AST_NODE_ROOT }; - } -} - -static AstNodeIndex parseExprPrecedence(Parser* p, int32_t min_prec) { - assert(min_prec >= 0); - - AstNodeIndex node = parsePrefixExpr(p); - if (node == 0) - return null_node; - - int8_t banned_prec = -1; + AstNodeIndex res = parsePrimaryTypeExpr(p); + if (res == 0) + return res; while (true) { - const TokenizerTag tok_tag = p->token_tags[p->tok_i]; - const OperInfo info = operTable(tok_tag); - if (info.prec < min_prec) - break; - - if (info.prec == banned_prec) { - fail(p, "chained comparison operators"); + const AstNodeIndex suffix_op = parseSuffixOp(p, res); + if (suffix_op != 0) { + res = suffix_op; + continue; } + const AstTokenIndex lparen = eatToken(p, TOKEN_L_PAREN); + if (lparen == null_token) + return res; - const AstTokenIndex oper_token = nextToken(p); - if (tok_tag == TOKEN_KEYWORD_CATCH) - parsePayload(p); - const AstNodeIndex rhs = parseExprPrecedence(p, info.prec + 1); - if (rhs == 0) { - fail(p, "expected expression"); - } - - { - const uint32_t tok_len = tokenTagLexemeLen(tok_tag); - if (tok_len > 0) { - const uint32_t tok_start = p->token_starts[oper_token]; - const char char_before = p->source[tok_start - 1]; - const char char_after = p->source[tok_start + tok_len]; - if (tok_tag == TOKEN_AMPERSAND && char_after == '&') { - fail(p, "invalid ampersand ampersand"); - } else if (isspace((unsigned char)char_before) - != isspace((unsigned char)char_after)) { - fail(p, "mismatched binary op whitespace"); - } + CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch))) + = initCleanupScratch(p); + while (true) { + if (eatToken(p, TOKEN_R_PAREN) != null_token) + break; + const AstNodeIndex arg = expectExpr(p); + SLICE_APPEND(AstNodeIndex, &p->scratch, arg); + if (p->token_tags[p->tok_i] == TOKEN_COMMA) { + p->tok_i++; + continue; } + expectToken(p, TOKEN_R_PAREN); + break; } - node = addNode( + const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA; + const uint32_t params_len = p->scratch.len - scratch_top.old_len; + switch (params_len) { + case 0: + res = addNode( &p->nodes, (AstNodeItem) { - .tag = info.tag, - .main_token = oper_token, + .tag = comma ? AST_NODE_CALL_ONE_COMMA : AST_NODE_CALL_ONE, + .main_token = lparen, .data = { - .lhs = node, - .rhs = rhs, + .lhs = res, + .rhs = 0, }, }); - - if (info.assoc == ASSOC_NONE) - banned_prec = info.prec; - } - - return node; -} - -static AstNodeIndex parseExpr(Parser* p) { return parseExprPrecedence(p, 0); } - -static AstNodeIndex expectExpr(Parser* p) { - const AstNodeIndex node = parseExpr(p); - if (node == 0) { - fail(p, "expected expression"); - } - return node; -} - -static AstNodeIndex parseAsmOutputItem(Parser* p) { - if (p->token_tags[p->tok_i] == TOKEN_L_BRACKET) { - p->tok_i++; // [ - const AstTokenIndex ident = expectToken(p, TOKEN_IDENTIFIER); - expectToken(p, TOKEN_R_BRACKET); - expectToken(p, TOKEN_STRING_LITERAL); - expectToken(p, TOKEN_L_PAREN); - AstNodeIndex type_expr = 0; - if (eatToken(p, TOKEN_ARROW) != null_token) { - type_expr = parseTypeExpr(p); - } else { - expectToken(p, TOKEN_IDENTIFIER); + break; + case 1: + res = addNode( + &p->nodes, + (AstNodeItem) { + .tag = comma ? AST_NODE_CALL_ONE_COMMA : AST_NODE_CALL_ONE, + .main_token = lparen, + .data = { + .lhs = res, + .rhs = p->scratch.arr[scratch_top.old_len], + }, + }); + break; + default:; + const AstSubRange span = listToSpan( + p, &p->scratch.arr[scratch_top.old_len], params_len); + res = addNode( + &p->nodes, + (AstNodeItem) { + .tag = comma ? AST_NODE_CALL_COMMA : AST_NODE_CALL, + .main_token = lparen, + .data = { + .lhs = res, + .rhs = addExtra(p, (AstNodeIndex[]) { + span.start, + span.end, + }, 2), + }, + }); + break; } - const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_ASM_OUTPUT, - .main_token = ident, - .data = { .lhs = type_expr, .rhs = rparen }, - }); } - return null_node; } -static AstNodeIndex parseAsmInputItem(Parser* p) { - if (p->token_tags[p->tok_i] == TOKEN_L_BRACKET) { - p->tok_i++; // [ - const AstTokenIndex ident = expectToken(p, TOKEN_IDENTIFIER); - expectToken(p, TOKEN_R_BRACKET); - expectToken(p, TOKEN_STRING_LITERAL); - expectToken(p, TOKEN_L_PAREN); - const AstNodeIndex operand = expectExpr(p); +static AstNodeIndex parsePrimaryTypeExpr(Parser* p) { + const TokenizerTag tok = p->token_tags[p->tok_i]; + switch (tok) { + case TOKEN_CHAR_LITERAL: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_CHAR_LITERAL, + .main_token = nextToken(p), + .data = {}, + }); + case TOKEN_NUMBER_LITERAL: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_NUMBER_LITERAL, + .main_token = nextToken(p), + .data = {}, + }); + case TOKEN_KEYWORD_UNREACHABLE: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_UNREACHABLE_LITERAL, + .main_token = nextToken(p), + .data = {}, + }); + case TOKEN_KEYWORD_ANYFRAME: + fail(p, "unsupported primary type expression"); + case TOKEN_STRING_LITERAL: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_STRING_LITERAL, + .main_token = nextToken(p), + .data = {}, + }); + case TOKEN_BUILTIN: + return parseBuiltinCall(p); + case TOKEN_KEYWORD_FN: + return parseFnProto(p); + case TOKEN_KEYWORD_IF: + return parseIfExpr(p); + case TOKEN_KEYWORD_SWITCH: + return parseSwitchExpr(p); + case TOKEN_KEYWORD_EXTERN: + case TOKEN_KEYWORD_PACKED: + // extern/packed can precede struct/union/enum + switch (p->token_tags[p->tok_i + 1]) { + case TOKEN_KEYWORD_STRUCT: + case TOKEN_KEYWORD_UNION: + case TOKEN_KEYWORD_ENUM: + p->tok_i++; // consume extern/packed + return parseContainerDeclAuto(p); + default: + fail(p, "unsupported primary type expression"); + } + case TOKEN_KEYWORD_STRUCT: + case TOKEN_KEYWORD_OPAQUE: + case TOKEN_KEYWORD_ENUM: + case TOKEN_KEYWORD_UNION: + return parseContainerDeclAuto(p); + case TOKEN_KEYWORD_COMPTIME: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_COMPTIME, + .main_token = nextToken(p), + .data = { .lhs = parseTypeExpr(p), .rhs = 0 }, + }); + case TOKEN_MULTILINE_STRING_LITERAL_LINE: { + const AstTokenIndex first = nextToken(p); + AstTokenIndex last = first; + while (p->token_tags[p->tok_i] == TOKEN_MULTILINE_STRING_LITERAL_LINE) + last = nextToken(p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_MULTILINE_STRING_LITERAL, + .main_token = first, + .data = { .lhs = first, .rhs = last }, + }); + } + case TOKEN_IDENTIFIER: + if (p->token_tags[p->tok_i + 1] == TOKEN_COLON) { + switch (p->token_tags[p->tok_i + 2]) { + case TOKEN_L_BRACE: { + // Labeled block: label: { ... } + nextToken(p); // consume label + nextToken(p); // consume ':' + return parseBlock(p); + } + case TOKEN_KEYWORD_WHILE: + return parseLabeledStatement(p); + case TOKEN_KEYWORD_FOR: + return parseLabeledStatement(p); + default: + break; + } + } + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_IDENTIFIER, + .main_token = nextToken(p), + .data = {}, + }); + case TOKEN_KEYWORD_FOR: + return parseForExpr(p); + case TOKEN_KEYWORD_WHILE: + return parseWhileExpr(p); + case TOKEN_KEYWORD_INLINE: + case TOKEN_PERIOD: + switch (p->token_tags[p->tok_i + 1]) { + case TOKEN_IDENTIFIER: { + const AstTokenIndex dot = nextToken(p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_ENUM_LITERAL, + .main_token = nextToken(p), + .data = { .lhs = dot, .rhs = 0 }, + }); + } + case TOKEN_L_BRACE: { + // Anonymous struct/array init: .{ ... } + const AstTokenIndex lbrace = p->tok_i + 1; + p->tok_i = lbrace + 1; + return parseInitList(p, null_node, lbrace); + } + default: + fail(p, "unsupported period suffix"); + } + return 0; // tcc + case TOKEN_KEYWORD_ERROR: + switch (p->token_tags[p->tok_i + 1]) { + case TOKEN_PERIOD: { + const AstTokenIndex error_token = nextToken(p); + const AstTokenIndex dot = nextToken(p); + const AstTokenIndex value = expectToken(p, TOKEN_IDENTIFIER); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_ERROR_VALUE, + .main_token = error_token, + .data = { .lhs = dot, .rhs = value }, + }); + } + case TOKEN_L_BRACE: { + const AstTokenIndex error_token = nextToken(p); + const AstTokenIndex lbrace = nextToken(p); + while (p->token_tags[p->tok_i] != TOKEN_R_BRACE) + p->tok_i++; + const AstTokenIndex rbrace = nextToken(p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_ERROR_SET_DECL, + .main_token = error_token, + .data = { .lhs = lbrace, .rhs = rbrace }, + }); + } + default: { + const AstTokenIndex main_token = nextToken(p); + const AstTokenIndex period = eatToken(p, TOKEN_PERIOD); + if (period == null_token) { + fail(p, "expected '.'"); + } + const AstTokenIndex identifier = eatToken(p, TOKEN_IDENTIFIER); + if (identifier == null_token) { + fail(p, "expected identifier"); + } + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_ERROR_VALUE, + .main_token = main_token, + .data = { .lhs = period, .rhs = identifier }, + }); + } + } + case TOKEN_L_PAREN: { + const AstTokenIndex lparen = nextToken(p); + const AstNodeIndex inner = expectExpr(p); const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN); return addNode(&p->nodes, (AstNodeItem) { - .tag = AST_NODE_ASM_INPUT, - .main_token = ident, - .data = { .lhs = operand, .rhs = rparen }, + .tag = AST_NODE_GROUPED_EXPRESSION, + .main_token = lparen, + .data = { .lhs = inner, .rhs = rparen }, }); } - return null_node; + default: + return null_node; + } +} + +static AstNodeIndex parseSwitchExpr(Parser* p) { + const AstTokenIndex switch_token = eatToken(p, TOKEN_KEYWORD_SWITCH); + if (switch_token == null_token) + return null_node; + + expectToken(p, TOKEN_L_PAREN); + const AstNodeIndex operand = expectExpr(p); + expectToken(p, TOKEN_R_PAREN); + expectToken(p, TOKEN_L_BRACE); + + const AstSubRange span = parseSwitchProngList(p); + const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA; + return addNode(&p->nodes, + (AstNodeItem) { + .tag = comma ? AST_NODE_SWITCH_COMMA : AST_NODE_SWITCH, + .main_token = switch_token, + .data = { + .lhs = operand, + .rhs = addExtra(p, + (AstNodeIndex[]) { span.start, span.end }, 2), + }, + }); } static AstNodeIndex parseAsmExpr(Parser* p) { @@ -2361,23 +2739,138 @@ static AstNodeIndex parseAsmExpr(Parser* p) { }); } -static AstNodeIndex parseSwitchItem(Parser* p) { - const AstNodeIndex expr = parseExpr(p); - if (expr == 0) - return null_node; - if (p->token_tags[p->tok_i] == TOKEN_ELLIPSIS3) { - const AstTokenIndex range_tok = nextToken(p); - const AstNodeIndex range_end = expectExpr(p); +static AstNodeIndex parseAsmOutputItem(Parser* p) { + if (p->token_tags[p->tok_i] == TOKEN_L_BRACKET) { + p->tok_i++; // [ + const AstTokenIndex ident = expectToken(p, TOKEN_IDENTIFIER); + expectToken(p, TOKEN_R_BRACKET); + expectToken(p, TOKEN_STRING_LITERAL); + expectToken(p, TOKEN_L_PAREN); + AstNodeIndex type_expr = 0; + if (eatToken(p, TOKEN_ARROW) != null_token) { + type_expr = parseTypeExpr(p); + } else { + expectToken(p, TOKEN_IDENTIFIER); + } + const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN); return addNode(&p->nodes, (AstNodeItem) { - .tag = AST_NODE_SWITCH_RANGE, - .main_token = range_tok, - .data = { .lhs = expr, .rhs = range_end }, + .tag = AST_NODE_ASM_OUTPUT, + .main_token = ident, + .data = { .lhs = type_expr, .rhs = rparen }, }); } + return null_node; +} + +static AstNodeIndex parseAsmInputItem(Parser* p) { + if (p->token_tags[p->tok_i] == TOKEN_L_BRACKET) { + p->tok_i++; // [ + const AstTokenIndex ident = expectToken(p, TOKEN_IDENTIFIER); + expectToken(p, TOKEN_R_BRACKET); + expectToken(p, TOKEN_STRING_LITERAL); + expectToken(p, TOKEN_L_PAREN); + const AstNodeIndex operand = expectExpr(p); + const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_ASM_INPUT, + .main_token = ident, + .data = { .lhs = operand, .rhs = rparen }, + }); + } + return null_node; +} + +static AstTokenIndex parseBreakLabel(Parser* p) { + if (eatToken(p, TOKEN_COLON) == null_token) + return null_token; + return expectToken(p, TOKEN_IDENTIFIER); +} + +static AstTokenIndex parseBlockLabel(Parser* p) { + if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER + && p->token_tags[p->tok_i + 1] == TOKEN_COLON) { + const AstTokenIndex identifier = p->tok_i; + p->tok_i += 2; + return identifier; + } + return null_node; +} + +// parseFieldInit tries to parse .field_name = expr; returns 0 if not a +// field init +static AstNodeIndex parseFieldInit(Parser* p) { + if (p->token_tags[p->tok_i] == TOKEN_PERIOD + && p->token_tags[p->tok_i + 1] == TOKEN_IDENTIFIER + && p->token_tags[p->tok_i + 2] == TOKEN_EQUAL) { + p->tok_i += 3; + return expectExpr(p); + } + return null_node; +} + +static AstNodeIndex parseLinkSection(Parser* p) { + if (eatToken(p, TOKEN_KEYWORD_LINKSECTION) == null_token) + return null_node; + expectToken(p, TOKEN_L_PAREN); + const AstNodeIndex expr = expectExpr(p); + expectToken(p, TOKEN_R_PAREN); return expr; } +static AstNodeIndex parseCallconv(Parser* p) { + if (eatToken(p, TOKEN_KEYWORD_CALLCONV) == null_token) + return null_node; + expectToken(p, TOKEN_L_PAREN); + const AstNodeIndex expr = expectExpr(p); + expectToken(p, TOKEN_R_PAREN); + return expr; +} + +static AstNodeIndex parseAddrSpace(Parser* p) { + if (eatToken(p, TOKEN_KEYWORD_ADDRSPACE) == null_token) + return null_node; + expectToken(p, TOKEN_L_PAREN); + const AstNodeIndex expr = expectExpr(p); + expectToken(p, TOKEN_R_PAREN); + return expr; +} + +static AstNodeIndex expectParamDecl(Parser* p) { + eatDocComments(p); + eatToken(p, TOKEN_KEYWORD_COMPTIME); + eatToken(p, TOKEN_KEYWORD_NOALIAS); + if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER + && p->token_tags[p->tok_i + 1] == TOKEN_COLON) + p->tok_i += 2; + if (eatToken(p, TOKEN_KEYWORD_ANYTYPE) != null_token) + return 0; + return parseTypeExpr(p); +} + +static void parsePayload(Parser* p) { + if (eatToken(p, TOKEN_PIPE) == null_token) + return; + expectToken(p, TOKEN_IDENTIFIER); + expectToken(p, TOKEN_PIPE); +} + +static void parsePtrPayload(Parser* p) { + if (eatToken(p, TOKEN_PIPE) == null_token) + return; + while (true) { + eatToken(p, TOKEN_ASTERISK); + expectToken(p, TOKEN_IDENTIFIER); + if (p->token_tags[p->tok_i] == TOKEN_COMMA) { + p->tok_i++; + continue; + } + break; + } + expectToken(p, TOKEN_PIPE); +} + static AstNodeIndex parseSwitchProng(Parser* p) { const uint32_t items_old_len = p->scratch.len; @@ -2438,6 +2931,287 @@ static AstNodeIndex parseSwitchProng(Parser* p) { return case_node; } +static AstNodeIndex parseSwitchItem(Parser* p) { + const AstNodeIndex expr = parseExpr(p); + if (expr == 0) + return null_node; + if (p->token_tags[p->tok_i] == TOKEN_ELLIPSIS3) { + const AstTokenIndex range_tok = nextToken(p); + const AstNodeIndex range_end = expectExpr(p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_SWITCH_RANGE, + .main_token = range_tok, + .data = { .lhs = expr, .rhs = range_end }, + }); + } + return expr; +} + +static PtrModifiers parsePtrModifiers(Parser* p) { + PtrModifiers mods = {}; + + while (true) { + switch (p->token_tags[p->tok_i]) { + case TOKEN_KEYWORD_CONST: + case TOKEN_KEYWORD_VOLATILE: + case TOKEN_KEYWORD_ALLOWZERO: + p->tok_i++; + continue; + case TOKEN_KEYWORD_ALIGN: + p->tok_i++; + expectToken(p, TOKEN_L_PAREN); + mods.align_node = expectExpr(p); + if (eatToken(p, TOKEN_COLON) != null_token) { + mods.bit_range_start = expectExpr(p); + expectToken(p, TOKEN_COLON); + mods.bit_range_end = expectExpr(p); + } + expectToken(p, TOKEN_R_PAREN); + continue; + case TOKEN_KEYWORD_ADDRSPACE: + p->tok_i++; + expectToken(p, TOKEN_L_PAREN); + mods.addrspace_node = expectExpr(p); + expectToken(p, TOKEN_R_PAREN); + continue; + default: + return mods; + } + } +} + +static AstNodeIndex parseSuffixOp(Parser* p, AstNodeIndex lhs) { + const TokenizerTag tok = p->token_tags[p->tok_i]; + switch (tok) { + case TOKEN_L_BRACKET: { + const AstTokenIndex lbracket = nextToken(p); + const AstNodeIndex index_expr = expectExpr(p); + switch (p->token_tags[p->tok_i]) { + case TOKEN_R_BRACKET: + p->tok_i++; + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_ARRAY_ACCESS, + .main_token = lbracket, + .data = { .lhs = lhs, .rhs = index_expr }, + }); + case TOKEN_ELLIPSIS2: { + p->tok_i++; // consume .. + const AstNodeIndex end_expr = parseExpr(p); + if (eatToken(p, TOKEN_COLON) != null_token) { + const AstNodeIndex sentinel = expectExpr(p); + expectToken(p, TOKEN_R_BRACKET); + // end_expr 0 means "no end" — encode as ~0 for + // OptionalIndex.none + const AstNodeIndex opt_end + = end_expr == 0 ? ~(AstNodeIndex)0 : end_expr; + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_SLICE_SENTINEL, + .main_token = lbracket, + .data = { + .lhs = lhs, + .rhs = addExtra(p, + (AstNodeIndex[]) { + index_expr, opt_end, sentinel }, + 3), + }, + }); + } + expectToken(p, TOKEN_R_BRACKET); + if (end_expr == 0) { + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_SLICE_OPEN, + .main_token = lbracket, + .data = { .lhs = lhs, .rhs = index_expr }, + }); + } + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_SLICE, + .main_token = lbracket, + .data = { + .lhs = lhs, + .rhs = addExtra(p, + (AstNodeIndex[]) { index_expr, end_expr }, 2), + }, + }); + } + default: + fail(p, "parseSuffixOp: expected ] or .. after index expr"); + } + return 0; // tcc + } + case TOKEN_PERIOD_ASTERISK: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_DEREF, + .main_token = nextToken(p), + .data = { .lhs = lhs, .rhs = 0 }, + }); + case TOKEN_INVALID_PERIODASTERISKS: + fail(p, "unsupported suffix op"); + case TOKEN_PERIOD: + if (p->token_tags[p->tok_i + 1] == TOKEN_IDENTIFIER) { + const AstTokenIndex dot = nextToken(p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_FIELD_ACCESS, + .main_token = dot, + .data = { .lhs = lhs, .rhs = nextToken(p) }, + }); + } + if (p->token_tags[p->tok_i + 1] == TOKEN_ASTERISK) { + const AstTokenIndex dot = nextToken(p); + nextToken(p); // consume the * + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_DEREF, + .main_token = dot, + .data = { .lhs = lhs, .rhs = 0 }, + }); + } + if (p->token_tags[p->tok_i + 1] == TOKEN_QUESTION_MARK) { + const AstTokenIndex dot = nextToken(p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_UNWRAP_OPTIONAL, + .main_token = dot, + .data = { .lhs = lhs, .rhs = nextToken(p) }, + }); + } + fail(p, "parseSuffixOp: unsupported period suffix"); + return 0; // tcc + default: + return null_node; + } +} + +static AstNodeIndex parseContainerDeclAuto(Parser* p) { + const AstTokenIndex main_token = nextToken(p); + AstNodeIndex arg_expr = null_node; + switch (p->token_tags[main_token]) { + case TOKEN_KEYWORD_OPAQUE: + break; + case TOKEN_KEYWORD_STRUCT: + case TOKEN_KEYWORD_ENUM: + if (eatToken(p, TOKEN_L_PAREN) != null_token) { + arg_expr = expectExpr(p); + expectToken(p, TOKEN_R_PAREN); + } + break; + case TOKEN_KEYWORD_UNION: + if (eatToken(p, TOKEN_L_PAREN) != null_token) { + if (eatToken(p, TOKEN_KEYWORD_ENUM) != null_token) { + if (eatToken(p, TOKEN_L_PAREN) != null_token) { + const AstNodeIndex enum_tag_expr = expectExpr(p); + expectToken(p, TOKEN_R_PAREN); + expectToken(p, TOKEN_R_PAREN); + expectToken(p, TOKEN_L_BRACE); + const Members members = parseContainerMembers(p); + const AstSubRange members_span = membersToSpan(members, p); + expectToken(p, TOKEN_R_BRACE); + return addNode( + &p->nodes, + (AstNodeItem) { + .tag = members.trailing + ? AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING + : AST_NODE_TAGGED_UNION_ENUM_TAG, + .main_token = main_token, + .data = { + .lhs = enum_tag_expr, + .rhs = addExtra(p, + (AstNodeIndex[]) { + members_span.start, + members_span.end }, + 2), + }, + }); + } + expectToken(p, TOKEN_R_PAREN); + expectToken(p, TOKEN_L_BRACE); + const Members members = parseContainerMembers(p); + expectToken(p, TOKEN_R_BRACE); + if (members.len <= 2) { + return addNode(&p->nodes, + (AstNodeItem) { + .tag = members.trailing + ? AST_NODE_TAGGED_UNION_TWO_TRAILING + : AST_NODE_TAGGED_UNION_TWO, + .main_token = main_token, + .data = { .lhs = members.lhs, .rhs = members.rhs }, + }); + } + const AstSubRange span = membersToSpan(members, p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = members.trailing + ? AST_NODE_TAGGED_UNION_TRAILING + : AST_NODE_TAGGED_UNION, + .main_token = main_token, + .data = { .lhs = span.start, .rhs = span.end }, + }); + } + arg_expr = expectExpr(p); + expectToken(p, TOKEN_R_PAREN); + } + break; + default: + fail(p, "parseContainerDeclAuto: unexpected token"); + } + + expectToken(p, TOKEN_L_BRACE); + const Members members = parseContainerMembers(p); + expectToken(p, TOKEN_R_BRACE); + + if (arg_expr == null_node) { + if (members.len <= 2) { + return addNode(&p->nodes, + (AstNodeItem) { + .tag = members.trailing + ? AST_NODE_CONTAINER_DECL_TWO_TRAILING + : AST_NODE_CONTAINER_DECL_TWO, + .main_token = main_token, + .data = { .lhs = members.lhs, .rhs = members.rhs }, + }); + } + const AstSubRange span = membersToSpan(members, p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = members.trailing ? AST_NODE_CONTAINER_DECL_TRAILING + : AST_NODE_CONTAINER_DECL, + .main_token = main_token, + .data = { .lhs = span.start, .rhs = span.end }, + }); + } + + const AstSubRange span = membersToSpan(members, p); + return addNode( + &p->nodes, + (AstNodeItem) { + .tag = members.trailing + ? AST_NODE_CONTAINER_DECL_ARG_TRAILING + : AST_NODE_CONTAINER_DECL_ARG, + .main_token = main_token, + .data = { + .lhs = arg_expr, + .rhs = addExtra(p, + (AstNodeIndex[]) { span.start, span.end }, 2), + }, + }); +} + +static AstNodeIndex parseByteAlign(Parser* p) { + if (eatToken(p, TOKEN_KEYWORD_ALIGN) == null_token) + return null_node; + expectToken(p, TOKEN_L_PAREN); + const AstNodeIndex expr = expectExpr(p); + expectToken(p, TOKEN_R_PAREN); + return expr; +} + static AstSubRange parseSwitchProngList(Parser* p) { CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch))) = initCleanupScratch(p); @@ -2456,974 +3230,202 @@ static AstSubRange parseSwitchProngList(Parser* p) { return listToSpan(p, &p->scratch.arr[scratch_top.old_len], cases_len); } -static AstNodeIndex parseSwitchExpr(Parser* p) { - const AstTokenIndex switch_token = eatToken(p, TOKEN_KEYWORD_SWITCH); - if (switch_token == null_token) - return null_node; - +static SmallSpan parseParamDeclList(Parser* p) { expectToken(p, TOKEN_L_PAREN); - const AstNodeIndex operand = expectExpr(p); - expectToken(p, TOKEN_R_PAREN); - expectToken(p, TOKEN_L_BRACE); - const AstSubRange span = parseSwitchProngList(p); - const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA; - return addNode(&p->nodes, - (AstNodeItem) { - .tag = comma ? AST_NODE_SWITCH_COMMA : AST_NODE_SWITCH, - .main_token = switch_token, - .data = { - .lhs = operand, - .rhs = addExtra(p, - (AstNodeIndex[]) { span.start, span.end }, 2), - }, - }); -} + CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch))) + = initCleanupScratch(p); + + // 0 = none, 1 = seen, 2 = nonfinal + int varargs = 0; -static void parsePtrPayload(Parser* p) { - if (eatToken(p, TOKEN_PIPE) == null_token) - return; while (true) { - eatToken(p, TOKEN_ASTERISK); - expectToken(p, TOKEN_IDENTIFIER); + if (eatToken(p, TOKEN_R_PAREN) != null_token) + break; + if (varargs == 1) + varargs = 2; + + if (p->token_tags[p->tok_i] == TOKEN_ELLIPSIS3) { + p->tok_i++; + if (varargs == 0) + varargs = 1; + if (eatToken(p, TOKEN_R_PAREN) != null_token) + break; + expectToken(p, TOKEN_COMMA); + continue; + } + + const AstNodeIndex type_expr = expectParamDecl(p); + if (type_expr != 0) + SLICE_APPEND(AstNodeIndex, &p->scratch, type_expr); + if (p->token_tags[p->tok_i] == TOKEN_COMMA) { p->tok_i++; continue; } + expectToken(p, TOKEN_R_PAREN); break; } - expectToken(p, TOKEN_PIPE); -} -static void parsePayload(Parser* p) { - if (eatToken(p, TOKEN_PIPE) == null_token) - return; - expectToken(p, TOKEN_IDENTIFIER); - expectToken(p, TOKEN_PIPE); -} - -static AstNodeIndex parseIfExpr(Parser* p) { - const AstTokenIndex if_token = eatToken(p, TOKEN_KEYWORD_IF); - if (if_token == null_token) - return null_node; - - expectToken(p, TOKEN_L_PAREN); - const AstNodeIndex condition = expectExpr(p); - expectToken(p, TOKEN_R_PAREN); - parsePtrPayload(p); - - const AstNodeIndex then_expr = expectExpr(p); - - if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_IF_SIMPLE, - .main_token = if_token, - .data = { .lhs = condition, .rhs = then_expr }, - }); + if (varargs == 2) { + fail(p, "varargs_nonfinal"); } - parsePayload(p); - const AstNodeIndex else_expr = expectExpr(p); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_IF, - .main_token = if_token, - .data = { - .lhs = condition, - .rhs = addExtra(p, - (AstNodeIndex[]) { then_expr, else_expr }, 2), - }, - }); -} - -static AstNodeIndex parsePrimaryExpr(Parser* p) { - switch (p->token_tags[p->tok_i]) { - case TOKEN_KEYWORD_ASM: - return parseAsmExpr(p); - case TOKEN_KEYWORD_IF: - return parseIfExpr(p); - case TOKEN_KEYWORD_BREAK: - return addNode( - &p->nodes, - (AstNodeItem) { - .tag = AST_NODE_BREAK, - .main_token = nextToken(p), - .data = { - .lhs = parseBreakLabel(p), - .rhs = parseExpr(p), - }, - }); - case TOKEN_KEYWORD_CONTINUE: - return addNode( - &p->nodes, - (AstNodeItem) { - .tag = AST_NODE_CONTINUE, - .main_token = nextToken(p), - .data = { - .lhs = parseBreakLabel(p), - .rhs = parseExpr(p), - }, - }); - case TOKEN_KEYWORD_COMPTIME: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_COMPTIME, - .main_token = nextToken(p), - .data = { .lhs = expectExpr(p), .rhs = 0 }, - }); - case TOKEN_KEYWORD_NOSUSPEND: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_NOSUSPEND, - .main_token = nextToken(p), - .data = { .lhs = expectExpr(p), .rhs = 0 }, - }); - case TOKEN_KEYWORD_RESUME: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_RESUME, - .main_token = nextToken(p), - .data = { .lhs = expectExpr(p), .rhs = 0 }, - }); - case TOKEN_KEYWORD_RETURN: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_RETURN, - .main_token = nextToken(p), - .data = { .lhs = parseExpr(p), .rhs = 0 }, - }); - case TOKEN_IDENTIFIER: - if (p->token_tags[p->tok_i + 1] == TOKEN_COLON) { - switch (p->token_tags[p->tok_i + 2]) { - case TOKEN_KEYWORD_INLINE: - p->tok_i += 3; - if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_FOR) - return parseForExpr(p); - if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_WHILE) - return parseWhileExpr(p); - fail(p, "expected for or while after inline"); - return 0; // tcc - case TOKEN_KEYWORD_FOR: - p->tok_i += 2; - return parseForExpr(p); - case TOKEN_KEYWORD_WHILE: - p->tok_i += 2; - return parseWhileExpr(p); - case TOKEN_L_BRACE: - p->tok_i += 2; - return parseBlock(p); - default: - return parseCurlySuffixExpr(p); - } - } else { - return parseCurlySuffixExpr(p); - } - case TOKEN_KEYWORD_WHILE: - return parseWhileExpr(p); - case TOKEN_KEYWORD_FOR: - return parseForExpr(p); - case TOKEN_KEYWORD_INLINE: - p->tok_i++; - if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_FOR) - return parseForExpr(p); - if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_WHILE) - return parseWhileExpr(p); - fail(p, "parsePrimaryExpr: inline without for/while"); - return 0; // tcc - case TOKEN_L_BRACE: - return parseBlock(p); - default: - return parseCurlySuffixExpr(p); - } - - return 0; // tcc -} - -static AstNodeIndex parsePrefixExpr(Parser* p) { - AstNodeTag tag; - switch (p->token_tags[p->tok_i]) { - case TOKEN_BANG: - tag = AST_NODE_BOOL_NOT; - break; - case TOKEN_MINUS: - tag = AST_NODE_NEGATION; - break; - case TOKEN_TILDE: - tag = AST_NODE_BIT_NOT; - break; - case TOKEN_MINUS_PERCENT: - tag = AST_NODE_NEGATION_WRAP; - break; - case TOKEN_AMPERSAND: - tag = AST_NODE_ADDRESS_OF; - break; - case TOKEN_KEYWORD_TRY: - tag = AST_NODE_TRY; - break; - case TOKEN_KEYWORD_AWAIT: - tag = AST_NODE_AWAIT; - break; - default: - return parsePrimaryExpr(p); - } - return addNode( - &p->nodes, - (AstNodeItem) { - .tag = tag, - .main_token = nextToken(p), - .data = { - .lhs = parsePrefixExpr(p), - .rhs = 0, - }, - }); -} - -static AstNodeTag assignOpNode(TokenizerTag tok) { - switch (tok) { - case TOKEN_EQUAL: - return AST_NODE_ASSIGN; - case TOKEN_PLUS_EQUAL: - return AST_NODE_ASSIGN_ADD; - case TOKEN_MINUS_EQUAL: - return AST_NODE_ASSIGN_SUB; - case TOKEN_ASTERISK_EQUAL: - return AST_NODE_ASSIGN_MUL; - case TOKEN_SLASH_EQUAL: - return AST_NODE_ASSIGN_DIV; - case TOKEN_PERCENT_EQUAL: - return AST_NODE_ASSIGN_MOD; - case TOKEN_AMPERSAND_EQUAL: - return AST_NODE_ASSIGN_BIT_AND; - case TOKEN_PIPE_EQUAL: - return AST_NODE_ASSIGN_BIT_OR; - case TOKEN_CARET_EQUAL: - return AST_NODE_ASSIGN_BIT_XOR; - case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_EQUAL: - return AST_NODE_ASSIGN_SHL; - case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT_EQUAL: - return AST_NODE_ASSIGN_SHR; - case TOKEN_PLUS_PERCENT_EQUAL: - return AST_NODE_ASSIGN_ADD_WRAP; - case TOKEN_MINUS_PERCENT_EQUAL: - return AST_NODE_ASSIGN_SUB_WRAP; - case TOKEN_ASTERISK_PERCENT_EQUAL: - return AST_NODE_ASSIGN_MUL_WRAP; - case TOKEN_PLUS_PIPE_EQUAL: - return AST_NODE_ASSIGN_ADD_SAT; - case TOKEN_MINUS_PIPE_EQUAL: - return AST_NODE_ASSIGN_SUB_SAT; - case TOKEN_ASTERISK_PIPE_EQUAL: - return AST_NODE_ASSIGN_MUL_SAT; - case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE_EQUAL: - return AST_NODE_ASSIGN_SHL_SAT; - default: - return AST_NODE_ROOT; // not an assignment op + const uint32_t params_len = p->scratch.len - scratch_top.old_len; + switch (params_len) { + case 0: + return (SmallSpan) { + .tag = SMALL_SPAN_ZERO_OR_ONE, + .payload = { .zero_or_one = 0 }, + }; + case 1: + return (SmallSpan) { + .tag = SMALL_SPAN_ZERO_OR_ONE, + .payload = { .zero_or_one = p->scratch.arr[scratch_top.old_len] }, + }; + default:; + const AstSubRange span + = listToSpan(p, &p->scratch.arr[scratch_top.old_len], params_len); + return (SmallSpan) { + .tag = SMALL_SPAN_MULTI, + .payload = { .multi = span }, + }; } } -static AstNodeIndex finishAssignExpr(Parser* p, AstNodeIndex lhs) { - const AstNodeTag assign_tag = assignOpNode(p->token_tags[p->tok_i]); - if (assign_tag == AST_NODE_ROOT) - return lhs; +static AstNodeIndex parseBuiltinCall(Parser* p) { + const AstTokenIndex builtin_token = assertToken(p, TOKEN_BUILTIN); + assertToken(p, TOKEN_L_PAREN); - const AstTokenIndex op_token = nextToken(p); - const AstNodeIndex rhs = expectExpr(p); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = assign_tag, - .main_token = op_token, - .data = { .lhs = lhs, .rhs = rhs }, - }); -} - -static AstNodeIndex parseAssignExpr(Parser* p) { - const AstNodeIndex expr = parseExpr(p); - if (expr == 0) - return null_node; - return finishAssignExpr(p, expr); -} - -static AstNodeIndex parseSingleAssignExpr(Parser* p) { - const AstNodeIndex expr = parseExpr(p); - if (expr == 0) - return null_node; - const AstNodeTag tag = assignOpNode(p->token_tags[p->tok_i]); - if (tag == AST_NODE_ROOT) - return expr; - const AstTokenIndex op_token = nextToken(p); - const AstNodeIndex rhs = expectExpr(p); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = tag, - .main_token = op_token, - .data = { .lhs = expr, .rhs = rhs }, - }); -} - -static AstNodeIndex parseBlockExpr(Parser* p) { - if (p->token_tags[p->tok_i] == TOKEN_L_BRACE) - return parseBlock(p); - if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER - && p->token_tags[p->tok_i + 1] == TOKEN_COLON - && p->token_tags[p->tok_i + 2] == TOKEN_L_BRACE) { - p->tok_i += 2; - return parseBlock(p); - } - return null_node; -} - -static AstNodeIndex expectBlockExprStatement(Parser* p) { - const AstNodeIndex block_expr = parseBlockExpr(p); - if (block_expr != 0) - return block_expr; - // Assign expr + semicolon - const AstNodeIndex expr = parseAssignExpr(p); - if (expr != 0) { - expectSemicolon(p); - return expr; - } - fail(p, "expectBlockExprStatement: expected block or expr"); - return 0; // tcc -} - -static AstNodeIndex expectVarDeclExprStatement( - Parser* p, AstTokenIndex comptime_token) { CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch))) = initCleanupScratch(p); while (true) { - const AstNodeIndex var_decl_proto = parseVarDeclProto(p); - if (var_decl_proto != 0) { - SLICE_APPEND(AstNodeIndex, &p->scratch, var_decl_proto); - } else { - const AstNodeIndex expr = parseExpr(p); - SLICE_APPEND(AstNodeIndex, &p->scratch, expr); - } - if (eatToken(p, TOKEN_COMMA) == null_token) + if (eatToken(p, TOKEN_R_PAREN) != null_token) break; - } - const uint32_t lhs_count = p->scratch.len - scratch_top.old_len; - assert(lhs_count > 0); - - // Try to eat '=' for assignment/initialization - // (matches upstream: `const equal_token = p.eatToken(.equal) orelse eql:`) - AstTokenIndex equal_token = eatToken(p, TOKEN_EQUAL); - if (equal_token == null_token) { - if (lhs_count > 1) { - // Destructure requires '=' - fail(p, "expected '='"); - } - const AstNodeIndex lhs = p->scratch.arr[scratch_top.old_len]; - const AstNodeTag lhs_tag = p->nodes.tags[lhs]; - if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL - || lhs_tag == AST_NODE_ALIGNED_VAR_DECL - || lhs_tag == AST_NODE_LOCAL_VAR_DECL - || lhs_tag == AST_NODE_GLOBAL_VAR_DECL) { - // var decl without init requires '=' - fail(p, "expected '='"); - } - // Expression statement: finish with assignment operators or semicolon - const AstNodeIndex expr = finishAssignExpr(p, lhs); - // Semicolon is optional for block-terminated expressions - eatToken(p, TOKEN_SEMICOLON); - if (comptime_token != null_token) { - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_COMPTIME, - .main_token = comptime_token, - .data = { .lhs = expr, .rhs = 0 }, - }); - } - return expr; - } - - // Have '=', parse RHS and semicolon - const AstNodeIndex rhs = expectExpr(p); - expectSemicolon(p); - - if (lhs_count == 1) { - const AstNodeIndex lhs = p->scratch.arr[scratch_top.old_len]; - const AstNodeTag lhs_tag = p->nodes.tags[lhs]; - if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL - || lhs_tag == AST_NODE_ALIGNED_VAR_DECL - || lhs_tag == AST_NODE_LOCAL_VAR_DECL - || lhs_tag == AST_NODE_GLOBAL_VAR_DECL) { - // var decl initialization: const x = val; - p->nodes.datas[lhs].rhs = rhs; - return lhs; - } - // Simple assignment: x = val; - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_ASSIGN, - .main_token = equal_token, - .data = { .lhs = lhs, .rhs = rhs }, - }); - } - - // Destructure: a, b, c = rhs - // rhs and semicolon already parsed above - - // Store count + lhs nodes in extra_data - const AstNodeIndex extra_start = p->extra_data.len; - SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, lhs_count + 1); - p->extra_data.arr[p->extra_data.len++] = lhs_count; - memcpy(p->extra_data.arr + p->extra_data.len, - &p->scratch.arr[scratch_top.old_len], - lhs_count * sizeof(AstNodeIndex)); - p->extra_data.len += lhs_count; - - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_ASSIGN_DESTRUCTURE, - .main_token = equal_token, - .data = { .lhs = extra_start, .rhs = rhs }, - }); -} - -static AstNodeIndex expectIfStatement(Parser* p) { - const AstTokenIndex if_token = assertToken(p, TOKEN_KEYWORD_IF); - expectToken(p, TOKEN_L_PAREN); - const AstNodeIndex condition = expectExpr(p); - expectToken(p, TOKEN_R_PAREN); - parsePtrPayload(p); - bool else_required = false; - AstNodeIndex then_body; - const AstNodeIndex block2 = parseBlockExpr(p); - if (block2 != 0) { - then_body = block2; - } else { - then_body = parseAssignExpr(p); - if (then_body == 0) - fail(p, "expected block or assignment"); - if (eatToken(p, TOKEN_SEMICOLON) != null_token) - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_IF_SIMPLE, - .main_token = if_token, - .data = { .lhs = condition, .rhs = then_body }, - }); - else_required = true; - } - if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { - if (else_required) - fail(p, "expected_semi_or_else"); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_IF_SIMPLE, - .main_token = if_token, - .data = { .lhs = condition, .rhs = then_body }, - }); - } - parsePayload(p); - const AstNodeIndex else_body = expectStatement(p, false); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_IF, - .main_token = if_token, - .data = { - .lhs = condition, - .rhs = addExtra(p, - (AstNodeIndex[]) { then_body, else_body }, 2), - }, - }); -} - -static AstNodeIndex expectStatement(Parser* p, bool allow_defer_var) { - const AstTokenIndex comptime_token = eatToken(p, TOKEN_KEYWORD_COMPTIME); - if (comptime_token != null_token) { - // comptime followed by block => comptime block statement - const AstNodeIndex block = parseBlock(p); - if (block != 0) { - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_COMPTIME, - .main_token = comptime_token, - .data = { .lhs = block, .rhs = 0 }, - }); - } - // comptime var decl or expression - if (allow_defer_var) - return expectVarDeclExprStatement(p, comptime_token); - { - const AstNodeIndex assign = parseAssignExpr(p); - if (assign == 0) { - fail(p, "expected expression"); - } - expectSemicolon(p); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_COMPTIME, - .main_token = comptime_token, - .data = { .lhs = assign, .rhs = 0 }, - }); - } - } - - const AstNodeIndex tok = p->token_tags[p->tok_i]; - switch (tok) { - case TOKEN_KEYWORD_DEFER: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_DEFER, - .main_token = nextToken(p), - .data = { - .lhs = expectBlockExprStatement(p), - .rhs = 0, - }, - }); - case TOKEN_KEYWORD_ERRDEFER: { - const AstTokenIndex errdefer_token = nextToken(p); - AstTokenIndex payload = null_token; - if (p->token_tags[p->tok_i] == TOKEN_PIPE) { + const AstNodeIndex param = expectExpr(p); + SLICE_APPEND(AstNodeIndex, &p->scratch, param); + switch (p->token_tags[p->tok_i]) { + case TOKEN_COMMA: p->tok_i++; - payload = expectToken(p, TOKEN_IDENTIFIER); - expectToken(p, TOKEN_PIPE); + break; + case TOKEN_R_PAREN: + p->tok_i++; + goto end_loop; + default: + fail(p, "expected comma after arg"); } - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_ERRDEFER, - .main_token = errdefer_token, - .data = { - .lhs = payload, - .rhs = expectBlockExprStatement(p), - }, - }); - } - case TOKEN_KEYWORD_NOSUSPEND: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_NOSUSPEND, - .main_token = nextToken(p), - .data = { - .lhs = expectBlockExprStatement(p), - .rhs = 0, - }, - }); - case TOKEN_KEYWORD_SUSPEND: - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_SUSPEND, - .main_token = nextToken(p), - .data = { - .lhs = expectBlockExprStatement(p), - .rhs = 0, - }, - }); - case TOKEN_KEYWORD_IF: - return expectIfStatement(p); - case TOKEN_KEYWORD_ENUM: - case TOKEN_KEYWORD_STRUCT: - case TOKEN_KEYWORD_UNION:; - fail(p, "unsupported statement keyword"); - default:; } +end_loop:; - const AstNodeIndex labeled_statement = parseLabeledStatement(p); - if (labeled_statement != 0) - return labeled_statement; - - if (allow_defer_var) { - return expectVarDeclExprStatement(p, null_token); - } else { - const AstNodeIndex assign_expr = parseAssignExpr(p); - expectSemicolon(p); - return assign_expr; - } -} - -static AstNodeIndex parseBlock(Parser* p) { - const AstNodeIndex lbrace = eatToken(p, TOKEN_L_BRACE); - if (lbrace == null_token) - return null_node; - - CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch))) - = initCleanupScratch(p); - - while (1) { - if (p->token_tags[p->tok_i] == TOKEN_R_BRACE) - break; - - // "const AstNodeIndex statement" once tinycc supports typeof_unqual - // (C23) - AstNodeIndex statement = expectStatement(p, true); - if (statement == 0) - break; - SLICE_APPEND(AstNodeIndex, &p->scratch, statement); - } - expectToken(p, TOKEN_R_BRACE); - const bool semicolon = (p->token_tags[p->tok_i - 2] == TOKEN_SEMICOLON); - - const uint32_t statements_len = p->scratch.len - scratch_top.old_len; - switch (statements_len) { + const bool comma = (p->token_tags[p->tok_i - 2] == TOKEN_COMMA); + const uint32_t params_len = p->scratch.len - scratch_top.old_len; + switch (params_len) { case 0: - return addNode( - &p->nodes, + return addNode(&p->nodes, (AstNodeItem) { - .tag = AST_NODE_BLOCK_TWO, - .main_token = lbrace, + .tag = AST_NODE_BUILTIN_CALL_TWO, + .main_token = builtin_token, .data = { .lhs = 0, .rhs = 0, }, }); case 1: - return addNode( - &p->nodes, + return addNode(&p->nodes, (AstNodeItem) { - .tag = semicolon ? AST_NODE_BLOCK_TWO_SEMICOLON : AST_NODE_BLOCK_TWO, - .main_token = lbrace, + .tag = comma ? + AST_NODE_BUILTIN_CALL_TWO_COMMA : + AST_NODE_BUILTIN_CALL_TWO, + .main_token = builtin_token, .data = { .lhs = p->scratch.arr[scratch_top.old_len], .rhs = 0, }, }); case 2: - return addNode( - &p->nodes, + return addNode(&p->nodes, (AstNodeItem) { - .tag = semicolon ? AST_NODE_BLOCK_TWO_SEMICOLON : AST_NODE_BLOCK_TWO, - .main_token = lbrace, + .tag = comma ? + AST_NODE_BUILTIN_CALL_TWO_COMMA : + AST_NODE_BUILTIN_CALL_TWO, + .main_token = builtin_token, .data = { .lhs = p->scratch.arr[scratch_top.old_len], - .rhs = p->scratch.arr[scratch_top.old_len + 1], + .rhs = p->scratch.arr[scratch_top.old_len+1], }, }); - default:; - const AstSubRange span = listToSpan( - p, &p->scratch.arr[scratch_top.old_len], statements_len); - return addNode( - &p->nodes, - (AstNodeItem) { - .tag = semicolon ? AST_NODE_BLOCK_SEMICOLON : AST_NODE_BLOCK, - .main_token = lbrace, - .data = { - .lhs = span.start, - .rhs = span.end, - }, - }); - } - - return 0; -} - -static AstNodeIndex parseLabeledStatement(Parser* p) { - const AstNodeIndex label_token = parseBlockLabel(p); - const AstNodeIndex block = parseBlock(p); - if (block != 0) - return block; - - const AstNodeIndex loop_stmt = parseLoopStatement(p); - if (loop_stmt != 0) - return loop_stmt; - - if (label_token != 0) { - fail(p, "parseLabeledStatement does not support labels"); - } - - return null_node; -} - -static AstNodeIndex parseGlobalVarDecl(Parser* p) { - const AstNodeIndex var_decl = parseVarDeclProto(p); - if (var_decl == 0) { - return null_node; - } - - if (eatToken(p, TOKEN_EQUAL) != null_token) { - const AstNodeIndex init_expr = expectExpr(p); - p->nodes.datas[var_decl].rhs = init_expr; - } - expectToken(p, TOKEN_SEMICOLON); - return var_decl; -} - -static AstNodeIndex expectTopLevelDecl(Parser* p) { - AstTokenIndex extern_export_inline_token = nextToken(p); - - switch (p->token_tags[extern_export_inline_token]) { - case TOKEN_KEYWORD_EXTERN: - eatToken(p, TOKEN_STRING_LITERAL); - break; - case TOKEN_KEYWORD_EXPORT: - case TOKEN_KEYWORD_INLINE: - case TOKEN_KEYWORD_NOINLINE: - break; - default: - p->tok_i--; - } - - AstNodeIndex fn_proto = parseFnProto(p); - if (fn_proto != 0) { - switch (p->token_tags[p->tok_i]) { - case TOKEN_SEMICOLON: - p->tok_i++; - return fn_proto; - case TOKEN_L_BRACE:; - AstNodeIndex fn_decl_index = reserveNode(p, AST_NODE_FN_DECL); - AstNodeIndex body_block = parseBlock(p); - return setNode(p, fn_decl_index, - (AstNodeItem) { - .tag = AST_NODE_FN_DECL, - .main_token = p->nodes.main_tokens[fn_proto], - .data = { .lhs = fn_proto, .rhs = body_block }, - }); - default: - fail(p, "expected semicolon or lbrace"); - } - } - - eatToken(p, TOKEN_KEYWORD_THREADLOCAL); - AstNodeIndex var_decl = parseGlobalVarDecl(p); - if (var_decl != 0) { - return var_decl; - } - - // assuming the program is correct... - fail(p, "the next token should be usingnamespace, which is not supported"); - return 0; // make tcc happy -} - -static void findNextContainerMember(Parser* p) { - uint32_t level = 0; - - while (true) { - AstTokenIndex tok = nextToken(p); - - switch (p->token_tags[tok]) { - // Any of these can start a new top level declaration - case TOKEN_KEYWORD_TEST: - case TOKEN_KEYWORD_COMPTIME: - case TOKEN_KEYWORD_PUB: - case TOKEN_KEYWORD_EXPORT: - case TOKEN_KEYWORD_EXTERN: - case TOKEN_KEYWORD_INLINE: - case TOKEN_KEYWORD_NOINLINE: - case TOKEN_KEYWORD_USINGNAMESPACE: - case TOKEN_KEYWORD_THREADLOCAL: - case TOKEN_KEYWORD_CONST: - case TOKEN_KEYWORD_VAR: - case TOKEN_KEYWORD_FN: - if (level == 0) { - p->tok_i--; - return; - } - break; - case TOKEN_IDENTIFIER: - if (p->token_tags[tok + 1] == TOKEN_COMMA && level == 0) { - p->tok_i--; - return; - } - break; - case TOKEN_COMMA: - case TOKEN_SEMICOLON: - // This decl was likely meant to end here - if (level == 0) - return; - break; - case TOKEN_L_PAREN: - case TOKEN_L_BRACKET: - case TOKEN_L_BRACE: - level++; - break; - case TOKEN_R_PAREN: - case TOKEN_R_BRACKET: - if (level != 0) - level--; - break; - case TOKEN_R_BRACE: - if (level == 0) { - // end of container, exit - p->tok_i--; - return; - } - level--; - break; - case TOKEN_EOF: - p->tok_i--; - return; - default: - break; - } - } -} - -static AstNodeIndex expectTestDecl(Parser* p) { - const AstTokenIndex test_token = assertToken(p, TOKEN_KEYWORD_TEST); - const AstTokenIndex test_name - = (p->token_tags[p->tok_i] == TOKEN_STRING_LITERAL - || p->token_tags[p->tok_i] == TOKEN_IDENTIFIER) - ? nextToken(p) - : null_token; - const AstNodeIndex body = parseBlock(p); - if (body == 0) - fail(p, "expected block after test"); - return addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_TEST_DECL, - .main_token = test_token, - .data = { .lhs = test_name, .rhs = body }, - }); -} - -static Members parseContainerMembers(Parser* p) { - CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch))) - = initCleanupScratch(p); - while (eatToken(p, TOKEN_CONTAINER_DOC_COMMENT) != null_token) - ; - - FieldState field_state = { .tag = FIELD_STATE_NONE }; - - bool trailing = false; - while (1) { - const AstTokenIndex doc_comment = eatDocComments(p); - switch (p->token_tags[p->tok_i]) { - case TOKEN_KEYWORD_TEST: { - if (doc_comment != null_token) - fail(p, "test_doc_comment"); - const AstNodeIndex test_decl = expectTestDecl(p); - SLICE_APPEND(AstNodeIndex, &p->scratch, test_decl); - trailing = p->token_tags[p->tok_i - 1] == TOKEN_R_BRACE; - break; - } - case TOKEN_KEYWORD_USINGNAMESPACE:; - fail(p, "not implemented in parseContainerMembers"); - case TOKEN_KEYWORD_COMPTIME: - // comptime can be a container field modifier or a comptime - // block/decl. Check if it's followed by a block (comptime { ... - // }). - if (p->token_tags[p->tok_i + 1] == TOKEN_L_BRACE) { - if (doc_comment != null_token) { - fail(p, "comptime_doc_comment"); - } - const AstTokenIndex comptime_token = nextToken(p); - const AstNodeIndex block_node = parseBlock(p); - SLICE_APPEND(AstNodeIndex, &p->scratch, - addNode(&p->nodes, - (AstNodeItem) { - .tag = AST_NODE_COMPTIME, - .main_token = comptime_token, - .data = { .lhs = block_node, .rhs = 0 }, - })); - trailing = false; - break; - } - // Otherwise it's a container field with comptime modifier - goto container_field; - case TOKEN_KEYWORD_PUB: { - p->tok_i++; - AstNodeIndex top_level_decl = expectTopLevelDecl(p); - if (top_level_decl != 0) { - if (field_state.tag == FIELD_STATE_SEEN) { - field_state.tag = FIELD_STATE_END; - field_state.payload.end = top_level_decl; - } - SLICE_APPEND(AstNodeIndex, &p->scratch, top_level_decl); - } - trailing = p->token_tags[p->tok_i - 1] == TOKEN_SEMICOLON; - break; - } - case TOKEN_KEYWORD_CONST: - case TOKEN_KEYWORD_VAR: - case TOKEN_KEYWORD_THREADLOCAL: - case TOKEN_KEYWORD_EXPORT: - case TOKEN_KEYWORD_EXTERN: - case TOKEN_KEYWORD_INLINE: - case TOKEN_KEYWORD_NOINLINE: - case TOKEN_KEYWORD_FN: { - const AstNodeIndex top_level_decl = expectTopLevelDecl(p); - if (top_level_decl != 0) { - if (field_state.tag == FIELD_STATE_SEEN) { - field_state.tag = FIELD_STATE_END; - field_state.payload.end = top_level_decl; - } - SLICE_APPEND(AstNodeIndex, &p->scratch, top_level_decl); - } - trailing = (p->token_tags[p->tok_i - 1] == TOKEN_SEMICOLON); - break; - } - case TOKEN_EOF: - case TOKEN_R_BRACE: - goto break_loop; - container_field: - default:; - // skip parseCStyleContainer - const AstNodeIndex field_node = expectContainerField(p); - switch (field_state.tag) { - case FIELD_STATE_NONE: - field_state.tag = FIELD_STATE_SEEN; - break; - case FIELD_STATE_SEEN: - break; - case FIELD_STATE_END: - fail(p, "parseContainerMembers error condition"); - } - SLICE_APPEND(AstNodeIndex, &p->scratch, field_node); - switch (p->token_tags[p->tok_i]) { - case TOKEN_COMMA: - p->tok_i++; - trailing = true; - continue; - case TOKEN_R_BRACE: - case TOKEN_EOF: - trailing = false; - goto break_loop; - default: - fail(p, "expected comma after field"); - } - } - } - -break_loop:; - - const uint32_t items_len = p->scratch.len - scratch_top.old_len; - switch (items_len) { - case 0: - return (Members) { - .len = 0, - .lhs = 0, - .rhs = 0, - .trailing = trailing, - }; - case 1: - return (Members) { - .len = 1, - .lhs = p->scratch.arr[scratch_top.old_len], - .rhs = 0, - .trailing = trailing, - }; - case 2: - return (Members) { - .len = 2, - .lhs = p->scratch.arr[scratch_top.old_len], - .rhs = p->scratch.arr[scratch_top.old_len + 1], - .trailing = trailing, - }; default:; const AstSubRange span - = listToSpan(p, &p->scratch.arr[scratch_top.old_len], items_len); - return (Members) { - .len = items_len, - .lhs = span.start, - .rhs = span.end, - .trailing = trailing, - }; + = listToSpan(p, &p->scratch.arr[scratch_top.old_len], params_len); + return addNode( + &p->nodes, + (AstNodeItem) { + .tag = comma ? + AST_NODE_BUILTIN_CALL_COMMA : + AST_NODE_BUILTIN_CALL, + .main_token = builtin_token, + .data = { + .lhs = span.start, + .rhs = span.end, + }, + }); } } -void parseRoot(Parser* p) { - addNode( - &p->nodes, (AstNodeItem) { .tag = AST_NODE_ROOT, .main_token = 0 }); - - Members root_members = parseContainerMembers(p); - AstSubRange root_decls = membersToSpan(root_members, p); - - if (p->token_tags[p->tok_i] != TOKEN_EOF) { - fail(p, "expected EOF"); +static AstTokenIndex eatDocComments(Parser* p) { + AstTokenIndex first = null_token; + AstTokenIndex tok; + while ((tok = eatToken(p, TOKEN_DOC_COMMENT)) != null_token) { + if (first == null_token) { + if (tok > 0 && tokensOnSameLine(p, tok - 1, tok)) { + fail(p, "same_line_doc_comment"); + } + first = tok; + } } - - p->nodes.datas[0].lhs = root_decls.start; - p->nodes.datas[0].rhs = root_decls.end; + return first; } + +static bool tokensOnSameLine( + Parser* p, AstTokenIndex tok1, AstTokenIndex tok2) { + const uint32_t start1 = p->token_starts[tok1]; + const uint32_t start2 = p->token_starts[tok2]; + for (uint32_t i = start1; i < start2; i++) { + if (p->source[i] == '\n') + return false; + } + return true; +} + +static AstTokenIndex eatToken(Parser* p, TokenizerTag tag) { + if (p->token_tags[p->tok_i] == tag) { + return nextToken(p); + } else { + return null_token; + } +} + +static AstTokenIndex assertToken(Parser* p, TokenizerTag tag) { + const AstTokenIndex token = nextToken(p); + if (p->token_tags[token] != tag) { + fail(p, "unexpected token"); + } + return token; +} + +static AstTokenIndex expectToken(Parser* p, TokenizerTag tag) { + if (p->token_tags[p->tok_i] == tag) { + return nextToken(p); + } else { + fail(p, "unexpected token"); + } + return 0; // tcc +} + +static AstNodeIndex expectSemicolon(Parser* p) { + return expectToken(p, TOKEN_SEMICOLON); +} + +static AstTokenIndex nextToken(Parser* p) { return p->tok_i++; }