mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
parse if maybe expression
This commit is contained in:
parent
5943f99510
commit
1f8e3871ee
@ -44,10 +44,11 @@ compromises backward compatibility.
|
||||
|
||||
### Current Status
|
||||
|
||||
* Core language features are lacking such as structs, enums, loops.
|
||||
* Have a look in the examples/ folder to see some code examples.
|
||||
* Some language features available such as loops, inline assembly, expressions,
|
||||
literals, functions.
|
||||
* Only Linux x86_64 is supported.
|
||||
* Only building for the native target is supported.
|
||||
* Have a look in the examples/ folder to see some code examples.
|
||||
* Optimized machine code that Zig produces is indistinguishable from
|
||||
optimized machine code produced from equivalent C program.
|
||||
* Zig can generate dynamic libraries, executables, object files, and C
|
||||
|
||||
@ -102,7 +102,11 @@ BoolOrExpression : BoolAndExpression token(BoolOr) BoolOrExpression | BoolAndExp
|
||||
|
||||
ReturnExpression : token(Return) option(Expression)
|
||||
|
||||
IfExpression : token(If) Expression Block option(Else | ElseIf)
|
||||
IfExpression : IfVarExpression | IfBoolExpression
|
||||
|
||||
IfBoolExpression : token(If) option((token) Expression Block option(Else | ElseIf)
|
||||
|
||||
IfVarExpression : token(If) (token(Const) | token(Var)) token(Symbol) option(token(Colon) Type) Token(MaybeAssign) Expression Block Option(Else | ElseIf)
|
||||
|
||||
ElseIf : token(Else) IfExpression
|
||||
|
||||
|
||||
@ -12,17 +12,16 @@ fn main(argc: isize, argv: &&u8, env: &&u8) -> i32 {
|
||||
const answer = rand_int(&rand_state, 0, 100) + 1;
|
||||
|
||||
while true {
|
||||
Buffer line = readline("\nGuess a number between 1 and 100: ");
|
||||
line = readline("\nGuess a number between 1 and 100: ");
|
||||
|
||||
(const err, const guess) = parse_number(line);
|
||||
if err == Error.None {
|
||||
if guess == answer {
|
||||
print_str("You win!\n");
|
||||
return 0;
|
||||
} else if guess < answer {
|
||||
if const guess ?= parse_number(line) {
|
||||
if (guess > answer) {
|
||||
print_str("Guess lower.\n");
|
||||
} else if (guess < answer) {
|
||||
print_str("Guess higher.\n");
|
||||
} else {
|
||||
print_str("Guess lower.\n");
|
||||
print_str("You win!\n");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
print_str("Invalid number format.\n");
|
||||
|
||||
@ -45,7 +45,8 @@ static AstNode *first_executing_node(AstNode *node) {
|
||||
case NodeTypeUse:
|
||||
case NodeTypeVoid:
|
||||
case NodeTypeBoolLiteral:
|
||||
case NodeTypeIfExpr:
|
||||
case NodeTypeIfBoolExpr:
|
||||
case NodeTypeIfVarExpr:
|
||||
case NodeTypeLabel:
|
||||
case NodeTypeGoto:
|
||||
case NodeTypeBreak:
|
||||
@ -528,7 +529,8 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import,
|
||||
case NodeTypeSymbol:
|
||||
case NodeTypeCastExpr:
|
||||
case NodeTypePrefixOpExpr:
|
||||
case NodeTypeIfExpr:
|
||||
case NodeTypeIfBoolExpr:
|
||||
case NodeTypeIfVarExpr:
|
||||
case NodeTypeWhileExpr:
|
||||
case NodeTypeLabel:
|
||||
case NodeTypeGoto:
|
||||
@ -598,7 +600,8 @@ static void preview_types(CodeGen *g, ImportTableEntry *import, AstNode *node) {
|
||||
case NodeTypeSymbol:
|
||||
case NodeTypeCastExpr:
|
||||
case NodeTypePrefixOpExpr:
|
||||
case NodeTypeIfExpr:
|
||||
case NodeTypeIfBoolExpr:
|
||||
case NodeTypeIfVarExpr:
|
||||
case NodeTypeWhileExpr:
|
||||
case NodeTypeLabel:
|
||||
case NodeTypeGoto:
|
||||
@ -1394,6 +1397,39 @@ static TypeTableEntry *analyze_continue_expr(CodeGen *g, ImportTableEntry *impor
|
||||
return g->builtin_types.entry_unreachable;
|
||||
}
|
||||
|
||||
static TypeTableEntry *analyze_if_bool_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
||||
TypeTableEntry *expected_type, AstNode *node)
|
||||
{
|
||||
analyze_expression(g, import, context, g->builtin_types.entry_bool, node->data.if_bool_expr.condition);
|
||||
|
||||
TypeTableEntry *then_type = analyze_expression(g, import, context, expected_type,
|
||||
node->data.if_bool_expr.then_block);
|
||||
|
||||
TypeTableEntry *else_type;
|
||||
if (node->data.if_bool_expr.else_node) {
|
||||
else_type = analyze_expression(g, import, context, expected_type, node->data.if_bool_expr.else_node);
|
||||
} else {
|
||||
else_type = g->builtin_types.entry_void;
|
||||
else_type = resolve_type_compatibility(g, context, node, expected_type, else_type);
|
||||
}
|
||||
|
||||
|
||||
if (expected_type) {
|
||||
return (then_type->id == TypeTableEntryIdUnreachable) ? else_type : then_type;
|
||||
} else {
|
||||
return resolve_peer_type_compatibility(g, context, node,
|
||||
node->data.if_bool_expr.then_block, node->data.if_bool_expr.else_node,
|
||||
then_type, else_type);
|
||||
}
|
||||
}
|
||||
|
||||
static TypeTableEntry *analyze_if_var_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
||||
TypeTableEntry *expected_type, AstNode *node)
|
||||
{
|
||||
assert(node->type == NodeTypeIfVarExpr);
|
||||
zig_panic("TODO analyze_if_var_expr");
|
||||
}
|
||||
|
||||
static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
||||
TypeTableEntry *expected_type, AstNode *node)
|
||||
{
|
||||
@ -1651,31 +1687,12 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NodeTypeIfExpr:
|
||||
{
|
||||
analyze_expression(g, import, context, g->builtin_types.entry_bool, node->data.if_expr.condition);
|
||||
|
||||
TypeTableEntry *then_type = analyze_expression(g, import, context, expected_type,
|
||||
node->data.if_expr.then_block);
|
||||
|
||||
TypeTableEntry *else_type;
|
||||
if (node->data.if_expr.else_node) {
|
||||
else_type = analyze_expression(g, import, context, expected_type, node->data.if_expr.else_node);
|
||||
} else {
|
||||
else_type = g->builtin_types.entry_void;
|
||||
else_type = resolve_type_compatibility(g, context, node, expected_type, else_type);
|
||||
}
|
||||
|
||||
|
||||
if (expected_type) {
|
||||
return_type = (then_type->id == TypeTableEntryIdUnreachable) ? else_type : then_type;
|
||||
} else {
|
||||
return_type = resolve_peer_type_compatibility(g, context, node,
|
||||
node->data.if_expr.then_block, node->data.if_expr.else_node,
|
||||
then_type, else_type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NodeTypeIfBoolExpr:
|
||||
return_type = analyze_if_bool_expr(g, import, context, expected_type, node);
|
||||
break;
|
||||
case NodeTypeIfVarExpr:
|
||||
return_type = analyze_if_var_expr(g, import, context, expected_type, node);
|
||||
break;
|
||||
case NodeTypeWhileExpr:
|
||||
return_type = analyze_while_expr(g, import, context, expected_type, node);
|
||||
break;
|
||||
@ -1828,7 +1845,8 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import,
|
||||
case NodeTypeSymbol:
|
||||
case NodeTypeCastExpr:
|
||||
case NodeTypePrefixOpExpr:
|
||||
case NodeTypeIfExpr:
|
||||
case NodeTypeIfBoolExpr:
|
||||
case NodeTypeIfVarExpr:
|
||||
case NodeTypeWhileExpr:
|
||||
case NodeTypeLabel:
|
||||
case NodeTypeGoto:
|
||||
|
||||
@ -769,18 +769,18 @@ static LLVMValueRef gen_return_expr(CodeGen *g, AstNode *node) {
|
||||
}
|
||||
}
|
||||
|
||||
static LLVMValueRef gen_if_expr(CodeGen *g, AstNode *node) {
|
||||
assert(node->type == NodeTypeIfExpr);
|
||||
assert(node->data.if_expr.condition);
|
||||
assert(node->data.if_expr.then_block);
|
||||
static LLVMValueRef gen_if_bool_expr(CodeGen *g, AstNode *node) {
|
||||
assert(node->type == NodeTypeIfBoolExpr);
|
||||
assert(node->data.if_bool_expr.condition);
|
||||
assert(node->data.if_bool_expr.then_block);
|
||||
|
||||
LLVMValueRef cond_value = gen_expr(g, node->data.if_expr.condition);
|
||||
LLVMValueRef cond_value = gen_expr(g, node->data.if_bool_expr.condition);
|
||||
|
||||
TypeTableEntry *then_type = get_expr_type(node->data.if_expr.then_block);
|
||||
TypeTableEntry *then_type = get_expr_type(node->data.if_bool_expr.then_block);
|
||||
bool use_expr_value = (then_type->id != TypeTableEntryIdUnreachable &&
|
||||
then_type->id != TypeTableEntryIdVoid);
|
||||
|
||||
if (node->data.if_expr.else_node) {
|
||||
if (node->data.if_bool_expr.else_node) {
|
||||
LLVMBasicBlockRef then_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "Then");
|
||||
LLVMBasicBlockRef else_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "Else");
|
||||
LLVMBasicBlockRef endif_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "EndIf");
|
||||
@ -788,13 +788,13 @@ static LLVMValueRef gen_if_expr(CodeGen *g, AstNode *node) {
|
||||
LLVMBuildCondBr(g->builder, cond_value, then_block, else_block);
|
||||
|
||||
LLVMPositionBuilderAtEnd(g->builder, then_block);
|
||||
LLVMValueRef then_expr_result = gen_expr(g, node->data.if_expr.then_block);
|
||||
if (get_expr_type(node->data.if_expr.then_block)->id != TypeTableEntryIdUnreachable)
|
||||
LLVMValueRef then_expr_result = gen_expr(g, node->data.if_bool_expr.then_block);
|
||||
if (get_expr_type(node->data.if_bool_expr.then_block)->id != TypeTableEntryIdUnreachable)
|
||||
LLVMBuildBr(g->builder, endif_block);
|
||||
|
||||
LLVMPositionBuilderAtEnd(g->builder, else_block);
|
||||
LLVMValueRef else_expr_result = gen_expr(g, node->data.if_expr.else_node);
|
||||
if (get_expr_type(node->data.if_expr.else_node)->id != TypeTableEntryIdUnreachable)
|
||||
LLVMValueRef else_expr_result = gen_expr(g, node->data.if_bool_expr.else_node);
|
||||
if (get_expr_type(node->data.if_bool_expr.else_node)->id != TypeTableEntryIdUnreachable)
|
||||
LLVMBuildBr(g->builder, endif_block);
|
||||
|
||||
LLVMPositionBuilderAtEnd(g->builder, endif_block);
|
||||
@ -818,14 +818,19 @@ static LLVMValueRef gen_if_expr(CodeGen *g, AstNode *node) {
|
||||
LLVMBuildCondBr(g->builder, cond_value, then_block, endif_block);
|
||||
|
||||
LLVMPositionBuilderAtEnd(g->builder, then_block);
|
||||
gen_expr(g, node->data.if_expr.then_block);
|
||||
if (get_expr_type(node->data.if_expr.then_block)->id != TypeTableEntryIdUnreachable)
|
||||
gen_expr(g, node->data.if_bool_expr.then_block);
|
||||
if (get_expr_type(node->data.if_bool_expr.then_block)->id != TypeTableEntryIdUnreachable)
|
||||
LLVMBuildBr(g->builder, endif_block);
|
||||
|
||||
LLVMPositionBuilderAtEnd(g->builder, endif_block);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) {
|
||||
assert(node->type == NodeTypeIfVarExpr);
|
||||
zig_panic("TODO gen_if_var_expr");
|
||||
}
|
||||
|
||||
static LLVMValueRef gen_block(CodeGen *g, AstNode *block_node, TypeTableEntry *implicit_return_type) {
|
||||
assert(block_node->type == NodeTypeBlock);
|
||||
|
||||
@ -1112,8 +1117,10 @@ static LLVMValueRef gen_expr_no_cast(CodeGen *g, AstNode *node) {
|
||||
return LLVMConstAllOnes(LLVMInt1Type());
|
||||
else
|
||||
return LLVMConstNull(LLVMInt1Type());
|
||||
case NodeTypeIfExpr:
|
||||
return gen_if_expr(g, node);
|
||||
case NodeTypeIfBoolExpr:
|
||||
return gen_if_bool_expr(g, node);
|
||||
case NodeTypeIfVarExpr:
|
||||
return gen_if_var_expr(g, node);
|
||||
case NodeTypeWhileExpr:
|
||||
return gen_while_expr(g, node);
|
||||
case NodeTypeAsmExpr:
|
||||
|
||||
@ -114,8 +114,10 @@ const char *node_type_str(NodeType node_type) {
|
||||
return "Void";
|
||||
case NodeTypeBoolLiteral:
|
||||
return "BoolLiteral";
|
||||
case NodeTypeIfExpr:
|
||||
return "IfExpr";
|
||||
case NodeTypeIfBoolExpr:
|
||||
return "IfBoolExpr";
|
||||
case NodeTypeIfVarExpr:
|
||||
return "IfVarExpr";
|
||||
case NodeTypeWhileExpr:
|
||||
return "WhileExpr";
|
||||
case NodeTypeLabel:
|
||||
@ -321,14 +323,27 @@ void ast_print(AstNode *node, int indent) {
|
||||
case NodeTypeBoolLiteral:
|
||||
fprintf(stderr, "%s '%s'\n", node_type_str(node->type), node->data.bool_literal ? "true" : "false");
|
||||
break;
|
||||
case NodeTypeIfExpr:
|
||||
case NodeTypeIfBoolExpr:
|
||||
fprintf(stderr, "%s\n", node_type_str(node->type));
|
||||
if (node->data.if_expr.condition)
|
||||
ast_print(node->data.if_expr.condition, indent + 2);
|
||||
ast_print(node->data.if_expr.then_block, indent + 2);
|
||||
if (node->data.if_expr.else_node)
|
||||
ast_print(node->data.if_expr.else_node, indent + 2);
|
||||
if (node->data.if_bool_expr.condition)
|
||||
ast_print(node->data.if_bool_expr.condition, indent + 2);
|
||||
ast_print(node->data.if_bool_expr.then_block, indent + 2);
|
||||
if (node->data.if_bool_expr.else_node)
|
||||
ast_print(node->data.if_bool_expr.else_node, indent + 2);
|
||||
break;
|
||||
case NodeTypeIfVarExpr:
|
||||
{
|
||||
Buf *name_buf = &node->data.if_var_expr.var_decl.symbol;
|
||||
fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf));
|
||||
if (node->data.if_var_expr.var_decl.type)
|
||||
ast_print(node->data.if_var_expr.var_decl.type, indent + 2);
|
||||
if (node->data.if_var_expr.var_decl.expr)
|
||||
ast_print(node->data.if_var_expr.var_decl.expr, indent + 2);
|
||||
ast_print(node->data.if_var_expr.then_block, indent + 2);
|
||||
if (node->data.if_var_expr.else_node)
|
||||
ast_print(node->data.if_var_expr.else_node, indent + 2);
|
||||
break;
|
||||
}
|
||||
case NodeTypeWhileExpr:
|
||||
fprintf(stderr, "%s\n", node_type_str(node->type));
|
||||
ast_print(node->data.while_expr.condition, indent + 2);
|
||||
@ -1644,7 +1659,9 @@ static AstNode *ast_parse_else_or_else_if(ParseContext *pc, int *token_index, bo
|
||||
}
|
||||
|
||||
/*
|
||||
IfExpression : token(If) Expression Block option(Else | ElseIf)
|
||||
IfExpression : IfVarExpression | IfBoolExpression
|
||||
IfBoolExpression : token(If) option((token) Expression Block option(Else | ElseIf)
|
||||
IfVarExpression : token(If) (token(Const) | token(Var)) token(Symbol) option(token(Colon) Type) Token(Eq) Expression Block Option(Else | ElseIf)
|
||||
*/
|
||||
static AstNode *ast_parse_if_expr(ParseContext *pc, int *token_index, bool mandatory) {
|
||||
Token *if_tok = &pc->tokens->at(*token_index);
|
||||
@ -1657,11 +1674,37 @@ static AstNode *ast_parse_if_expr(ParseContext *pc, int *token_index, bool manda
|
||||
}
|
||||
*token_index += 1;
|
||||
|
||||
AstNode *node = ast_create_node(pc, NodeTypeIfExpr, if_tok);
|
||||
node->data.if_expr.condition = ast_parse_expression(pc, token_index, true);
|
||||
node->data.if_expr.then_block = ast_parse_block(pc, token_index, true);
|
||||
node->data.if_expr.else_node = ast_parse_else_or_else_if(pc, token_index, false);
|
||||
return node;
|
||||
Token *token = &pc->tokens->at(*token_index);
|
||||
if (token->id == TokenIdKeywordConst || token->id == TokenIdKeywordVar) {
|
||||
AstNode *node = ast_create_node(pc, NodeTypeIfVarExpr, if_tok);
|
||||
node->data.if_var_expr.var_decl.is_const = (token->id == TokenIdKeywordConst);
|
||||
*token_index += 1;
|
||||
|
||||
Token *name_token = ast_eat_token(pc, token_index, TokenIdSymbol);
|
||||
ast_buf_from_token(pc, name_token, &node->data.if_var_expr.var_decl.symbol);
|
||||
|
||||
Token *eq_or_colon = &pc->tokens->at(*token_index);
|
||||
*token_index += 1;
|
||||
if (eq_or_colon->id == TokenIdMaybeAssign) {
|
||||
node->data.if_var_expr.var_decl.expr = ast_parse_expression(pc, token_index, true);
|
||||
} else if (eq_or_colon->id == TokenIdColon) {
|
||||
node->data.if_var_expr.var_decl.type = ast_parse_type(pc, token_index);
|
||||
|
||||
ast_eat_token(pc, token_index, TokenIdMaybeAssign);
|
||||
node->data.if_var_expr.var_decl.expr = ast_parse_expression(pc, token_index, true);
|
||||
} else {
|
||||
ast_invalid_token_error(pc, eq_or_colon);
|
||||
}
|
||||
node->data.if_var_expr.then_block = ast_parse_block(pc, token_index, true);
|
||||
node->data.if_var_expr.else_node = ast_parse_else_or_else_if(pc, token_index, false);
|
||||
return node;
|
||||
} else {
|
||||
AstNode *node = ast_create_node(pc, NodeTypeIfBoolExpr, if_tok);
|
||||
node->data.if_bool_expr.condition = ast_parse_expression(pc, token_index, true);
|
||||
node->data.if_bool_expr.then_block = ast_parse_block(pc, token_index, true);
|
||||
node->data.if_bool_expr.else_node = ast_parse_else_or_else_if(pc, token_index, false);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@ -44,7 +44,8 @@ enum NodeType {
|
||||
NodeTypeUse,
|
||||
NodeTypeVoid,
|
||||
NodeTypeBoolLiteral,
|
||||
NodeTypeIfExpr,
|
||||
NodeTypeIfBoolExpr,
|
||||
NodeTypeIfVarExpr,
|
||||
NodeTypeWhileExpr,
|
||||
NodeTypeLabel,
|
||||
NodeTypeGoto,
|
||||
@ -217,12 +218,18 @@ struct AstNodeUse {
|
||||
ZigList<AstNode *> *directives;
|
||||
};
|
||||
|
||||
struct AstNodeIfExpr {
|
||||
struct AstNodeIfBoolExpr {
|
||||
AstNode *condition;
|
||||
AstNode *then_block;
|
||||
AstNode *else_node; // null, block node, or other if expr node
|
||||
};
|
||||
|
||||
struct AstNodeIfVarExpr {
|
||||
AstNodeVariableDeclaration var_decl;
|
||||
AstNode *then_block;
|
||||
AstNode *else_node; // null, block node, or other if expr node
|
||||
};
|
||||
|
||||
struct AstNodeWhileExpr {
|
||||
AstNode *condition;
|
||||
AstNode *body;
|
||||
@ -341,7 +348,8 @@ struct AstNode {
|
||||
AstNodeFnCallExpr fn_call_expr;
|
||||
AstNodeArrayAccessExpr array_access_expr;
|
||||
AstNodeUse use;
|
||||
AstNodeIfExpr if_expr;
|
||||
AstNodeIfBoolExpr if_bool_expr;
|
||||
AstNodeIfVarExpr if_var_expr;
|
||||
AstNodeWhileExpr while_expr;
|
||||
AstNodeLabel label;
|
||||
AstNodeGoto go_to;
|
||||
|
||||
@ -125,6 +125,7 @@ enum TokenizeState {
|
||||
TokenizeStateSawGreaterThanGreaterThan,
|
||||
TokenizeStateSawDot,
|
||||
TokenizeStateSawDotDot,
|
||||
TokenizeStateSawQuestionMark,
|
||||
TokenizeStateError,
|
||||
};
|
||||
|
||||
@ -402,10 +403,28 @@ void tokenize(Buf *buf, Tokenization *out) {
|
||||
begin_token(&t, TokenIdDot);
|
||||
t.state = TokenizeStateSawDot;
|
||||
break;
|
||||
case '?':
|
||||
begin_token(&t, TokenIdMaybe);
|
||||
t.state = TokenizeStateSawQuestionMark;
|
||||
break;
|
||||
default:
|
||||
tokenize_error(&t, "invalid character: '%c'", c);
|
||||
}
|
||||
break;
|
||||
case TokenizeStateSawQuestionMark:
|
||||
switch (c) {
|
||||
case '=':
|
||||
t.cur_tok->id = TokenIdMaybeAssign;
|
||||
end_token(&t);
|
||||
t.state = TokenizeStateStart;
|
||||
break;
|
||||
default:
|
||||
t.pos -= 1;
|
||||
end_token(&t);
|
||||
t.state = TokenizeStateStart;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case TokenizeStateSawDot:
|
||||
switch (c) {
|
||||
case '.':
|
||||
@ -917,6 +936,7 @@ void tokenize(Buf *buf, Tokenization *out) {
|
||||
case TokenizeStateSawGreaterThan:
|
||||
case TokenizeStateSawGreaterThanGreaterThan:
|
||||
case TokenizeStateSawDot:
|
||||
case TokenizeStateSawQuestionMark:
|
||||
end_token(&t);
|
||||
break;
|
||||
case TokenizeStateSawDotDot:
|
||||
@ -1012,6 +1032,8 @@ static const char * token_name(Token *token) {
|
||||
case TokenIdPercent: return "Percent";
|
||||
case TokenIdDot: return "Dot";
|
||||
case TokenIdEllipsis: return "Ellipsis";
|
||||
case TokenIdMaybe: return "Maybe";
|
||||
case TokenIdMaybeAssign: return "MaybeAssign";
|
||||
}
|
||||
return "(invalid token)";
|
||||
}
|
||||
|
||||
@ -83,6 +83,8 @@ enum TokenId {
|
||||
TokenIdPercent,
|
||||
TokenIdDot,
|
||||
TokenIdEllipsis,
|
||||
TokenIdMaybe,
|
||||
TokenIdMaybeAssign,
|
||||
};
|
||||
|
||||
struct Token {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user