commit 5b69d6705bb88093a443a44fcc1bca18c721338c (tree)
parent e3500106501736f53a64775e7f551e78a3409918
Author: Motiejus Jakštys <motiejus.jakstys@chronosphere.io>
Date: Wed, 11 Feb 2026 14:24:37 +0000
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 <noreply@anthropic.com>
Diffstat:
| M | parser.c | | | 4754 | ++++++++++++++++++++++++++++++++++++++++--------------------------------------- |
1 file changed, 2378 insertions(+), 2376 deletions(-)
diff --git 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,798 +220,1321 @@ 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++;
-}
-
-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 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 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 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;
-}
-
-typedef struct {
- AstNodeIndex align_expr, value_expr;
-} NodeContainerField;
+void parseRoot(Parser* p) {
+ addNode(
+ &p->nodes, (AstNodeItem) { .tag = AST_NODE_ROOT, .main_token = 0 });
-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;
+ Members root_members = parseContainerMembers(p);
+ AstSubRange root_decls = membersToSpan(root_members, p);
- const AstNodeIndex type_expr = parseTypeExpr(p);
- if (type_expr == 0) {
- fail(p, "expected type expression");
+ if (p->token_tags[p->tok_i] != TOKEN_EOF) {
+ fail(p, "expected EOF");
}
- 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),
- },
- });
- }
+ p->nodes.datas[0].lhs = root_decls.start;
+ p->nodes.datas[0].rhs = root_decls.end;
}
-static AstNodeIndex parseBuiltinCall(Parser* p) {
- const AstTokenIndex builtin_token = assertToken(p, TOKEN_BUILTIN);
- assertToken(p, TOKEN_L_PAREN);
-
+static Members parseContainerMembers(Parser* p) {
CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
= initCleanupScratch(p);
+ while (eatToken(p, TOKEN_CONTAINER_DOC_COMMENT) != null_token)
+ ;
- while (true) {
- if (eatToken(p, TOKEN_R_PAREN) != null_token)
- break;
+ FieldState field_state = { .tag = FIELD_STATE_NONE };
- const AstNodeIndex param = expectExpr(p);
- SLICE_APPEND(AstNodeIndex, &p->scratch, param);
+ bool trailing = false;
+ while (1) {
+ const AstTokenIndex doc_comment = eatDocComments(p);
switch (p->token_tags[p->tok_i]) {
- case TOKEN_COMMA:
- 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_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,
+ 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 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;
+
+ 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 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 = p->scratch.arr[scratch_top.old_len],
- .rhs = 0,
+ .lhs = type_expr,
+ .rhs = value_expr,
},
});
- case 2:
- return addNode(&p->nodes,
+ } else if (value_expr == 0) {
+ return addNode(
+ &p->nodes,
(AstNodeItem) {
- .tag = comma ?
- AST_NODE_BUILTIN_CALL_TWO_COMMA :
- AST_NODE_BUILTIN_CALL_TWO,
- .main_token = builtin_token,
+ .tag = AST_NODE_CONTAINER_FIELD_ALIGN,
+ .main_token = main_token,
.data = {
- .lhs = p->scratch.arr[scratch_top.old_len],
- .rhs = p->scratch.arr[scratch_top.old_len+1],
+ .lhs = type_expr,
+ .rhs = align_expr,
},
});
- default:;
- const AstSubRange span
- = listToSpan(p, &p->scratch.arr[scratch_top.old_len], params_len);
+ } else {
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,
- },
- });
+ &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 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);
+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 },
+ });
}
- 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 },
- });
+ // 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");
}
- arg_expr = expectExpr(p);
- expectToken(p, TOKEN_R_PAREN);
+ expectSemicolon(p);
+ return addNode(&p->nodes,
+ (AstNodeItem) {
+ .tag = AST_NODE_COMPTIME,
+ .main_token = comptime_token,
+ .data = { .lhs = assign, .rhs = 0 },
+ });
}
- break;
- default:
- fail(p, "parseContainerDeclAuto: unexpected token");
}
- expectToken(p, TOKEN_L_BRACE);
- const Members members = parseContainerMembers(p);
- expectToken(p, TOKEN_R_BRACE);
+ 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) {
+ 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.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,
- .data = {
- .lhs = arg_expr,
- .rhs = addExtra(p,
- (AstNodeIndex[]) { span.start, span.end }, 2),
- },
+ .tag = AST_NODE_ASSIGN_DESTRUCTURE,
+ .main_token = equal_token,
+ .data = { .lhs = extra_start, .rhs = rhs },
});
}
-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:
+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_STRING_LITERAL,
- .main_token = nextToken(p),
- .data = {},
+ .tag = AST_NODE_IF_SIMPLE,
+ .main_token = if_token,
+ .data = { .lhs = condition, .rhs = then_body },
});
- 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");
+ }
+ 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;
+
+ if (label_token != 0) {
+ fail(p, "parseLabeledStatement does not support labels");
+ }
+
+ 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
+ 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");
}
- case TOKEN_KEYWORD_STRUCT:
- case TOKEN_KEYWORD_OPAQUE:
- case TOKEN_KEYWORD_ENUM:
- case TOKEN_KEYWORD_UNION:
- return parseContainerDeclAuto(p);
- case TOKEN_KEYWORD_COMPTIME:
+ 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_COMPTIME,
- .main_token = nextToken(p),
- .data = { .lhs = parseTypeExpr(p), .rhs = 0 },
+ .tag = AST_NODE_FOR,
+ .main_token = for_token,
+ .data = {
+ .lhs = span.start,
+ .rhs = ((uint32_t)inputs & 0x7FFFFFFF) | (1u << 31),
+ },
});
- 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);
+ }
+
+ 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_MULTILINE_STRING_LITERAL,
- .main_token = first,
- .data = { .lhs = first, .rhs = last },
+ .tag = AST_NODE_FOR_SIMPLE,
+ .main_token = for_token,
+ .data = { .lhs = input, .rhs = then_body },
});
}
- 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;
- }
- }
+
+ 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 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_IDENTIFIER,
- .main_token = nextToken(p),
- .data = {},
+ .tag = AST_NODE_FOR,
+ .main_token = for_token,
+ .data = {
+ .lhs = span.start,
+ .rhs = ((uint32_t)inputs & 0x7FFFFFFF) | (1u << 31),
+ },
});
- 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);
+ }
+
+ if (inputs == 1) {
+ const AstNodeIndex input = p->scratch.arr[scratch_top];
+ p->scratch.len = scratch_top;
return addNode(&p->nodes,
(AstNodeItem) {
- .tag = AST_NODE_GROUPED_EXPRESSION,
- .main_token = lparen,
- .data = { .lhs = inner, .rhs = rparen },
+ .tag = AST_NODE_FOR_SIMPLE,
+ .main_token = for_token,
+ .data = { .lhs = input, .rhs = then_expr },
});
}
- default:
- return null_node;
- }
+
+ 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 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 },
- });
- }
+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_SLICE,
- .main_token = lbracket,
+ .tag = AST_NODE_WHILE_CONT,
+ .main_token = while_token,
.data = {
- .lhs = lhs,
+ .lhs = condition,
.rhs = addExtra(p,
- (AstNodeIndex[]) { index_expr, end_expr }, 2),
+ (AstNodeIndex[]) { cont_expr, body }, 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 },
+ .tag = AST_NODE_WHILE_SIMPLE,
+ .main_token = while_token,
+ .data = { .lhs = condition, .rhs = body },
});
- 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:
+ }
+
+ 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 parseSuffixExpr(Parser* p) {
- if (eatToken(p, TOKEN_KEYWORD_ASYNC) != null_token) {
- fail(p, "async not supported");
+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;
+}
- AstNodeIndex res = parsePrimaryTypeExpr(p);
- if (res == 0)
- return res;
+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 AstNodeIndex suffix_op = parseSuffixOp(p, res);
- if (suffix_op != 0) {
- res = suffix_op;
- continue;
+ 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 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;
+ 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");
+ }
}
- 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(
+ node = addNode(
&p->nodes,
(AstNodeItem) {
- .tag = comma ? AST_NODE_CALL_COMMA : AST_NODE_CALL,
- .main_token = lparen,
+ .tag = info.tag,
+ .main_token = oper_token,
.data = {
- .lhs = res,
- .rhs = addExtra(p, (AstNodeIndex[]) {
- span.start,
- span.end,
- }, 2),
+ .lhs = node,
+ .rhs = rhs,
},
});
- break;
- }
- }
-}
-static AstTokenIndex expectToken(Parser* p, TokenizerTag tag) {
- if (p->token_tags[p->tok_i] == tag) {
- return nextToken(p);
- } else {
- fail(p, "unexpected token");
+ if (info.assoc == ASSOC_NONE)
+ banned_prec = info.prec;
}
- return 0; // tcc
+
+ return node;
}
-static AstNodeIndex expectSemicolon(Parser* p) {
- return expectToken(p, TOKEN_SEMICOLON);
+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 parseErrorUnionExpr(Parser* p) {
- const AstNodeIndex suffix_expr = parseSuffixExpr(p);
- if (suffix_expr == 0)
- return null_node;
+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 };
- const AstNodeIndex bang = eatToken(p, TOKEN_BANG);
- if (bang == null_token)
- return suffix_expr;
+ 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
+ };
- return addNode(
- &p->nodes,
- (AstNodeItem) {
- .tag = AST_NODE_ERROR_UNION,
- .main_token = bang,
- .data = {
- .lhs = suffix_expr,
- .rhs = parseTypeExpr(p),
- },
- });
-}
+ 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 };
-static PtrModifiers parsePtrModifiers(Parser* p) {
- PtrModifiers mods = {};
+ 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 };
- 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;
- }
- }
-}
+ 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 };
-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,
- },
- });
+ 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 };
}
- if (sentinel != 0) {
- return addNode(&p->nodes,
- (AstNodeItem) {
- .tag = AST_NODE_PTR_TYPE_SENTINEL,
- .main_token = main_token,
- .data = { .lhs = sentinel, .rhs = elem_type },
- });
+}
+
+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,
+ 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,373 +1745,317 @@ 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 SmallSpan parseParamDeclList(Parser* p) {
- expectToken(p, TOKEN_L_PAREN);
-
- 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)
- 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;
+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 (varargs == 2) {
- fail(p, "varargs_nonfinal");
+ 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,
+ },
+ });
}
-
- 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 },
- };
+ 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 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,
+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_FN_PROTO_SIMPLE,
- .main_token = fn_token,
+ .tag = AST_NODE_BREAK,
+ .main_token = nextToken(p),
.data = {
- .lhs = params.payload.zero_or_one,
- .rhs = return_type_expr,
+ .lhs = parseBreakLabel(p),
+ .rhs = parseExpr(p),
},
});
- case SMALL_SPAN_MULTI:
- return setNode(p, fn_proto_index,
+ case TOKEN_KEYWORD_CONTINUE:
+ return addNode(
+ &p->nodes,
(AstNodeItem) {
- .tag = AST_NODE_FN_PROTO_MULTI,
- .main_token = fn_token,
+ .tag = AST_NODE_CONTINUE,
+ .main_token = nextToken(p),
.data = {
- .lhs = addExtra(p,
- (AstNodeIndex[]) {
- params.payload.multi.start,
- params.payload.multi.end },
- 2),
- .rhs = return_type_expr,
+ .lhs = parseBreakLabel(p),
+ .rhs = parseExpr(p),
},
});
- }
- }
-
- // Complex fn proto with align/section/callconv/addrspace
- switch (params.tag) {
- case SMALL_SPAN_ZERO_OR_ONE:
- return setNode(p, fn_proto_index,
+ 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_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,
- },
+ .tag = AST_NODE_RESUME,
+ .main_token = nextToken(p),
+ .data = { .lhs = expectExpr(p), .rhs = 0 },
});
- case SMALL_SPAN_MULTI:
- return setNode(p, fn_proto_index,
+ case TOKEN_KEYWORD_RETURN:
+ return addNode(&p->nodes,
(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,
- },
+ .tag = AST_NODE_RETURN,
+ .main_token = nextToken(p),
+ .data = { .lhs = parseExpr(p), .rhs = 0 },
});
- }
- 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;
-}
-
-// 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;
+ 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);
}
- expectToken(p, TOKEN_PIPE);
- break;
+ } 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 inputs;
+
+ return 0; // tcc
}
-static AstNodeIndex parseForExpr(Parser* p) {
- const AstTokenIndex for_token = eatToken(p, TOKEN_KEYWORD_FOR);
- if (for_token == null_token)
+static AstNodeIndex parseIfExpr(Parser* p) {
+ const AstTokenIndex if_token = eatToken(p, TOKEN_KEYWORD_IF);
+ if (if_token == null_token)
return null_node;
- const uint32_t scratch_top = p->scratch.len;
- const uint32_t inputs = forPrefix(p);
+ 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) {
- 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;
+ if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) {
return addNode(&p->nodes,
(AstNodeItem) {
- .tag = AST_NODE_FOR_SIMPLE,
- .main_token = for_token,
- .data = { .lhs = input, .rhs = then_expr },
+ .tag = AST_NODE_IF_SIMPLE,
+ .main_token = if_token,
+ .data = { .lhs = condition, .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;
+ parsePayload(p);
+ const AstNodeIndex else_expr = expectExpr(p);
return addNode(&p->nodes,
(AstNodeItem) {
- .tag = AST_NODE_FOR,
- .main_token = for_token,
+ .tag = AST_NODE_IF,
+ .main_token = if_token,
.data = {
- .lhs = span.start,
- .rhs = (uint32_t)inputs & 0x7FFFFFFF,
+ .lhs = condition,
+ .rhs = addExtra(p,
+ (AstNodeIndex[]) { then_expr, else_expr }, 2),
},
});
}
-static AstNodeIndex parseForStatement(Parser* p) {
- const AstTokenIndex for_token = eatToken(p, TOKEN_KEYWORD_FOR);
- if (for_token == null_token)
+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;
- const uint32_t inputs = forPrefix(p);
+ CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
+ = initCleanupScratch(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;
+ 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);
- 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,
+ const uint32_t statements_len = p->scratch.len - scratch_top.old_len;
+ 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.old_len],
+ .rhs = 0,
+ },
+ });
+ case 2:
+ return addNode(
+ &p->nodes,
(AstNodeItem) {
- .tag = AST_NODE_FOR,
- .main_token = for_token,
+ .tag = semicolon ? AST_NODE_BLOCK_TWO_SEMICOLON : AST_NODE_BLOCK_TWO,
+ .main_token = lbrace,
.data = {
- .lhs = span.start,
- .rhs = ((uint32_t)inputs & 0x7FFFFFFF) | (1u << 31),
+ .lhs = p->scratch.arr[scratch_top.old_len],
+ .rhs = p->scratch.arr[scratch_top.old_len + 1],
},
});
- }
-
- 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,
+ default:;
+ const AstSubRange span = listToSpan(
+ p, &p->scratch.arr[scratch_top.old_len], statements_len);
+ return addNode(
+ &p->nodes,
(AstNodeItem) {
- .tag = AST_NODE_FOR_SIMPLE,
- .main_token = for_token,
- .data = { .lhs = input, .rhs = then_body },
+ .tag = semicolon ? AST_NODE_BLOCK_SEMICOLON : AST_NODE_BLOCK,
+ .main_token = lbrace,
+ .data = {
+ .lhs = span.start,
+ .rhs = span.end,
+ },
});
}
- 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,
- },
- });
+ return 0;
}
-static AstNodeIndex parseWhileContinueExpr(Parser* p) {
- if (eatToken(p, TOKEN_COLON) == null_token)
- return null_node;
+// 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);
- const AstNodeIndex expr = parseAssignExpr(p);
- expectToken(p, TOKEN_R_PAREN);
- return expr;
+
+ 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) {
@@ -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)
+static AstNodeIndex parseWhileContinueExpr(Parser* p) {
+ if (eatToken(p, TOKEN_COLON) == null_token)
return null_node;
-
expectToken(p, TOKEN_L_PAREN);
- const AstNodeIndex condition = expectExpr(p);
+ const AstNodeIndex expr = parseAssignExpr(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) {
- if (eatToken(p, TOKEN_COLON) == null_token)
- return null_token;
- return expectToken(p, TOKEN_IDENTIFIER);
+ 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;
-
- return parseInitList(p, lhs, lbrace);
-}
-
-typedef struct {
- int8_t prec;
- AstNodeTag tag;
- enum {
- ASSOC_LEFT,
- ASSOC_NONE,
- } assoc;
-} OperInfo;
+ const AstNodeIndex bang = eatToken(p, TOKEN_BANG);
+ if (bang == null_token)
+ return suffix_expr;
-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,
+ (AstNodeItem) {
+ .tag = AST_NODE_ERROR_UNION,
+ .main_token = bang,
+ .data = {
+ .lhs = suffix_expr,
+ .rhs = parseTypeExpr(p),
+ },
+ });
}
-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 parseSuffixExpr(Parser* p) {
+ if (eatToken(p, TOKEN_KEYWORD_ASYNC) != null_token) {
+ fail(p, "async not supported");
}
-}
-
-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 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 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 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 = 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 = 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 = p->scratch.arr[scratch_top.old_len],
},
});
-
- 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");
+ 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;
+ }
}
- 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);
+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");
}
- const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
+ 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_ASM_OUTPUT,
- .main_token = ident,
- .data = { .lhs = type_expr, .rhs = rparen },
+ .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 },
});
}
- 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);
+ 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,1069 +2739,693 @@ 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);
- return addNode(&p->nodes,
- (AstNodeItem) {
- .tag = AST_NODE_SWITCH_RANGE,
- .main_token = range_tok,
- .data = { .lhs = expr, .rhs = range_end },
- });
- }
- return expr;
-}
-
-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 AstSubRange parseSwitchProngList(Parser* p) {
- CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
- = initCleanupScratch(p);
- 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.old_len;
- 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;
-
- 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 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;
+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;
- }
- 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) {
+ const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
return addNode(&p->nodes,
(AstNodeItem) {
- .tag = AST_NODE_IF_SIMPLE,
- .main_token = if_token,
- .data = { .lhs = condition, .rhs = then_expr },
+ .tag = AST_NODE_ASM_OUTPUT,
+ .main_token = ident,
+ .data = { .lhs = type_expr, .rhs = rparen },
});
}
-
- 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),
- },
- });
+ return null_node;
}
-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:
+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_RETURN,
- .main_token = nextToken(p),
- .data = { .lhs = parseExpr(p), .rhs = 0 },
+ .tag = AST_NODE_ASM_INPUT,
+ .main_token = ident,
+ .data = { .lhs = operand, .rhs = rparen },
});
- 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 null_node;
+}
- return 0; // tcc
+static AstTokenIndex parseBreakLabel(Parser* p) {
+ if (eatToken(p, TOKEN_COLON) == null_token)
+ return null_token;
+ return expectToken(p, TOKEN_IDENTIFIER);
}
-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);
+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 addNode(
- &p->nodes,
- (AstNodeItem) {
- .tag = tag,
- .main_token = nextToken(p),
- .data = {
- .lhs = parsePrefixExpr(p),
- .rhs = 0,
- },
- });
+ return null_node;
}
-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
+// 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 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 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 parseAssignExpr(Parser* p) {
- const AstNodeIndex expr = parseExpr(p);
- if (expr == 0)
+static AstNodeIndex parseCallconv(Parser* p) {
+ if (eatToken(p, TOKEN_KEYWORD_CALLCONV) == null_token)
return null_node;
- return finishAssignExpr(p, expr);
+ expectToken(p, TOKEN_L_PAREN);
+ const AstNodeIndex expr = expectExpr(p);
+ expectToken(p, TOKEN_R_PAREN);
+ return expr;
}
-static AstNodeIndex parseSingleAssignExpr(Parser* p) {
- const AstNodeIndex expr = parseExpr(p);
- if (expr == 0)
+static AstNodeIndex parseAddrSpace(Parser* p) {
+ if (eatToken(p, TOKEN_KEYWORD_ADDRSPACE) == null_token)
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 },
- });
+ expectToken(p, TOKEN_L_PAREN);
+ const AstNodeIndex expr = expectExpr(p);
+ expectToken(p, TOKEN_R_PAREN);
+ return expr;
}
-static AstNodeIndex parseBlockExpr(Parser* p) {
- if (p->token_tags[p->tok_i] == TOKEN_L_BRACE)
- return parseBlock(p);
+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->token_tags[p->tok_i + 2] == TOKEN_L_BRACE) {
+ && p->token_tags[p->tok_i + 1] == TOKEN_COLON)
p->tok_i += 2;
- return parseBlock(p);
- }
- return null_node;
+ if (eatToken(p, TOKEN_KEYWORD_ANYTYPE) != null_token)
+ return 0;
+ return parseTypeExpr(p);
}
-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 void parsePayload(Parser* p) {
+ if (eatToken(p, TOKEN_PIPE) == null_token)
+ return;
+ expectToken(p, TOKEN_IDENTIFIER);
+ expectToken(p, TOKEN_PIPE);
}
-static AstNodeIndex expectVarDeclExprStatement(
- Parser* p, AstTokenIndex comptime_token) {
- CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
- = initCleanupScratch(p);
-
+static void parsePtrPayload(Parser* p) {
+ if (eatToken(p, TOKEN_PIPE) == null_token)
+ return;
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);
+ eatToken(p, TOKEN_ASTERISK);
+ expectToken(p, TOKEN_IDENTIFIER);
+ if (p->token_tags[p->tok_i] == TOKEN_COMMA) {
+ p->tok_i++;
+ continue;
}
- if (eatToken(p, TOKEN_COMMA) == null_token)
- break;
+ break;
}
+ expectToken(p, TOKEN_PIPE);
+}
- const uint32_t lhs_count = p->scratch.len - scratch_top.old_len;
- assert(lhs_count > 0);
+static AstNodeIndex parseSwitchProng(Parser* p) {
+ const uint32_t items_old_len = p->scratch.len;
- // 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 },
- });
+ 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;
}
- return expr;
+ if (p->scratch.len == items_old_len)
+ return null_node;
}
- // Have '=', parse RHS and semicolon
- const AstNodeIndex rhs = expectExpr(p);
- expectSemicolon(p);
+ 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");
+ }
- 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,
+ 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_ASSIGN,
- .main_token = equal_token,
- .data = { .lhs = lhs, .rhs = rhs },
+ .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;
}
- // 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 },
- });
+ p->scratch.len = items_old_len;
+ return case_node;
}
-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");
+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_IF_SIMPLE,
- .main_token = if_token,
- .data = { .lhs = condition, .rhs = then_body },
+ .tag = AST_NODE_SWITCH_RANGE,
+ .main_token = range_tok,
+ .data = { .lhs = expr, .rhs = range_end },
});
}
- 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),
- },
- });
+ return expr;
}
-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) {
+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_COMPTIME,
- .main_token = comptime_token,
- .data = { .lhs = block, .rhs = 0 },
+ .tag = AST_NODE_ARRAY_ACCESS,
+ .main_token = lbracket,
+ .data = { .lhs = lhs, .rhs = index_expr },
});
- }
- // 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");
+ 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 },
+ });
}
- expectSemicolon(p);
return addNode(&p->nodes,
(AstNodeItem) {
- .tag = AST_NODE_COMPTIME,
- .main_token = comptime_token,
- .data = { .lhs = assign, .rhs = 0 },
+ .tag = AST_NODE_SLICE,
+ .main_token = lbracket,
+ .data = {
+ .lhs = lhs,
+ .rhs = addExtra(p,
+ (AstNodeIndex[]) { index_expr, end_expr }, 2),
+ },
});
}
- }
-
- 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);
+ default:
+ fail(p, "parseSuffixOp: expected ] or .. after index expr");
}
- return addNode(&p->nodes,
- (AstNodeItem) {
- .tag = AST_NODE_ERRDEFER,
- .main_token = errdefer_token,
- .data = {
- .lhs = payload,
- .rhs = expectBlockExprStatement(p),
- },
- });
+ return 0; // tcc
}
- 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:
+ case TOKEN_PERIOD_ASTERISK:
return addNode(&p->nodes,
(AstNodeItem) {
- .tag = AST_NODE_SUSPEND,
+ .tag = AST_NODE_DEREF,
.main_token = nextToken(p),
- .data = {
- .lhs = expectBlockExprStatement(p),
- .rhs = 0,
- },
+ .data = { .lhs = lhs, .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 parseBlock(Parser* p) {
- const AstNodeIndex lbrace = eatToken(p, TOKEN_L_BRACE);
- if (lbrace == null_token)
+ 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;
-
- 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) {
- 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.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], 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,
- },
- });
+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");
}
- 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;
+ expectToken(p, TOKEN_L_BRACE);
+ const Members members = parseContainerMembers(p);
+ expectToken(p, TOKEN_R_BRACE);
- if (label_token != 0) {
- fail(p, "parseLabeledStatement does not support labels");
+ 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 },
+ });
}
- return null_node;
+ 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 parseGlobalVarDecl(Parser* p) {
- const AstNodeIndex var_decl = parseVarDeclProto(p);
- if (var_decl == 0) {
+static AstNodeIndex parseByteAlign(Parser* p) {
+ if (eatToken(p, TOKEN_KEYWORD_ALIGN) == null_token)
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;
+ expectToken(p, TOKEN_L_PAREN);
+ const AstNodeIndex expr = expectExpr(p);
+ expectToken(p, TOKEN_R_PAREN);
+ return expr;
}
-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:
+static AstSubRange parseSwitchProngList(Parser* p) {
+ CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
+ = initCleanupScratch(p);
+ 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++;
- 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");
- }
}
+ const uint32_t cases_len = p->scratch.len - scratch_top.old_len;
+ return listToSpan(p, &p->scratch.arr[scratch_top.old_len], cases_len);
+}
- eatToken(p, TOKEN_KEYWORD_THREADLOCAL);
- AstNodeIndex var_decl = parseGlobalVarDecl(p);
- if (var_decl != 0) {
- return var_decl;
- }
+static SmallSpan parseParamDeclList(Parser* p) {
+ expectToken(p, TOKEN_L_PAREN);
- // assuming the program is correct...
- fail(p, "the next token should be usingnamespace, which is not supported");
- return 0; // make tcc happy
-}
+ CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
+ = initCleanupScratch(p);
-static void findNextContainerMember(Parser* p) {
- uint32_t level = 0;
+ // 0 = none, 1 = seen, 2 = nonfinal
+ int varargs = 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:
+ 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;
}
-}
-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 },
- });
+ if (varargs == 2) {
+ fail(p, "varargs_nonfinal");
+ }
+
+ 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 Members parseContainerMembers(Parser* p) {
+static AstNodeIndex parseBuiltinCall(Parser* p) {
+ const AstTokenIndex builtin_token = assertToken(p, TOKEN_BUILTIN);
+ assertToken(p, TOKEN_L_PAREN);
+
CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
= initCleanupScratch(p);
- while (eatToken(p, TOKEN_CONTAINER_DOC_COMMENT) != null_token)
- ;
- FieldState field_state = { .tag = FIELD_STATE_NONE };
+ while (true) {
+ if (eatToken(p, TOKEN_R_PAREN) != null_token)
+ break;
- bool trailing = false;
- while (1) {
- const AstTokenIndex doc_comment = eatDocComments(p);
+ const AstNodeIndex param = expectExpr(p);
+ SLICE_APPEND(AstNodeIndex, &p->scratch, param);
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: {
+ case TOKEN_COMMA:
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");
- }
+ case TOKEN_R_PAREN:
+ p->tok_i++;
+ goto end_loop;
+ default:
+ fail(p, "expected comma after arg");
}
}
+end_loop:;
-break_loop:;
-
- const uint32_t items_len = p->scratch.len - scratch_top.old_len;
- switch (items_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 (Members) {
- .len = 0,
- .lhs = 0,
- .rhs = 0,
- .trailing = trailing,
- };
+ return addNode(&p->nodes,
+ (AstNodeItem) {
+ .tag = AST_NODE_BUILTIN_CALL_TWO,
+ .main_token = builtin_token,
+ .data = {
+ .lhs = 0,
+ .rhs = 0,
+ },
+ });
case 1:
- return (Members) {
- .len = 1,
- .lhs = p->scratch.arr[scratch_top.old_len],
- .rhs = 0,
- .trailing = trailing,
- };
+ 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 (Members) {
- .len = 2,
- .lhs = p->scratch.arr[scratch_top.old_len],
- .rhs = p->scratch.arr[scratch_top.old_len + 1],
- .trailing = trailing,
- };
+ 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], 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 });
+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;
+}
- Members root_members = parseContainerMembers(p);
- AstSubRange root_decls = membersToSpan(root_members, p);
+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;
+}
- if (p->token_tags[p->tok_i] != TOKEN_EOF) {
- fail(p, "expected EOF");
+static AstTokenIndex eatToken(Parser* p, TokenizerTag tag) {
+ if (p->token_tags[p->tok_i] == tag) {
+ return nextToken(p);
+ } else {
+ return null_token;
}
+}
- p->nodes.datas[0].lhs = root_decls.start;
- p->nodes.datas[0].rhs = root_decls.end;
+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++; }