diff --git a/lang/ast/expr/expr.c b/lang/ast/expr/expr.c index 70d6581..5234438 100644 --- a/lang/ast/expr/expr.c +++ b/lang/ast/expr/expr.c @@ -9,7 +9,8 @@ void expr_add_terminator(struct expr_parser_state *state, unsigned short tok) } } -void expr_copy_terminators(const struct expr_parser_state *src, struct expr_parser_state *dest) +void expr_copy_terminators( + const struct expr_parser_state *src, struct expr_parser_state *dest) { dest->s_nr_terminators = src->s_nr_terminators; @@ -77,11 +78,14 @@ struct ast_node_type expr_node_ops = { .n_keyword_parsers = { /* statement keywords */ KW_PARSER(FOR, stmt_parse_for), + KW_PARSER(TRY, stmt_parse_try), KW_PARSER(WHILE, stmt_parse_while), KW_PARSER(MATCH, stmt_parse_match), KW_PARSER(IF, stmt_parse_if), KW_PARSER(THEN, stmt_parse_end), KW_PARSER(ELSE, stmt_parse_end), + KW_PARSER(CATCH, stmt_parse_end), + KW_PARSER(FINALLY, stmt_parse_end), KW_PARSER(END, stmt_parse_end), /* operator/block keywords */ diff --git a/lang/ast/expr/expr.h b/lang/ast/expr/expr.h index 6608100..dfa6d7b 100644 --- a/lang/ast/expr/expr.h +++ b/lang/ast/expr/expr.h @@ -92,8 +92,10 @@ struct expr_parser_state { /* general functions */ extern void expr_add_terminator(struct expr_parser_state *state, unsigned short tok); -extern void expr_copy_terminators(const struct expr_parser_state *src, struct expr_parser_state *dest); -extern bool expr_terminates_at_token(struct expr_parser_state *state, unsigned short tok); +extern void expr_copy_terminators( + const struct expr_parser_state *src, struct expr_parser_state *dest); +extern bool expr_terminates_at_token( + struct expr_parser_state *state, unsigned short tok); extern struct token_parse_result expr_finalise( struct ivy_parser *ctx, struct expr_parser_state *state, @@ -152,6 +154,8 @@ extern struct token_parse_result arith_parse_do( /* statement parser callbacks */ +extern struct token_parse_result stmt_parse_try( + struct ivy_parser *ctx, struct ivy_token *tok); extern struct token_parse_result stmt_parse_for( struct ivy_parser *ctx, struct ivy_token *tok); extern struct token_parse_result stmt_parse_while( diff --git a/lang/ast/expr/stmt.c b/lang/ast/expr/stmt.c index 952a2e1..b0068c6 100644 --- a/lang/ast/expr/stmt.c +++ b/lang/ast/expr/stmt.c @@ -6,6 +6,39 @@ #include #include +struct token_parse_result stmt_parse_try( + struct ivy_parser *ctx, struct ivy_token *tok) +{ + struct expr_parser_state *state + = parser_get_state(ctx, struct expr_parser_state); + + if (expr_terminates_at_token(state, IVY_KW_TRY)) { + /* treat this as a statement terminator. */ + struct token_parse_result result + = expr_finalise_and_return(ctx, state); + result.r_flags |= PARSE_REPEAT_TOKEN; + return result; + } + + if (state->s_sub_type == EXPR_SUBTYPE_KEYWORD_ARG) { + /* try-catch cannot be used inline. */ + return PARSE_RESULT(IVY_ERR_BAD_SYNTAX, 0); + } + + state->s_prev_token = IVY_KW_TRY; + + if (b_queue_empty(&state->s_operator_stack) + && b_queue_empty(&state->s_output_queue)) { + parser_pop_state(ctx, 0); + } + + /* if expr is NULL, this is an if-then-else-end statement, + * otherwise, this is an expr-if-else-expr. */ + parser_push_state(ctx, IVY_AST_TRY, 0); + + return PARSE_RESULT(IVY_OK, 0); +} + struct token_parse_result stmt_parse_for( struct ivy_parser *ctx, struct ivy_token *tok) { @@ -14,7 +47,8 @@ struct token_parse_result stmt_parse_for( if (expr_terminates_at_token(state, IVY_KW_FOR)) { /* treat this as a statement terminator. */ - struct token_parse_result result = expr_finalise_and_return(ctx, state); + struct token_parse_result result + = expr_finalise_and_return(ctx, state); result.r_flags |= PARSE_REPEAT_TOKEN; return result; } @@ -128,7 +162,8 @@ struct token_parse_result stmt_parse_if( if (expr_terminates_at_token(state, IVY_KW_IF)) { /* treat this as a statement terminator. */ - struct token_parse_result result = expr_finalise_and_return(ctx, state); + struct token_parse_result result + = expr_finalise_and_return(ctx, state); result.r_flags |= PARSE_REPEAT_TOKEN; return result; } @@ -196,4 +231,4 @@ struct token_parse_result stmt_parse_end( struct token_parse_result result = expr_finalise_and_return(ctx, state); result.r_flags |= PARSE_REPEAT_TOKEN; return result; -} \ No newline at end of file +} diff --git a/lang/ast/node.c b/lang/ast/node.c index 9c0a138..b93545f 100644 --- a/lang/ast/node.c +++ b/lang/ast/node.c @@ -37,6 +37,8 @@ extern struct ast_node_type pkg_static_node_ops; extern struct ast_node_type pkg_static_item_node_ops; extern struct ast_node_type pkg_dynamic_node_ops; extern struct ast_node_type discard_node_ops; +extern struct ast_node_type try_node_ops; +extern struct ast_node_type try_catch_node_ops; static const struct ast_node_type *node_ops[] = { [IVY_AST_UNIT] = &unit_node_ops, @@ -70,6 +72,8 @@ static const struct ast_node_type *node_ops[] = { [IVY_AST_PKG_ITEM] = &pkg_static_item_node_ops, [IVY_AST_PKG_DYNAMIC] = &pkg_dynamic_node_ops, [IVY_AST_DISCARD] = &discard_node_ops, + [IVY_AST_TRY] = &try_node_ops, + [IVY_AST_TRY_CATCH] = &try_catch_node_ops, }; static const size_t nr_node_ops = sizeof node_ops / sizeof node_ops[0]; @@ -116,7 +120,6 @@ enum token_expr_type get_token_expr_type(struct ivy_token *tok) case IVY_KW_FOR: case IVY_KW_WHILE: case IVY_KW_TRY: - case IVY_KW_CATCH: case IVY_KW_THROW: return TOK_EXPR_BEGIN; default: @@ -283,6 +286,8 @@ const char *ivy_ast_node_type_to_string(enum ivy_ast_node_type v) ENUM_STR(IVY_AST_PKG_ITEM); ENUM_STR(IVY_AST_PKG_DYNAMIC); ENUM_STR(IVY_AST_RETURN); + ENUM_STR(IVY_AST_TRY); + ENUM_STR(IVY_AST_TRY_CATCH); ENUM_STR(IVY_AST_TYPE_COUNT); default: return ""; diff --git a/lang/ast/try.c b/lang/ast/try.c new file mode 100644 index 0000000..45f6ea7 --- /dev/null +++ b/lang/ast/try.c @@ -0,0 +1,279 @@ +#include "block.h" +#include "expr/expr.h" +#include "iterate.h" + +struct try_parser_state { + struct parser_state s_base; + unsigned int s_prev_token; + + struct ivy_ast_node *s_prev_node; + + struct ivy_ast_block_node *s_try, *s_finally; + + b_queue s_catch_branches; + struct ivy_ast_try_catch_node *s_cur_catch_branch; +}; + +static enum ivy_status flush_catch_branch(struct try_parser_state *state) +{ + b_queue_push_back( + &state->s_catch_branches, + &state->s_cur_catch_branch->n_base.n_entry); + + state->s_cur_catch_branch + = (struct ivy_ast_try_catch_node *)ast_node_create( + IVY_AST_TRY_CATCH); + + if (!state->s_cur_catch_branch) { + return IVY_ERR_NO_MEMORY; + } + + return IVY_OK; +} + +static enum ivy_status finalise_try(struct try_parser_state *state) +{ + struct ivy_ast_try_node *try + = (struct ivy_ast_try_node *)state->s_base.s_node; + try->n_try = (struct ivy_ast_node *)state->s_try; + try->n_finally = (struct ivy_ast_node *)state->s_finally; + try->n_catch = state->s_catch_branches; + + state->s_try = NULL; + state->s_finally = NULL; + state->s_catch_branches = B_QUEUE_INIT; + + return IVY_OK; +} + +struct token_parse_result parse_catch(struct ivy_parser *ctx, struct ivy_token *tok) +{ + struct try_parser_state *try + = parser_get_state(ctx, struct try_parser_state); + + if (!try->s_prev_node || try->s_prev_node->n_type != IVY_AST_BLOCK) { + /* there must always be a block between a catch and the keyword + * that came before it */ + return PARSE_RESULT(IVY_ERR_BAD_SYNTAX, 0); + } + + enum ivy_status status = IVY_OK; + + if (try->s_prev_token == IVY_KW_TRY) { + try->s_try = (struct ivy_ast_block_node *)try->s_prev_node; + try->s_prev_node = NULL; + } else if (try->s_prev_token == IVY_KW_IN) { + try->s_cur_catch_branch->n_block = try->s_prev_node; + try->s_prev_node = NULL; + + status = flush_catch_branch(try); + + if (status != IVY_OK) { + return PARSE_RESULT(status, 0); + } + } else { + /* catch can only come after `try` or `in` */ + return PARSE_RESULT(IVY_ERR_BAD_SYNTAX, 0); + } + + /* next input should be the catch pattern */ + struct expr_parser_state *pattern + = (struct expr_parser_state *)parser_push_state( + ctx, IVY_AST_EXPR, 0); + + expr_add_terminator(pattern, IVY_KW_IN); + + try->s_prev_token = IVY_KW_CATCH; + + return PARSE_RESULT(IVY_OK, 0); +} + +static struct token_parse_result parse_in( + struct ivy_parser *ctx, struct ivy_token *tok) +{ + struct try_parser_state *try + = parser_get_state(ctx, struct try_parser_state); + + if (try->s_prev_token != IVY_KW_CATCH) { + return PARSE_RESULT(IVY_ERR_BAD_SYNTAX, 0); + } + + if (!try->s_prev_node) { + /* there must always be an expression between catch and in */ + return PARSE_RESULT(IVY_ERR_BAD_SYNTAX, 0); + } + + try->s_cur_catch_branch->n_pattern = try->s_prev_node; + try->s_prev_node = NULL; + try->s_prev_token = IVY_KW_IN; + + struct block_parser_state *block + = (struct block_parser_state *)parser_push_state( + ctx, IVY_AST_BLOCK, 0); + block_add_terminator(block, IVY_KW_CATCH); + block_add_terminator(block, IVY_KW_FINALLY); + block_add_terminator(block, IVY_KW_END); + + return PARSE_RESULT(IVY_OK, 0); +} + +struct token_parse_result parse_finally( + struct ivy_parser *ctx, struct ivy_token *tok) +{ + struct try_parser_state *try + = parser_get_state(ctx, struct try_parser_state); + + if (try->s_prev_token != IVY_KW_IN && try->s_prev_token != IVY_KW_TRY) { + return PARSE_RESULT(IVY_ERR_BAD_SYNTAX, 0); + } + + if (!try->s_prev_node || try->s_prev_node->n_type != IVY_AST_BLOCK) { + /* there must always be a block between a catch and the keyword + * that came before it */ + return PARSE_RESULT(IVY_ERR_BAD_SYNTAX, 0); + } + + enum ivy_status status = IVY_OK; + + if (try->s_prev_token == IVY_KW_TRY) { + try->s_try = (struct ivy_ast_block_node *)try->s_prev_node; + try->s_prev_node = NULL; + } else if (try->s_prev_token == IVY_KW_IN) { + try->s_cur_catch_branch->n_block = try->s_prev_node; + try->s_prev_node = NULL; + + status = flush_catch_branch(try); + + if (status != IVY_OK) { + return PARSE_RESULT(status, 0); + } + } else { + /* finally can only come after `try` or `in` */ + return PARSE_RESULT(IVY_ERR_BAD_SYNTAX, 0); + } + + try->s_prev_token = IVY_KW_FINALLY; + + struct block_parser_state *block + = (struct block_parser_state *)parser_push_state( + ctx, IVY_AST_BLOCK, 0); + block_add_terminator(block, IVY_KW_END); + + return PARSE_RESULT(IVY_OK, 0); +} + +struct token_parse_result parse_end(struct ivy_parser *ctx, struct ivy_token *tok) +{ + struct try_parser_state *try + = parser_get_state(ctx, struct try_parser_state); + + if (!try->s_prev_node || try->s_prev_node->n_type != IVY_AST_BLOCK) { + /* there must always be a block between an end and the keyword + * that came before it */ + return PARSE_RESULT(IVY_ERR_BAD_SYNTAX, 0); + } + + enum ivy_status status = IVY_OK; + + if (try->s_prev_token == IVY_KW_TRY) { + try->s_try = (struct ivy_ast_block_node *)try->s_prev_node; + try->s_prev_node = NULL; + } else if (try->s_prev_token == IVY_KW_FINALLY) { + try->s_finally = (struct ivy_ast_block_node *)try->s_prev_node; + try->s_prev_node = NULL; + } else if (try->s_prev_token == IVY_KW_IN) { + try->s_cur_catch_branch->n_block = try->s_prev_node; + status = flush_catch_branch(try); + + if (status != IVY_OK) { + return PARSE_RESULT(status, 0); + } + } else { + /* catch can only come after `try`, `in`, or `finally` */ + return PARSE_RESULT(IVY_ERR_BAD_SYNTAX, 0); + } + + finalise_try(try); + parser_pop_state(ctx, STATE_ADD_NODE_TO_PARENT); + + return PARSE_RESULT(IVY_OK, 0); +} + +static void init_state(struct ivy_parser *ctx, struct parser_state *sp, uintptr_t arg) +{ + struct try_parser_state *state = (struct try_parser_state *)sp; + state->s_prev_token = IVY_KW_TRY; + + state->s_cur_catch_branch + = (struct ivy_ast_try_catch_node *)ast_node_create( + IVY_AST_TRY_CATCH); + + struct block_parser_state *block + = (struct block_parser_state *)parser_push_state( + ctx, IVY_AST_BLOCK, 0); + block_add_terminator(block, IVY_KW_CATCH); + block_add_terminator(block, IVY_KW_FINALLY); + block_add_terminator(block, IVY_KW_END); +} + +static enum ivy_status add_child( + struct parser_state *parent, struct ivy_ast_node *child) +{ + struct try_parser_state *state = (struct try_parser_state *)parent; + + if (state->s_prev_node) { + return IVY_ERR_BAD_SYNTAX; + } + + state->s_prev_node = child; + return IVY_OK; +} + +static void try_collect_children( + struct ivy_ast_node *node, struct ivy_ast_node_iterator *iterator) +{ + struct ivy_ast_try_node *try = (struct ivy_ast_try_node *)node; + + ast_node_iterator_enqueue_node(iterator, node, try->n_try); + + b_queue_iterator it = {0}; + b_queue_foreach (&it, &try->n_catch) { + struct ivy_ast_node *catch + = b_unbox(struct ivy_ast_node, it.entry, n_entry); + ast_node_iterator_enqueue_node(iterator, node, catch); + } + + if (try->n_finally) { + ast_node_iterator_enqueue_node(iterator, node, try->n_finally); + } +} + +static void try_catch_collect_children( + struct ivy_ast_node *node, struct ivy_ast_node_iterator *iterator) +{ + struct ivy_ast_try_catch_node *catch + = (struct ivy_ast_try_catch_node *)node; + + ast_node_iterator_enqueue_node(iterator, node, catch->n_pattern); + ast_node_iterator_enqueue_node(iterator, node, catch->n_block); +} + +struct ast_node_type try_node_ops = { + .n_init_state = init_state, + .n_add_child = add_child, + .n_collect_children = try_collect_children, + .n_state_size = sizeof(struct try_parser_state), + .n_node_size = sizeof(struct ivy_ast_try_node), + .n_keyword_parsers = { + KW_PARSER(CATCH, parse_catch), + KW_PARSER(IN, parse_in), + KW_PARSER(FINALLY, parse_finally), + KW_PARSER(END, parse_end), + }, +}; + +struct ast_node_type try_catch_node_ops = { + .n_state_size = sizeof(struct parser_state), + .n_node_size = sizeof(struct ivy_ast_try_catch_node), + .n_collect_children = try_catch_collect_children, +}; diff --git a/lang/include/ivy/lang/ast.h b/lang/include/ivy/lang/ast.h index d0ba743..95a5a25 100644 --- a/lang/include/ivy/lang/ast.h +++ b/lang/include/ivy/lang/ast.h @@ -38,14 +38,16 @@ enum ivy_ast_node_type { IVY_AST_MATCH, IVY_AST_COND_GROUP, IVY_AST_COND, + IVY_AST_TRY, + IVY_AST_TRY_CATCH, IVY_AST_TUPLE, IVY_AST_PKG_STATIC, IVY_AST_PKG_ITEM, IVY_AST_PKG_DYNAMIC, - /* these are pseudo-types. a finished AST will never have these nodes in it, - * but they are necessary to identifier AST parsers that will produce nodes - * of other, related types. */ + /* these are pseudo-types. a finished AST will never have these nodes in + * it, but they are necessary to identifier AST parsers that will + * produce nodes of other, related types. */ IVY_AST_PKG, IVY_AST_EXPR, @@ -242,6 +244,19 @@ struct ivy_ast_cond_group_node { b_queue n_branches; }; +struct ivy_ast_try_catch_node { + struct ivy_ast_node n_base; + struct ivy_ast_node *n_pattern; + struct ivy_ast_node *n_block; +}; + +struct ivy_ast_try_node { + struct ivy_ast_node n_base; + struct ivy_ast_node *n_try; + b_queue n_catch; + struct ivy_ast_node *n_finally; +}; + struct ivy_ast_match_node { struct ivy_ast_node n_base; /* the match condition. the branch that matches this condition will be evaluated. */