parser: refactor to match upstream Parse.zig structure
Extract shared helpers and fix error handling to align with upstream: - Replace 7 assert() calls that crash on valid input with fprintf+exit - Extract parsePtrModifiers() and makePtrTypeNode() to deduplicate pointer modifier parsing from 4 inline copies into 1 shared function - Extract parseBlockExpr() and parseWhileContinueExpr() helpers - Move comptime wrapping into expectVarDeclExprStatement() via comptime_token parameter - Extract finishAssignExpr(), parseSwitchItem(), parseSwitchProng() Net effect: 3233 → 3106 lines. All 298+ parser tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
585
parser.c
585
parser.c
@@ -838,76 +838,75 @@ static AstNodeIndex parseErrorUnionExpr(Parser* p) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsePtrModifiersAndType parses pointer modifiers (allowzero, align,
|
typedef struct {
|
||||||
// addrspace, const, volatile, sentinel) and the child type for a pointer
|
AstNodeIndex align_node;
|
||||||
// started at main_token.
|
AstNodeIndex addrspace_node;
|
||||||
static AstNodeIndex parsePtrModifiersAndType(
|
AstNodeIndex bit_range_start;
|
||||||
Parser* p, AstTokenIndex main_token) {
|
AstNodeIndex bit_range_end;
|
||||||
AstNodeIndex sentinel = 0;
|
} PtrModifiers;
|
||||||
AstNodeIndex align_expr = 0;
|
|
||||||
AstNodeIndex bit_range_start = 0;
|
|
||||||
AstNodeIndex bit_range_end = 0;
|
|
||||||
AstNodeIndex addrspace_expr = 0;
|
|
||||||
|
|
||||||
// sentinel: *:0
|
static PtrModifiers parsePtrModifiers(Parser* p) {
|
||||||
if (eatToken(p, TOKEN_COLON) != null_token)
|
PtrModifiers mods = {};
|
||||||
sentinel = expectExpr(p);
|
|
||||||
|
|
||||||
// allowzero, const, volatile (before align)
|
while (true) {
|
||||||
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|
switch (p->token_tags[p->tok_i]) {
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|
case TOKEN_KEYWORD_CONST:
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
|
case TOKEN_KEYWORD_VOLATILE:
|
||||||
p->tok_i++;
|
case TOKEN_KEYWORD_ALLOWZERO:
|
||||||
|
p->tok_i++;
|
||||||
// align(expr) or align(expr:expr:expr)
|
continue;
|
||||||
if (eatToken(p, TOKEN_KEYWORD_ALIGN) != null_token) {
|
case TOKEN_KEYWORD_ALIGN:
|
||||||
expectToken(p, TOKEN_L_PAREN);
|
p->tok_i++;
|
||||||
align_expr = expectExpr(p);
|
expectToken(p, TOKEN_L_PAREN);
|
||||||
if (eatToken(p, TOKEN_COLON) != null_token) {
|
mods.align_node = expectExpr(p);
|
||||||
bit_range_start = expectExpr(p);
|
if (eatToken(p, TOKEN_COLON) != null_token) {
|
||||||
expectToken(p, TOKEN_COLON);
|
mods.bit_range_start = expectExpr(p);
|
||||||
bit_range_end = 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;
|
||||||
}
|
}
|
||||||
expectToken(p, TOKEN_R_PAREN);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// addrspace
|
static AstNodeIndex makePtrTypeNode(Parser* p, AstTokenIndex main_token,
|
||||||
addrspace_expr = parseAddrSpace(p);
|
AstNodeIndex sentinel, PtrModifiers mods, AstNodeIndex elem_type) {
|
||||||
|
if (mods.bit_range_start != 0) {
|
||||||
// const, volatile, allowzero (after align/addrspace)
|
|
||||||
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
|
|
||||||
p->tok_i++;
|
|
||||||
|
|
||||||
const AstNodeIndex child_type = parseTypeExpr(p);
|
|
||||||
|
|
||||||
if (bit_range_start != 0) {
|
|
||||||
return addNode(&p->nodes,
|
return addNode(&p->nodes,
|
||||||
(AstNodeItem) {
|
(AstNodeItem) {
|
||||||
.tag = AST_NODE_PTR_TYPE_BIT_RANGE,
|
.tag = AST_NODE_PTR_TYPE_BIT_RANGE,
|
||||||
.main_token = main_token,
|
.main_token = main_token,
|
||||||
.data = {
|
.data = {
|
||||||
.lhs = addExtra(p,
|
.lhs = addExtra(p,
|
||||||
(AstNodeIndex[]) { OPT(sentinel), align_expr,
|
(AstNodeIndex[]) { OPT(sentinel), mods.align_node,
|
||||||
OPT(addrspace_expr), bit_range_start,
|
OPT(mods.addrspace_node), mods.bit_range_start,
|
||||||
bit_range_end },
|
mods.bit_range_end },
|
||||||
5),
|
5),
|
||||||
.rhs = child_type,
|
.rhs = elem_type,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (addrspace_expr != 0 || (sentinel != 0 && align_expr != 0)) {
|
if (mods.addrspace_node != 0 || (sentinel != 0 && mods.align_node != 0)) {
|
||||||
return addNode(&p->nodes,
|
return addNode(&p->nodes,
|
||||||
(AstNodeItem) {
|
(AstNodeItem) {
|
||||||
.tag = AST_NODE_PTR_TYPE,
|
.tag = AST_NODE_PTR_TYPE,
|
||||||
.main_token = main_token,
|
.main_token = main_token,
|
||||||
.data = {
|
.data = {
|
||||||
.lhs = addExtra(p,
|
.lhs = addExtra(p,
|
||||||
(AstNodeIndex[]) { OPT(sentinel), OPT(align_expr),
|
(AstNodeIndex[]) { OPT(sentinel),
|
||||||
OPT(addrspace_expr) },
|
OPT(mods.align_node),
|
||||||
|
OPT(mods.addrspace_node) },
|
||||||
3),
|
3),
|
||||||
.rhs = child_type,
|
.rhs = elem_type,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -916,14 +915,14 @@ static AstNodeIndex parsePtrModifiersAndType(
|
|||||||
(AstNodeItem) {
|
(AstNodeItem) {
|
||||||
.tag = AST_NODE_PTR_TYPE_SENTINEL,
|
.tag = AST_NODE_PTR_TYPE_SENTINEL,
|
||||||
.main_token = main_token,
|
.main_token = main_token,
|
||||||
.data = { .lhs = sentinel, .rhs = child_type },
|
.data = { .lhs = sentinel, .rhs = elem_type },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return addNode(&p->nodes,
|
return addNode(&p->nodes,
|
||||||
(AstNodeItem) {
|
(AstNodeItem) {
|
||||||
.tag = AST_NODE_PTR_TYPE_ALIGNED,
|
.tag = AST_NODE_PTR_TYPE_ALIGNED,
|
||||||
.main_token = main_token,
|
.main_token = main_token,
|
||||||
.data = { .lhs = align_expr, .rhs = child_type },
|
.data = { .lhs = mods.align_node, .rhs = elem_type },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -943,77 +942,20 @@ static AstNodeIndex parseTypeExpr(Parser* p) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
case TOKEN_ASTERISK: {
|
case TOKEN_ASTERISK: {
|
||||||
const AstTokenIndex asterisk = nextToken(p);
|
const AstTokenIndex asterisk = nextToken(p);
|
||||||
return parsePtrModifiersAndType(p, asterisk);
|
const PtrModifiers mods = parsePtrModifiers(p);
|
||||||
|
const AstNodeIndex elem_type = parseTypeExpr(p);
|
||||||
|
return makePtrTypeNode(p, asterisk, 0, mods, elem_type);
|
||||||
}
|
}
|
||||||
case TOKEN_ASTERISK_ASTERISK: {
|
case TOKEN_ASTERISK_ASTERISK: {
|
||||||
// ** is two nested pointer types sharing the same token.
|
|
||||||
// Inner pointer gets modifiers, outer wraps it with none.
|
|
||||||
// (Matches upstream Parse.zig asterisk_asterisk case)
|
|
||||||
const AstTokenIndex asterisk = nextToken(p);
|
const AstTokenIndex asterisk = nextToken(p);
|
||||||
|
const PtrModifiers mods = parsePtrModifiers(p);
|
||||||
// Parse inner pointer modifiers (no sentinel for **)
|
|
||||||
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
|
|
||||||
p->tok_i++;
|
|
||||||
AstNodeIndex align_expr = 0;
|
|
||||||
AstNodeIndex bit_range_start = 0;
|
|
||||||
AstNodeIndex bit_range_end = 0;
|
|
||||||
if (eatToken(p, TOKEN_KEYWORD_ALIGN) != null_token) {
|
|
||||||
expectToken(p, TOKEN_L_PAREN);
|
|
||||||
align_expr = expectExpr(p);
|
|
||||||
if (eatToken(p, TOKEN_COLON) != null_token) {
|
|
||||||
bit_range_start = expectExpr(p);
|
|
||||||
expectToken(p, TOKEN_COLON);
|
|
||||||
bit_range_end = expectExpr(p);
|
|
||||||
}
|
|
||||||
expectToken(p, TOKEN_R_PAREN);
|
|
||||||
}
|
|
||||||
const AstNodeIndex addrspace_expr = parseAddrSpace(p);
|
|
||||||
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
|
|
||||||
p->tok_i++;
|
|
||||||
const AstNodeIndex elem_type = parseTypeExpr(p);
|
const AstNodeIndex elem_type = parseTypeExpr(p);
|
||||||
assert(elem_type != 0);
|
if (elem_type == 0) {
|
||||||
|
fprintf(stderr, "expected type expression\n");
|
||||||
AstNodeIndex inner;
|
exit(1);
|
||||||
if (bit_range_start != 0) {
|
|
||||||
inner = addNode(&p->nodes,
|
|
||||||
(AstNodeItem) {
|
|
||||||
.tag = AST_NODE_PTR_TYPE_BIT_RANGE,
|
|
||||||
.main_token = asterisk,
|
|
||||||
.data = {
|
|
||||||
.lhs = addExtra(p,
|
|
||||||
(AstNodeIndex[]) { OPT(0), align_expr,
|
|
||||||
OPT(addrspace_expr), bit_range_start,
|
|
||||||
bit_range_end },
|
|
||||||
5),
|
|
||||||
.rhs = elem_type,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else if (addrspace_expr != 0) {
|
|
||||||
inner = addNode(&p->nodes,
|
|
||||||
(AstNodeItem) {
|
|
||||||
.tag = AST_NODE_PTR_TYPE,
|
|
||||||
.main_token = asterisk,
|
|
||||||
.data = {
|
|
||||||
.lhs = addExtra(p,
|
|
||||||
(AstNodeIndex[]) { OPT(0), OPT(align_expr),
|
|
||||||
addrspace_expr },
|
|
||||||
3),
|
|
||||||
.rhs = elem_type,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
inner = addNode(&p->nodes,
|
|
||||||
(AstNodeItem) {
|
|
||||||
.tag = AST_NODE_PTR_TYPE_ALIGNED,
|
|
||||||
.main_token = asterisk,
|
|
||||||
.data = { .lhs = align_expr, .rhs = elem_type },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
const AstNodeIndex inner
|
||||||
|
= makePtrTypeNode(p, asterisk, 0, mods, elem_type);
|
||||||
return addNode(&p->nodes,
|
return addNode(&p->nodes,
|
||||||
(AstNodeItem) {
|
(AstNodeItem) {
|
||||||
.tag = AST_NODE_PTR_TYPE_ALIGNED,
|
.tag = AST_NODE_PTR_TYPE_ALIGNED,
|
||||||
@@ -1029,7 +971,6 @@ static AstNodeIndex parseTypeExpr(Parser* p) {
|
|||||||
AstNodeIndex sentinel = 0;
|
AstNodeIndex sentinel = 0;
|
||||||
if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER) {
|
if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER) {
|
||||||
// Check for 'c' modifier: [*c]
|
// Check for 'c' modifier: [*c]
|
||||||
// The 'c' is a regular identifier token
|
|
||||||
const char c = p->source[p->token_starts[p->tok_i]];
|
const char c = p->source[p->token_starts[p->tok_i]];
|
||||||
if (c == 'c'
|
if (c == 'c'
|
||||||
&& p->token_starts[p->tok_i + 1]
|
&& p->token_starts[p->tok_i + 1]
|
||||||
@@ -1041,133 +982,19 @@ static AstNodeIndex parseTypeExpr(Parser* p) {
|
|||||||
sentinel = expectExpr(p);
|
sentinel = expectExpr(p);
|
||||||
}
|
}
|
||||||
expectToken(p, TOKEN_R_BRACKET);
|
expectToken(p, TOKEN_R_BRACKET);
|
||||||
// Reuse shared pointer modifier + type parsing
|
const PtrModifiers mods = parsePtrModifiers(p);
|
||||||
// If we captured a sentinel from [*:s], temporarily store it
|
|
||||||
// and let parsePtrModifiersAndType handle the rest.
|
|
||||||
// But parsePtrModifiersAndType expects sentinel after main
|
|
||||||
// token via `:`. Since we already consumed it, we need to
|
|
||||||
// handle this inline.
|
|
||||||
|
|
||||||
// allowzero, const, volatile (before align)
|
|
||||||
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
|
|
||||||
p->tok_i++;
|
|
||||||
|
|
||||||
AstNodeIndex align_expr = 0;
|
|
||||||
AstNodeIndex bit_range_start = 0;
|
|
||||||
AstNodeIndex bit_range_end = 0;
|
|
||||||
if (eatToken(p, TOKEN_KEYWORD_ALIGN) != null_token) {
|
|
||||||
expectToken(p, TOKEN_L_PAREN);
|
|
||||||
align_expr = expectExpr(p);
|
|
||||||
if (eatToken(p, TOKEN_COLON) != null_token) {
|
|
||||||
bit_range_start = expectExpr(p);
|
|
||||||
expectToken(p, TOKEN_COLON);
|
|
||||||
bit_range_end = expectExpr(p);
|
|
||||||
}
|
|
||||||
expectToken(p, TOKEN_R_PAREN);
|
|
||||||
}
|
|
||||||
const AstNodeIndex addrspace_expr = parseAddrSpace(p);
|
|
||||||
|
|
||||||
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
|
|
||||||
p->tok_i++;
|
|
||||||
|
|
||||||
const AstNodeIndex elem_type = parseTypeExpr(p);
|
const AstNodeIndex elem_type = parseTypeExpr(p);
|
||||||
|
return makePtrTypeNode(p, lbracket, sentinel, mods, elem_type);
|
||||||
if (bit_range_start != 0) {
|
|
||||||
return addNode(&p->nodes,
|
|
||||||
(AstNodeItem) {
|
|
||||||
.tag = AST_NODE_PTR_TYPE_BIT_RANGE,
|
|
||||||
.main_token = lbracket,
|
|
||||||
.data = {
|
|
||||||
.lhs = addExtra(p,
|
|
||||||
(AstNodeIndex[]) { OPT(sentinel),
|
|
||||||
align_expr, OPT(addrspace_expr),
|
|
||||||
bit_range_start, bit_range_end },
|
|
||||||
5),
|
|
||||||
.rhs = elem_type,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (addrspace_expr != 0 || (sentinel != 0 && align_expr != 0)) {
|
|
||||||
return addNode(&p->nodes,
|
|
||||||
(AstNodeItem) {
|
|
||||||
.tag = AST_NODE_PTR_TYPE,
|
|
||||||
.main_token = lbracket,
|
|
||||||
.data = {
|
|
||||||
.lhs = addExtra(p,
|
|
||||||
(AstNodeIndex[]) { OPT(sentinel),
|
|
||||||
OPT(align_expr),
|
|
||||||
OPT(addrspace_expr) },
|
|
||||||
3),
|
|
||||||
.rhs = elem_type,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (sentinel != 0) {
|
|
||||||
return addNode(&p->nodes,
|
|
||||||
(AstNodeItem) {
|
|
||||||
.tag = AST_NODE_PTR_TYPE_SENTINEL,
|
|
||||||
.main_token = lbracket,
|
|
||||||
.data = { .lhs = sentinel, .rhs = elem_type },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return addNode(&p->nodes,
|
|
||||||
(AstNodeItem) {
|
|
||||||
.tag = AST_NODE_PTR_TYPE_ALIGNED,
|
|
||||||
.main_token = lbracket,
|
|
||||||
.data = { .lhs = align_expr, .rhs = elem_type },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
const AstNodeIndex len_expr = parseExpr(p);
|
const AstNodeIndex len_expr = parseExpr(p);
|
||||||
const AstNodeIndex sentinel
|
const AstNodeIndex sentinel
|
||||||
= eatToken(p, TOKEN_COLON) != null_token ? expectExpr(p) : 0;
|
= eatToken(p, TOKEN_COLON) != null_token ? expectExpr(p) : 0;
|
||||||
expectToken(p, TOKEN_R_BRACKET);
|
expectToken(p, TOKEN_R_BRACKET);
|
||||||
if (len_expr == 0) {
|
if (len_expr == 0) {
|
||||||
// Slice type: []T or [:s]T — reuse shared modifier parsing
|
// Slice type: []T or [:s]T
|
||||||
// allowzero, const, volatile
|
const PtrModifiers mods = parsePtrModifiers(p);
|
||||||
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
|
|
||||||
p->tok_i++;
|
|
||||||
const AstNodeIndex align_expr = parseByteAlign(p);
|
|
||||||
const AstNodeIndex addrspace_expr = parseAddrSpace(p);
|
|
||||||
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|
|
||||||
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
|
|
||||||
p->tok_i++;
|
|
||||||
const AstNodeIndex elem_type = parseTypeExpr(p);
|
const AstNodeIndex elem_type = parseTypeExpr(p);
|
||||||
if (addrspace_expr != 0 || (sentinel != 0 && align_expr != 0)) {
|
return makePtrTypeNode(p, lbracket, sentinel, mods, elem_type);
|
||||||
return addNode(&p->nodes,
|
|
||||||
(AstNodeItem) {
|
|
||||||
.tag = AST_NODE_PTR_TYPE,
|
|
||||||
.main_token = lbracket,
|
|
||||||
.data = {
|
|
||||||
.lhs = addExtra(p,
|
|
||||||
(AstNodeIndex[]) { OPT(sentinel),
|
|
||||||
OPT(align_expr),
|
|
||||||
OPT(addrspace_expr) },
|
|
||||||
3),
|
|
||||||
.rhs = elem_type,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (sentinel != 0) {
|
|
||||||
return addNode(&p->nodes,
|
|
||||||
(AstNodeItem) {
|
|
||||||
.tag = AST_NODE_PTR_TYPE_SENTINEL,
|
|
||||||
.main_token = lbracket,
|
|
||||||
.data = { .lhs = sentinel, .rhs = elem_type },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return addNode(&p->nodes,
|
|
||||||
(AstNodeItem) {
|
|
||||||
.tag = AST_NODE_PTR_TYPE_ALIGNED,
|
|
||||||
.main_token = lbracket,
|
|
||||||
.data = { .lhs = align_expr, .rhs = elem_type },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// Array type: [N]T or [N:s]T
|
// Array type: [N]T or [N:s]T
|
||||||
const AstNodeIndex elem_type = parseTypeExpr(p);
|
const AstNodeIndex elem_type = parseTypeExpr(p);
|
||||||
@@ -1493,7 +1320,10 @@ static AstNodeIndex parseForStatement(Parser* p) {
|
|||||||
then_body = block;
|
then_body = block;
|
||||||
} else {
|
} else {
|
||||||
then_body = parseAssignExpr(p);
|
then_body = parseAssignExpr(p);
|
||||||
assert(then_body != 0);
|
if (then_body == 0) {
|
||||||
|
fprintf(stderr, "expected expression\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
if (eatToken(p, TOKEN_SEMICOLON) != null_token)
|
if (eatToken(p, TOKEN_SEMICOLON) != null_token)
|
||||||
seen_semicolon = true;
|
seen_semicolon = true;
|
||||||
}
|
}
|
||||||
@@ -1545,6 +1375,15 @@ static AstNodeIndex parseForStatement(Parser* p) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static AstNodeIndex parseWhileContinueExpr(Parser* p) {
|
||||||
|
if (eatToken(p, TOKEN_COLON) == null_token)
|
||||||
|
return null_node;
|
||||||
|
expectToken(p, TOKEN_L_PAREN);
|
||||||
|
const AstNodeIndex expr = parseAssignExpr(p);
|
||||||
|
expectToken(p, TOKEN_R_PAREN);
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
static AstNodeIndex parseWhileExpr(Parser* p) {
|
static AstNodeIndex parseWhileExpr(Parser* p) {
|
||||||
const AstTokenIndex while_token = eatToken(p, TOKEN_KEYWORD_WHILE);
|
const AstTokenIndex while_token = eatToken(p, TOKEN_KEYWORD_WHILE);
|
||||||
if (while_token == null_token)
|
if (while_token == null_token)
|
||||||
@@ -1555,13 +1394,7 @@ static AstNodeIndex parseWhileExpr(Parser* p) {
|
|||||||
expectToken(p, TOKEN_R_PAREN);
|
expectToken(p, TOKEN_R_PAREN);
|
||||||
parsePtrPayload(p);
|
parsePtrPayload(p);
|
||||||
|
|
||||||
// Continue expression: : (expr)
|
const AstNodeIndex cont_expr = parseWhileContinueExpr(p);
|
||||||
AstNodeIndex cont_expr = 0;
|
|
||||||
if (eatToken(p, TOKEN_COLON) != null_token) {
|
|
||||||
expectToken(p, TOKEN_L_PAREN);
|
|
||||||
cont_expr = parseAssignExpr(p);
|
|
||||||
expectToken(p, TOKEN_R_PAREN);
|
|
||||||
}
|
|
||||||
|
|
||||||
const AstNodeIndex body = expectExpr(p);
|
const AstNodeIndex body = expectExpr(p);
|
||||||
|
|
||||||
@@ -1611,12 +1444,7 @@ static AstNodeIndex parseWhileStatement(Parser* p) {
|
|||||||
expectToken(p, TOKEN_R_PAREN);
|
expectToken(p, TOKEN_R_PAREN);
|
||||||
parsePtrPayload(p);
|
parsePtrPayload(p);
|
||||||
|
|
||||||
AstNodeIndex cont_expr = 0;
|
const AstNodeIndex cont_expr = parseWhileContinueExpr(p);
|
||||||
if (eatToken(p, TOKEN_COLON) != null_token) {
|
|
||||||
expectToken(p, TOKEN_L_PAREN);
|
|
||||||
cont_expr = parseAssignExpr(p);
|
|
||||||
expectToken(p, TOKEN_R_PAREN);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Statement body: block, or assign expr
|
// Statement body: block, or assign expr
|
||||||
AstNodeIndex body;
|
AstNodeIndex body;
|
||||||
@@ -1626,7 +1454,10 @@ static AstNodeIndex parseWhileStatement(Parser* p) {
|
|||||||
body = block;
|
body = block;
|
||||||
} else {
|
} else {
|
||||||
body = parseAssignExpr(p);
|
body = parseAssignExpr(p);
|
||||||
assert(body != 0);
|
if (body == 0) {
|
||||||
|
fprintf(stderr, "expected expression\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
if (eatToken(p, TOKEN_SEMICOLON) != null_token)
|
if (eatToken(p, TOKEN_SEMICOLON) != null_token)
|
||||||
seen_semicolon = true;
|
seen_semicolon = true;
|
||||||
}
|
}
|
||||||
@@ -2073,7 +1904,10 @@ static AstNodeIndex parseExprPrecedence(Parser* p, int32_t min_prec) {
|
|||||||
if (tok_tag == TOKEN_KEYWORD_CATCH)
|
if (tok_tag == TOKEN_KEYWORD_CATCH)
|
||||||
parsePayload(p);
|
parsePayload(p);
|
||||||
const AstNodeIndex rhs = parseExprPrecedence(p, info.prec + 1);
|
const AstNodeIndex rhs = parseExprPrecedence(p, info.prec + 1);
|
||||||
assert(rhs != 0);
|
if (rhs == 0) {
|
||||||
|
fprintf(stderr, "expected expression\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
node = addNode(
|
node = addNode(
|
||||||
&p->nodes,
|
&p->nodes,
|
||||||
@@ -2097,7 +1931,10 @@ static AstNodeIndex parseExpr(Parser* p) { return parseExprPrecedence(p, 0); }
|
|||||||
|
|
||||||
static AstNodeIndex expectExpr(Parser* p) {
|
static AstNodeIndex expectExpr(Parser* p) {
|
||||||
const AstNodeIndex node = parseExpr(p);
|
const AstNodeIndex node = parseExpr(p);
|
||||||
assert(node != 0);
|
if (node == 0) {
|
||||||
|
fprintf(stderr, "expected expression\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2258,6 +2095,84 @@ 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) {
|
||||||
|
fprintf(stderr, "expected expression\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t items_len = p->scratch.len - items_old_len;
|
||||||
|
AstNodeIndex case_node;
|
||||||
|
switch (items_len) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
case_node = addNode(&p->nodes,
|
||||||
|
(AstNodeItem) {
|
||||||
|
.tag = AST_NODE_SWITCH_CASE_ONE,
|
||||||
|
.main_token = arrow,
|
||||||
|
.data = {
|
||||||
|
.lhs
|
||||||
|
= items_len >= 1 ? p->scratch.arr[items_old_len] : 0,
|
||||||
|
.rhs = case_body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
const AstSubRange span
|
||||||
|
= listToSpan(p, &p->scratch.arr[items_old_len], items_len);
|
||||||
|
case_node = addNode(&p->nodes,
|
||||||
|
(AstNodeItem) {
|
||||||
|
.tag = AST_NODE_SWITCH_CASE,
|
||||||
|
.main_token = arrow,
|
||||||
|
.data = {
|
||||||
|
.lhs = addExtra(p,
|
||||||
|
(AstNodeIndex[]) { span.start, span.end }, 2),
|
||||||
|
.rhs = case_body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->scratch.len = items_old_len;
|
||||||
|
return case_node;
|
||||||
|
}
|
||||||
|
|
||||||
static AstNodeIndex parseSwitchExpr(Parser* p) {
|
static AstNodeIndex parseSwitchExpr(Parser* p) {
|
||||||
const AstTokenIndex switch_token = eatToken(p, TOKEN_KEYWORD_SWITCH);
|
const AstTokenIndex switch_token = eatToken(p, TOKEN_KEYWORD_SWITCH);
|
||||||
if (switch_token == null_token)
|
if (switch_token == null_token)
|
||||||
@@ -2275,77 +2190,10 @@ static AstNodeIndex parseSwitchExpr(Parser* p) {
|
|||||||
if (eatToken(p, TOKEN_R_BRACE) != null_token)
|
if (eatToken(p, TOKEN_R_BRACE) != null_token)
|
||||||
break;
|
break;
|
||||||
eatDocComments(p);
|
eatDocComments(p);
|
||||||
// Parse switch case items
|
const AstNodeIndex case_node = parseSwitchProng(p);
|
||||||
const uint32_t items_old_len = p->scratch.len;
|
if (case_node == 0)
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_ELSE) {
|
|
||||||
p->tok_i++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (p->token_tags[p->tok_i] == TOKEN_EQUAL_ANGLE_BRACKET_RIGHT)
|
|
||||||
break;
|
|
||||||
const AstNodeIndex item = expectExpr(p);
|
|
||||||
if (p->token_tags[p->tok_i] == TOKEN_ELLIPSIS3) {
|
|
||||||
const AstTokenIndex range_tok = nextToken(p);
|
|
||||||
const AstNodeIndex range_end = expectExpr(p);
|
|
||||||
SLICE_APPEND(AstNodeIndex, &p->scratch,
|
|
||||||
addNode(&p->nodes,
|
|
||||||
(AstNodeItem) {
|
|
||||||
.tag = AST_NODE_SWITCH_RANGE,
|
|
||||||
.main_token = range_tok,
|
|
||||||
.data = { .lhs = item, .rhs = range_end },
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
SLICE_APPEND(AstNodeIndex, &p->scratch, item);
|
|
||||||
}
|
|
||||||
if (p->token_tags[p->tok_i] == TOKEN_COMMA)
|
|
||||||
p->tok_i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AstTokenIndex arrow
|
|
||||||
= expectToken(p, TOKEN_EQUAL_ANGLE_BRACKET_RIGHT);
|
|
||||||
parsePtrPayload(p);
|
|
||||||
const AstNodeIndex case_body = parseAssignExpr(p);
|
|
||||||
assert(case_body != 0);
|
|
||||||
|
|
||||||
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;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore scratch to before items but keep case_node count
|
|
||||||
p->scratch.len = items_old_len;
|
|
||||||
SLICE_APPEND(AstNodeIndex, &p->scratch, case_node);
|
SLICE_APPEND(AstNodeIndex, &p->scratch, case_node);
|
||||||
|
|
||||||
if (p->token_tags[p->tok_i] == TOKEN_COMMA)
|
if (p->token_tags[p->tok_i] == TOKEN_COMMA)
|
||||||
p->tok_i++;
|
p->tok_i++;
|
||||||
}
|
}
|
||||||
@@ -2601,14 +2449,10 @@ static AstNodeTag assignOpTag(TokenizerTag tok) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static AstNodeIndex parseAssignExpr(Parser* p) {
|
static AstNodeIndex finishAssignExpr(Parser* p, AstNodeIndex lhs) {
|
||||||
const AstNodeIndex expr = parseExpr(p);
|
|
||||||
if (expr == 0)
|
|
||||||
return null_node;
|
|
||||||
|
|
||||||
const AstNodeTag assign_tag = assignOpTag(p->token_tags[p->tok_i]);
|
const AstNodeTag assign_tag = assignOpTag(p->token_tags[p->tok_i]);
|
||||||
if (assign_tag == AST_NODE_ROOT)
|
if (assign_tag == AST_NODE_ROOT)
|
||||||
return expr;
|
return lhs;
|
||||||
|
|
||||||
const AstTokenIndex op_token = nextToken(p);
|
const AstTokenIndex op_token = nextToken(p);
|
||||||
const AstNodeIndex rhs = expectExpr(p);
|
const AstNodeIndex rhs = expectExpr(p);
|
||||||
@@ -2616,12 +2460,18 @@ static AstNodeIndex parseAssignExpr(Parser* p) {
|
|||||||
(AstNodeItem) {
|
(AstNodeItem) {
|
||||||
.tag = assign_tag,
|
.tag = assign_tag,
|
||||||
.main_token = op_token,
|
.main_token = op_token,
|
||||||
.data = { .lhs = expr, .rhs = rhs },
|
.data = { .lhs = lhs, .rhs = rhs },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static AstNodeIndex expectBlockExprStatement(Parser* p) {
|
static AstNodeIndex parseAssignExpr(Parser* p) {
|
||||||
// Try block first (labeled or unlabeled)
|
const AstNodeIndex expr = parseExpr(p);
|
||||||
|
if (expr == 0)
|
||||||
|
return null_node;
|
||||||
|
return finishAssignExpr(p, expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static AstNodeIndex parseBlockExpr(Parser* p) {
|
||||||
if (p->token_tags[p->tok_i] == TOKEN_L_BRACE)
|
if (p->token_tags[p->tok_i] == TOKEN_L_BRACE)
|
||||||
return parseBlock(p);
|
return parseBlock(p);
|
||||||
if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER
|
if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER
|
||||||
@@ -2630,6 +2480,13 @@ static AstNodeIndex expectBlockExprStatement(Parser* p) {
|
|||||||
p->tok_i += 2;
|
p->tok_i += 2;
|
||||||
return parseBlock(p);
|
return parseBlock(p);
|
||||||
}
|
}
|
||||||
|
return null_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
static AstNodeIndex expectBlockExprStatement(Parser* p) {
|
||||||
|
const AstNodeIndex block_expr = parseBlockExpr(p);
|
||||||
|
if (block_expr != 0)
|
||||||
|
return block_expr;
|
||||||
// Assign expr + semicolon
|
// Assign expr + semicolon
|
||||||
const AstNodeIndex expr = parseAssignExpr(p);
|
const AstNodeIndex expr = parseAssignExpr(p);
|
||||||
if (expr != 0) {
|
if (expr != 0) {
|
||||||
@@ -2641,7 +2498,8 @@ static AstNodeIndex expectBlockExprStatement(Parser* p) {
|
|||||||
return 0; // tcc
|
return 0; // tcc
|
||||||
}
|
}
|
||||||
|
|
||||||
static AstNodeIndex expectVarDeclExprStatement(Parser* p) {
|
static AstNodeIndex expectVarDeclExprStatement(
|
||||||
|
Parser* p, AstTokenIndex comptime_token) {
|
||||||
CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
|
CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
|
||||||
= initCleanupScratch(p);
|
= initCleanupScratch(p);
|
||||||
|
|
||||||
@@ -2665,15 +2523,46 @@ static AstNodeIndex expectVarDeclExprStatement(Parser* p) {
|
|||||||
switch (p->token_tags[p->tok_i]) {
|
switch (p->token_tags[p->tok_i]) {
|
||||||
case TOKEN_SEMICOLON:
|
case TOKEN_SEMICOLON:
|
||||||
p->tok_i++;
|
p->tok_i++;
|
||||||
|
if (comptime_token != null_token) {
|
||||||
|
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) {
|
||||||
|
return addNode(&p->nodes,
|
||||||
|
(AstNodeItem) {
|
||||||
|
.tag = AST_NODE_COMPTIME,
|
||||||
|
.main_token = comptime_token,
|
||||||
|
.data = { .lhs = lhs, .rhs = 0 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
return lhs;
|
return lhs;
|
||||||
case TOKEN_R_BRACE:
|
case TOKEN_R_BRACE:
|
||||||
// Expression that doesn't need semicolon (block-terminated)
|
// Expression that doesn't need semicolon (block-terminated)
|
||||||
|
if (comptime_token != null_token) {
|
||||||
|
return addNode(&p->nodes,
|
||||||
|
(AstNodeItem) {
|
||||||
|
.tag = AST_NODE_COMPTIME,
|
||||||
|
.main_token = comptime_token,
|
||||||
|
.data = { .lhs = lhs, .rhs = 0 },
|
||||||
|
});
|
||||||
|
}
|
||||||
return lhs;
|
return lhs;
|
||||||
default: {
|
default: {
|
||||||
// Check if expression ended with a block (previous token is })
|
// Check if expression ended with a block (previous token is })
|
||||||
// and thus doesn't need a semicolon
|
// and thus doesn't need a semicolon
|
||||||
if (p->token_tags[p->tok_i - 1] == TOKEN_R_BRACE)
|
if (p->token_tags[p->tok_i - 1] == TOKEN_R_BRACE) {
|
||||||
|
if (comptime_token != null_token) {
|
||||||
|
return addNode(&p->nodes,
|
||||||
|
(AstNodeItem) {
|
||||||
|
.tag = AST_NODE_COMPTIME,
|
||||||
|
.main_token = comptime_token,
|
||||||
|
.data = { .lhs = lhs, .rhs = 0 },
|
||||||
|
});
|
||||||
|
}
|
||||||
return lhs;
|
return lhs;
|
||||||
|
}
|
||||||
const AstNodeTag assign_tag = assignOpTag(p->token_tags[p->tok_i]);
|
const AstNodeTag assign_tag = assignOpTag(p->token_tags[p->tok_i]);
|
||||||
if (assign_tag == AST_NODE_ROOT) {
|
if (assign_tag == AST_NODE_ROOT) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
@@ -2743,27 +2632,8 @@ static AstNodeIndex expectStatement(Parser* p, bool allow_defer_var) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// comptime var decl or expression
|
// comptime var decl or expression
|
||||||
if (allow_defer_var) {
|
if (allow_defer_var)
|
||||||
// Pass through to expectVarDeclExprStatement.
|
return expectVarDeclExprStatement(p, comptime_token);
|
||||||
// For var decls, the comptime prefix is detected from token
|
|
||||||
// positions by the renderer (no wrapping needed).
|
|
||||||
// For expressions, the result is wrapped in a comptime node.
|
|
||||||
const AstNodeIndex inner = expectVarDeclExprStatement(p);
|
|
||||||
const AstNodeTag inner_tag = p->nodes.tags[inner];
|
|
||||||
if (inner_tag == AST_NODE_SIMPLE_VAR_DECL
|
|
||||||
|| inner_tag == AST_NODE_ALIGNED_VAR_DECL
|
|
||||||
|| inner_tag == AST_NODE_LOCAL_VAR_DECL
|
|
||||||
|| inner_tag == AST_NODE_GLOBAL_VAR_DECL
|
|
||||||
|| inner_tag == AST_NODE_ASSIGN_DESTRUCTURE) {
|
|
||||||
return inner;
|
|
||||||
}
|
|
||||||
return addNode(&p->nodes,
|
|
||||||
(AstNodeItem) {
|
|
||||||
.tag = AST_NODE_COMPTIME,
|
|
||||||
.main_token = comptime_token,
|
|
||||||
.data = { .lhs = inner, .rhs = 0 },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr, "expectStatement: comptime keyword not supported here\n");
|
stderr, "expectStatement: comptime keyword not supported here\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
@@ -2834,7 +2704,7 @@ static AstNodeIndex expectStatement(Parser* p, bool allow_defer_var) {
|
|||||||
return labeled_statement;
|
return labeled_statement;
|
||||||
|
|
||||||
if (allow_defer_var) {
|
if (allow_defer_var) {
|
||||||
return expectVarDeclExprStatement(p);
|
return expectVarDeclExprStatement(p, null_token);
|
||||||
} else {
|
} else {
|
||||||
const AstNodeIndex assign_expr = parseAssignExpr(p);
|
const AstNodeIndex assign_expr = parseAssignExpr(p);
|
||||||
expectSemicolon(p);
|
expectSemicolon(p);
|
||||||
@@ -3082,7 +2952,10 @@ static Members parseContainerMembers(Parser* p) {
|
|||||||
? nextToken(p)
|
? nextToken(p)
|
||||||
: null_token;
|
: null_token;
|
||||||
const AstNodeIndex body = parseBlock(p);
|
const AstNodeIndex body = parseBlock(p);
|
||||||
assert(body != 0);
|
if (body == 0) {
|
||||||
|
fprintf(stderr, "expected block after test\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
const AstNodeIndex test_decl = addNode(&p->nodes,
|
const AstNodeIndex test_decl = addNode(&p->nodes,
|
||||||
(AstNodeItem) {
|
(AstNodeItem) {
|
||||||
.tag = AST_NODE_TEST_DECL,
|
.tag = AST_NODE_TEST_DECL,
|
||||||
|
|||||||
Reference in New Issue
Block a user