mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
1145 lines
30 KiB
C
1145 lines
30 KiB
C
// Copyright (c) 2020 Christoffer Lerno. All rights reserved.
|
|
// Use of this source code is governed by a LGPLv3.0
|
|
// a copy of which can be found in the LICENSE file.
|
|
|
|
#include "compiler_internal.h"
|
|
#include "parser_internal.h"
|
|
|
|
|
|
Ast *parse_unreachable_stmt(Context *context);
|
|
|
|
#pragma mark --- Internal functions
|
|
|
|
|
|
/**
|
|
* declaration_stmt
|
|
* : declaration ';'
|
|
*/
|
|
static inline Ast *parse_declaration_stmt(Context *context)
|
|
{
|
|
Ast *decl_stmt = AST_NEW_TOKEN(AST_DECLARE_STMT, context->tok);
|
|
decl_stmt->declare_stmt = TRY_DECL_OR(parse_decl(context), poisoned_ast);
|
|
CONSUME_OR(TOKEN_EOS, poisoned_ast);
|
|
return decl_stmt;
|
|
}
|
|
|
|
static inline Decl *parse_optional_label(Context *context, Ast *parent)
|
|
{
|
|
if (!TOKEN_IS(TOKEN_CONST_IDENT)) return NULL;
|
|
Decl *decl = decl_new(DECL_LABEL, context->tok.id, VISIBLE_LOCAL);
|
|
decl->label.parent = astid(parent);
|
|
advance_and_verify(context, TOKEN_CONST_IDENT);
|
|
if (!try_consume(context, TOKEN_COLON))
|
|
{
|
|
SEMA_ERROR(decl, "The name must be followed by a ':', did you forget it?");
|
|
return poisoned_decl;
|
|
}
|
|
return decl;
|
|
}
|
|
|
|
static inline void parse_optional_label_target(Context *context, Label *label)
|
|
{
|
|
if (TOKEN_IS(TOKEN_CONST_IDENT))
|
|
{
|
|
label->span = context->tok.id;
|
|
label->name = TOKSTR(context->tok);
|
|
advance_and_verify(context, TOKEN_CONST_IDENT);
|
|
}
|
|
}
|
|
|
|
static inline bool parse_asm_param(Context *context, AsmOperand **list)
|
|
{
|
|
TODO
|
|
/*
|
|
AsmOperand operand;
|
|
// Reset parser
|
|
context->lexer.current = context->token->span.loc + context->lexer.file_begin;
|
|
operand.constraints = lexer_scan_asm_constraint(&context->lexer);
|
|
if (operand.constraints.type == TOKEN_INVALID_TOKEN) return false;
|
|
|
|
// Restore state
|
|
TODO
|
|
//context->tok = lexer_scan_token(&context->lexer);
|
|
//context->next_tok = lexer_scan_token(&context->lexer);
|
|
|
|
operand.expr = TRY_EXPR_OR(parse_expr(context), false);
|
|
|
|
if (try_consume(context, TOKEN_AS))
|
|
{
|
|
EXPECT_OR(TOKEN_IDENT, false);
|
|
//operand.alias = context->tok;
|
|
advance(context);
|
|
}
|
|
vec_add(*list, operand);
|
|
return true;*/
|
|
}
|
|
|
|
static inline bool parse_asm_paramlist(Context *context, AsmOperand **list)
|
|
{
|
|
if (TOKEN_IS(TOKEN_EOS) || TOKEN_IS(TOKEN_RPAREN)) return true;
|
|
while (1)
|
|
{
|
|
if (!parse_asm_param(context, list)) return false;
|
|
if (TOKEN_IS(TOKEN_EOS) || TOKEN_IS(TOKEN_RPAREN)) return true;
|
|
CONSUME_OR(TOKEN_COMMA, false);
|
|
}
|
|
}
|
|
|
|
static inline bool parse_asm_params(Context *context, Ast *asm_ast)
|
|
{
|
|
/*
|
|
// Might be empty.
|
|
if (try_consume(context, TOKEN_RPAREN)) return true;
|
|
|
|
AsmParams *params = malloc_arena(sizeof(AsmParams));
|
|
asm_ast->asm_stmt.params = params;
|
|
|
|
// Parse outputs
|
|
if (!parse_asm_paramlist(context, ¶ms->inputs)) return false;
|
|
|
|
// Might not have any more params
|
|
if (try_consume(context, TOKEN_RPAREN)) return true;
|
|
|
|
// Consume the ';'
|
|
advance_and_verify(context, TOKEN_EOS);
|
|
|
|
// Parse inputs
|
|
if (!parse_asm_paramlist(context, ¶ms->inputs)) return false;
|
|
|
|
// Might not have any more params
|
|
if (try_consume(context, TOKEN_RPAREN)) return true;
|
|
|
|
while (1)
|
|
{
|
|
EXPECT_OR(TOKEN_IDENT, false);
|
|
vec_add(params->clobbers, context->token);
|
|
if (!try_consume(context, TOKEN_COMMA)) break;
|
|
}
|
|
|
|
// Might not have any more params
|
|
if (try_consume(context, TOKEN_RPAREN)) return true;
|
|
|
|
// Consume the ';'
|
|
CONSUME_OR(TOKEN_EOS, false);
|
|
|
|
while (1)
|
|
{
|
|
EXPECT_OR(TOKEN_IDENT, false);
|
|
vec_add(params->labels, context->token);
|
|
if (!try_consume(context, TOKEN_COMMA)) break;
|
|
}
|
|
|
|
// Consume the ')'
|
|
CONSUME_OR(TOKEN_RPAREN, false);
|
|
|
|
return true;
|
|
*/
|
|
TODO
|
|
}
|
|
/**
|
|
* asm ::= 'asm' '(' string ')'
|
|
* @param context
|
|
* @return
|
|
*/
|
|
static inline Ast* parse_asm_stmt(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_ASM_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_ASM);
|
|
ast->asm_stmt.is_volatile = try_consume(context, TOKEN_VOLATILE);
|
|
CONSUME_OR(TOKEN_LPAREN, poisoned_ast);
|
|
ast->asm_stmt.body = TRY_EXPR_OR(parse_expr(context), poisoned_ast);
|
|
ast->asm_stmt.is_volatile = true;
|
|
CONSUME_OR(TOKEN_RPAREN, poisoned_ast);
|
|
RANGE_EXTEND_PREV(ast);
|
|
CONSUME_OR(TOKEN_EOS, poisoned_ast);
|
|
return ast;
|
|
}
|
|
|
|
|
|
/**
|
|
* do_stmt
|
|
* : DO statement WHILE '(' expression ')' ';'
|
|
*/
|
|
static inline Ast* parse_do_stmt(Context *context)
|
|
{
|
|
Ast *do_ast = AST_NEW_TOKEN(AST_DO_STMT, context->tok);
|
|
|
|
advance_and_verify(context, TOKEN_DO);
|
|
|
|
do_ast->do_stmt.flow.label = TRY_DECL_OR(parse_optional_label(context, do_ast), poisoned_ast);
|
|
do_ast->do_stmt.body = TRY_AST(parse_stmt(context));
|
|
|
|
if (try_consume(context, TOKEN_WHILE))
|
|
{
|
|
CONSUME_OR(TOKEN_LPAREN, poisoned_ast);
|
|
do_ast->do_stmt.expr = TRY_EXPR_OR(parse_expr(context), poisoned_ast);
|
|
CONSUME_OR(TOKEN_RPAREN, poisoned_ast);
|
|
CONSUME_OR(TOKEN_EOS, poisoned_ast);
|
|
}
|
|
|
|
return do_ast;
|
|
}
|
|
|
|
static inline bool token_type_ends_case(TokenType type, TokenType case_type, TokenType default_type)
|
|
{
|
|
return type == case_type || type == default_type || type == TOKEN_RBRACE || type == TOKEN_CT_ENDSWITCH;
|
|
}
|
|
|
|
static inline Ast *parse_case_stmts(Context *context, TokenType case_type, TokenType default_type)
|
|
{
|
|
if (token_type_ends_case(context->tok.type, case_type, default_type)) return NULL;
|
|
Ast *compound = AST_NEW_TOKEN(AST_COMPOUND_STMT, context->tok);
|
|
while (!token_type_ends_case(context->tok.type, case_type, default_type))
|
|
{
|
|
Ast *stmt = TRY_AST(parse_stmt(context));
|
|
vec_add(compound->compound_stmt.stmts, stmt);
|
|
}
|
|
return compound;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* defer_stmt
|
|
* : DEFER statement
|
|
*/
|
|
static inline Ast* parse_defer_stmt(Context *context)
|
|
{
|
|
advance_and_verify(context, TOKEN_DEFER);
|
|
Ast *defer_stmt = AST_NEW_TOKEN(AST_DEFER_STMT, context->tok);
|
|
defer_stmt->defer_stmt.body = TRY_AST(parse_stmt(context));
|
|
return defer_stmt;
|
|
}
|
|
|
|
/**
|
|
* while_stmt
|
|
* : WHILE '(' control_expression ')' statement
|
|
*/
|
|
static inline Ast* parse_while_stmt(Context *context)
|
|
{
|
|
Ast *while_ast = AST_NEW_TOKEN(AST_WHILE_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_WHILE);
|
|
|
|
while_ast->while_stmt.flow.label = TRY_DECL_OR(parse_optional_label(context, while_ast), poisoned_ast);
|
|
|
|
CONSUME_OR(TOKEN_LPAREN, poisoned_ast);
|
|
while_ast->while_stmt.cond = TRY_EXPR_OR(parse_cond(context), poisoned_ast);
|
|
CONSUME_OR(TOKEN_RPAREN, poisoned_ast);
|
|
while_ast->while_stmt.body = TRY_AST(parse_stmt(context));
|
|
return while_ast;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* if_expr
|
|
* : failable_type IDENT '=' initializer
|
|
* | failable_type IDENT NOFAIL_ASSIGN expression
|
|
* | expression
|
|
* ;
|
|
*
|
|
* if_cond_expr
|
|
* : if_expr
|
|
* | if_cond_expr ',' if_expr
|
|
* ;
|
|
*
|
|
* if_stmt
|
|
* : IF '(' control_expression ')' statement
|
|
* | IF '(' control_expression ')' compound_statement ELSE compound_statement
|
|
* ;
|
|
*/
|
|
static inline Ast* parse_if_stmt(Context *context)
|
|
{
|
|
Ast *if_ast = AST_NEW_TOKEN(AST_IF_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_IF);
|
|
if_ast->if_stmt.flow.label = TRY_DECL_OR(parse_optional_label(context, if_ast), poisoned_ast);
|
|
CONSUME_OR(TOKEN_LPAREN, poisoned_ast);
|
|
if_ast->if_stmt.cond = TRY_EXPR_OR(parse_cond(context), poisoned_ast);
|
|
CONSUME_OR(TOKEN_RPAREN, poisoned_ast);
|
|
// Special case, we might have if ( ) { case ... }
|
|
if (tok_is(context, TOKEN_LBRACE) && (context->next_tok.type == TOKEN_CASE || context->next_tok.type == TOKEN_DEFAULT))
|
|
{
|
|
Ast *stmt = AST_NEW_TOKEN(AST_IF_CATCH_SWITCH_STMT, context->tok);
|
|
Ast **cases = NULL;
|
|
if (!parse_switch_body(context, &cases, TOKEN_CASE, TOKEN_DEFAULT, true)) return poisoned_ast;
|
|
stmt->switch_stmt.cases = cases;
|
|
if_ast->if_stmt.then_body = stmt;
|
|
}
|
|
else
|
|
{
|
|
if_ast->if_stmt.then_body = TRY_AST(parse_stmt(context));
|
|
}
|
|
if (!try_consume(context, TOKEN_ELSE))
|
|
{
|
|
return if_ast;
|
|
}
|
|
if_ast->if_stmt.else_body = TRY_AST(parse_stmt(context));
|
|
return if_ast;
|
|
}
|
|
|
|
|
|
|
|
static bool parse_type_or_expr(Context *context, TypeInfo **type_info, Expr **expr)
|
|
{
|
|
if (parse_next_is_case_type(context))
|
|
{
|
|
*type_info = TRY_TYPE_OR(parse_type(context), false);
|
|
return true;
|
|
}
|
|
*expr = TRY_EXPR_OR(parse_constant_expr(context), false);;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* case_stmt
|
|
* : CASE constant_expression ':' case_stmts
|
|
* | CASE constant_expression ELLIPSIS constant_expression ':' cast_stmts
|
|
* | CAST type ':' cast_stmts
|
|
* ;
|
|
*/
|
|
static inline Ast* parse_case_stmt(Context *context, TokenType case_type, TokenType default_type, bool allow_multiple_values)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_CASE_STMT, context->tok);
|
|
advance(context);
|
|
TypeInfo *type = NULL;
|
|
Expr *expr = NULL;
|
|
if (!parse_type_or_expr(context, &type, &expr)) return poisoned_ast;
|
|
if (type)
|
|
{
|
|
ast->case_stmt.is_type = true;
|
|
ast->case_stmt.type_info = type;
|
|
}
|
|
else
|
|
{
|
|
ast->case_stmt.expr = expr;
|
|
}
|
|
if (type && allow_multiple_values && try_consume(context, TOKEN_COMMA))
|
|
{
|
|
ast->case_stmt.is_type_list = true;
|
|
TypeInfo **type_infos = NULL;
|
|
vec_add(type_infos, type);
|
|
while (1)
|
|
{
|
|
type = TRY_TYPE_OR(parse_type(context), false);
|
|
vec_add(type_infos, type);
|
|
if (!try_consume(context, TOKEN_COMMA)) break;
|
|
}
|
|
ast->case_stmt.type_infos = type_infos;
|
|
}
|
|
TRY_CONSUME(TOKEN_COLON, "Missing ':' after case");
|
|
extend_ast_with_prev_token(context, ast);
|
|
ast->case_stmt.body = TRY_AST(parse_case_stmts(context, case_type, default_type));
|
|
return ast;
|
|
}
|
|
|
|
/**
|
|
* default_stmt
|
|
* : DEFAULT ':' case_stmts
|
|
*/
|
|
static inline Ast *parse_default_stmt(Context *context, TokenType case_type, TokenType default_type)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_DEFAULT_STMT, context->tok);
|
|
advance(context);
|
|
TRY_CONSUME_OR(TOKEN_COLON, "Expected ':' after 'default'.", poisoned_ast);
|
|
extend_ast_with_prev_token(context, ast);
|
|
ast->case_stmt.body = TRY_AST(parse_case_stmts(context, case_type, default_type));
|
|
ast->case_stmt.expr = NULL;
|
|
return ast;
|
|
}
|
|
|
|
/**
|
|
* switch_body
|
|
* : case_stmt
|
|
* | default_stmt
|
|
* | case_stmt switch_body
|
|
* | default_stmt switch body
|
|
* ;
|
|
*/
|
|
bool parse_switch_body(Context *context, Ast ***cases, TokenType case_type, TokenType default_type,
|
|
bool allow_multiple_values)
|
|
{
|
|
CONSUME_OR(TOKEN_LBRACE, false);
|
|
while (!try_consume(context, TOKEN_RBRACE))
|
|
{
|
|
Ast *result;
|
|
TokenType next = context->tok.type;
|
|
if (next == case_type)
|
|
{
|
|
result = TRY_AST_OR(parse_case_stmt(context, case_type, default_type, allow_multiple_values), false);
|
|
}
|
|
else if (next == default_type)
|
|
{
|
|
result = TRY_AST_OR(parse_default_stmt(context, case_type, default_type), false);
|
|
}
|
|
else
|
|
{
|
|
SEMA_TOKEN_ERROR(context->tok, "A 'case' or 'default' would be needed here, '%.*s' is not allowed.", TOKLEN(context->tok.id), TOKSTR(context->tok.id));
|
|
return false;
|
|
}
|
|
vec_add((*cases), result);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* switch
|
|
* : SWITCH '(' decl_expr_list ')' '{' switch_body '}'
|
|
*/
|
|
static inline Ast* parse_switch_stmt(Context *context)
|
|
{
|
|
Ast *switch_ast = AST_NEW_TOKEN(AST_SWITCH_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_SWITCH);
|
|
switch_ast->switch_stmt.flow.label = TRY_DECL_OR(parse_optional_label(context, switch_ast), poisoned_ast);
|
|
CONSUME_OR(TOKEN_LPAREN, poisoned_ast);
|
|
switch_ast->switch_stmt.cond = TRY_EXPR_OR(parse_cond(context), poisoned_ast);
|
|
CONSUME_OR(TOKEN_RPAREN, poisoned_ast);
|
|
|
|
if (!parse_switch_body(context, &switch_ast->switch_stmt.cases, TOKEN_CASE, TOKEN_DEFAULT, false)) return poisoned_ast;
|
|
return switch_ast;
|
|
}
|
|
|
|
|
|
/**
|
|
* for_statement
|
|
* : FOR '(' decl_expr_list ';' expression ';' ')' statement
|
|
* | FOR '(' decl_expr_list ';' ';' expression_list ')' statement
|
|
* | FOR '(' decl_expr_list ';' expression ';' expression_list ')' statement
|
|
* | FOR '(' ';' expression ';' ')' statement
|
|
* | FOR '(' ';' ';' expression_list ')' statement
|
|
* | FOR '(' ';' expression ';' expression_list ')' statement
|
|
* ;
|
|
*/
|
|
static inline Ast* parse_for_stmt(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_FOR_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_FOR);
|
|
ast->for_stmt.flow.label = TRY_DECL_OR(parse_optional_label(context, ast), poisoned_ast);
|
|
CONSUME_OR(TOKEN_LPAREN, poisoned_ast);
|
|
|
|
if (!TOKEN_IS(TOKEN_EOS))
|
|
{
|
|
ast->for_stmt.init = TRY_EXPR_OR(parse_cond(context), poisoned_ast);
|
|
}
|
|
else
|
|
{
|
|
ast->for_stmt.init = NULL;
|
|
}
|
|
|
|
CONSUME_OR(TOKEN_EOS, poisoned_ast);
|
|
|
|
if (!TOKEN_IS(TOKEN_EOS))
|
|
{
|
|
ast->for_stmt.cond = TRY_EXPR_OR(parse_expr(context), poisoned_ast);
|
|
}
|
|
|
|
CONSUME_OR(TOKEN_EOS, poisoned_ast);
|
|
|
|
if (!TOKEN_IS(TOKEN_RPAREN))
|
|
{
|
|
ast->for_stmt.incr = parse_expression_list(context);
|
|
}
|
|
|
|
CONSUME_OR(TOKEN_RPAREN, poisoned_ast);
|
|
|
|
extend_ast_with_prev_token(context, ast);
|
|
ast->for_stmt.body = TRY_AST(parse_stmt(context));
|
|
return ast;
|
|
}
|
|
|
|
static inline bool parse_foreach_var(Context *context, Ast *foreach)
|
|
{
|
|
TypeInfo *type = NULL;
|
|
|
|
bool failable = false;
|
|
|
|
// If we don't get foreach (foo ... or foreach (*foo ... then a type is expected.
|
|
if (!TOKEN_IS(TOKEN_IDENT) && !TOKEN_IS(TOKEN_AMP))
|
|
{
|
|
type = TRY_TYPE_OR(parse_type(context), false);
|
|
failable = try_consume(context, TOKEN_BANG);
|
|
// Add the failable to the type for nicer error reporting.
|
|
RANGE_EXTEND_PREV(type);
|
|
}
|
|
if (try_consume(context, TOKEN_AMP))
|
|
{
|
|
foreach->foreach_stmt.value_by_ref = true;
|
|
}
|
|
if (!try_consume(context, TOKEN_IDENT))
|
|
{
|
|
if (type)
|
|
{
|
|
SEMA_TOKEN_ERROR(context->tok, "Expected an identifier after the type.");
|
|
return false;
|
|
}
|
|
SEMA_TOKEN_ERROR(context->tok, "Expected an identifier or type.");
|
|
return false;
|
|
}
|
|
Decl *var = decl_new_var(context->prev_tok, type, VARDECL_LOCAL, VISIBLE_LOCAL);
|
|
var->var.failable = failable;
|
|
foreach->foreach_stmt.variable = var;
|
|
return true;
|
|
}
|
|
/**
|
|
* foreach_statement
|
|
* : FOREACH (CONST_IDENT ':')? '(' type? '*'? IDENT (',' type? '*'? IDENT) ':' expression ')' statement
|
|
* ;
|
|
*/
|
|
static inline Ast* parse_foreach_stmt(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_FOREACH_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_FOREACH);
|
|
ast->foreach_stmt.flow.label = TRY_DECL_OR(parse_optional_label(context, ast), poisoned_ast);
|
|
CONSUME_OR(TOKEN_LPAREN, poisoned_ast);
|
|
|
|
// Parse the first variable.
|
|
if (!parse_foreach_var(context, ast)) return poisoned_ast;
|
|
|
|
// Do we have a second variable?
|
|
if (try_consume(context, TOKEN_COMMA))
|
|
{
|
|
// Copy the first variable to "index"
|
|
ast->foreach_stmt.index = ast->foreach_stmt.variable;
|
|
ast->foreach_stmt.index_by_ref = ast->foreach_stmt.value_by_ref;
|
|
ast->foreach_stmt.value_by_ref = false;
|
|
|
|
// Parse the second variable
|
|
if (!parse_foreach_var(context, ast)) return poisoned_ast;
|
|
}
|
|
|
|
CONSUME_OR(TOKEN_COLON, poisoned_ast);
|
|
|
|
ast->foreach_stmt.enumeration = TRY_EXPR_OR(parse_initializer(context), poisoned_ast);
|
|
|
|
CONSUME_OR(TOKEN_RPAREN, poisoned_ast);
|
|
|
|
extend_ast_with_prev_token(context, ast);
|
|
ast->foreach_stmt.body = TRY_AST(parse_stmt(context));
|
|
return ast;
|
|
}
|
|
|
|
/**
|
|
* continue_stmt
|
|
* : CONTINUE
|
|
*/
|
|
static inline Ast* parse_continue(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_CONTINUE_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_CONTINUE);
|
|
parse_optional_label_target(context, &ast->contbreak_stmt.label);
|
|
if (ast->contbreak_stmt.label.name) ast->contbreak_stmt.is_label = true;
|
|
return ast;
|
|
}
|
|
|
|
|
|
/**
|
|
* next
|
|
* : NEXT
|
|
* | NEXT expr
|
|
*/
|
|
static inline Ast* parse_next(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_NEXT_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_NEXTCASE);
|
|
if (!TOKEN_IS(TOKEN_EOS))
|
|
{
|
|
if (TOKEN_IS(TOKEN_CONST_IDENT) && context->next_tok.type == TOKEN_COLON)
|
|
{
|
|
parse_optional_label_target(context, &ast->next_stmt.label);
|
|
advance_and_verify(context, TOKEN_COLON);
|
|
}
|
|
TypeInfo *type = NULL;
|
|
Expr *expr = NULL;
|
|
if (!parse_type_or_expr(context, &type, &expr)) return poisoned_ast;
|
|
if (type)
|
|
{
|
|
ast->next_stmt.is_type = true;
|
|
ast->next_stmt.type_info = type;
|
|
}
|
|
else
|
|
{
|
|
ast->next_stmt.target = expr;
|
|
}
|
|
}
|
|
return ast;
|
|
}
|
|
|
|
/**
|
|
* break
|
|
* : BREAK
|
|
*/
|
|
static inline Ast* parse_break(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_BREAK_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_BREAK);
|
|
parse_optional_label_target(context, &ast->contbreak_stmt.label);
|
|
if (ast->contbreak_stmt.label.name) ast->contbreak_stmt.is_label = true;
|
|
return ast;
|
|
}
|
|
|
|
/**
|
|
* expr_stmt
|
|
* : expression EOS
|
|
* ;
|
|
*/
|
|
static inline Ast *parse_expr_stmt(Context *context)
|
|
{
|
|
Ast *stmt = AST_NEW_TOKEN(AST_EXPR_STMT, context->tok);
|
|
stmt->expr_stmt = TRY_EXPR_OR(parse_expr(context), poisoned_ast);
|
|
TRY_CONSUME_EOS();
|
|
return stmt;
|
|
}
|
|
|
|
|
|
|
|
static inline Ast *parse_decl_or_expr_stmt(Context *context)
|
|
{
|
|
Expr *expr = TRY_EXPR_OR(parse_expr(context), poisoned_ast);
|
|
Ast *ast = ast_calloc();
|
|
ast->span = expr->span;
|
|
bool failable = false;
|
|
// We might be parsing "int!"
|
|
// If so we need to unwrap this.
|
|
if (expr->expr_kind == EXPR_FAILABLE && expr->failable_expr->expr_kind == EXPR_TYPEINFO)
|
|
{
|
|
failable = true;
|
|
expr_replace(expr, expr->failable_expr);
|
|
}
|
|
if (expr->expr_kind == EXPR_TYPEINFO)
|
|
{
|
|
ast->ast_kind = AST_DECLARE_STMT;
|
|
ast->declare_stmt = TRY_DECL_OR(parse_decl_after_type(context, expr->type_expr), poisoned_ast);
|
|
ast->declare_stmt->var.failable = failable;
|
|
}
|
|
else
|
|
{
|
|
ast->ast_kind = AST_EXPR_STMT;
|
|
ast->expr_stmt = expr;
|
|
}
|
|
CONSUME_OR(TOKEN_EOS, poisoned_ast);
|
|
return ast;
|
|
}
|
|
|
|
/**
|
|
* define_stmt
|
|
* : define CT_IDENT '=' const_expr EOS
|
|
* | define CT_TYPE '=' const_expr EOS
|
|
* | define CT_IDENT EOS
|
|
* | define CT_TYPE EOS
|
|
* ;
|
|
*/
|
|
static inline Ast *parse_define_stmt(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_DEFINE_STMT, context->tok);
|
|
TokenId start = context->tok.id;
|
|
advance_and_verify(context, TOKEN_DEFINE);
|
|
Decl *decl;
|
|
switch (context->tok.type)
|
|
{
|
|
case TOKEN_CT_IDENT:
|
|
decl = decl_new_var(context->tok.id, NULL, VARDECL_LOCAL_CT, VISIBLE_LOCAL);
|
|
advance(context);
|
|
if (try_consume(context, TOKEN_EQ))
|
|
{
|
|
decl->var.init_expr = TRY_EXPR_OR(parse_expr(context), poisoned_ast);
|
|
}
|
|
break;
|
|
case TOKEN_CT_TYPE_IDENT:
|
|
decl = decl_new_var(context->tok.id, NULL, VARDECL_LOCAL_CT_TYPE, VISIBLE_LOCAL);
|
|
advance(context);
|
|
if (try_consume(context, TOKEN_EQ))
|
|
{
|
|
decl->var.type_info = TRY_TYPE_OR(parse_type(context), poisoned_ast);
|
|
}
|
|
break;
|
|
default:
|
|
SEMA_TOKEN_ERROR(context->tok, "Expected a compile time variable name ('$Foo' or '$foo').");
|
|
return poisoned_ast;
|
|
}
|
|
decl->span.loc = start;
|
|
ast->define_stmt = decl;
|
|
RANGE_EXTEND_PREV(decl);
|
|
RANGE_EXTEND_PREV(ast);
|
|
TRY_CONSUME_EOS();
|
|
return ast;
|
|
}
|
|
|
|
static inline Ast* parse_ct_compound_stmt(Context *context)
|
|
{
|
|
Ast *stmts = AST_NEW_TOKEN(AST_CT_COMPOUND_STMT, context->tok);
|
|
while (1)
|
|
{
|
|
TokenType token = context->tok.type;
|
|
if (token == TOKEN_CT_ELSE || token == TOKEN_CT_ELIF || token == TOKEN_CT_ENDIF) break;
|
|
Ast *stmt = TRY_AST(parse_stmt(context));
|
|
vec_add(stmts->ct_compound_stmt, stmt);
|
|
RANGE_EXTEND_PREV(stmts);
|
|
}
|
|
return stmts;
|
|
}
|
|
|
|
/**
|
|
* ct_else_stmt
|
|
* : CT_ELSE ':' ct_compound_stmt
|
|
*/
|
|
static inline Ast* parse_ct_else_stmt(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_CT_ELSE_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_CT_ELSE);
|
|
TRY_CONSUME(TOKEN_COLON, "$else needs a ':', did you forget it?");
|
|
ast->ct_else_stmt = TRY_AST(parse_ct_compound_stmt(context));
|
|
return ast;
|
|
}
|
|
|
|
/**
|
|
* ct_elif_stmt
|
|
* : CT_ELIF '(' expression ')' ':' ct_compound_stmt (ct_elif_stmt | ct_else_stmt)?
|
|
*/
|
|
static inline Ast *parse_ct_elif_stmt(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_CT_ELIF_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_CT_ELIF);
|
|
ast->ct_elif_stmt.expr = TRY_EXPR_OR(parse_const_paren_expr(context), poisoned_ast);
|
|
TRY_CONSUME(TOKEN_COLON, "$elif needs a ':' after the expression, did you forget it?");
|
|
ast->ct_elif_stmt.then = TRY_AST(parse_ct_compound_stmt(context));
|
|
if (TOKEN_IS(TOKEN_CT_ELIF))
|
|
{
|
|
ast->ct_elif_stmt.elif = TRY_AST(parse_ct_elif_stmt(context));
|
|
}
|
|
else if (TOKEN_IS(TOKEN_CT_ELSE))
|
|
{
|
|
ast->ct_elif_stmt.elif = TRY_AST(parse_ct_else_stmt(context));
|
|
}
|
|
return ast;
|
|
}
|
|
|
|
/**
|
|
* ct_if_stmt
|
|
* : CT_IF '(' expression ')' ':' ct_compound_stmt (ct_elif_stmt | ct_else_stmt) CT_ENDIF EOS
|
|
* ;
|
|
*/
|
|
static inline Ast* parse_ct_if_stmt(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_CT_IF_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_CT_IF);
|
|
ast->ct_if_stmt.expr = TRY_EXPR_OR(parse_const_paren_expr(context), poisoned_ast);
|
|
TRY_CONSUME(TOKEN_COLON, "$if needs a ':' after the expression, did you forget it?");
|
|
ast->ct_if_stmt.then = TRY_AST(parse_ct_compound_stmt(context));
|
|
if (TOKEN_IS(TOKEN_CT_ELIF))
|
|
{
|
|
ast->ct_if_stmt.elif = TRY_AST(parse_ct_elif_stmt(context));
|
|
}
|
|
else if (TOKEN_IS(TOKEN_CT_ELSE))
|
|
{
|
|
ast->ct_if_stmt.elif = TRY_AST(parse_ct_else_stmt(context));
|
|
}
|
|
advance_and_verify(context, TOKEN_CT_ENDIF);
|
|
RANGE_EXTEND_PREV(ast);
|
|
TRY_CONSUME_EOS();
|
|
return ast;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* return
|
|
* : RETURN expression
|
|
* | RETURN
|
|
* ;
|
|
*/
|
|
static inline Ast *parse_return(Context *context)
|
|
{
|
|
advance_and_verify(context, TOKEN_RETURN);
|
|
Ast *ast = AST_NEW_TOKEN(AST_RETURN_STMT, context->tok);
|
|
ast->return_stmt.defer = 0;
|
|
if (!TOKEN_IS(TOKEN_EOS))
|
|
{
|
|
ast->return_stmt.expr = TRY_EXPR_OR(parse_expr(context), poisoned_ast);
|
|
}
|
|
return ast;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* volatile_stmt
|
|
* : VOLATILE compound_stmt
|
|
*/
|
|
static Ast *parse_volatile_stmt(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_VOLATILE_STMT, context->tok);
|
|
ast->volatile_stmt = TRY_AST_OR(parse_compound_stmt(context), poisoned_ast);
|
|
return ast;
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* ct_for_stmt
|
|
* : CT_FOR '(' CT_IDENT IN expression ')' statement
|
|
* | CT_FOR '(' CT_IDENT, CT_IDENT IN expression ')' statement
|
|
* ;
|
|
*
|
|
* @return
|
|
*/
|
|
static inline Ast* parse_ct_for_stmt(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_CT_FOR_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_CT_FOR);
|
|
CONSUME_OR(TOKEN_LPAREN, poisoned_ast);
|
|
if (context->next_tok.type == TOKEN_COMMA)
|
|
{
|
|
ast->ct_for_stmt.index = context->tok.id;
|
|
TRY_CONSUME_OR(TOKEN_CT_IDENT, "Expected a compile time index variable", poisoned_ast);
|
|
advance_and_verify(context, TOKEN_COMMA);
|
|
}
|
|
ast->ct_for_stmt.value = context->tok.id;
|
|
TRY_CONSUME_OR(TOKEN_CT_IDENT, "Expected a compile time variable", poisoned_ast);
|
|
TRY_CONSUME_OR(TOKEN_COLON, "Expected ':'.", poisoned_ast);
|
|
ast->ct_for_stmt.expr = TRY_EXPR_OR(parse_expr(context), poisoned_ast);
|
|
CONSUME_OR(TOKEN_RPAREN, poisoned_ast);
|
|
ast->ct_for_stmt.body = TRY_AST(parse_stmt(context));
|
|
return ast;
|
|
}
|
|
|
|
/**
|
|
* CTSWITCH '(' expression ')' ':' '{' ct_switch_body '}'
|
|
*
|
|
* ct_switch_body
|
|
* : ct_case_statement
|
|
* | ct_switch_body ct_case_statement
|
|
* ;
|
|
*
|
|
* ct_case_statement
|
|
* : CTCASE type_list ':' statement
|
|
* | CTDEFAULT ':' statement
|
|
* ;
|
|
*
|
|
* @return
|
|
*/
|
|
static inline Ast* parse_ct_switch_stmt(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_CT_SWITCH_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_CT_SWITCH);
|
|
ast->ct_switch_stmt.cond = TRY_EXPR_OR(parse_const_paren_expr(context), poisoned_ast);
|
|
TRY_CONSUME(TOKEN_COLON, "Expected ':' after $switch expression, did you forget it?");
|
|
Ast **cases = NULL;
|
|
while (!try_consume(context, TOKEN_CT_ENDSWITCH))
|
|
{
|
|
Ast *result;
|
|
TokenType next = context->tok.type;
|
|
if (next == TOKEN_CT_CASE)
|
|
{
|
|
result = TRY_AST_OR(parse_case_stmt(context, TOKEN_CT_CASE, TOKEN_CT_DEFAULT, true), poisoned_ast);
|
|
}
|
|
else if (next == TOKEN_CT_DEFAULT)
|
|
{
|
|
result = TRY_AST_OR(parse_default_stmt(context, TOKEN_CT_CASE, TOKEN_CT_DEFAULT), poisoned_ast);
|
|
}
|
|
else
|
|
{
|
|
SEMA_TOKEN_ERROR(context->tok, "A '$case' or '$default' would be needed here, '%.*s' is not allowed.", TOKLEN(context->tok.id), TOKSTR(context->tok.id));
|
|
return poisoned_ast;
|
|
}
|
|
vec_add(cases, result);
|
|
}
|
|
TRY_CONSUME_EOS();
|
|
ast->ct_switch_stmt.body = cases;
|
|
return ast;
|
|
}
|
|
|
|
static inline Ast *parse_assert_stmt(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_ASSERT_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_ASSERT);
|
|
TRY_CONSUME_OR(TOKEN_LPAREN, "'assert' needs a '(' here, did you forget it?", poisoned_ast);
|
|
ast->assert_stmt.expr = TRY_EXPR_OR(parse_assert_expr(context), poisoned_ast);
|
|
if (try_consume(context, TOKEN_COMMA))
|
|
{
|
|
ast->assert_stmt.message = TRY_EXPR_OR(parse_expr(context), poisoned_ast);
|
|
}
|
|
TRY_CONSUME_OR(TOKEN_RPAREN, "The ending ')' was expected here.", poisoned_ast);
|
|
TRY_CONSUME_EOS();
|
|
return ast;
|
|
}
|
|
|
|
#pragma mark --- External functions
|
|
|
|
/**
|
|
* ct_assert_stmt ::= CT_ASSERT '(' constant_expression (',' constant_expression) ')' ';'
|
|
* @param context
|
|
* @return
|
|
*/
|
|
Ast *parse_ct_assert_stmt(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_CT_ASSERT, context->tok);
|
|
advance_and_verify(context, TOKEN_CT_ASSERT);
|
|
TRY_CONSUME_OR(TOKEN_LPAREN, "'$assert' needs a '(' here, did you forget it?", poisoned_ast);
|
|
ast->ct_assert_stmt.expr = TRY_EXPR_OR(parse_constant_expr(context), poisoned_ast);
|
|
if (try_consume(context, TOKEN_COMMA))
|
|
{
|
|
ast->ct_assert_stmt.message = TRY_EXPR_OR(parse_constant_expr(context), poisoned_ast);
|
|
}
|
|
TRY_CONSUME_OR(TOKEN_RPAREN, "The ending ')' was expected here.", poisoned_ast);
|
|
TRY_CONSUME_EOS();
|
|
return ast;
|
|
}
|
|
|
|
Ast *parse_stmt(Context *context)
|
|
{
|
|
switch (context->tok.type)
|
|
{
|
|
case TOKEN_ASM_STRING:
|
|
case TOKEN_ASM_CONSTRAINT:
|
|
case TOKEN_DOCS_DIRECTIVE:
|
|
UNREACHABLE
|
|
case TOKEN_LBRACE:
|
|
return parse_compound_stmt(context);
|
|
case TYPELIKE_TOKENS:
|
|
case TOKEN_HASH_TYPE_IDENT:
|
|
case TOKEN_HASH_CONST_IDENT:
|
|
case TOKEN_HASH_IDENT:
|
|
case TOKEN_IDENT:
|
|
case TOKEN_CONST_IDENT:
|
|
return parse_decl_or_expr_stmt(context);
|
|
case TOKEN_DEFINE:
|
|
return parse_define_stmt(context);
|
|
case TOKEN_STATIC: // Static means declaration!
|
|
case TOKEN_CONST: // Const means declaration!
|
|
return parse_declaration_stmt(context);
|
|
case TOKEN_AT:
|
|
return parse_expr_stmt(context);
|
|
case TOKEN_RETURN:
|
|
{
|
|
Ast *ast = TRY_AST(parse_return(context));
|
|
RETURN_AFTER_EOS(ast);
|
|
}
|
|
case TOKEN_IF:
|
|
return parse_if_stmt(context);
|
|
case TOKEN_WHILE:
|
|
return parse_while_stmt(context);
|
|
case TOKEN_DEFER:
|
|
return parse_defer_stmt(context);
|
|
case TOKEN_SWITCH:
|
|
return parse_switch_stmt(context);
|
|
case TOKEN_DO:
|
|
return parse_do_stmt(context);
|
|
case TOKEN_FOR:
|
|
return parse_for_stmt(context);
|
|
case TOKEN_FOREACH:
|
|
return parse_foreach_stmt(context);
|
|
case TOKEN_CONTINUE:
|
|
{
|
|
Ast *ast = TRY_AST(parse_continue(context));
|
|
RETURN_AFTER_EOS(ast);
|
|
}
|
|
case TOKEN_CASE:
|
|
SEMA_TOKEN_ERROR(context->tok, "'case' was found outside of 'switch', did you mismatch a '{ }' pair?");
|
|
advance(context);
|
|
return poisoned_ast;
|
|
case TOKEN_BREAK:
|
|
{
|
|
Ast *ast = TRY_AST(parse_break(context));
|
|
RETURN_AFTER_EOS(ast);
|
|
}
|
|
case TOKEN_NEXTCASE:
|
|
{
|
|
Ast *ast = TRY_AST(parse_next(context));
|
|
RETURN_AFTER_EOS(ast);
|
|
}
|
|
case TOKEN_ASM:
|
|
return parse_asm_stmt(context);
|
|
case TOKEN_DEFAULT:
|
|
SEMA_TOKEN_ERROR(context->tok, "'default' was found outside of 'switch', did you mismatch a '{ }' pair?");
|
|
advance(context);
|
|
return poisoned_ast;
|
|
case TOKEN_CT_ASSERT:
|
|
return parse_ct_assert_stmt(context);
|
|
case TOKEN_CT_IF:
|
|
return parse_ct_if_stmt(context);
|
|
case TOKEN_CT_SWITCH:
|
|
return parse_ct_switch_stmt(context);
|
|
case TOKEN_CT_FOR:
|
|
return parse_ct_for_stmt(context);
|
|
case TOKEN_VOLATILE:
|
|
return parse_volatile_stmt(context);
|
|
case TOKEN_CT_UNREACHABLE:
|
|
return parse_unreachable_stmt(context);
|
|
case TOKEN_STAR:
|
|
case TOKEN_AMP:
|
|
case TOKEN_INTEGER:
|
|
case TOKEN_CHAR_LITERAL:
|
|
case TOKEN_BIT_NOT:
|
|
case TOKEN_BIT_OR:
|
|
case TOKEN_BIT_XOR:
|
|
case TOKEN_LPAREN:
|
|
case TOKEN_MINUS:
|
|
case TOKEN_BANG:
|
|
case TOKEN_OR:
|
|
case TOKEN_PLUS:
|
|
case TOKEN_MINUSMINUS:
|
|
case TOKEN_PLUSPLUS:
|
|
case TOKEN_CT_CONST_IDENT:
|
|
case TOKEN_CT_IDENT:
|
|
case TOKEN_STRING:
|
|
case TOKEN_REAL:
|
|
case TOKEN_FALSE:
|
|
case TOKEN_NULL:
|
|
case TOKEN_TRUE:
|
|
case TOKEN_LBRAPIPE:
|
|
case TOKEN_CT_OFFSETOF:
|
|
case TOKEN_CT_ALIGNOF:
|
|
case TOKEN_CT_EXTNAMEOF:
|
|
case TOKEN_CT_SIZEOF:
|
|
case TOKEN_CT_QNAMEOF:
|
|
case TOKEN_CT_NAMEOF:
|
|
case TOKEN_TRY:
|
|
case TOKEN_CATCH:
|
|
case TOKEN_BYTES:
|
|
return parse_expr_stmt(context);
|
|
case TOKEN_ASSERT:
|
|
return parse_assert_stmt(context);
|
|
case TOKEN_INVALID_TOKEN:
|
|
advance(context);
|
|
return poisoned_ast;
|
|
case TOKEN_COLON:
|
|
case TOKEN_COMMA:
|
|
case TOKEN_EQ:
|
|
case TOKEN_GREATER:
|
|
case TOKEN_DIV:
|
|
case TOKEN_DOLLAR:
|
|
case TOKEN_DOT:
|
|
case TOKEN_HASH:
|
|
case TOKEN_LESS:
|
|
case TOKEN_LBRACKET:
|
|
case TOKEN_MOD:
|
|
case TOKEN_QUESTION:
|
|
case TOKEN_AND:
|
|
case TOKEN_ARROW:
|
|
case TOKEN_BIT_AND_ASSIGN:
|
|
case TOKEN_BIT_OR_ASSIGN:
|
|
case TOKEN_BIT_XOR_ASSIGN:
|
|
case TOKEN_DIV_ASSIGN:
|
|
case TOKEN_DOTDOT:
|
|
case TOKEN_ELVIS:
|
|
case TOKEN_EQEQ:
|
|
case TOKEN_GREATER_EQ:
|
|
case TOKEN_LESS_EQ:
|
|
case TOKEN_MINUS_ASSIGN:
|
|
case TOKEN_MOD_ASSIGN:
|
|
case TOKEN_MULT_ASSIGN:
|
|
case TOKEN_NOT_EQUAL:
|
|
case TOKEN_PLUS_ASSIGN:
|
|
case TOKEN_ELLIPSIS:
|
|
case TOKEN_SCOPE:
|
|
case TOKEN_SHR:
|
|
case TOKEN_SHL:
|
|
case TOKEN_SHR_ASSIGN:
|
|
case TOKEN_SHL_ASSIGN:
|
|
case TOKEN_ALIAS:
|
|
case TOKEN_AS:
|
|
case TOKEN_ELSE:
|
|
case TOKEN_ENUM:
|
|
case TOKEN_FUNC:
|
|
case TOKEN_GENERIC:
|
|
case TOKEN_IMPORT:
|
|
case TOKEN_MACRO:
|
|
case TOKEN_MODULE:
|
|
case TOKEN_EXTERN:
|
|
case TOKEN_STRUCT:
|
|
case TOKEN_ERRTYPE:
|
|
case TOKEN_INTERFACE:
|
|
case TOKEN_UNION:
|
|
case TOKEN_ATTRIBUTE:
|
|
case TOKEN_VAR:
|
|
case TOKEN_DOCS_START:
|
|
case TOKEN_DOCS_END:
|
|
case TOKEN_DOCS_EOL:
|
|
case TOKEN_DOC_COMMENT:
|
|
case TOKEN_COMMENT:
|
|
case TOKEN_DOCS_LINE:
|
|
case TOKEN_CT_CASE:
|
|
case TOKEN_CT_ELIF:
|
|
case TOKEN_CT_ELSE:
|
|
case TOKEN_CT_DEFAULT:
|
|
case TOKEN_CT_ENDIF:
|
|
case TOKEN_CT_ENDSWITCH:
|
|
case TOKEN_RBRAPIPE:
|
|
case TOKEN_BANGBANG:
|
|
case TOKEN_UNDERSCORE:
|
|
case TOKEN_PRIVATE:
|
|
case TOKEN_PLACEHOLDER:
|
|
case TOKEN_BITSTRUCT:
|
|
SEMA_TOKEN_ERROR(context->tok, "Unexpected '%s' found when expecting a statement.", token_type_to_string(context->tok.type));
|
|
advance(context);
|
|
return poisoned_ast;
|
|
case TOKEN_RPAREN:
|
|
case TOKEN_RBRACE:
|
|
case TOKEN_RBRACKET:
|
|
SEMA_TOKEN_ERROR(context->tok, "Mismatched '%s' found.", token_type_to_string(context->tok.type));
|
|
advance(context);
|
|
return poisoned_ast;
|
|
case TOKEN_EOS:
|
|
advance(context);
|
|
return AST_NEW_TOKEN(AST_NOP_STMT, context->tok);
|
|
case TOKEN_EOF:
|
|
// TODO
|
|
SEMA_TOKID_ERROR(context->tok.id, "Reached the end of the file when expecting a statement.");
|
|
return poisoned_ast;
|
|
}
|
|
UNREACHABLE
|
|
}
|
|
|
|
Ast *parse_unreachable_stmt(Context *context)
|
|
{
|
|
Ast *ast = AST_NEW_TOKEN(AST_UNREACHABLE_STMT, context->tok);
|
|
advance_and_verify(context, TOKEN_CT_UNREACHABLE);
|
|
TRY_CONSUME_EOS_OR(poisoned_ast);
|
|
return ast;
|
|
}
|
|
|
|
Ast *parse_jump_stmt_no_eos(Context *context)
|
|
{
|
|
switch (context->tok.type)
|
|
{
|
|
case TOKEN_NEXTCASE:
|
|
return parse_next(context);
|
|
case TOKEN_RETURN:
|
|
return parse_return(context);
|
|
case TOKEN_BREAK:
|
|
return parse_break(context);
|
|
case TOKEN_CONTINUE:
|
|
return parse_continue(context);
|
|
default:
|
|
UNREACHABLE
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* compound_stmt
|
|
* : '{' stmt_list '}'
|
|
* ;
|
|
*
|
|
* stmt_list
|
|
* : stmt
|
|
* | stmt stmt_list
|
|
* ;
|
|
*
|
|
* @param context
|
|
* @return a compound statement
|
|
*/
|
|
Ast* parse_compound_stmt(Context *context)
|
|
{
|
|
CONSUME_OR(TOKEN_LBRACE, poisoned_ast);
|
|
Ast *ast = AST_NEW_TOKEN(AST_COMPOUND_STMT, context->tok);
|
|
while (!try_consume(context, TOKEN_RBRACE))
|
|
{
|
|
Ast *stmt = TRY_AST(parse_stmt(context));
|
|
ast->compound_stmt.stmts = VECADD(ast->compound_stmt.stmts, stmt);
|
|
}
|
|
return ast;
|
|
}
|