#include "common.h" #include #include #include #include #include "ast.h" #include "parser.h" const AstNodeIndex null_node = 0; const AstTokenIndex null_token = ~(AstTokenIndex)(0); // OPT encodes a node index as OptionalIndex: 0 → ~0 (none) #define OPT(x) ((x) == 0 ? ~(AstNodeIndex)0 : (x)) typedef struct { uint32_t len; AstNodeIndex lhs; AstNodeIndex rhs; bool trailing; } Members; typedef struct { enum { FIELD_STATE_NONE, FIELD_STATE_SEEN, FIELD_STATE_END } tag; union { uint32_t end; } payload; } FieldState; typedef struct { enum { SMALL_SPAN_ZERO_OR_ONE, SMALL_SPAN_MULTI } tag; union { AstNodeIndex zero_or_one; AstSubRange multi; } payload; } SmallSpan; typedef struct { AstNodeIndex align_node; AstNodeIndex addrspace_node; AstNodeIndex bit_range_start; 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); static AstTokenIndex assertToken(Parser*, TokenizerTag); static void astNodeListEnsureCapacity(AstNodeList*, uint32_t); static AstTokenIndex eatDocComments(Parser*); static AstTokenIndex eatToken(Parser*, TokenizerTag); static AstNodeIndex expectBlockExprStatement(Parser*); static AstNodeIndex expectContainerField(Parser*); static AstNodeIndex expectExpr(Parser*); static AstNodeIndex expectIfStatement(Parser*); static AstNodeIndex expectParamDecl(Parser*); static AstNodeIndex expectSemicolon(Parser*); static AstNodeIndex expectStatement(Parser*, bool); static AstNodeIndex expectTestDecl(Parser*); static AstTokenIndex expectToken(Parser*, TokenizerTag); static AstNodeIndex expectTopLevelDecl(Parser*); static AstNodeIndex expectVarDeclExprStatement(Parser*, AstTokenIndex); static void findNextContainerMember(Parser*); static AstNodeIndex finishAssignExpr(Parser*, AstNodeIndex); static uint32_t forPrefix(Parser*); static AstSubRange listToSpan(Parser*, const AstNodeIndex*, uint32_t); 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*); static AstNodeIndex parseAsmOutputItem(Parser*); static AstNodeIndex parseAssignExpr(Parser*); static AstNodeIndex parseBlock(Parser*); static AstNodeIndex parseBlockExpr(Parser*); static AstTokenIndex parseBlockLabel(Parser*); static AstTokenIndex parseBreakLabel(Parser*); static AstNodeIndex parseBuiltinCall(Parser*); static AstNodeIndex parseByteAlign(Parser*); static AstNodeIndex parseCallconv(Parser*); static AstNodeIndex parseContainerDeclAuto(Parser*); static Members parseContainerMembers(Parser*); static AstNodeIndex parseCurlySuffixExpr(Parser*); static AstNodeIndex parseErrorUnionExpr(Parser*); static AstNodeIndex parseExpr(Parser*); static AstNodeIndex parseExprPrecedence(Parser*, int32_t); static AstNodeIndex parseFieldInit(Parser*); static AstNodeIndex parseFnProto(Parser*); static AstNodeIndex parseForExpr(Parser*); static AstNodeIndex parseForStatement(Parser*); static AstNodeIndex parseGlobalVarDecl(Parser*); static AstNodeIndex parseIfExpr(Parser*); 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*); static AstNodeIndex parsePrimaryTypeExpr(Parser*); static PtrModifiers parsePtrModifiers(Parser*); static void parsePtrPayload(Parser*); static AstNodeIndex parseSingleAssignExpr(Parser*); static AstNodeIndex parseSuffixExpr(Parser*); static AstNodeIndex parseSuffixOp(Parser*, AstNodeIndex); static AstNodeIndex parseSwitchExpr(Parser*); static AstNodeIndex parseSwitchItem(Parser*); static AstNodeIndex parseSwitchProng(Parser*); static AstSubRange parseSwitchProngList(Parser*); static AstNodeIndex parseTypeExpr(Parser*); static AstNodeIndex parseVarDeclProto(Parser*); static AstNodeIndex parseWhileContinueExpr(Parser*); static AstNodeIndex parseWhileExpr(Parser*); static AstNodeIndex parseWhileStatement(Parser*); static uint32_t reserveNode(Parser*, AstNodeTag); static AstNodeIndex setNode(Parser*, uint32_t, AstNodeItem); static uint32_t tokenTagLexemeLen(TokenizerTag); static bool tokensOnSameLine(Parser*, AstTokenIndex, AstTokenIndex); 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); memcpy(p->extra_data.arr + p->extra_data.len, list, count * sizeof(AstNodeIndex)); p->extra_data.len += count; return (AstSubRange) { .start = p->extra_data.len - count, .end = p->extra_data.len, }; } 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) { p->nodes.tags[i] = item.tag; p->nodes.main_tokens[i] = item.main_token; p->nodes.datas[i] = item.data; 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) { return; } const uint32_t new_cap = new_len > list->cap * 2 ? new_len : list->cap * 2; list->tags = realloc(list->tags, new_cap * sizeof(AstNodeTag)); list->main_tokens = realloc(list->main_tokens, new_cap * sizeof(AstTokenIndex)); list->datas = realloc(list->datas, new_cap * sizeof(AstData)); if (!list->tags || !list->main_tokens || !list->datas) exit(1); list->cap = new_cap; } 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 Members parseContainerMembers(Parser* p) { const uint32_t scratch_top = p->scratch.len; 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); if (field_state.tag == FIELD_STATE_SEEN) { field_state.tag = FIELD_STATE_END; field_state.payload.end = test_decl; } SLICE_APPEND(AstNodeIndex, &p->scratch, test_decl); trailing = false; break; } 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; p->scratch.len = scratch_top; 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], .rhs = 0, .trailing = trailing, }; case 2: return (Members) { .len = 2, .lhs = p->scratch.arr[scratch_top], .rhs = p->scratch.arr[scratch_top + 1], .trailing = trailing, }; default:; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], items_len); return (Members) { .len = items_len, .lhs = span.start, .rhs = span.end, .trailing = trailing, }; } } 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_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); bool is_extern = false; switch (p->token_tags[extern_export_inline_token]) { case TOKEN_KEYWORD_EXTERN: eatToken(p, TOKEN_STRING_LITERAL); is_extern = true; 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:; if (is_extern) { fail(p, "extern_fn_body"); } 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; 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 (return_type_expr == 0) { fail(p, "expected_return_type"); } 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 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; } 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 expectContainerField(Parser* p) { eatToken(p, TOKEN_KEYWORD_COMPTIME); const AstTokenIndex main_token = p->tok_i; if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER && p->token_tags[p->tok_i + 1] == TOKEN_COLON) p->tok_i += 2; const AstNodeIndex type_expr = parseTypeExpr(p); if (type_expr == 0) { fail(p, "expected type expression"); } const AstNodeIndex align_expr = parseByteAlign(p); const AstNodeIndex value_expr = eatToken(p, TOKEN_EQUAL) != null_token ? expectExpr(p) : 0; if (align_expr == 0) { return addNode( &p->nodes, (AstNodeItem) { .tag = AST_NODE_CONTAINER_FIELD_INIT, .main_token = main_token, .data = { .lhs = type_expr, .rhs = value_expr, }, }); } else if (value_expr == 0) { return addNode( &p->nodes, (AstNodeItem) { .tag = AST_NODE_CONTAINER_FIELD_ALIGN, .main_token = main_token, .data = { .lhs = type_expr, .rhs = align_expr, }, }); } else { return addNode( &p->nodes, (AstNodeItem) { .tag = AST_NODE_CONTAINER_FIELD, .main_token = main_token, .data = { .lhs = type_expr, .rhs = addExtra(p, (AstNodeIndex[]) { align_expr, value_expr }, 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 = parseBlockExpr(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: if (allow_defer_var) return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_DEFER, .main_token = nextToken(p), .data = { .lhs = expectBlockExprStatement(p), .rhs = 0, }, }); break; case TOKEN_KEYWORD_ERRDEFER: if (allow_defer_var) { 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), }, }); } break; 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) { const uint32_t scratch_top = p->scratch.len; 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) break; } const uint32_t lhs_count = p->scratch.len - scratch_top; 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]; p->scratch.len = scratch_top; 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]; p->scratch.len = scratch_top; 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; const AstNodeIndex assign = addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ASSIGN, .main_token = equal_token, .data = { .lhs = lhs, .rhs = rhs }, }); if (comptime_token != null_token) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_COMPTIME, .main_token = comptime_token, .data = { .lhs = assign, .rhs = 0 }, }); } return assign; } // 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], lhs_count * sizeof(AstNodeIndex)); p->extra_data.len += lhs_count; p->scratch.len = scratch_top; 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 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; const AstNodeIndex switch_expr = parseSwitchExpr(p); if (switch_expr != 0) return switch_expr; if (label_token != 0) { fail(p, "expected_labelable"); } 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; fail(p, "seen 'inline', there should have been a 'for' or 'while'"); return 0; // tcc } 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 bool else_required = false; bool seen_semicolon = false; AstNodeIndex then_body; const AstNodeIndex block = parseBlock(p); if (block != 0) { then_body = block; } else { then_body = parseAssignExpr(p); if (then_body == 0) { fail(p, "expected_block_or_assignment"); } if (eatToken(p, TOKEN_SEMICOLON) != null_token) { seen_semicolon = true; } else { else_required = true; } } bool has_else = false; if (!seen_semicolon && eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) { parsePayload(p); SLICE_APPEND(AstNodeIndex, &p->scratch, then_body); const AstNodeIndex else_body = expectStatement(p, false); SLICE_APPEND(AstNodeIndex, &p->scratch, else_body); has_else = true; } else if (inputs == 1) { if (else_required) fail(p, "expected_semi_or_else"); p->scratch.len = scratch_top; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_FOR_SIMPLE, .main_token = for_token, .data = { .lhs = p->scratch.arr[scratch_top], .rhs = then_body, }, }); } else { if (else_required) fail(p, "expected_semi_or_else"); 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) | (has_else ? (1u << 31) : 0), }, }); } 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 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 bool else_required = false; AstNodeIndex body; const AstNodeIndex block = parseBlock(p); if (block != 0) { body = block; } else { body = parseAssignExpr(p); if (body == 0) { fail(p, "expected_block_or_assignment"); } if (eatToken(p, TOKEN_SEMICOLON) != null_token) { 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 }, }); } else_required = true; } if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { if (else_required) 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 = expectStatement(p, false); 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 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 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; } 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; } } 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; default: return parsePrimaryExpr(p); } return addNode( &p->nodes, (AstNodeItem) { .tag = tag, .main_token = nextToken(p), .data = { .lhs = parsePrefixExpr(p), .rhs = 0, }, }); } static AstNodeIndex parseTypeExpr(Parser* p) { const TokenizerTag tok = p->token_tags[p->tok_i]; switch (tok) { case TOKEN_QUESTION_MARK: return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_OPTIONAL_TYPE, .main_token = nextToken(p), .data = { .lhs = parseTypeExpr(p), .rhs = 0 }, }); case TOKEN_KEYWORD_ANYFRAME: fail(p, "unsupported type expression"); case TOKEN_ASTERISK: { const AstTokenIndex asterisk = nextToken(p); const PtrModifiers mods = parsePtrModifiers(p); const AstNodeIndex elem_type = parseTypeExpr(p); return makePtrTypeNode(p, asterisk, 0, mods, elem_type); } case TOKEN_ASTERISK_ASTERISK: { const AstTokenIndex asterisk = nextToken(p); const PtrModifiers mods = parsePtrModifiers(p); const AstNodeIndex elem_type = parseTypeExpr(p); if (elem_type == 0) { fail(p, "expected type expression"); } const AstNodeIndex inner = makePtrTypeNode(p, asterisk, 0, mods, elem_type); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_PTR_TYPE_ALIGNED, .main_token = asterisk, .data = { .lhs = 0, .rhs = inner }, }); } case TOKEN_L_BRACKET: { const AstTokenIndex lbracket = nextToken(p); if (p->token_tags[p->tok_i] == TOKEN_ASTERISK) { // [*] many-item pointer, [*c] C pointer, [*:s] sentinel p->tok_i++; // consume * AstNodeIndex sentinel = 0; if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER) { // Check for 'c' modifier: [*c] const char c = p->source[p->token_starts[p->tok_i]]; if (c == 'c' && p->token_starts[p->tok_i + 1] - p->token_starts[p->tok_i] <= 2) { p->tok_i++; // consume 'c' } } else if (eatToken(p, TOKEN_COLON) != null_token) { sentinel = expectExpr(p); } expectToken(p, TOKEN_R_BRACKET); const PtrModifiers mods = parsePtrModifiers(p); const AstNodeIndex elem_type = parseTypeExpr(p); return makePtrTypeNode(p, lbracket, sentinel, mods, elem_type); } const AstNodeIndex len_expr = parseExpr(p); const AstNodeIndex sentinel = eatToken(p, TOKEN_COLON) != null_token ? expectExpr(p) : 0; expectToken(p, TOKEN_R_BRACKET); if (len_expr == 0) { // Slice type: []T or [:s]T const PtrModifiers mods = parsePtrModifiers(p); const AstNodeIndex elem_type = parseTypeExpr(p); if (mods.bit_range_start != 0) { fail(p, "invalid_bit_range"); } return makePtrTypeNode(p, lbracket, sentinel, mods, elem_type); } // Array type: [N]T or [N:s]T const AstNodeIndex elem_type = parseTypeExpr(p); if (sentinel == 0) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ARRAY_TYPE, .main_token = lbracket, .data = { .lhs = len_expr, .rhs = elem_type }, }); } return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ARRAY_TYPE_SENTINEL, .main_token = lbracket, .data = { .lhs = len_expr, .rhs = addExtra(p, (AstNodeIndex[]) { sentinel, elem_type }, 2), }, }); } case TOKEN_KEYWORD_IF: { // if-type-expr: uses parseTypeExpr for branches instead of parseExpr const AstTokenIndex if_token = nextToken(p); expectToken(p, TOKEN_L_PAREN); const AstNodeIndex condition = expectExpr(p); expectToken(p, TOKEN_R_PAREN); parsePtrPayload(p); const AstNodeIndex then_expr = parseTypeExpr(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 = parseTypeExpr(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), }, }); } case TOKEN_KEYWORD_FOR: { // for-type-expr: uses parseTypeExpr for body instead of parseExpr const AstTokenIndex for_token = nextToken(p); const uint32_t scratch_top2 = p->scratch.len; const uint32_t inputs = forPrefix(p); const AstNodeIndex body = parseTypeExpr(p); bool has_else = false; if (eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) { parsePayload(p); SLICE_APPEND(AstNodeIndex, &p->scratch, body); const AstNodeIndex else_expr = parseTypeExpr(p); SLICE_APPEND(AstNodeIndex, &p->scratch, else_expr); has_else = true; } else if (inputs == 1) { p->scratch.len = scratch_top2; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_FOR_SIMPLE, .main_token = for_token, .data = { .lhs = p->scratch.arr[scratch_top2], .rhs = body, }, }); } else { SLICE_APPEND(AstNodeIndex, &p->scratch, body); } const uint32_t total = p->scratch.len - scratch_top2; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top2], total); p->scratch.len = scratch_top2; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_FOR, .main_token = for_token, .data = { .lhs = span.start, .rhs = ((uint32_t)inputs & 0x7FFFFFFF) | (has_else ? (1u << 31) : 0), }, }); } case TOKEN_KEYWORD_WHILE: { // while-type-expr: uses parseTypeExpr for body instead of parseExpr const AstTokenIndex while_token = nextToken(p); expectToken(p, TOKEN_L_PAREN); const AstNodeIndex condition = expectExpr(p); expectToken(p, TOKEN_R_PAREN); parsePtrPayload(p); const AstNodeIndex cont_expr = parseWhileContinueExpr(p); const AstNodeIndex body = parseTypeExpr(p); if (eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) { parsePayload(p); const AstNodeIndex else_expr = parseTypeExpr(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_WHILE, .main_token = while_token, .data = { .lhs = condition, .rhs = addExtra(p, (AstNodeIndex[]) { cont_expr, body, else_expr }, 3), }, }); } 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 }, }); } default: return parseErrorUnionExpr(p); } return 0; // tcc } 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 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; const uint32_t scratch_top = p->scratch.len; 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 uint32_t statements_len = p->scratch.len - scratch_top; p->scratch.len = scratch_top; const bool semicolon = statements_len != 0 && (p->token_tags[p->tok_i - 2] == TOKEN_SEMICOLON); switch (statements_len) { case 0: return addNode( &p->nodes, (AstNodeItem) { .tag = AST_NODE_BLOCK_TWO, .main_token = lbrace, .data = { .lhs = 0, .rhs = 0, }, }); case 1: 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], .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], .rhs = p->scratch.arr[scratch_top + 1], }, }); default:; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], 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; } // forPrefix parses the for prefix: (expr, expr, ...) |captures|. // Returns the number of input expressions. The inputs are appended // to the scratch buffer. static uint32_t forPrefix(Parser* p) { const uint32_t start = p->scratch.len; expectToken(p, TOKEN_L_PAREN); while (true) { AstNodeIndex input = expectExpr(p); if (eatToken(p, TOKEN_ELLIPSIS2) != null_token) { const AstTokenIndex ellipsis = p->tok_i - 1; const AstNodeIndex end = parseExpr(p); input = addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_FOR_RANGE, .main_token = ellipsis, .data = { .lhs = input, .rhs = end }, }); } SLICE_APPEND(AstNodeIndex, &p->scratch, input); if (p->token_tags[p->tok_i] == TOKEN_COMMA) { p->tok_i++; if (eatToken(p, TOKEN_R_PAREN) != null_token) break; continue; } expectToken(p, TOKEN_R_PAREN); break; } const uint32_t inputs = p->scratch.len - start; // Parse payload |a, *b, c| if (eatToken(p, TOKEN_PIPE) == null_token) { fail(p, "expected loop payload"); } { while (true) { eatToken(p, TOKEN_ASTERISK); expectToken(p, TOKEN_IDENTIFIER); if (p->token_tags[p->tok_i] == TOKEN_COMMA) { p->tok_i++; if (eatToken(p, TOKEN_PIPE) != null_token) break; continue; } expectToken(p, TOKEN_PIPE); break; } } return inputs; } static AstNodeIndex parseWhileExpr(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); const AstNodeIndex body = expectExpr(p); if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { 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_expr = expectExpr(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_expr }, 3), }, }); } 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 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. // lhs is the type expression (0 for anonymous .{...}). // lbrace is the lbrace token index. static AstNodeIndex parseInitList( Parser* p, AstNodeIndex lhs, AstTokenIndex lbrace) { const uint32_t scratch_top = p->scratch.len; const AstNodeIndex field_init = parseFieldInit(p); if (field_init != 0) { // Struct init SLICE_APPEND(AstNodeIndex, &p->scratch, field_init); while (true) { if (p->token_tags[p->tok_i] == TOKEN_COMMA) p->tok_i++; else if (p->token_tags[p->tok_i] == TOKEN_R_BRACE) { p->tok_i++; break; } else { fail(p, "parseInitList: expected , or } in struct init"); } if (eatToken(p, TOKEN_R_BRACE) != null_token) break; const AstNodeIndex next = parseFieldInit(p); assert(next != 0); SLICE_APPEND(AstNodeIndex, &p->scratch, next); } const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA; const uint32_t inits_len = p->scratch.len - scratch_top; p->scratch.len = scratch_top; if (lhs == 0) { // Anonymous struct init: .{...} switch (inits_len) { case 0: case 1: case 2: return addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_STRUCT_INIT_DOT_TWO_COMMA : AST_NODE_STRUCT_INIT_DOT_TWO, .main_token = lbrace, .data = { .lhs = inits_len >= 1 ? p->scratch.arr[scratch_top] : 0, .rhs = inits_len >= 2 ? p->scratch.arr[scratch_top + 1] : 0, }, }); default:; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], inits_len); return addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_STRUCT_INIT_DOT_COMMA : AST_NODE_STRUCT_INIT_DOT, .main_token = lbrace, .data = { .lhs = span.start, .rhs = span.end }, }); } } // Named struct init: X{...} switch (inits_len) { case 0: case 1: return addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_STRUCT_INIT_ONE_COMMA : AST_NODE_STRUCT_INIT_ONE, .main_token = lbrace, .data = { .lhs = lhs, .rhs = inits_len >= 1 ? p->scratch.arr[scratch_top] : 0, }, }); default:; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], inits_len); return addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_STRUCT_INIT_COMMA : AST_NODE_STRUCT_INIT, .main_token = lbrace, .data = { .lhs = lhs, .rhs = addExtra(p, (AstNodeIndex[]) { span.start, span.end }, 2), }, }); } } // Array init or empty init while (true) { if (eatToken(p, TOKEN_R_BRACE) != null_token) break; const AstNodeIndex elem = expectExpr(p); SLICE_APPEND(AstNodeIndex, &p->scratch, elem); if (p->token_tags[p->tok_i] == TOKEN_COMMA) p->tok_i++; else if (p->token_tags[p->tok_i] == TOKEN_R_BRACE) { p->tok_i++; break; } else { fail(p, "parseInitList: expected , or } in array init"); } } const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA; const uint32_t elems_len = p->scratch.len - scratch_top; p->scratch.len = scratch_top; if (lhs == 0) { // Anonymous array init: .{a, b, ...} switch (elems_len) { case 0: case 1: case 2: return addNode(&p->nodes, (AstNodeItem) { .tag = (elems_len == 0) ? AST_NODE_STRUCT_INIT_DOT_TWO : (comma ? AST_NODE_ARRAY_INIT_DOT_TWO_COMMA : AST_NODE_ARRAY_INIT_DOT_TWO), .main_token = lbrace, .data = { .lhs = elems_len >= 1 ? p->scratch.arr[scratch_top] : 0, .rhs = elems_len >= 2 ? p->scratch.arr[scratch_top + 1] : 0, }, }); default:; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], elems_len); return addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_ARRAY_INIT_DOT_COMMA : AST_NODE_ARRAY_INIT_DOT, .main_token = lbrace, .data = { .lhs = span.start, .rhs = span.end }, }); } } // Named init: X{a, b, ...} switch (elems_len) { case 0: // Empty init X{} — treat as struct init return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_STRUCT_INIT_ONE, .main_token = lbrace, .data = { .lhs = lhs, .rhs = 0 }, }); case 1: return addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_ARRAY_INIT_ONE_COMMA : AST_NODE_ARRAY_INIT_ONE, .main_token = lbrace, .data = { .lhs = lhs, .rhs = p->scratch.arr[scratch_top], }, }); default:; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], elems_len); return addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_ARRAY_INIT_COMMA : AST_NODE_ARRAY_INIT, .main_token = lbrace, .data = { .lhs = lhs, .rhs = addExtra(p, (AstNodeIndex[]) { span.start, span.end }, 2), }, }); } } static AstNodeIndex parseErrorUnionExpr(Parser* p) { const AstNodeIndex suffix_expr = parseSuffixExpr(p); if (suffix_expr == 0) return null_node; const AstNodeIndex bang = eatToken(p, TOKEN_BANG); if (bang == null_token) return suffix_expr; return addNode( &p->nodes, (AstNodeItem) { .tag = AST_NODE_ERROR_UNION, .main_token = bang, .data = { .lhs = suffix_expr, .rhs = parseTypeExpr(p), }, }); } static AstNodeIndex parseSuffixExpr(Parser* p) { 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; const uint32_t scratch_top = p->scratch.len; 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; p->scratch.len = scratch_top; 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], }, }); break; default:; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], 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 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_GROUPED_EXPRESSION, .main_token = lparen, .data = { .lhs = inner, .rhs = rparen }, }); } 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) { const AstTokenIndex asm_token = nextToken(p); assert(p->token_tags[asm_token] == TOKEN_KEYWORD_ASM); eatToken(p, TOKEN_KEYWORD_VOLATILE); expectToken(p, TOKEN_L_PAREN); const AstNodeIndex template = expectExpr(p); // Simple asm: asm("...") if (eatToken(p, TOKEN_R_PAREN) != null_token) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ASM_SIMPLE, .main_token = asm_token, .data = { .lhs = template, .rhs = p->tok_i - 1 }, }); } // Complex asm with outputs, inputs, clobbers expectToken(p, TOKEN_COLON); const uint32_t scratch_top = p->scratch.len; // Parse outputs while (true) { const AstNodeIndex output = parseAsmOutputItem(p); if (output == 0) break; SLICE_APPEND(AstNodeIndex, &p->scratch, output); if (eatToken(p, TOKEN_COMMA) == null_token) break; } // Parse inputs (after second colon) if (eatToken(p, TOKEN_COLON) != null_token) { while (true) { const AstNodeIndex input = parseAsmInputItem(p); if (input == 0) break; SLICE_APPEND(AstNodeIndex, &p->scratch, input); if (eatToken(p, TOKEN_COMMA) == null_token) break; } } // Parse clobbers (after third colon) if (eatToken(p, TOKEN_COLON) != null_token) { if (p->token_tags[p->tok_i] == TOKEN_STRING_LITERAL) { // Legacy clobber format: "str1", "str2", ... // Produces asm_legacy node while (p->token_tags[p->tok_i] == TOKEN_STRING_LITERAL) { p->tok_i++; if (eatToken(p, TOKEN_COMMA) == null_token) break; } const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN); const uint32_t items_len = p->scratch.len - scratch_top; const AstSubRange items_span = listToSpan(p, &p->scratch.arr[scratch_top], items_len); p->scratch.len = scratch_top; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ASM_LEGACY, .main_token = asm_token, .data = { .lhs = template, .rhs = addExtra(p, (AstNodeIndex[]) { items_span.start, items_span.end, rparen }, 3), }, }); } // New clobber format: expression (e.g. .{ .clobber = true }) AstNodeIndex clobbers = 0; if (p->token_tags[p->tok_i] != TOKEN_R_PAREN) clobbers = expectExpr(p); const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN); const uint32_t items_len = p->scratch.len - scratch_top; const AstSubRange items_span = listToSpan(p, &p->scratch.arr[scratch_top], items_len); p->scratch.len = scratch_top; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ASM, .main_token = asm_token, .data = { .lhs = template, .rhs = addExtra(p, (AstNodeIndex[]) { items_span.start, items_span.end, OPT(clobbers), rparen }, 4), }, }); } // No clobbers const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN); const uint32_t items_len = p->scratch.len - scratch_top; const AstSubRange items_span = listToSpan(p, &p->scratch.arr[scratch_top], items_len); p->scratch.len = scratch_top; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ASM, .main_token = asm_token, .data = { .lhs = template, .rhs = addExtra(p, (AstNodeIndex[]) { items_span.start, items_span.end, OPT((AstNodeIndex)0), rparen }, 4), }, }); } 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_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; if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { while (true) { const AstNodeIndex item = parseSwitchItem(p); if (item == 0) break; SLICE_APPEND(AstNodeIndex, &p->scratch, item); if (eatToken(p, TOKEN_COMMA) == null_token) break; } if (p->scratch.len == items_old_len) return null_node; } const AstTokenIndex arrow = expectToken(p, TOKEN_EQUAL_ANGLE_BRACKET_RIGHT); parsePtrPayload(p); const AstNodeIndex case_body = parseAssignExpr(p); if (case_body == 0) { fail(p, "expected expression"); } const uint32_t items_len = p->scratch.len - items_old_len; AstNodeIndex case_node; switch (items_len) { case 0: case 1: case_node = addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_SWITCH_CASE_ONE, .main_token = arrow, .data = { .lhs = items_len >= 1 ? p->scratch.arr[items_old_len] : 0, .rhs = case_body, }, }); break; default: { const AstSubRange span = listToSpan(p, &p->scratch.arr[items_old_len], items_len); case_node = addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_SWITCH_CASE, .main_token = arrow, .data = { .lhs = addExtra(p, (AstNodeIndex[]) { span.start, span.end }, 2), .rhs = case_body, }, }); } break; } p->scratch.len = items_old_len; 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) { const uint32_t scratch_top = p->scratch.len; while (true) { if (eatToken(p, TOKEN_R_BRACE) != null_token) break; eatDocComments(p); const AstNodeIndex case_node = parseSwitchProng(p); if (case_node == 0) break; SLICE_APPEND(AstNodeIndex, &p->scratch, case_node); if (p->token_tags[p->tok_i] == TOKEN_COMMA) p->tok_i++; } const uint32_t cases_len = p->scratch.len - scratch_top; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], cases_len); p->scratch.len = scratch_top; return span; } static SmallSpan parseParamDeclList(Parser* p) { expectToken(p, TOKEN_L_PAREN); const uint32_t scratch_top = p->scratch.len; // 0 = none, 1 = seen, 2 = nonfinal int varargs = 0; while (true) { 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; } if (varargs == 2) { fail(p, "varargs_nonfinal"); } const uint32_t params_len = p->scratch.len - scratch_top; p->scratch.len = scratch_top; 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] }, }; default:; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], params_len); return (SmallSpan) { .tag = SMALL_SPAN_MULTI, .payload = { .multi = span }, }; } } static AstNodeIndex parseBuiltinCall(Parser* p) { const AstTokenIndex builtin_token = assertToken(p, TOKEN_BUILTIN); assertToken(p, TOKEN_L_PAREN); const uint32_t scratch_top = p->scratch.len; while (true) { if (eatToken(p, TOKEN_R_PAREN) != 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; p->scratch.len = scratch_top; 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], .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], .rhs = p->scratch.arr[scratch_top+1], }, }); default:; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], 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 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 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++; }