From 666159ad45af2eeb161749b29ab0b65a651bcc4c Mon Sep 17 00:00:00 2001 From: Laurent Huberdeau Date: Sat, 28 Sep 2024 16:29:54 -0400 Subject: [PATCH 1/6] Simplify comp_body --- sh.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sh.c b/sh.c index 5d2c8a4e..2406e642 100644 --- a/sh.c +++ b/sh.c @@ -1758,14 +1758,13 @@ void comp_body(ast node) { in_tail_position = false; in_block_head_position = true; - if (node != 0) { - while (get_op(node) == '{') { + + while (node != 0) { // Last statement of body is in tail position if the body itself is in tail position if (get_op(get_child(node, 1)) != '{') in_tail_position = start_in_tail_position; comp_statement(get_child(node, 0), false); node = get_child(node, 1); in_block_head_position = false; - } } } From 60406b501daf6d24b8a8320bb1a978e27496f228 Mon Sep 17 00:00:00 2001 From: Laurent Huberdeau Date: Sun, 29 Sep 2024 19:15:49 -0400 Subject: [PATCH 2/6] Factor out if and while out of comp_statement This makes comp_statement much smaller and shares the loop code for while, do_while and for loops. --- sh.c | 168 ++++++++++++++++++++++++++++++----------------------------- 1 file changed, 86 insertions(+), 82 deletions(-) diff --git a/sh.c b/sh.c index 2406e642..420c1b33 100644 --- a/sh.c +++ b/sh.c @@ -1903,107 +1903,111 @@ void comp_switch(ast node) { append_glo_decl(wrap_str_lit("esac")); } -void comp_statement(ast node, int else_if) { - int op = get_op(node); - text str; - int start_loop_end_actions_start; - int start_loop_end_actions_end; - - gensym_ix = 0; - - if (op == IF_KW) { - append_glo_decl(string_concat3( +void comp_if(ast node, bool else_if) { + append_glo_decl(string_concat3( wrap_str_lit(else_if ? "elif " : "if "), comp_rvalue(get_child(node, 0), else_if ? RVALUE_CTX_TEST_ELSEIF : RVALUE_CTX_TEST), wrap_str_lit(" ; then") )); - nest_level += 1; - if (get_child(node, 1) != 0) { comp_statement(get_child(node, 1), false); } - else { append_glo_decl(wrap_char(':')); } - nest_level -= 1; + nest_level += 1; + if (get_child(node, 1) != 0) { comp_statement(get_child(node, 1), false); } + else { append_glo_decl(wrap_char(':')); } + nest_level -= 1; - if (get_child(node, 2) != 0) { - // Compile sequence of if else if using elif - if (get_op(get_child(node, 2)) == IF_KW) { - comp_statement(get_child(node, 2), true); // comp_statement with else_if == true emits elif - } else { - append_glo_decl(wrap_str_lit("else")); - nest_level += 1; - comp_statement(get_child(node, 2), false); - nest_level -= 1; - } + if (get_child(node, 2) != 0) { + // Compile sequence of if else if using elif + if (get_op(get_child(node, 2)) == IF_KW) { + comp_statement(get_child(node, 2), true); // comp_statement with else_if == true emits elif + } else { + append_glo_decl(wrap_str_lit("else")); + nest_level += 1; + comp_statement(get_child(node, 2), false); + nest_level -= 1; } - if (!else_if) append_glo_decl(wrap_str_lit("fi")); - } else if (op == WHILE_KW) { - append_glo_decl(string_concat3( - wrap_str_lit("while "), - comp_rvalue(get_child(node, 0), RVALUE_CTX_TEST), - wrap_str_lit(" ; do") - )); + } + if (!else_if) append_glo_decl(wrap_str_lit("fi")); +} + +// Function for compiling while, do_while and for loops +// last_line and loop_end_stmt are mutually exclusive +// last_line is the last line of the loop +// loop_end_stmt is the statement that should be executed at the end of the for loop (increment, etc.) +void comp_loop(text cond, ast body, ast loop_end_stmt, text last_line) { + // Save loop end actions from possible outer loop + int start_loop_end_actions_start = loop_end_actions_start; + int start_loop_end_actions_end = loop_end_actions_end; + + // This is a little bit of a hack, but it makes things so much simpler. + // Because we need to capture the code for the end of loop actions + // (increment, etc.), and those can be any statement expression, we somehow + // need to get the text generated by the call to comp_statement. Instead of + // modifying comp_statement to accomodate this, we just remove the code + // generated by comp_statement using undo_glo_decls and save the indices of + // the declarations of the loop end actions so they can replayed later. + if (loop_end_stmt) { + loop_end_actions_start = glo_decl_ix; + comp_statement(loop_end_stmt, false); + undo_glo_decls(loop_end_actions_start); + loop_end_actions_end = glo_decl_ix; + } - loop_nesting_level += 1; - nest_level += 1; - if (get_child(node, 1) != 0) { comp_statement(get_child(node, 1), false); } - else { append_glo_decl(wrap_char(':')); } - nest_level -= 1; - loop_nesting_level -= 1; + append_glo_decl(string_concat3(wrap_str_lit("while "), cond ? cond : wrap_char(':'), wrap_str_lit("; do"))); + loop_nesting_level += 1; + nest_level += 1; + if (body != 0) { + comp_statement(body, false); + } else if (last_line == 0 && loop_end_stmt == 0) { + // We can't generate empty loops, so we add a no-op statement + append_glo_decl(wrap_char(':')); + } + append_glo_decl(last_line); + replay_glo_decls(loop_end_actions_start, loop_end_actions_end, true); + nest_level -= 1; + loop_nesting_level -= 1; + append_glo_decl(wrap_str_lit("done")); - append_glo_decl(wrap_str_lit("done")); - } else if (op == DO_KW) { - append_glo_decl(wrap_str_lit("while :; do")); - loop_nesting_level += 1; - nest_level += 1; + loop_end_actions_start = start_loop_end_actions_start; + loop_end_actions_end = start_loop_end_actions_end; +} - if (get_child(node, 0) != 0) { - comp_statement(get_child(node, 0), false); - } else { - append_glo_decl(wrap_char(':')); - } +void comp_statement(ast node, int else_if) { + int op; + text str = wrap_char(':'); - append_glo_decl(string_concat(comp_rvalue(get_child(node, 1), RVALUE_CTX_TEST), wrap_str_lit("|| break"))); + if (node == 0) return; - nest_level -= 1; - loop_nesting_level -= 1; - append_glo_decl(wrap_str_lit("done")); - } else if (op == FOR_KW) { - // Save loop end actions from possible outer loop - start_loop_end_actions_start = loop_end_actions_start; - start_loop_end_actions_end = loop_end_actions_end; + op = get_op(node); + + gensym_ix = 0; - if (get_child(node, 0)) comp_statement(get_child(node, 0), false); + if (op == IF_KW) { + comp_if(node, else_if); + } else if (op == WHILE_KW) { + comp_loop(comp_rvalue(get_child(node, 0), RVALUE_CTX_TEST), + get_child(node, 1), + 0, // No loop end statement + 0 // No last line + ); + } else if (op == DO_KW) { + comp_loop(wrap_str_lit(":"), + get_child(node, 0), + 0, // No loop end statement + string_concat(comp_rvalue(get_child(node, 1), RVALUE_CTX_TEST), wrap_str_lit("|| break")) + ); + } else if (op == FOR_KW) { + comp_statement(get_child(node, 0), false); str = wrap_char(':'); // Empty statement if (get_child(node, 1)) { str = comp_rvalue(get_child(node, 1), RVALUE_CTX_TEST); } - append_glo_decl(string_concat3(wrap_str_lit("while "), str, wrap_str_lit(" ; do"))); - - // This is a little bit of a hack, but it makes things so much simpler. - // Because we need to capture the code for the end of loop actions - // (increment, etc.), and those can be any statement expression, we somehow - // need to get the text generated by the call to comp_statement. Instead of - // modifying comp_statement to accomodate this, we just remove the code - // generated by comp_statement using undo_glo_decls and save the indices of - // the declarations of the loop end actions so they can replayed later. - loop_end_actions_start = glo_decl_ix; - if (get_child(node, 2)) comp_statement(get_child(node, 2), false); - undo_glo_decls(loop_end_actions_start); - loop_end_actions_end = glo_decl_ix; - - loop_nesting_level += 1; - nest_level += 1; - if (get_child(node, 3) != 0) { comp_statement(get_child(node, 3), false); } - else if (get_child(node, 2) == 0) { append_glo_decl(wrap_char(':')); } - replay_glo_decls(loop_end_actions_start, loop_end_actions_end, true); - - nest_level -= 1; - loop_nesting_level -= 1; - loop_end_actions_start = start_loop_end_actions_start; - loop_end_actions_end = start_loop_end_actions_end; - - append_glo_decl(wrap_str_lit("done")); + comp_loop(str, + get_child(node, 3), // Body + get_child(node, 2), // End of loop statement + 0 // No last line + ); } else if (op == SWITCH_KW) { comp_switch(node); } else if (op == BREAK_KW) { From 8534227b868b0e21e96c869944e227dd66d3a970 Mon Sep 17 00:00:00 2001 From: Laurent Huberdeau Date: Sun, 29 Sep 2024 19:43:58 -0400 Subject: [PATCH 3/6] More correct empty-block detection logic The old logic was very ad-hoc and was located with break/continue/return statements since those are the statements that are typically omitted. When writing it, I didn't take into account is that compound statements can be empty, in which case a ':' also needs to be emitted. The new logic moves the responsability of emitting the ':' to the code creating the if/loop/function which simplifies the code and fixes the issue with compound statements. --- sh.c | 65 +++++++++++++++++++++----------------- tests/_all/compound.c | 13 ++++++++ tests/_all/compound.golden | 0 3 files changed, 49 insertions(+), 29 deletions(-) create mode 100644 tests/_all/compound.c create mode 100644 tests/_all/compound.golden diff --git a/sh.c b/sh.c index 420c1b33..90135c84 100644 --- a/sh.c +++ b/sh.c @@ -293,7 +293,6 @@ text glo_decls[GLO_DECL_SIZE]; // Generated code int glo_decl_ix = 0; // Index of last generated line of code int nest_level = 0; // Current level of indentation int in_tail_position = false; // Is the current statement in tail position? -int in_block_head_position = false; // Is the current statement in head position for the current block? int loop_nesting_level = 0; // Number of loops surrounding the current statement int loop_end_actions_start = 0; // Start position of declarations for the last action in a for loop int loop_end_actions_end = 0; // End position of declarations for the last action in a for loop @@ -343,17 +342,17 @@ void append_glo_decl(text decl) { } int append_glo_decl_fixup() { - glo_decls[glo_decl_ix] = nest_level; + glo_decls[glo_decl_ix] = -nest_level; // Negative value to indicate that it's a fixup glo_decls[glo_decl_ix + 1] = 1; // If it's active or not. Used by undo_glo_decls and replay_glo_decls - glo_decls[glo_decl_ix + 2] = -1; + glo_decls[glo_decl_ix + 2] = 0; glo_decl_ix += 3; return glo_decl_ix - 3; } void fixup_glo_decl(int fixup_ix, text decl) { - if (glo_decls[fixup_ix + 2] != -1) - fatal_error("fixup_glo_decl: invalid fixup"); + if (glo_decls[fixup_ix] >= 0) fatal_error("fixup_glo_decl: invalid fixup"); + glo_decls[fixup_ix] = -glo_decls[fixup_ix]; // Make nest level positive glo_decls[fixup_ix + 2] = decl; } @@ -371,6 +370,16 @@ void undo_glo_decls(int start) { } } +// Check if there are any active and non-empty declarations since the start index. +// This is used to determine if a ':' statement must be added to the current block. +bool any_active_glo_decls(int start) { + while (start < glo_decl_ix) { + if (glo_decls[start + 1] && glo_decls[start + 2] != 0) return true; + start += 3; + } + return false; +} + // Replay the declarations betwee start and end. Replayed declarations must first // be undone with undo_glo_decls. // The reindent parameter controls if the declarations should be replayed at the @@ -1756,15 +1765,12 @@ void comp_assignment(ast lhs, ast rhs) { void comp_body(ast node) { int start_in_tail_position = in_tail_position; in_tail_position = false; - in_block_head_position = true; - while (node != 0) { - // Last statement of body is in tail position if the body itself is in tail position - if (get_op(get_child(node, 1)) != '{') in_tail_position = start_in_tail_position; - comp_statement(get_child(node, 0), false); - node = get_child(node, 1); - in_block_head_position = false; + // Last statement of body is in tail position if the body itself is in tail position + if (get_op(get_child(node, 1)) != '{') in_tail_position = start_in_tail_position; + comp_statement(get_child(node, 0), false); + node = get_child(node, 1); } } @@ -1904,6 +1910,8 @@ void comp_switch(ast node) { } void comp_if(ast node, bool else_if) { + int start_glo_decl_idx; + append_glo_decl(string_concat3( wrap_str_lit(else_if ? "elif " : "if "), comp_rvalue(get_child(node, 0), else_if ? RVALUE_CTX_TEST_ELSEIF : RVALUE_CTX_TEST), @@ -1911,8 +1919,10 @@ void comp_if(ast node, bool else_if) { )); nest_level += 1; - if (get_child(node, 1) != 0) { comp_statement(get_child(node, 1), false); } - else { append_glo_decl(wrap_char(':')); } + start_glo_decl_idx = glo_decl_ix; + comp_statement(get_child(node, 1), false); + // ifs cannot be empty so we insert ':' if it's empty + if (!any_active_glo_decls(start_glo_decl_idx)) append_glo_decl(wrap_char(':')); nest_level -= 1; if (get_child(node, 2) != 0) { @@ -1922,7 +1932,9 @@ void comp_if(ast node, bool else_if) { } else { append_glo_decl(wrap_str_lit("else")); nest_level += 1; + start_glo_decl_idx = glo_decl_ix; comp_statement(get_child(node, 2), false); + if (!any_active_glo_decls(start_glo_decl_idx)) append_glo_decl(wrap_char(':')); nest_level -= 1; } } @@ -1937,6 +1949,7 @@ void comp_loop(text cond, ast body, ast loop_end_stmt, text last_line) { // Save loop end actions from possible outer loop int start_loop_end_actions_start = loop_end_actions_start; int start_loop_end_actions_end = loop_end_actions_end; + int start_glo_decl_idx; // This is a little bit of a hack, but it makes things so much simpler. // Because we need to capture the code for the end of loop actions @@ -1955,14 +1968,12 @@ void comp_loop(text cond, ast body, ast loop_end_stmt, text last_line) { append_glo_decl(string_concat3(wrap_str_lit("while "), cond ? cond : wrap_char(':'), wrap_str_lit("; do"))); loop_nesting_level += 1; nest_level += 1; - if (body != 0) { - comp_statement(body, false); - } else if (last_line == 0 && loop_end_stmt == 0) { - // We can't generate empty loops, so we add a no-op statement - append_glo_decl(wrap_char(':')); - } + start_glo_decl_idx = glo_decl_ix; + comp_statement(body, false); append_glo_decl(last_line); replay_glo_decls(loop_end_actions_start, loop_end_actions_end, true); + // while loops cannot be empty so we insert ':' if it's empty + if (!any_active_glo_decls(start_glo_decl_idx)) append_glo_decl(wrap_char(':')); nest_level -= 1; loop_nesting_level -= 1; append_glo_decl(wrap_str_lit("done")); @@ -2033,8 +2044,6 @@ void comp_statement(ast node, int else_if) { } if (in_tail_position && loop_nesting_level == 1) { append_glo_decl(wrap_str_lit("break")); // Break out of the loop, and the function prologue will do the rest - } else if (in_tail_position && in_block_head_position && get_child(node, 0) == 0) { - append_glo_decl(wrap_char(':')); // Block only contains a return statement so it's not empty } else if (!in_tail_position || loop_nesting_level != 0) { rest_loc_var_fixups = new_ast2(',', append_glo_decl_fixup(), rest_loc_var_fixups); append_glo_decl(wrap_str_lit("return")); @@ -2058,8 +2067,6 @@ void comp_statement(ast node, int else_if) { str = comp_rvalue(node, RVALUE_CTX_BASE); if (contains_side_effects) { append_glo_decl(string_concat(wrap_str_lit(": "), str)); - } else if (in_block_head_position && in_tail_position) { - append_glo_decl(wrap_char(':')); // Block only contains this statement so we have to make sure it's not empty } } } @@ -2177,6 +2184,7 @@ void comp_glo_fun_decl(ast node) { int params_ix; ast decls, vars, var; int save_loc_vars_fixup; + int start_glo_decl_idx; if (body == -1) return; // ignore forward declarations @@ -2230,6 +2238,7 @@ void comp_glo_fun_decl(ast node) { in_tail_position = true; nest_level += 1; + start_glo_decl_idx = glo_decl_ix; save_loc_vars_fixup = append_glo_decl_fixup(); // Fixup is done after compiling body @@ -2270,11 +2279,9 @@ void comp_glo_fun_decl(ast node) { local_vars = get_child(local_vars, 1); } - if (body == 0) { - append_glo_decl(wrap_char(':')); // Empty function - } else { - comp_body(body); - } + comp_body(body); + // functions cannot be empty so we insert ':' if it's empty + if (!any_active_glo_decls(start_glo_decl_idx)) append_glo_decl(wrap_char(':')); append_glo_decl(restore_local_vars(params_ix - 1)); diff --git a/tests/_all/compound.c b/tests/_all/compound.c new file mode 100644 index 00000000..f5b92b79 --- /dev/null +++ b/tests/_all/compound.c @@ -0,0 +1,13 @@ +#include + +void main() { + if (1) { + // Empty if + } + + if (1) { + { + // Empty compound statement + } + } +} diff --git a/tests/_all/compound.golden b/tests/_all/compound.golden new file mode 100644 index 00000000..e69de29b From a203866cdc4ab60c0f9a33a888dbd5e891082df2 Mon Sep 17 00:00:00 2001 From: Laurent Huberdeau Date: Sun, 29 Sep 2024 22:30:22 -0400 Subject: [PATCH 4/6] Merge switch end-of-block code with comp_statement --- sh.c | 281 ++++++++++++++++++++++++++++++----------------------------- 1 file changed, 145 insertions(+), 136 deletions(-) diff --git a/sh.c b/sh.c index 90135c84..82a4d4f2 100644 --- a/sh.c +++ b/sh.c @@ -58,12 +58,26 @@ enum TEXT_NODES { // Place prototype of mutually recursive functions here +typedef enum STMT_CTX { + // Default context + STMT_CTX_DEFAULT = 0, + // Indicates that the parent statement was a else statement so that if + // statement uses elif instead of if. + STMT_CTX_ELSE_IF = 1, + // Indicates that we are in a switch statement where breaks mean the end of + // the conditional block. + STMT_CTX_SWITCH = 2, + // Because we don't know in advance where the conditional block ends, this + // indicates to returns/breaks that they are in a switch in tail position + STMT_CTX_SWITCH_TAIL = 4, +} STMT_CTX; + text comp_lvalue_address(ast node); text comp_lvalue(ast node); text comp_fun_call_code(ast node, ast assign_to); void comp_fun_call(ast node, ast assign_to); -void comp_body(ast node); -void comp_statement(ast node, int else_if); +bool comp_body(ast node, STMT_CTX stmt_ctx); +bool comp_statement(ast node, STMT_CTX stmt_ctx); void mark_mutable_variables_body(ast node); void handle_enum_struct_union_type_decl(ast node); @@ -1762,93 +1776,56 @@ void comp_assignment(ast lhs, ast rhs) { } } -void comp_body(ast node) { +bool comp_body(ast node, STMT_CTX stmt_ctx) { int start_in_tail_position = in_tail_position; in_tail_position = false; while (node != 0) { // Last statement of body is in tail position if the body itself is in tail position if (get_op(get_child(node, 1)) != '{') in_tail_position = start_in_tail_position; - comp_statement(get_child(node, 0), false); + if (comp_statement(get_child(node, 0), stmt_ctx)) return true; // Statement always returns => block is terminated node = get_child(node, 1); } + return false; } -// Return if the statement is a break or return statement, meaning that the block should be terminated. -// A switch conditional block is considered terminated if it ends with a break or return statement. -bool comp_switch_block_statement(ast node, int statements_in_block, bool else_if, bool start_in_tail_position) { - bool termination_lhs = false; - bool termination_rhs = false; - - if (get_op(node) == BREAK_KW) { - if (statements_in_block == 0) { - append_glo_decl(wrap_char(':')); - } - return true; - } else if (get_op(node) == RETURN_KW) { - // A return marks the end of the conditional block, so it's in tail position - in_tail_position = start_in_tail_position; - comp_statement(node, false); - return true; - } else if (get_op(node) == CASE_KW || get_op(node) == DEFAULT_KW) { - fatal_error("comp_switch_block_statement: case must be at the beginning of a switch block, and each block must end with a break or return statement"); - return false; - } else if (get_op(node) == '{') { - while(node != 0) { - if (comp_switch_block_statement(get_child(node, 0), statements_in_block, false, start_in_tail_position)) { - return true; - } - statements_in_block += 1; - node = get_child(node, 1); - } - return false; - } else if (get_op(node) == IF_KW) { - append_glo_decl(string_concat3( - wrap_str_lit(else_if ? "elif " : "if "), - comp_rvalue(get_child(node, 0), else_if ? RVALUE_CTX_TEST_ELSEIF : RVALUE_CTX_TEST), - wrap_str_lit(" ; then") - )); - - nest_level += 1; - if (get_child(node, 1) != 0) { - termination_lhs = comp_switch_block_statement(get_child(node, 1), 0, false, start_in_tail_position); - } else { - append_glo_decl(wrap_char(':')); - } - nest_level -= 1; - - // else - if (get_child(node, 2) != 0) { - // Compile sequence of if else if using elif - if (get_op(get_child(node, 2)) == IF_KW) { - termination_rhs = comp_switch_block_statement(get_child(node, 2), 0, true, start_in_tail_position); // comp_statement with else_if == true emits elif - } else { - append_glo_decl(wrap_str_lit("else")); - nest_level += 1; - termination_rhs = comp_switch_block_statement(get_child(node, 2), 0, false, start_in_tail_position); - nest_level -= 1; - } - } - - if (!else_if) { - append_glo_decl(wrap_str_lit("fi")); - } +// Assemble switch pattern from case and default statements. +// Case and default statements are like labelled statements, meaning that they +// wrap the next statement. This function unwraps the next statements until a +// non-case statement is found. +// Because the non-case statement must be compiled as well, it is returned via +// the last_stmt global variable. +ast last_stmt; +text make_switch_pattern(ast statement) { + text str = 0; + + while (1) { // statement will never be null + switch (get_op(statement)) { + case DEFAULT_KW: + str = wrap_char('*'); + statement = get_child(statement, 0); + break; + + case CASE_KW: + // This is much more permissive than what a C compiler would allow, + // but Shell allows matching on arbitrary expression in case + // patterns so it's fine. If we wanted to do this right, we'd check + // that the pattern is a numeric literal or an enum identifier. + str = concatenate_strings_with(str, comp_rvalue(get_child(statement, 0), RVALUE_CTX_BASE), wrap_char('|')); + statement = get_child(statement, 1); + break; - if (termination_lhs ^ termination_rhs) { - fatal_error("comp_switch_block_statement: detected an early break out of a switch case, unsupported yet."); + default: + if (str == 0) fatal_error("Expected case in switch. Fallthrough is not supported."); + last_stmt = statement; + return string_concat(str, wrap_char(')')); } - - return termination_lhs && termination_rhs; - } else { - comp_statement(node, false); - return false; } } -void comp_switch(ast node) { +bool comp_switch(ast node) { int start_in_tail_position = in_tail_position; ast statement; - text str; append_glo_decl(string_concat3( wrap_str_lit("case "), @@ -1865,40 +1842,22 @@ void comp_switch(ast node) { while (get_op(node) == '{') { statement = get_child(node, 0); node = get_child(node, 1); - if (get_op(statement) != CASE_KW && get_op(statement) != DEFAULT_KW) { - fatal_error("comp_statement: switch body without case"); - } - // Assemble the patterns - if (get_op(statement) == CASE_KW) { - str = 0; - while (get_op(statement) == CASE_KW) { - // This is much more permissive than what a C compiler would allow, - // but Shell allows matching on arbitrary expression in case - // patterns so it's fine. If we wanted to do this right, we'd check - // that the pattern is a numeric literal or an enum identifier. - str = concatenate_strings_with(str, comp_rvalue(get_child(statement, 0), RVALUE_CTX_BASE), wrap_char('|')); - statement = get_child(statement, 1); - } - } else { - str = wrap_char('*'); - statement = get_child(statement, 0); - } - - append_glo_decl(string_concat(str, wrap_char(')'))); + append_glo_decl(make_switch_pattern(statement)); + statement = last_stmt; // last_stmt is set by make_switch_pattern nest_level += 1; in_tail_position = false; - // case and default nodes contain the first statement of the block. We add it to the list of statements to process. - node = new_ast2('{', statement, node); // Allocating memory isn't ideal but it makes the code tidier - - while (get_op(node) == '{') { - statement = get_child(node, 0); - node = get_child(node, 1); - // If we encounter a break or return statement, we stop processing the block - if (comp_switch_block_statement(statement, 0, false, start_in_tail_position)) break; + // We keep compiling statements until we encounter a statement that returns or breaks + // Case and default nodes contain the first statement of the block so we process that one first. + if (!comp_statement(statement, STMT_CTX_SWITCH | (start_in_tail_position ? STMT_CTX_SWITCH_TAIL : 0))) { + while (get_op(node) == '{') { + statement = get_child(node, 0); + node = get_child(node, 1); + if (comp_statement(statement, STMT_CTX_SWITCH | (start_in_tail_position ? STMT_CTX_SWITCH_TAIL : 0))) break; + } } nest_level -= 1; @@ -1907,10 +1866,28 @@ void comp_switch(ast node) { nest_level -= 1; append_glo_decl(wrap_str_lit("esac")); + + // Returning not-false is only important for nested switch statements. + // It could be useful to remove the need for the redundant trailing return + // when nesting switch statements that we know are exhaustive such as in + // eval_constant. + // + // I tried to make it return true if all cases of a switch end with a return + // but it wasn't working well because we don't know if the switch delimits the + // conditional block until it ends and so in_tail_position must be set to + // false which defeats the point of removing the trailing return (since the + // switch is not compiled in tail position mode even if it turns out to be the + // case. + return false; } -void comp_if(ast node, bool else_if) { +bool comp_if(ast node, STMT_CTX stmt_ctx) { int start_glo_decl_idx; + bool termination_lhs = false; + bool termination_rhs = false; + + bool else_if = stmt_ctx & STMT_CTX_ELSE_IF; + stmt_ctx = stmt_ctx & ~STMT_CTX_ELSE_IF; // Clear STMT_CTX_ELSE_IF bit to not pass it to the next if statement append_glo_decl(string_concat3( wrap_str_lit(else_if ? "elif " : "if "), @@ -1920,7 +1897,7 @@ void comp_if(ast node, bool else_if) { nest_level += 1; start_glo_decl_idx = glo_decl_ix; - comp_statement(get_child(node, 1), false); + termination_lhs = comp_statement(get_child(node, 1), stmt_ctx); // ifs cannot be empty so we insert ':' if it's empty if (!any_active_glo_decls(start_glo_decl_idx)) append_glo_decl(wrap_char(':')); nest_level -= 1; @@ -1928,39 +1905,46 @@ void comp_if(ast node, bool else_if) { if (get_child(node, 2) != 0) { // Compile sequence of if else if using elif if (get_op(get_child(node, 2)) == IF_KW) { - comp_statement(get_child(node, 2), true); // comp_statement with else_if == true emits elif + termination_rhs = comp_if(get_child(node, 2), stmt_ctx | STMT_CTX_ELSE_IF); // STMT_CTX_ELSE_IF => next if stmt will use elif } else { append_glo_decl(wrap_str_lit("else")); nest_level += 1; start_glo_decl_idx = glo_decl_ix; - comp_statement(get_child(node, 2), false); + termination_rhs = comp_statement(get_child(node, 2), stmt_ctx & ~STMT_CTX_ELSE_IF); // Clear STMT_CTX_ELSE_IF bit if (!any_active_glo_decls(start_glo_decl_idx)) append_glo_decl(wrap_char(':')); nest_level -= 1; } } if (!else_if) append_glo_decl(wrap_str_lit("fi")); + + if (stmt_ctx & STMT_CTX_SWITCH && termination_lhs ^ termination_rhs) { + fatal_error("Early break out of a switch case is unsupported"); + } + + return termination_lhs && termination_rhs; } // Function for compiling while, do_while and for loops // last_line and loop_end_stmt are mutually exclusive // last_line is the last line of the loop // loop_end_stmt is the statement that should be executed at the end of the for loop (increment, etc.) -void comp_loop(text cond, ast body, ast loop_end_stmt, text last_line) { +bool comp_loop(text cond, ast body, ast loop_end_stmt, text last_line, STMT_CTX stmt_ctx) { // Save loop end actions from possible outer loop int start_loop_end_actions_start = loop_end_actions_start; int start_loop_end_actions_end = loop_end_actions_end; int start_glo_decl_idx; + bool always_returns = false; // This is a little bit of a hack, but it makes things so much simpler. // Because we need to capture the code for the end of loop actions // (increment, etc.), and those can be any statement expression, we somehow - // need to get the text generated by the call to comp_statement. Instead of + // need to get the text generated by the comp_statement call. Instead of // modifying comp_statement to accomodate this, we just remove the code // generated by comp_statement using undo_glo_decls and save the indices of // the declarations of the loop end actions so they can replayed later. if (loop_end_stmt) { loop_end_actions_start = glo_decl_ix; - comp_statement(loop_end_stmt, false); + comp_statement(loop_end_stmt, stmt_ctx); undo_glo_decls(loop_end_actions_start); loop_end_actions_end = glo_decl_ix; } @@ -1969,7 +1953,7 @@ void comp_loop(text cond, ast body, ast loop_end_stmt, text last_line) { loop_nesting_level += 1; nest_level += 1; start_glo_decl_idx = glo_decl_ix; - comp_statement(body, false); + always_returns = comp_statement(body, stmt_ctx); append_glo_decl(last_line); replay_glo_decls(loop_end_actions_start, loop_end_actions_end, true); // while loops cannot be empty so we insert ':' if it's empty @@ -1980,57 +1964,69 @@ void comp_loop(text cond, ast body, ast loop_end_stmt, text last_line) { loop_end_actions_start = start_loop_end_actions_start; loop_end_actions_end = start_loop_end_actions_end; + + // If the condition is always true and the loop always returns + return cond == wrap_char(':') && always_returns; } -void comp_statement(ast node, int else_if) { +// Returns whether the statement always returns/breaks. +// This is used to delimit the end of conditional blocks of switch statements. +bool comp_statement(ast node, STMT_CTX stmt_ctx) { int op; - text str = wrap_char(':'); + text str; - if (node == 0) return; + if (node == 0) return false; // Empty statement never returns op = get_op(node); - gensym_ix = 0; + gensym_ix = 0; // Reuse gensym names for each statement if (op == IF_KW) { - comp_if(node, else_if); + return comp_if(node, stmt_ctx); } else if (op == WHILE_KW) { - comp_loop(comp_rvalue(get_child(node, 0), RVALUE_CTX_TEST), - get_child(node, 1), - 0, // No loop end statement - 0 // No last line - ); + return comp_loop(comp_rvalue(get_child(node, 0), RVALUE_CTX_TEST), + get_child(node, 1), + 0, // No loop end statement + 0, // No last line + stmt_ctx + ); } else if (op == DO_KW) { - comp_loop(wrap_str_lit(":"), - get_child(node, 0), - 0, // No loop end statement - string_concat(comp_rvalue(get_child(node, 1), RVALUE_CTX_TEST), wrap_str_lit("|| break")) - ); + return comp_loop(wrap_str_lit(":"), + get_child(node, 0), + 0, // No loop end statement + string_concat(comp_rvalue(get_child(node, 1), RVALUE_CTX_TEST), wrap_str_lit("|| break")), + stmt_ctx + ); } else if (op == FOR_KW) { - comp_statement(get_child(node, 0), false); + comp_statement(get_child(node, 0), STMT_CTX_DEFAULT); // Assuming this statement never returns... str = wrap_char(':'); // Empty statement if (get_child(node, 1)) { str = comp_rvalue(get_child(node, 1), RVALUE_CTX_TEST); } - comp_loop(str, - get_child(node, 3), // Body - get_child(node, 2), // End of loop statement - 0 // No last line - ); + return comp_loop(str, + get_child(node, 3), // Body + get_child(node, 2), // End of loop statement + 0, // No last line + stmt_ctx + ); } else if (op == SWITCH_KW) { - comp_switch(node); + return comp_switch(node); } else if (op == BREAK_KW) { - if (loop_nesting_level == 0) fatal_error("comp_statement: break not in loop"); - // TODO: What's the semantic of break? Should we run the end of loop action before breaking? - append_glo_decl(wrap_str_lit("break")); + if ((stmt_ctx & STMT_CTX_SWITCH) == 0) { + if (loop_nesting_level == 0) fatal_error("comp_statement: break not in loop"); + append_glo_decl(wrap_str_lit("break")); + } + return true; // Break out of switch statement } else if (op == CONTINUE_KW) { if (loop_nesting_level == 0) fatal_error("comp_statement: continue not in loop"); replay_glo_decls(loop_end_actions_start, loop_end_actions_end, true); // We could remove the continue when in tail position, but it's not worth doing append_glo_decl(wrap_str_lit("continue")); + return false; } else if (op == RETURN_KW) { + // First we assign the return value... if (get_child(node, 0) != 0) { if (get_op(get_child(node, 0)) == '(') { // Check if function call comp_fun_call(get_child(node, 0), new_ast0(IDENTIFIER_DOLLAR, 1)); @@ -2042,32 +2038,45 @@ void comp_statement(ast node, int else_if) { )); } } + + in_tail_position |= stmt_ctx & STMT_CTX_SWITCH_TAIL; + + // ...and then we take care of the control flow part of the return statement if (in_tail_position && loop_nesting_level == 1) { append_glo_decl(wrap_str_lit("break")); // Break out of the loop, and the function prologue will do the rest } else if (!in_tail_position || loop_nesting_level != 0) { rest_loc_var_fixups = new_ast2(',', append_glo_decl_fixup(), rest_loc_var_fixups); append_glo_decl(wrap_str_lit("return")); } + return true; } else if (op == '(') { // six.call comp_fun_call(node, new_ast0(IDENTIFIER_EMPTY, 0)); // Reuse IDENTIFIER_EMPTY ast? + return false; } else if (op == '{') { // six.compound - comp_body(node); + return comp_body(node, stmt_ctx); } else if (op == '=') { // six.x=y comp_assignment(get_child(node, 0), get_child(node, 1)); + return false; } else if (op == ':') { // Labelled statement are not very useful as gotos are not supported in the // Shell backend, but we still emit a label comment for readability. append_glo_decl(string_concat3(wrap_str_lit("# "), wrap_str_pool(get_val(get_val(get_child(node, 0)))), wrap_char(':'))); - comp_statement(get_child(node, 1), false); + return comp_statement(get_child(node, 1), stmt_ctx); } else if (op == GOTO_KW) { fatal_error("goto statements not supported"); + return false; + } else if (get_op(node) == CASE_KW || get_op(node) == DEFAULT_KW) { + fatal_error("case/default must be at the beginning of a switch conditional block"); + return false; } else if (op == VAR_DECLS) { fatal_error("variable declaration must be at the beginning of a function"); + return false; } else { str = comp_rvalue(node, RVALUE_CTX_BASE); if (contains_side_effects) { append_glo_decl(string_concat(wrap_str_lit(": "), str)); } + return false; } } @@ -2279,7 +2288,7 @@ void comp_glo_fun_decl(ast node) { local_vars = get_child(local_vars, 1); } - comp_body(body); + comp_body(body, STMT_CTX_DEFAULT); // functions cannot be empty so we insert ':' if it's empty if (!any_active_glo_decls(start_glo_decl_idx)) append_glo_decl(wrap_char(':')); From 4955f0f6a5505e251c85a0cc880fcd3d662e65e2 Mon Sep 17 00:00:00 2001 From: Laurent Huberdeau Date: Sun, 29 Sep 2024 23:30:48 -0400 Subject: [PATCH 5/6] Regenerate examples --- examples/compiled/all-chars.sh | 2 +- examples/compiled/base64.sh | 10 ++--- examples/compiled/c4.sh | 64 +++++++++++++++--------------- examples/compiled/cat.sh | 4 +- examples/compiled/cp.sh | 2 +- examples/compiled/echo.sh | 2 +- examples/compiled/fib.sh | 2 +- examples/compiled/non_zero.sh | 2 +- examples/compiled/print-reverse.sh | 6 +-- examples/compiled/repl.sh | 32 +++++++-------- examples/compiled/reverse.sh | 2 +- examples/compiled/select-file.sh | 8 ++-- examples/compiled/sha256sum.sh | 24 +++++------ examples/compiled/sum-array.sh | 6 +-- examples/compiled/wc-stdin.sh | 2 +- examples/compiled/wc.sh | 6 +-- examples/compiled/welcome.sh | 2 +- examples/compiled/winterpi.sh | 6 +-- 18 files changed, 91 insertions(+), 91 deletions(-) diff --git a/examples/compiled/all-chars.sh b/examples/compiled/all-chars.sh index 4b993753..0b3083d0 100755 --- a/examples/compiled/all-chars.sh +++ b/examples/compiled/all-chars.sh @@ -6,7 +6,7 @@ LC_ALL=C _main() { let c c=0 - while [ $c -lt 128 ] ; do + while [ $c -lt 128 ]; do printf \\$((c/64))$((c/8%8))$((c%8)) : $((c += 1)) done diff --git a/examples/compiled/base64.sh b/examples/compiled/base64.sh index e45ee2ee..bf91bb75 100755 --- a/examples/compiled/base64.sh +++ b/examples/compiled/base64.sh @@ -74,7 +74,7 @@ _codes=$__str_0 : $((b3 = b2 = b1 = 0)) _encode() { let b1; let b2; let b3 - while [ 1 != 0 ] ; do + while [ 1 != 0 ]; do _getchar b1 if [ $b1 -lt 0 ] ; then break @@ -107,7 +107,7 @@ defarr _lut 256 : $((__t1 = c = 0)) _get() { let c; let __t1 - while _getchar __t1; [ $((c = __t1)) -ge 0 ] ; do + while _getchar __t1; [ $((c = __t1)) -ge 0 ]; do if [ $((c = _$((_lut + c)))) -ge 0 ] ; then break fi @@ -120,16 +120,16 @@ _get() { _decode() { let i; let c1; let c2; let c3; let c4; let __t1 i=0 - while [ $i -lt 256 ] ; do + while [ $i -lt 256 ]; do : $((_$((_lut + i)) = -1)) : $((i += 1)) done i=0 - while [ $i -lt 64 ] ; do + while [ $i -lt 64 ]; do : $((_$((_lut + 255 & _$((_codes + i)))) = i)) : $((i += 1)) done - while _get __t1; [ $((c1 = __t1)) -ge 0 ] ; do + while _get __t1; [ $((c1 = __t1)) -ge 0 ]; do if _get __t1; [ $((c2 = __t1)) -lt 0 ] ; then exit 1 fi diff --git a/examples/compiled/c4.sh b/examples/compiled/c4.sh index 16fff17e..03f8cbaf 100755 --- a/examples/compiled/c4.sh +++ b/examples/compiled/c4.sh @@ -9,7 +9,7 @@ _memset() { # b: $2, c: $3, len: $4 c=$3 len=$4 p=$b - while [ $(((len -= 1) + 1)) != 0 ] ; do + while [ $(((len -= 1) + 1)) != 0 ]; do : $((_$(((p += 1) - 1)) = c)) done : $(($1 = b)) @@ -24,7 +24,7 @@ _memcmp() { # vl: $2, vr: $3, n: $4 n=$4 l=$vl r=$vr - while [ $n != 0 ] && [ $((_$l)) = $((_$r)) ] ; do + while [ $n != 0 ] && [ $((_$l)) = $((_$r)) ]; do : $((n -= 1)) : $((l += 1)) : $((r += 1)) @@ -143,13 +143,13 @@ readonly _Idsz=9 : $((__t1 = pp = 0)) _next() { set $@ $pp $__t1 - while [ $((_tk = _$_p)) != 0 ] ; do + while [ $((_tk = _$_p)) != 0 ]; do : $((_p += 1)) if [ $_tk = $__NEWLINE__ ] ; then if [ $_src != 0 ] ; then printf "%d: (%d - %d = %d) %.*s" $_line $_p $_lp $((_p - _lp)) $((_p - _lp)) "$(_put_pstr __ $_lp)" _lp=$_p - while [ $_le -lt $_e ] ; do + while [ $_le -lt $_e ]; do defstr __str_0 "LEA ,IMM ,JMP ,JSR ,BZ ,BNZ ,ENT ,ADJ ,LEV ,LI ,LC ,SI ,SC ,PSH ,OR ,XOR ,AND ,EQ ,NE ,LT ,GT ,LE ,GE ,SHL ,SHR ,ADD ,SUB ,MUL ,DIV ,MOD ,OPEN,READ,CLOS,PRTF,MALC,FREE,MSET,MCMP,EXIT," printf "%8.4s" "$(_put_pstr __ $((__str_0 + _$((_le += 1)) * 5)))" if [ $((_$_le)) -le $_ADJ ] ; then @@ -161,17 +161,17 @@ _next() { fi : $((_line += 1)) elif [ $_tk = $__SHARP__ ] ; then - while [ $((_$_p)) != 0 ] && [ $((_$_p)) != $__NEWLINE__ ] ; do + while [ $((_$_p)) != 0 ] && [ $((_$_p)) != $__NEWLINE__ ]; do : $((_p += 1)) done elif { [ $_tk -ge $__a__ ] && [ $_tk -le $__z__ ]; } || { [ $_tk -ge $__A__ ] && [ $_tk -le $__Z__ ]; } || [ $_tk = $__UNDERSCORE__ ] ; then pp=$((_p - 1)) - while { [ $((_$_p)) -ge $__a__ ] && [ $((_$_p)) -le $__z__ ]; } || { [ $((_$_p)) -ge $__A__ ] && [ $((_$_p)) -le $__Z__ ]; } || { [ $((_$_p)) -ge $__0__ ] && [ $((_$_p)) -le $__9__ ]; } || [ $((_$_p)) = $__UNDERSCORE__ ] ; do + while { [ $((_$_p)) -ge $__a__ ] && [ $((_$_p)) -le $__z__ ]; } || { [ $((_$_p)) -ge $__A__ ] && [ $((_$_p)) -le $__Z__ ]; } || { [ $((_$_p)) -ge $__0__ ] && [ $((_$_p)) -le $__9__ ]; } || [ $((_$_p)) = $__UNDERSCORE__ ]; do _tk=$(((_tk * 147) + _$(((_p += 1) - 1)))) done _tk=$(((_tk << 6) + (_p - pp))) _id=$_sym - while [ $((_$((_id + _Tk)))) != 0 ] ; do + while [ $((_$((_id + _Tk)))) != 0 ]; do if [ $_tk = $((_$((_id + _Hash)))) ] && { _memcmp __t1 $((_$((_id + _Name)))) $pp $((_p - pp)); [ $((!__t1)) != 0 ]; } ; then _tk=$((_$((_id + _Tk)))) : $((__tmp = $1)) $((pp = $2)) $((__t1 = $3)) $(($1 = __tmp)) @@ -185,15 +185,15 @@ _next() { break elif [ $_tk -ge $__0__ ] && [ $_tk -le $__9__ ] ; then if [ $((_ival = _tk - __0__)) != 0 ] ; then - while [ $((_$_p)) -ge $__0__ ] && [ $((_$_p)) -le $__9__ ] ; do + while [ $((_$_p)) -ge $__0__ ] && [ $((_$_p)) -le $__9__ ]; do _ival=$((((_ival * 10) + _$(((_p += 1) - 1))) - __0__)) done elif [ $((_$_p)) = $__x__ ] || [ $((_$_p)) = $__X__ ] ; then - while [ $((_tk = _$((_p += 1)))) != 0 ] && { { [ $_tk -ge $__0__ ] && [ $_tk -le $__9__ ]; } || { [ $_tk -ge $__a__ ] && [ $_tk -le $__f__ ]; } || { [ $_tk -ge $__A__ ] && [ $_tk -le $__F__ ]; }; } ; do + while [ $((_tk = _$((_p += 1)))) != 0 ] && { { [ $_tk -ge $__0__ ] && [ $_tk -le $__9__ ]; } || { [ $_tk -ge $__a__ ] && [ $_tk -le $__f__ ]; } || { [ $_tk -ge $__A__ ] && [ $_tk -le $__F__ ]; }; }; do _ival=$(((_ival * 16) + (_tk & 15) + ((_tk >= __A__) ? 9: 0))) done else - while [ $((_$_p)) -ge $__0__ ] && [ $((_$_p)) -le $__7__ ] ; do + while [ $((_$_p)) -ge $__0__ ] && [ $((_$_p)) -le $__7__ ]; do _ival=$((((_ival * 8) + _$(((_p += 1) - 1))) - __0__)) done fi @@ -202,7 +202,7 @@ _next() { elif [ $_tk = $__SLASH__ ] ; then if [ $((_$_p)) = $__SLASH__ ] ; then : $((_p += 1)) - while [ $((_$_p)) != 0 ] && [ $((_$_p)) != $__NEWLINE__ ] ; do + while [ $((_$_p)) != 0 ] && [ $((_$_p)) != $__NEWLINE__ ]; do : $((_p += 1)) done else @@ -211,7 +211,7 @@ _next() { fi elif [ $_tk = $__QUOTE__ ] || [ $_tk = $__DQUOTE__ ] ; then pp=$_data - while [ $((_$_p)) != 0 ] && [ $((_$_p)) != $_tk ] ; do + while [ $((_$_p)) != 0 ] && [ $((_$_p)) != $_tk ]; do if [ $((_ival = _$(((_p += 1) - 1)))) = $__BACKSLASH__ ] ; then if [ $((_ival = _$(((_p += 1) - 1)))) = $__n__ ] ; then _ival=$__NEWLINE__ @@ -334,7 +334,7 @@ _expr() { # lev: $2 : $((_$((_e += 1)) = _IMM)) : $((_$((_e += 1)) = _ival)) _next __ - while [ $_tk = $__DQUOTE__ ] ; do + while [ $_tk = $__DQUOTE__ ]; do _next __ done _data=$(((_data + 1) & -(1))) @@ -354,7 +354,7 @@ _expr() { # lev: $2 _next __ _ty=$_CHAR fi - while [ $_tk = $_Mul ] ; do + while [ $_tk = $_Mul ]; do _next __ _ty=$((_ty + _PTR)) done @@ -373,7 +373,7 @@ _expr() { # lev: $2 if [ $_tk = $__LPAREN__ ] ; then _next __ t=0 - while [ $_tk != $__RPAREN__ ] ; do + while [ $_tk != $__RPAREN__ ]; do _expr __ $_Assign : $((_$((_e += 1)) = _PSH)) : $((t += 1)) @@ -418,7 +418,7 @@ _expr() { # lev: $2 if [ $_tk = $_Int ] || [ $_tk = $_Char ] ; then t=$(((_tk == _Int) ? _INT: _CHAR)) _next __ - while [ $_tk = $_Mul ] ; do + while [ $_tk = $_Mul ]; do _next __ t=$((t + _PTR)) done @@ -515,7 +515,7 @@ _expr() { # lev: $2 printf "%d: bad expression\n" $_line exit -1 fi - while [ $_tk -ge $lev ] ; do + while [ $_tk -ge $lev ]; do t=$_ty if [ $_tk = $_Assign ] ; then _next __ @@ -787,7 +787,7 @@ _stmt() { fi elif [ $_tk = $__LBRACE__ ] ; then _next __ - while [ $_tk != $__RBRACE__ ] ; do + while [ $_tk != $__RBRACE__ ]; do _stmt __ done _next __ @@ -864,12 +864,12 @@ _main() { # argc: $2, argv: $3 defstr __str_1 "char else enum if int return sizeof while open read close printf malloc free memset memcmp exit void main" _p=$__str_1 i=$_Char - while [ $i -le $_While ] ; do + while [ $i -le $_While ]; do _next __ : $((_$((_id + _Tk)) = (i += 1) - 1)) done i=$_OPEN - while [ $i -le $_EXIT ] ; do + while [ $i -le $_EXIT ]; do _next __ : $((_$((_id + _Class)) = _Sys)) : $((_$((_id + _Type)) = _INT)) @@ -895,7 +895,7 @@ _main() { # argc: $2, argv: $3 _close __ $fd _line=1 _next __ - while [ $_tk != 0 ] ; do + while [ $_tk != 0 ]; do bt=$_INT if [ $_tk = $_Int ] ; then _next __ @@ -910,7 +910,7 @@ _main() { # argc: $2, argv: $3 if [ $_tk = $__LBRACE__ ] ; then _next __ i=0 - while [ $_tk != $__RBRACE__ ] ; do + while [ $_tk != $__RBRACE__ ]; do if [ $_tk != $_Id ] ; then printf "%d: bad enum identifier %d\n" $_line $_tk : $(($1 = -1)) @@ -939,9 +939,9 @@ _main() { # argc: $2, argv: $3 _next __ fi fi - while [ $_tk != $__SEMICOLON__ ] && [ $_tk != $__RBRACE__ ] ; do + while [ $_tk != $__SEMICOLON__ ] && [ $_tk != $__RBRACE__ ]; do ty=$bt - while [ $_tk = $_Mul ] ; do + while [ $_tk = $_Mul ]; do _next __ ty=$((ty + _PTR)) done @@ -964,7 +964,7 @@ _main() { # argc: $2, argv: $3 : $((_$((_id + _Val)) = (_e + 1))) _next __ i=0 - while [ $_tk != $__RPAREN__ ] ; do + while [ $_tk != $__RPAREN__ ]; do ty=$_INT if [ $_tk = $_Int ] ; then _next __ @@ -972,7 +972,7 @@ _main() { # argc: $2, argv: $3 _next __ ty=$_CHAR fi - while [ $_tk = $_Mul ] ; do + while [ $_tk = $_Mul ]; do _next __ ty=$((ty + _PTR)) done @@ -1008,12 +1008,12 @@ _main() { # argc: $2, argv: $3 fi _loc=$((i += 1)) _next __ - while [ $_tk = $_Int ] || [ $_tk = $_Char ] ; do + while [ $_tk = $_Int ] || [ $_tk = $_Char ]; do bt=$(((_tk == _Int) ? _INT: _CHAR)) _next __ - while [ $_tk != $__SEMICOLON__ ] ; do + while [ $_tk != $__SEMICOLON__ ]; do ty=$bt - while [ $_tk = $_Mul ] ; do + while [ $_tk = $_Mul ]; do _next __ ty=$((ty + _PTR)) done @@ -1044,12 +1044,12 @@ _main() { # argc: $2, argv: $3 done : $((_$((_e += 1)) = _ENT)) : $((_$((_e += 1)) = i - _loc)) - while [ $_tk != $__RBRACE__ ] ; do + while [ $_tk != $__RBRACE__ ]; do _stmt __ done : $((_$((_e += 1)) = _LEV)) _id=$_sym - while [ $((_$((_id + _Tk)))) != 0 ] ; do + while [ $((_$((_id + _Tk)))) != 0 ]; do if [ $((_$((_id + _Class)))) = $_Loc ] ; then : $((_$((_id + _Class)) = _$((_id + _HClass)))) : $((_$((_id + _Type)) = _$((_id + _HType)))) @@ -1087,7 +1087,7 @@ _main() { # argc: $2, argv: $3 : $((_$((sp -= 1)) = argv_)) : $((_$((sp -= 1)) = t)) cycle=0 - while [ 1 != 0 ] ; do + while [ 1 != 0 ]; do i=$((_$(((pc += 1) - 1)))) : $((cycle += 1)) if [ $_debug != 0 ] ; then diff --git a/examples/compiled/cat.sh b/examples/compiled/cat.sh index d1fb0448..68d01130 100755 --- a/examples/compiled/cat.sh +++ b/examples/compiled/cat.sh @@ -17,7 +17,7 @@ defarr _buf 1024 _cat_fd() { let fd $2 let n; let __t1 n=1024 - while [ $n = 1024 ] ; do + while [ $n = 1024 ]; do _read n $fd $_buf 1024 if [ $n -lt 0 ] || { _write __t1 1 $_buf $n; [ $__t1 != $n ]; } ; then exit 1 @@ -43,7 +43,7 @@ _main() { let argc $2; let myargv $3 let i if [ $argc -ge 2 ] ; then i=1 - while [ $i -lt $argc ] ; do + while [ $i -lt $argc ]; do if [ $((_$((_$((myargv + i)) + 0)))) = $__MINUS__ ] && [ $((_$((_$((myargv + i)) + 1)))) = $__NUL__ ] ; then _cat_fd __ 0 else diff --git a/examples/compiled/cp.sh b/examples/compiled/cp.sh index d0524c73..f8e28326 100755 --- a/examples/compiled/cp.sh +++ b/examples/compiled/cp.sh @@ -39,7 +39,7 @@ _main() { let argc $2; let args $3 if [ $dst = 0 ] ; then _file_error __ $((_$((args + 2)))) fi - while _read __t1 $src $_buffer 1024; [ $((len = __t1)) != 0 ] ; do + while _read __t1 $src $_buffer 1024; [ $((len = __t1)) != 0 ]; do _write __ $dst $_buffer $len done endlet $1 __t1 len c dst src args argc diff --git a/examples/compiled/echo.sh b/examples/compiled/echo.sh index be6d6196..432f525c 100755 --- a/examples/compiled/echo.sh +++ b/examples/compiled/echo.sh @@ -6,7 +6,7 @@ LC_ALL=C _main() { let argc $2; let argv_ $3 let i i=1 - while [ $i -lt $argc ] ; do + while [ $i -lt $argc ]; do _put_pstr __ $((_$((argv_ + i)))) printf " " : $((i += 1)) diff --git a/examples/compiled/fib.sh b/examples/compiled/fib.sh index b09886a9..6f1bb7b6 100755 --- a/examples/compiled/fib.sh +++ b/examples/compiled/fib.sh @@ -19,7 +19,7 @@ _fib() { let n $2 _main() { let n; let i i=0 - while [ $i -lt 20 ] ; do + while [ $i -lt 20 ]; do _fib n $i printf "fib(%d) = %d\n" $i $n : $((i += 1)) diff --git a/examples/compiled/non_zero.sh b/examples/compiled/non_zero.sh index 7c0ae482..e1abe4c9 100755 --- a/examples/compiled/non_zero.sh +++ b/examples/compiled/non_zero.sh @@ -9,7 +9,7 @@ _main() { while :; do printf "Enter a non-zero single-digit number:\r\n" _getchar n - while _getchar __t1; [ $__NEWLINE__ != $__t1 ] ; do + while _getchar __t1; [ $__NEWLINE__ != $__t1 ]; do : done [ $n = $__0__ ] || [ $((!((n >= __0__) && (n <= __9__)))) != 0 ]|| break diff --git a/examples/compiled/print-reverse.sh b/examples/compiled/print-reverse.sh index 5742e4e5..02a7f106 100755 --- a/examples/compiled/print-reverse.sh +++ b/examples/compiled/print-reverse.sh @@ -7,11 +7,11 @@ _reverse_str() { let str $2 let end; let tmp; let len; let i end=$str i=0 - while [ $((_$(((end += 1) - 1)))) != 0 ] ; do + while [ $((_$(((end += 1) - 1)))) != 0 ]; do : done len=$(((end - str) - 1)) - while [ $i -lt $((len / 2)) ] ; do + while [ $i -lt $((len / 2)) ]; do tmp=$((_$((str + i)))) : $((_$((str + i)) = _$((str + ((len - 1) - i))))) : $((_$((str + (len - 1) - i)) = tmp)) @@ -24,7 +24,7 @@ _reverse_str() { let str $2 _main() { let argc $2; let argv_ $3 let i i=1 - while [ $i -lt $argc ] ; do + while [ $i -lt $argc ]; do _reverse_str __ $((_$((argv_ + i)))) _put_pstr __ $i printf "\n" diff --git a/examples/compiled/repl.sh b/examples/compiled/repl.sh index 3fffbfa3..b4d589d3 100755 --- a/examples/compiled/repl.sh +++ b/examples/compiled/repl.sh @@ -192,7 +192,7 @@ _input=$__str_0 _putstr() { # s: $2 set $@ $s s=$2 - while [ $((_$s)) != 0 ] ; do + while [ $((_$s)) != 0 ]; do printf \\$(((_$s)/64))$(((_$s)/8%8))$(((_$s)%8)) : $(((s += 1) - 1)) done @@ -273,7 +273,7 @@ _gc() { _copy _pc $_pc _copy _FALSE $_FALSE _scan=$to_space - while [ $_scan != $_alloc ] ; do + while [ $_scan != $_alloc ]; do _copy _$_scan $((_$_scan)) : $(((_scan += 1) - 1)) done @@ -341,7 +341,7 @@ _list_tail() { # lst: $2, i: $3 set $@ $lst $i lst=$2 i=$3 - while [ $(((i -= 1) + 1)) != 0 ] ; do + while [ $(((i -= 1) + 1)) != 0 ]; do lst=$((_$((lst + __field1)))) done : $(($1 = lst)) @@ -353,7 +353,7 @@ _inst_tail() { # lst: $2, i: $3 set $@ $lst $i lst=$2 i=$3 - while [ $(((i -= 1) + 1)) != 0 ] ; do + while [ $(((i -= 1) + 1)) != 0 ]; do lst=$((_$((lst + __field2)))) done : $(($1 = lst)) @@ -388,7 +388,7 @@ _get_opnd() { # o: $2 _get_cont() { set $@ $s s=$_stack - while [ $((!(_$((s + __field2)) >> 1))) != 0 ] ; do + while [ $((!(_$((s + __field2)) >> 1))) != 0 ]; do s=$((_$((s + __field1)))) done : $(($1 = s)) @@ -400,7 +400,7 @@ _lst_length() { # list: $2 set $@ $list $l list=$2 l=0 - while [ $((!(list & 1))) != 0 ] && [ $((_$((list + __field2)) >> 1)) = 0 ] ; do + while [ $((!(list & 1))) != 0 ] && [ $((_$((list + __field2)) >> 1)) = 0 ]; do : $((l += 1)) list=$((_$((list + __field1)))) done @@ -461,13 +461,13 @@ _create_sym() { # name: $2 _build_sym_table() { set $@ $n $accum $c $__t1 _get_int n 0 - while [ $n -gt 0 ] ; do + while [ $n -gt 0 ]; do : $(((n -= 1) + 1)) _create_sym __t1 $((_$((_FALSE + __field1)))) _symbol_table=$__t1 done accum=$((_$((_FALSE + __field1)))) - while [ 1 != 0 ] ; do + while [ 1 != 0 ]; do _get_byte c if [ $c = 44 ] ; then _create_sym __t1 $accum @@ -510,11 +510,11 @@ _init_weights() { : $((__t1 = c = x = op = d = n = 0)) _decode() { set $@ $n $d $op $x $c $__t1 - while [ 1 != 0 ] ; do + while [ 1 != 0 ]; do _get_code x n=$x op=-1 - while [ $n -gt $((2 + (d = _$((_weights + (op += 1)))))) ] ; do + while [ $n -gt $((2 + (d = _$((_weights + (op += 1)))))) ]; do : $((n -= (d + 3))) done if [ $x -gt 90 ] ; then @@ -573,7 +573,7 @@ _scm2str() { # s: $2 current=$((_$((s + __field0)))) _malloc str $((length + 1)) i=0 - while [ $i -lt $length ] ; do + while [ $i -lt $length ]; do : $((_$((str + i)) = (_$((current + __field0)) >> 1))) current=$((_$((current + __field1)))) : $(((i += 1) - 1)) @@ -661,7 +661,7 @@ _prim() { # no: $2 num_args=0 : $((_$((_$((_FALSE + __field0)) + __field0)) = x)) arg=$y - while [ $arg != $((_$((_FALSE + __field1)))) ] ; do + while [ $arg != $((_$((_FALSE + __field1)))) ]; do _push2 __ $arg $(((0 << 1) | 1)) arg=$((_$((_stack + __field0)))) : $((_$((_stack + __field0)) = _$((arg + __field0)))) @@ -756,14 +756,14 @@ _prim() { # no: $2 : $((__t1 = opnd = x = p = rest = new_pc = k = c2 = s2 = proc = jump = vari = nparams = nparams_vari = nargs = instr = i = 0)) _run() { set $@ $i $instr $nargs $nparams_vari $nparams $vari $jump $proc $s2 $c2 $k $new_pc $rest $p $x $opnd $__t1 - while [ 1 != 0 ] ; do + while [ 1 != 0 ]; do instr=$((_$((_pc + __field0)) >> 1)) if [ $instr = 5 ] ; then exit 0 elif [ $instr = 0 ] ; then jump=$((_$((_pc + __field2)) == ((0 << 1) | 1))) _get_opnd proc $((_$((_pc + __field1)))) - while [ 1 != 0 ] ; do + while [ 1 != 0 ]; do if [ $((_$((proc + __field0)) & 1)) != 0 ] ; then _pop __ _prim proc $((_$((proc + __field0)) >> 1)) @@ -793,7 +793,7 @@ _run() { if [ $vari != 0 ] ; then rest=$((_$((_FALSE + __field1)))) i=0 - while [ $i -lt $nargs ] ; do + while [ $i -lt $nargs ]; do _pop __t1 _alloc_rib __t1 $__t1 $rest $s2 rest=$__t1 @@ -805,7 +805,7 @@ _run() { s2=$__t1 fi i=0 - while [ $i -lt $nparams ] ; do + while [ $i -lt $nparams ]; do _pop __t1 _alloc_rib __t1 $__t1 $s2 $(((0 << 1) | 1)) s2=$__t1 diff --git a/examples/compiled/reverse.sh b/examples/compiled/reverse.sh index 1565b7b1..1714d841 100755 --- a/examples/compiled/reverse.sh +++ b/examples/compiled/reverse.sh @@ -6,7 +6,7 @@ LC_ALL=C _main() { let argc $2; let argv_ $3 let i i=1 - while [ $i -lt $argc ] ; do + while [ $i -lt $argc ]; do _put_pstr __ $((_$((argv_ + (argc - i))))) printf " " : $((i += 1)) diff --git a/examples/compiled/select-file.sh b/examples/compiled/select-file.sh index 04db17b9..9f51bd9c 100755 --- a/examples/compiled/select-file.sh +++ b/examples/compiled/select-file.sh @@ -150,7 +150,7 @@ _wc() { # $2 = file (char*), $3 = lines addr (int*), $4 = words addr (int*), $5 _print_array() { let arr $2 let i i=0 - while [ $((_$((arr + i)))) != 0 ] ; do + while [ $((_$((arr + i)))) != 0 ]; do printf "%d: " $i _put_pstr __ $((_$((arr + i)))) printf "\n" $i @@ -163,7 +163,7 @@ _print_array() { let arr $2 _array_len() { let arr $2 let i i=0 - while [ $((_$((arr + i)))) != 0 ] ; do + while [ $((_$((arr + i)))) != 0 ]; do : $((i += 1)) done : $(($1 = i)) @@ -174,7 +174,7 @@ _array_len() { let arr $2 _read_int() { let n; let c n=0 - while [ 1 != 0 ] ; do + while [ 1 != 0 ]; do _getchar c if [ $c -ge $__0__ ] && [ $c -le $__9__ ] ; then n=$((((10 * n) + c) - __0__)) @@ -196,7 +196,7 @@ _main() { _ls files _array_len len $files _print_array __ $files - while [ 1 != 0 ] ; do + while [ 1 != 0 ]; do printf "Select a file to print: " _read_int ix if [ 0 -le $ix ] && [ $ix -lt $len ] ; then diff --git a/examples/compiled/sha256sum.sh b/examples/compiled/sha256sum.sh index 9f4b9d55..087a9359 100755 --- a/examples/compiled/sha256sum.sh +++ b/examples/compiled/sha256sum.sh @@ -100,7 +100,7 @@ _sha256_init() { _sha256_add_block() { let bytes $2 let b0; let b1; let b2; let b3; let s0; let s1; let i; let ch; let t1; let ma; let t2 i=0 - while [ $i -lt 16 ] ; do + while [ $i -lt 16 ]; do b0=$((255 & _$((bytes + (i * 4))))) b1=$((255 & _$((bytes + (i * 4) + 1)))) b2=$((255 & _$((bytes + (i * 4) + 2)))) @@ -109,19 +109,19 @@ _sha256_add_block() { let bytes $2 : $((i += 1)) done i=16 - while [ $i -lt 64 ] ; do + while [ $i -lt 64 ]; do s0=$(((((_$((_w + (i - 15))) >> 7) & (2147483647 >> (7 - 1))) | ((_$((_w + (i - 15))) << (32 - 7)) & -1)) ^ (((_$((_w + (i - 15))) >> 18) & (2147483647 >> (18 - 1))) | ((_$((_w + (i - 15))) << (32 - 18)) & -1)) ^ ((_$((_w + (i - 15))) >> 3) & 536870911))) s1=$(((((_$((_w + (i - 2))) >> 17) & (2147483647 >> (17 - 1))) | ((_$((_w + (i - 2))) << (32 - 17)) & -1)) ^ (((_$((_w + (i - 2))) >> 19) & (2147483647 >> (19 - 1))) | ((_$((_w + (i - 2))) << (32 - 19)) & -1)) ^ ((_$((_w + (i - 2))) >> 10) & 4194303))) : $((_$((_w + i)) = (_$((_w + (i - 16))) + s0 + _$((_w + (i - 7))) + s1) & -1)) : $((i += 1)) done i=0 - while [ $i -lt 8 ] ; do + while [ $i -lt 8 ]; do : $((_$((_temp + i)) = _$((_hash + i)))) : $((i += 1)) done i=0 - while [ $i -lt 64 ] ; do + while [ $i -lt 64 ]; do s1=$(((((_$((_temp + 4)) >> 6) & (2147483647 >> (6 - 1))) | ((_$((_temp + 4)) << (32 - 6)) & -1)) ^ (((_$((_temp + 4)) >> 11) & (2147483647 >> (11 - 1))) | ((_$((_temp + 4)) << (32 - 11)) & -1)) ^ (((_$((_temp + 4)) >> 25) & (2147483647 >> (25 - 1))) | ((_$((_temp + 4)) << (32 - 25)) & -1)))) ch=$(((_$((_temp + 4)) & _$((_temp + 5))) ^ (~(_$((_temp + 4))) & _$((_temp + 6))))) t1=$(((_$((_temp + 7)) + s1 + ch + _$((_k + i)) + _$((_w + i))) & -1)) @@ -139,7 +139,7 @@ _sha256_add_block() { let bytes $2 : $((i += 1)) done i=0 - while [ $i -lt 8 ] ; do + while [ $i -lt 8 ]; do : $((_$((_hash + i)) = (_$((_hash + i)) + _$((_temp + i))) & -1)) : $((i += 1)) done @@ -164,7 +164,7 @@ _process_file() { let filename $2 _sha256_setup __ _sha256_init __ _open fd $filename 0 - while [ $n = 64 ] ; do + while [ $n = 64 ]; do _read n $fd $_buf 64 if [ $n -lt 0 ] ; then : $(($1 = 1)) @@ -175,20 +175,20 @@ _process_file() { let filename $2 if [ $n -lt 64 ] ; then : $((_$((_buf + n)) = 128)) i=$((n + 1)) - while [ $i -lt 64 ] ; do + while [ $i -lt 64 ]; do : $((_$((_buf + i)) = 0)) : $((i += 1)) done if [ $n -ge $((64 - 9)) ] ; then _sha256_add_block __ $_buf i=0 - while [ $i -lt $((64 - 8)) ] ; do + while [ $i -lt $((64 - 8)) ]; do : $((_$((_buf + i)) = 0)) : $((i += 1)) done fi i=1 - while [ $i -le 8 ] ; do + while [ $i -le 8 ]; do : $((_$((_buf + 64 - i)) = 255 & _nbits)) : $((_nbits >>= 8)) : $((i += 1)) @@ -198,7 +198,7 @@ _process_file() { let filename $2 done _close __ $fd i=0 - while [ $i -lt 8 ] ; do + while [ $i -lt 8 ]; do h=$((_$((_hash + i)))) _hex __ $((h >> 24)) _hex __ $((h >> 16)) @@ -208,7 +208,7 @@ _process_file() { let filename $2 done printf \\$(((__SPACE__)/64))$(((__SPACE__)/8%8))$(((__SPACE__)%8)) printf \\$(((__SPACE__)/64))$(((__SPACE__)/8%8))$(((__SPACE__)%8)) - while [ $((_$filename)) != 0 ] ; do + while [ $((_$filename)) != 0 ]; do printf \\$(((_$filename)/64))$(((_$filename)/8%8))$(((_$filename)%8)) : $((filename += 1)) done @@ -221,7 +221,7 @@ _process_file() { let filename $2 _main() { let argc $2; let myargv $3 let i; let __t1 i=1 - while [ $i -lt $argc ] ; do + while [ $i -lt $argc ]; do if _process_file __t1 $((_$((myargv + i)))); [ $__t1 != 0 ] ; then break fi diff --git a/examples/compiled/sum-array.sh b/examples/compiled/sum-array.sh index 85f97fc9..38af17b8 100755 --- a/examples/compiled/sum-array.sh +++ b/examples/compiled/sum-array.sh @@ -7,7 +7,7 @@ _sum_array() { let n $2; let size $3 let i; let sum i=0 sum=0 - while [ $i -lt $size ] ; do + while [ $i -lt $size ]; do : $((sum += _$((n + i)))) : $(((i += 1) - 1)) done @@ -21,12 +21,12 @@ _main() { _malloc n 10000 i=0 sum=0 - while [ $i -lt 10000 ] ; do + while [ $i -lt 10000 ]; do : $((_$((n + i)) = i)) : $(((i += 1) - 1)) done i=0 - while [ $i -lt 10000 ] ; do + while [ $i -lt 10000 ]; do : $((sum += _$((n + i)))) : $(((i += 1) - 1)) done diff --git a/examples/compiled/wc-stdin.sh b/examples/compiled/wc-stdin.sh index 5e2dfc75..aac8f720 100755 --- a/examples/compiled/wc-stdin.sh +++ b/examples/compiled/wc-stdin.sh @@ -16,7 +16,7 @@ _main() { chars=0 sep=0 last_sep=0 - while _getchar __t1; [ $((c = __t1)) != -1 ] ; do + while _getchar __t1; [ $((c = __t1)) != -1 ]; do : $((chars += 1)) if [ $c = $__NEWLINE__ ] ; then : $((lines += 1)) diff --git a/examples/compiled/wc.sh b/examples/compiled/wc.sh index d6106228..3f0b6270 100755 --- a/examples/compiled/wc.sh +++ b/examples/compiled/wc.sh @@ -28,10 +28,10 @@ _wc_fd() { let fd $2; let filename $3 sep=0 last_sep=0 n=1024 - while [ $n = 1024 ] ; do + while [ $n = 1024 ]; do _read n $fd $_buf 1024 i=0 - while [ $i -lt $n ] ; do + while [ $i -lt $n ]; do : $((chars += 1)) if [ $((_$((_buf + i)))) = $__NEWLINE__ ] ; then : $((lines += 1)) @@ -71,7 +71,7 @@ _main() { let argc $2; let argv_ $3 let i if [ $argc -ge 2 ] ; then i=1 - while [ $i -lt $argc ] ; do + while [ $i -lt $argc ]; do _wc_file __ $((_$((argv_ + i)))) : $((i += 1)) done diff --git a/examples/compiled/welcome.sh b/examples/compiled/welcome.sh index 0ffb86a8..5a7e93fd 100755 --- a/examples/compiled/welcome.sh +++ b/examples/compiled/welcome.sh @@ -8,7 +8,7 @@ _main() { _malloc name 100 i=0 printf "What is your name?\n" - while { _getchar __t1; [ $((_$((name + i)) = __t1)) != -1 ]; } && [ $((_$((name + i)))) != $__NEWLINE__ ] ; do + while { _getchar __t1; [ $((_$((name + i)) = __t1)) != -1 ]; } && [ $((_$((name + i)))) != $__NEWLINE__ ]; do : $((i += 1)) done : $((_$((name + i)) = __NUL__)) diff --git a/examples/compiled/winterpi.sh b/examples/compiled/winterpi.sh index 74521fb6..d1eec710 100755 --- a/examples/compiled/winterpi.sh +++ b/examples/compiled/winterpi.sh @@ -18,16 +18,16 @@ _main() { let i; let k; let b; let d; let c c=0 i=0 - while [ $i -lt 2800 ] ; do + while [ $i -lt 2800 ]; do : $((_$((_r + i)) = 2000)) i=$((i + 1)) done : $((_$((_r + i)) = 0)) k=2800 - while [ $k -gt 0 ] ; do + while [ $k -gt 0 ]; do d=0 i=$k - while [ $i -gt 0 ] ; do + while [ $i -gt 0 ]; do d=$((d * i)) d=$((d + (_$((_r + i)) * 10000))) b=$(((2 * i) - 1)) From 8673510d7e771e65fed0b1cb1a0733d52e263833 Mon Sep 17 00:00:00 2001 From: Laurent Huberdeau Date: Sun, 29 Sep 2024 23:34:52 -0400 Subject: [PATCH 6/6] Fix compound test when OPTIMIZE_CONSTANT_PARAM --- sh.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sh.c b/sh.c index 82a4d4f2..ec11f53b 100644 --- a/sh.c +++ b/sh.c @@ -2109,6 +2109,8 @@ void mark_mutable_variables_statement(ast node) { int op = get_op(node); ast params; + if (node == 0) return; + if (op == IF_KW) { mark_mutable_variables_statement(get_child(node, 0)); if (get_child(node, 1)) mark_mutable_variables_body(get_child(node, 1));