diff options
| author | Kevin Newton <kddnewton@gmail.com> | 2024-04-03 10:36:10 -0400 |
|---|---|---|
| committer | Kevin Newton <kddnewton@gmail.com> | 2024-04-03 17:34:12 -0400 |
| commit | be48b733b61adbd9ab10f4560ca1daf1ca2d7dac (patch) | |
| tree | ee9b493ab59bc94756f983b8428c4e59e9bd76c7 | |
| parent | 1fb11824f31472bda09812af5532b2d63c0f95a5 (diff) | |
[ruby/prism] Pass block exits up the tree
https://github.com/ruby/prism/commit/168f72b9fe
| -rw-r--r-- | prism/node.h | 14 | ||||
| -rw-r--r-- | prism/parser.h | 3 | ||||
| -rw-r--r-- | prism/prism.c | 295 | ||||
| -rw-r--r-- | prism/templates/src/node.c.erb | 46 |
4 files changed, 242 insertions, 116 deletions
diff --git a/prism/node.h b/prism/node.h index f2f5d21133..8f32cb56bd 100644 --- a/prism/node.h +++ b/prism/node.h @@ -24,9 +24,10 @@ * this function returns false, otherwise it returns true. * * @param list The list to grow. + * @param size The number of nodes to grow the list by. * @return True if the list was successfully grown, false otherwise. */ -bool pm_node_list_grow(pm_node_list_t *list); +bool pm_node_list_grow(pm_node_list_t *list, size_t size); /** * Append a new node onto the end of the node list. @@ -42,8 +43,15 @@ void pm_node_list_append(pm_node_list_t *list, pm_node_t *node); * @param list The list to prepend to. * @param node The node to prepend. */ -void -pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node); +void pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node); + +/** + * Concatenate the given node list onto the end of the other node list. + * + * @param list The list to concatenate onto. + * @param other The list to concatenate. + */ +void pm_node_list_concat(pm_node_list_t *list, pm_node_list_t *other); /** * Free the internal memory associated with the given node list. diff --git a/prism/parser.h b/prism/parser.h index 9377c22d5f..fd121c2c5e 100644 --- a/prism/parser.h +++ b/prism/parser.h @@ -358,6 +358,9 @@ typedef enum { /** a singleton class definition */ PM_CONTEXT_SCLASS, + /** a ternary expression */ + PM_CONTEXT_TERNARY, + /** an unless statement */ PM_CONTEXT_UNLESS, diff --git a/prism/prism.c b/prism/prism.c index f8d52a88a8..f9c95df1fc 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -62,6 +62,7 @@ debug_context(pm_context_t context) { case PM_CONTEXT_RESCUE_ELSE_DEF: return "RESCUE_ELSE_DEF"; case PM_CONTEXT_RESCUE_DEF: return "RESCUE_DEF"; case PM_CONTEXT_SCLASS: return "SCLASS"; + case PM_CONTEXT_TERNARY: return "TERNARY"; case PM_CONTEXT_UNLESS: return "UNLESS"; case PM_CONTEXT_UNTIL: return "UNTIL"; case PM_CONTEXT_WHILE: return "WHILE"; @@ -7598,6 +7599,7 @@ context_terminator(pm_context_t context, pm_token_t *token) { case PM_CONTEXT_MAIN: case PM_CONTEXT_DEF_PARAMS: case PM_CONTEXT_DEFINED: + case PM_CONTEXT_TERNARY: return token->type == PM_TOKEN_EOF; case PM_CONTEXT_DEFAULT_PARAMS: return token->type == PM_TOKEN_COMMA || token->type == PM_TOKEN_PARENTHESIS_RIGHT; @@ -7770,6 +7772,7 @@ context_human(pm_context_t context) { case PM_CONTEXT_RESCUE: return "'rescue' clause"; case PM_CONTEXT_RESCUE_DEF: return "'rescue' clause"; case PM_CONTEXT_SCLASS: return "singleton class definition"; + case PM_CONTEXT_TERNARY: return "ternary expression"; case PM_CONTEXT_UNLESS: return "unless statement"; case PM_CONTEXT_UNTIL: return "until statement"; case PM_CONTEXT_WHILE: return "while statement"; @@ -13826,6 +13829,144 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept return found; } +/** + * Check that the block exit (next, break, redo) is allowed in the current + * context. If it isn't, add an error to the parser. + */ +static void +parse_block_exit(pm_parser_t *parser, pm_node_t *node, const char *type) { + pm_context_node_t *context_node = parser->current_context; + bool through_expression = false; + + while (context_node != NULL) { + switch (context_node->context) { + case PM_CONTEXT_BLOCK_BRACES: + case PM_CONTEXT_BLOCK_KEYWORDS: + case PM_CONTEXT_DEFINED: + case PM_CONTEXT_FOR: + case PM_CONTEXT_LAMBDA_BRACES: + case PM_CONTEXT_LAMBDA_DO_END: + case PM_CONTEXT_POSTEXE: + case PM_CONTEXT_UNTIL: + case PM_CONTEXT_WHILE: + // These are the good cases. We're allowed to have a block exit + // in these contexts. + return; + case PM_CONTEXT_CLASS: + case PM_CONTEXT_DEF: + case PM_CONTEXT_DEF_PARAMS: + case PM_CONTEXT_ENSURE_DEF: + case PM_CONTEXT_MAIN: + case PM_CONTEXT_MODULE: + case PM_CONTEXT_PREEXE: + case PM_CONTEXT_RESCUE_DEF: + case PM_CONTEXT_RESCUE_ELSE_DEF: + case PM_CONTEXT_SCLASS: + // These are the bad cases. We're not allowed to have a block + // exit in these contexts. + + if (through_expression) { + // If we get here, then we're about to mark this block exit + // as invalid. However, it could later _become_ valid if we + // find a trailing while/until on the expression. In this + // case instead of adding the error here, we'll add the + // block exit to the list of exits for the expression, and + // the node parsing will handle validating it instead. + assert(parser->current_block_exits != NULL); + pm_node_list_append(parser->current_block_exits, node); + } else { + // Otherwise, if we haven't gone through an expression + // context, then this is just invalid and we'll add the + // error here. + PM_PARSER_ERR_NODE_FORMAT(parser, node, PM_ERR_INVALID_BLOCK_EXIT, type); + } + + return; + case PM_CONTEXT_NONE: + // This case should never happen. + assert(false && "unreachable"); + break; + case PM_CONTEXT_BEGIN: + case PM_CONTEXT_CASE_IN: + case PM_CONTEXT_CASE_WHEN: + case PM_CONTEXT_ELSE: + case PM_CONTEXT_ELSIF: + case PM_CONTEXT_ENSURE: + case PM_CONTEXT_IF: + case PM_CONTEXT_PARENS: + case PM_CONTEXT_RESCUE_ELSE: + case PM_CONTEXT_RESCUE: + case PM_CONTEXT_TERNARY: + case PM_CONTEXT_UNLESS: + // If we got to an expression that could be modified by a + // trailing while/until, then we'll track that we have gotten + // here because we need to know it if this block exit is later + // marked as invalid. + through_expression = true; + break; + case PM_CONTEXT_EMBEXPR: + case PM_CONTEXT_DEFAULT_PARAMS: + case PM_CONTEXT_FOR_INDEX: + case PM_CONTEXT_PREDICATE: + // In these contexts we should continue walking up the list of + // contexts. + break; + } + + context_node = context_node->prev; + } +} + +/** + * When we hit an expression that could contain block exits, we need to stash + * the previous set and create a new one. + */ +static pm_node_list_t * +push_block_exits(pm_parser_t *parser, pm_node_list_t *current_block_exits) { + pm_node_list_t *previous_block_exits = parser->current_block_exits; + parser->current_block_exits = current_block_exits; + return previous_block_exits; +} + +/** + * Pop the current level of block exits from the parser, and add errors to the + * parser if any of them are deemed to be invalid. + */ +static void +pop_block_exits(pm_parser_t *parser, pm_node_list_t *previous_block_exits) { + if (match2(parser, PM_TOKEN_KEYWORD_WHILE_MODIFIER, PM_TOKEN_KEYWORD_UNTIL_MODIFIER)) { + // If we matched a trailing while/until, then all of the block exits in + // the contained list are valid. In this case we do not need to do + // anything. + } else if (previous_block_exits != NULL) { + // If we did not matching a trailing while/until, then all of the block + // exits contained in the list are invalid for this specific context. + // However, they could still become valid in a higher level context if + // there is another list above this one. In this case we'll push all of + // the block exits up to the previous list. + pm_node_list_concat(previous_block_exits, parser->current_block_exits); + } else { + // If we did not match a trailing while/until and this was the last + // chance to do so, then all of the block exits in the list are invalid + // and we need to add an error for each of them. + pm_node_t *block_exit; + PM_NODE_LIST_FOREACH(parser->current_block_exits, index, block_exit) { + const char *type; + + switch (PM_NODE_TYPE(block_exit)) { + case PM_BREAK_NODE: type = "break"; break; + case PM_NEXT_NODE: type = "next"; break; + case PM_REDO_NODE: type = "redo"; break; + default: assert(false && "unreachable"); type = ""; break; + } + + PM_PARSER_ERR_NODE_FORMAT(parser, block_exit, PM_ERR_INVALID_BLOCK_EXIT, type); + } + } + + parser->current_block_exits = previous_block_exits; +} + static inline pm_node_t * parse_predicate(pm_parser_t *parser, pm_binding_power_t binding_power, pm_context_t context, pm_token_t *then_keyword) { context_push(parser, PM_CONTEXT_PREDICATE); @@ -13850,6 +13991,9 @@ parse_predicate(pm_parser_t *parser, pm_binding_power_t binding_power, pm_contex static inline pm_node_t * parse_conditional(pm_parser_t *parser, pm_context_t context) { + pm_node_list_t current_block_exits = { 0 }; + pm_node_list_t *previous_block_exits = push_block_exits(parser, ¤t_block_exits); + pm_token_t keyword = parser->previous; pm_token_t then_keyword = not_provided(parser); @@ -13929,7 +14073,8 @@ parse_conditional(pm_parser_t *parser, pm_context_t context) { break; } } else { - // We should specialize this error message to refer to 'if' or 'unless' explicitly. + // We should specialize this error message to refer to 'if' or 'unless' + // explicitly. expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CONDITIONAL_TERM); } @@ -13966,6 +14111,9 @@ parse_conditional(pm_parser_t *parser, pm_context_t context) { break; } + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return parent; } @@ -15708,88 +15856,6 @@ pm_parser_err_prefix(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { } /** - * Check that the block exit (next, break, redo) is allowed in the current - * context. If it isn't, add an error to the parser. - */ -static void -parse_block_exit(pm_parser_t *parser, pm_node_t *node, const char *type) { - pm_context_node_t *context_node = parser->current_context; - bool through_begin = false; - - while (context_node != NULL) { - switch (context_node->context) { - case PM_CONTEXT_BLOCK_BRACES: - case PM_CONTEXT_BLOCK_KEYWORDS: - case PM_CONTEXT_DEFINED: - case PM_CONTEXT_FOR: - case PM_CONTEXT_LAMBDA_BRACES: - case PM_CONTEXT_LAMBDA_DO_END: - case PM_CONTEXT_POSTEXE: - case PM_CONTEXT_UNTIL: - case PM_CONTEXT_WHILE: - // These are the good cases. We're allowed to have a block exit - // in these contexts. - return; - case PM_CONTEXT_CLASS: - case PM_CONTEXT_DEF: - case PM_CONTEXT_DEF_PARAMS: - case PM_CONTEXT_ENSURE_DEF: - case PM_CONTEXT_MAIN: - case PM_CONTEXT_MODULE: - case PM_CONTEXT_PREEXE: - case PM_CONTEXT_RESCUE_DEF: - case PM_CONTEXT_RESCUE_ELSE_DEF: - case PM_CONTEXT_SCLASS: - if (through_begin) { - // If we get here, then we're about to mark this block exit - // as invalid. However, it could later _become_ valid if we - // find a trailing while/until on the begin. In this case - // instead of adding the error here, we'll add the block - // exit to the list of exits for the begin node, and the - // begin node parsing will handle validating it instead. - assert(parser->current_block_exits != NULL); - pm_node_list_append(parser->current_block_exits, node); - } else { - // These are the bad cases. We're not allowed to have a - // block exit in these contexts. - PM_PARSER_ERR_NODE_FORMAT(parser, node, PM_ERR_INVALID_BLOCK_EXIT, type); - } - - return; - case PM_CONTEXT_NONE: - // This case should never happen. - assert(false && "unreachable"); - break; - case PM_CONTEXT_BEGIN: - case PM_CONTEXT_RESCUE_ELSE: - case PM_CONTEXT_RESCUE: - // If we got to a begin node, then we'll track that we have - // gotten here because we need to know it if this block exit is - // later marked as invalid. - through_begin = true; - /* fallthrough */ - case PM_CONTEXT_CASE_WHEN: - case PM_CONTEXT_CASE_IN: - case PM_CONTEXT_DEFAULT_PARAMS: - case PM_CONTEXT_ELSE: - case PM_CONTEXT_ELSIF: - case PM_CONTEXT_EMBEXPR: - case PM_CONTEXT_ENSURE: - case PM_CONTEXT_FOR_INDEX: - case PM_CONTEXT_IF: - case PM_CONTEXT_PARENS: - case PM_CONTEXT_PREDICATE: - case PM_CONTEXT_UNLESS: - // In these contexts we should continue walking up the list of - // contexts. - break; - } - - context_node = context_node->prev; - } -} - -/** * Ensures that the current retry token is valid in the current context. */ static void @@ -15847,6 +15913,7 @@ parse_retry(pm_parser_t *parser, const pm_node_t *node) { case PM_CONTEXT_PARENS: case PM_CONTEXT_POSTEXE: case PM_CONTEXT_PREDICATE: + case PM_CONTEXT_TERNARY: case PM_CONTEXT_UNLESS: case PM_CONTEXT_UNTIL: case PM_CONTEXT_WHILE: @@ -15910,6 +15977,7 @@ parse_yield(pm_parser_t *parser, const pm_node_t *node) { case PM_CONTEXT_RESCUE_ELSE: case PM_CONTEXT_RESCUE: case PM_CONTEXT_SCLASS: + case PM_CONTEXT_TERNARY: case PM_CONTEXT_UNLESS: case PM_CONTEXT_UNTIL: case PM_CONTEXT_WHILE: @@ -16025,6 +16093,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b case PM_TOKEN_PARENTHESIS_LEFT: case PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES: { pm_token_t opening = parser->current; + + pm_node_list_t current_block_exits = { 0 }; + pm_node_list_t *previous_block_exits = push_block_exits(parser, ¤t_block_exits); + parser_lex(parser); while (accept2(parser, PM_TOKEN_SEMICOLON, PM_TOKEN_NEWLINE)); @@ -16032,6 +16104,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // we have an empty parentheses node, and we can immediately return. if (match2(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_EOF)) { expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_EXPECT_RPAREN); + + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return (pm_node_t *) pm_parentheses_node_create(parser, &opening, NULL, &parser->previous); } @@ -16057,9 +16133,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (opening.type == PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES) { lex_state_set(parser, PM_LEX_STATE_ENDARG); } + parser_lex(parser); pm_accepts_block_stack_pop(parser); + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + if (PM_NODE_TYPE_P(statement, PM_MULTI_TARGET_NODE) || PM_NODE_TYPE_P(statement, PM_SPLAT_NODE)) { // If we have a single statement and are ending on a right // parenthesis, then we need to check if this is possibly a @@ -16147,6 +16227,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_accepts_block_stack_pop(parser); expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_EXPECT_RPAREN); + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return (pm_node_t *) pm_parentheses_node_create(parser, &opening, (pm_node_t *) statements, &parser->previous); } case PM_TOKEN_BRACE_LEFT: { @@ -16578,6 +16661,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t case_keyword = parser->previous; pm_node_t *predicate = NULL; + pm_node_list_t current_block_exits = { 0 }; + pm_node_list_t *previous_block_exits = push_block_exits(parser, ¤t_block_exits); + if (accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) { while (accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)); predicate = NULL; @@ -16591,6 +16677,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } if (accept1(parser, PM_TOKEN_KEYWORD_END)) { + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + pm_parser_err_token(parser, &case_keyword, PM_ERR_CASE_MISSING_CONDITIONS); return (pm_node_t *) pm_case_node_create(parser, &case_keyword, predicate, &parser->previous); } @@ -16770,6 +16859,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_case_match_node_end_keyword_loc_set((pm_case_match_node_t *) node, &parser->previous); } + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return node; } case PM_TOKEN_KEYWORD_BEGIN: { @@ -16778,10 +16870,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t begin_keyword = parser->previous; accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON); - pm_node_list_t *previous_block_exits = parser->current_block_exits; pm_node_list_t current_block_exits = { 0 }; - parser->current_block_exits = ¤t_block_exits; - + pm_node_list_t *previous_block_exits = push_block_exits(parser, ¤t_block_exits); pm_statements_node_t *begin_statements = NULL; if (!match3(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_KEYWORD_END)) { @@ -16802,25 +16892,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_parser_err_node(parser, (pm_node_t *) begin_node->else_clause, PM_ERR_BEGIN_LONELY_ELSE); } - if (!match2(parser, PM_TOKEN_KEYWORD_WHILE_MODIFIER, PM_TOKEN_KEYWORD_UNTIL_MODIFIER)) { - // If we didn't find a while or until modifier, then we need to - // go back in and mark all of the block exits as invalid. - pm_node_t *block_exit; - PM_NODE_LIST_FOREACH(¤t_block_exits, block_exit_index, block_exit) { - const char *type; - - switch (PM_NODE_TYPE(block_exit)) { - case PM_BREAK_NODE: type = "break"; break; - case PM_NEXT_NODE: type = "next"; break; - case PM_REDO_NODE: type = "redo"; break; - default: assert(false && "unreachable"); type = ""; break; - } - - PM_PARSER_ERR_NODE_FORMAT(parser, block_exit, PM_ERR_INVALID_BLOCK_EXIT, type); - } - } - - parser->current_block_exits = previous_block_exits; + pop_block_exits(parser, previous_block_exits); pm_node_list_free(¤t_block_exits); return (pm_node_t *) begin_node; @@ -19146,8 +19218,13 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t return (pm_node_t *) pm_while_node_modifier_create(parser, &token, predicate, statements, PM_NODE_TYPE_P(node, PM_BEGIN_NODE) ? PM_LOOP_FLAGS_BEGIN_MODIFIER : 0); } case PM_TOKEN_QUESTION_MARK: { + context_push(parser, PM_CONTEXT_TERNARY); + pm_node_list_t current_block_exits = { 0 }; + pm_node_list_t *previous_block_exits = push_block_exits(parser, ¤t_block_exits); + pm_token_t qmark = parser->current; parser_lex(parser); + pm_node_t *true_expression = parse_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_TERNARY_EXPRESSION_TRUE); if (parser->recovering) { @@ -19160,6 +19237,10 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_token_t colon = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = parser->previous.end, .end = parser->previous.end }; pm_node_t *false_expression = (pm_node_t *) pm_missing_node_create(parser, colon.start, colon.end); + context_pop(parser); + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return (pm_node_t *) pm_if_node_ternary_create(parser, node, &qmark, true_expression, &colon, false_expression); } @@ -19169,6 +19250,10 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_token_t colon = parser->previous; pm_node_t *false_expression = parse_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_TERNARY_EXPRESSION_FALSE); + context_pop(parser); + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return (pm_node_t *) pm_if_node_ternary_create(parser, node, &qmark, true_expression, &colon, false_expression); } case PM_TOKEN_COLON_COLON: { diff --git a/prism/templates/src/node.c.erb b/prism/templates/src/node.c.erb index 967f156e6d..3390adcb16 100644 --- a/prism/templates/src/node.c.erb +++ b/prism/templates/src/node.c.erb @@ -21,13 +21,32 @@ pm_node_list_memsize(pm_node_list_t *node_list, pm_memsize_t *memsize) { * this function returns false, otherwise it returns true. */ bool -pm_node_list_grow(pm_node_list_t *list) { - if (list->size == list->capacity) { - list->capacity = list->capacity == 0 ? 4 : list->capacity * 2; - list->nodes = (pm_node_t **) xrealloc(list->nodes, sizeof(pm_node_t *) * list->capacity); - return list->nodes != NULL; +pm_node_list_grow(pm_node_list_t *list, size_t size) { + size_t requested_size = list->size + size; + + // If the requested size caused overflow, return false. + if (requested_size < list->size) return false; + + // If the requested size is within the existing capacity, return true. + if (requested_size < list->capacity) return true; + + // Otherwise, reallocate the list to be twice as large as it was before. + size_t next_capacity = list->capacity == 0 ? 4 : list->capacity * 2; + + // If multiplying by 2 caused overflow, return false. + if (next_capacity < list->capacity) return false; + + // If we didn't get enough by doubling, keep doubling until we do. + while (requested_size > next_capacity) { + size_t double_capacity = next_capacity * 2; + + // Ensure we didn't overflow by multiplying by 2. + if (double_capacity < next_capacity) return false; + next_capacity = double_capacity; } - return true; + + list->nodes = (pm_node_t **) xrealloc(list->nodes, sizeof(pm_node_t *) * next_capacity); + return list->nodes != NULL; } /** @@ -35,7 +54,7 @@ pm_node_list_grow(pm_node_list_t *list) { */ void pm_node_list_append(pm_node_list_t *list, pm_node_t *node) { - if (pm_node_list_grow(list)) { + if (pm_node_list_grow(list, 1)) { list->nodes[list->size++] = node; } } @@ -45,7 +64,7 @@ pm_node_list_append(pm_node_list_t *list, pm_node_t *node) { */ void pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node) { - if (pm_node_list_grow(list)) { + if (pm_node_list_grow(list, 1)) { memmove(list->nodes + 1, list->nodes, list->size * sizeof(pm_node_t *)); list->nodes[0] = node; list->size++; @@ -53,6 +72,17 @@ pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node) { } /** + * Concatenate the given node list onto the end of the other node list. + */ +void +pm_node_list_concat(pm_node_list_t *list, pm_node_list_t *other) { + if (other->size > 0 && pm_node_list_grow(list, other->size)) { + memcpy(list->nodes + list->size, other->nodes, other->size * sizeof(pm_node_t *)); + list->size += other->size; + } +} + +/** * Free the internal memory associated with the given node list. */ void |
