diff --git a/src/flow.c b/src/flow.c index 673e264..c251ffc 100644 --- a/src/flow.c +++ b/src/flow.c @@ -1,5 +1,5 @@ // ACME - a crossassembler for producing 6502/65c02/65816/65ce02 code. -// Copyright (C) 1998-2024 Marco Baye +// Copyright (C) 1998-2025 Marco Baye // Have a look at "acme.c" for further info // // Flow control stuff (loops, conditional assembly etc.) @@ -67,6 +67,7 @@ static void parse_ram_block(struct block *block) static void counting_for(struct for_loop *loop) { struct object loop_var; + enum shortcut shortcut; // init counter loop_var.type = &type_number; @@ -95,6 +96,15 @@ static void counting_for(struct for_loop *loop) parse_ram_block(&loop->block); loop_var.u.number.val.intval += loop->u.counter.increment; loop->iterations_left--; + + // was there a "!break" or "!continue"? + shortcut = parser_get_shortcut(); + if (shortcut == SHORTCUT_BREAK) { + parser_set_shortcut(SHORTCUT_NONE); + break; + } else if (shortcut == SHORTCUT_CONT) { + parser_set_shortcut(SHORTCUT_NONE); + } } // new algo wants illegal value in loop counter after block: if (loop->algorithm == FORALGO_NEWCOUNT) @@ -106,12 +116,22 @@ static void iterating_for(struct for_loop *loop) { intval_t index = 0; struct object obj; + enum shortcut shortcut; while (loop->iterations_left) { loop->u.iter.obj.type->at(&loop->u.iter.obj, &obj, index++); symbol_set_object(loop->symbol, &obj, POWER_CHANGE_VALUE | POWER_CHANGE_OBJTYPE); parse_ram_block(&loop->block); loop->iterations_left--; + + // was there a "!break" or "!continue"? + shortcut = parser_get_shortcut(); + if (shortcut == SHORTCUT_BREAK) { + parser_set_shortcut(SHORTCUT_NONE); + break; + } else if (shortcut == SHORTCUT_CONT) { + parser_set_shortcut(SHORTCUT_NONE); + } } } @@ -120,11 +140,14 @@ static void iterating_for(struct for_loop *loop) void flow_forloop(struct for_loop *loop) { struct inputchange_buf icb; + boolean break_cont_buf; // remember input and set up new one: inputchange_new_ram(&icb); // fix line number (not for block, but in case symbol handling throws errors) inputchange_set_ram(loop->block.line_number, NULL); + // remember break/cont state and allow: + break_cont_buf = parser_allow_break_cont(TRUE); switch (loop->algorithm) { case FORALGO_OLDCOUNT: @@ -138,6 +161,8 @@ void flow_forloop(struct for_loop *loop) BUG("IllegalLoopAlgo", loop->algorithm); } + // restore outer break/cont state: + parser_allow_break_cont(break_cont_buf); // restore outer input inputchange_back(&icb); } @@ -223,19 +248,37 @@ static boolean check_condition(struct condition *condition) void flow_do_while(struct do_while *loop) { struct inputchange_buf icb; + boolean break_cont_buf; + enum shortcut shortcut; // remember input and prepare new one: inputchange_new_ram(&icb); + // remember break/cont state and allow: + break_cont_buf = parser_allow_break_cont(TRUE); for (;;) { // check head condition if (!check_condition(&loop->head_cond)) break; + parse_ram_block(&loop->block); + + // was there a "!break" or "!continue"? + shortcut = parser_get_shortcut(); + if (shortcut == SHORTCUT_BREAK) { + parser_set_shortcut(SHORTCUT_NONE); + break; + } else if (shortcut == SHORTCUT_CONT) { + parser_set_shortcut(SHORTCUT_NONE); + } + // check tail condition if (!check_condition(&loop->tail_cond)) break; } + + // restore outer break/cont state: + parser_allow_break_cont(break_cont_buf); // restore outer input inputchange_back(&icb); } diff --git a/src/global.c b/src/global.c index 8346276..4681197 100644 --- a/src/global.c +++ b/src/global.c @@ -343,6 +343,33 @@ static void parse_forward_anon_def(void) } +static enum shortcut current_shortcut_state = SHORTCUT_NONE; +// return current shortcut state +enum shortcut parser_get_shortcut(void) +{ + return current_shortcut_state; +} +// start or end processing a !break/!continue/!return keyword +void parser_set_shortcut(enum shortcut new_shortcut) +{ + //printf("Changing shortcut state from %d to %d.\n", current_shortcut_state, new_shortcut); + switch (new_shortcut) { + case SHORTCUT_NONE: + if (current_shortcut_state == SHORTCUT_NONE) + BUG("ShortcutDoubleNone", 0); + current_shortcut_state = new_shortcut; + break; + case SHORTCUT_BREAK: + case SHORTCUT_CONT: + case SHORTCUT_RETURN: + if (current_shortcut_state != SHORTCUT_NONE) + BUG("ShortcutDouble", current_shortcut_state); + current_shortcut_state = new_shortcut; + break; + default: + BUG("IllegalShortcut", new_shortcut); + } +} // status var to tell mainloop (actually "statement loop") to exit. // this is better than the error handler exiting directly, because // there are cases where an error message is followed by an info message @@ -417,8 +444,14 @@ void parse_until_eob_or_eof(void) // did the error handler decide to give up? if (too_many_errors) exit(ACME_finalize(EXIT_FAILURE)); - // go on with next byte - GetByte(); //NEXTANDSKIPSPACE(); + // was any of the shortcut POs used? + if (current_shortcut_state == SHORTCUT_NONE) { + // go on with next byte + GetByte(); //NEXTANDSKIPSPACE(); + } else { + // ignore remainder of block: + input_block_skip(); + } } } @@ -448,6 +481,8 @@ void parse_source_code_file(FILE *fd, const char *eternal_plat_filename) { struct inputchange_buf icb; const char *ppb; // path buffer in platform format + boolean break_cont_allowed; + boolean return_allowed; // be verbose if (config.process_verbosity >= 3) @@ -458,12 +493,19 @@ void parse_source_code_file(FILE *fd, const char *eternal_plat_filename) input_plat_pathref_filename = eternal_plat_filename; // remember input and set up new one: inputchange_new_file(&icb, fd, eternal_plat_filename); + // remember whether break/continue/return are allowed and forbid them in + // new file (just to enforce "clean code"): + break_cont_allowed = parser_allow_break_cont(FALSE); + return_allowed = parser_allow_return(FALSE); // parse block and check end reason parse_until_eob_or_eof(); if (GotByte != CHAR_EOF) throw_error("Expected EOF, found '}' instead." ); + // restore states of break/continue/return + parser_allow_return(return_allowed); + parser_allow_break_cont(break_cont_allowed); // restore outer input inputchange_back(&icb); // restore outer base for relative paths @@ -493,6 +535,27 @@ bits parser_get_force_bit(void) return force_bit; } +// return current state and set new state of "allow !break and !continue" flag +boolean parser_allow_break_cont(boolean new_state) +{ + static boolean flag = FALSE; + boolean temp; + + temp = flag; + flag = new_state; + return temp; +} +// return current state and set new state of "allow !return" flag +boolean parser_allow_return(boolean new_state) +{ + static boolean flag = FALSE; + boolean temp; + + temp = flag; + flag = new_state; + return temp; +} + // Error handling diff --git a/src/global.h b/src/global.h index 0cbabf6..feec569 100644 --- a/src/global.h +++ b/src/global.h @@ -106,6 +106,12 @@ struct config { }; extern struct config config; +enum shortcut { + SHORTCUT_NONE, // normal execution + SHORTCUT_BREAK, // after "!break" + SHORTCUT_CONT, // after "!continue" + SHORTCUT_RETURN // after "!return" +}; struct pass { int number; // counts up from one struct { @@ -215,6 +221,18 @@ extern void parse_source_code_file(FILE *fd, const char *eternal_plat_filename); // read optional info about parameter length extern bits parser_get_force_bit(void); +// return current shortcut state +extern enum shortcut parser_get_shortcut(void); + +// start or end processing a !break/!continue/!return keyword +extern void parser_set_shortcut(enum shortcut); + +// return current state and set new state of "allow !break and !continue" flag +extern boolean parser_allow_break_cont(boolean new_state); + +// return current state and set new state of "allow !return" flag +extern boolean parser_allow_return(boolean new_state); + // generate a debug/info/warning/error message // if the "optional alternative location" given is NULL, the current location is used extern void throw_message(enum debuglevel level, const char msg[], struct location *opt_alt_loc); diff --git a/src/macro.c b/src/macro.c index 1f6bfdc..699bab3 100644 --- a/src/macro.c +++ b/src/macro.c @@ -1,5 +1,5 @@ // ACME - a crossassembler for producing 6502/65c02/65816/65ce02 code. -// Copyright (C) 1998-2024 Marco Baye +// Copyright (C) 1998-2025 Marco Baye // Have a look at "acme.c" for further info // // Macro stuff @@ -189,6 +189,8 @@ void macro_parse_call(void) // Now GotByte = first char of macro name symbol_scope; int arg_count = 0; int outer_msg_sum; + boolean break_cont_allowed; + boolean return_allowed; // make sure arg_table is ready (if not yet initialised, do it now) if (arg_table == NULL) @@ -285,12 +287,24 @@ void macro_parse_call(void) // Now GotByte = first char of macro name } while (parser_accept_comma()); } + // remember whether break/continue/return are allowed and set new states + break_cont_allowed = parser_allow_break_cont(FALSE); // forbid !break/!continue + return_allowed = parser_allow_return(TRUE); // allow !return + // and now, finally, parse the actual macro body // maybe call parse_ram_block(actual_macro->definition.line_number, actual_macro->body) inputchange_macro2_body(actual_macro->body.body); parse_until_eob_or_eof(); if (GotByte != CHAR_EOB) BUG("IllegalBlockTerminator", GotByte); + // was there a "!return"? + if (parser_get_shortcut() == SHORTCUT_RETURN) + parser_set_shortcut(SHORTCUT_NONE); + + // restore states of break/continue/return + parser_allow_return(return_allowed); + parser_allow_break_cont(break_cont_allowed); + // end section (free title memory, if needed) section_finalize(&new_section); // restore previous section diff --git a/src/pseudoopcodes.c b/src/pseudoopcodes.c index 88c1a9d..bafc85c 100644 --- a/src/pseudoopcodes.c +++ b/src/pseudoopcodes.c @@ -1,5 +1,5 @@ // ACME - a crossassembler for producing 6502/65c02/65816/65ce02 code. -// Copyright (C) 1998-2024 Marco Baye +// Copyright (C) 1998-2025 Marco Baye // Have a look at "acme.c" for further info // // pseudo opcode stuff @@ -28,6 +28,10 @@ static boolean line_uses_keyword_arguments(void) { // to be on the safe side, return FALSE if dialect < 0.98! +// if (config.dialect < V0_98__PATHS_AND_SYMBOLCHANGE) +// return FALSE; +// +// // read line to buffer, check if it begins with '[a-zA-Z]+=' // change input to RAM, return result // ...and the caller needs to pass us some struct so it can change input back later on! @@ -943,15 +947,14 @@ static enum eos po_source(void) // now GotByte = illegal char FILE *stream; const char *eternal_plat_filename; - // enter new nesting level - // quit program if recursion too deep - if (--sanity.source_recursions_left < 0) - throw_serious_error("Too deeply nested. Recursive \"!source\"?"); - // read file name and convert from UNIX style to platform style if (input_read_input_filename(&flags)) return SKIP_REMAINDER; // if missing or unterminated, give up + // enter new nesting level + // quit program if recursion too deep + if (--sanity.source_recursions_left < 0) + throw_serious_error("Too deeply nested. Recursive \"!source\"?"); // if file could be opened, parse it. otherwise, complain stream = includepaths_open_ro(&flags); if (stream) { @@ -1378,6 +1381,52 @@ static enum eos po_nowarn(void) // now GotByte = illegal char } +// exit innermost loop +static enum eos po_break(void) // now GotByte = illegal char +{ + boolean allowed; + + allowed = parser_allow_break_cont(FALSE); + parser_allow_break_cont(allowed); + if (allowed) + parser_set_shortcut(SHORTCUT_BREAK); + else + throw_error("!break not within a loop."); + return ENSURE_EOS; +} + +// end innermost loop iteration +static enum eos po_continue(void) // now GotByte = illegal char +{ + boolean allowed; + + allowed = parser_allow_break_cont(FALSE); + parser_allow_break_cont(allowed); + if (allowed) + parser_set_shortcut(SHORTCUT_CONT); + else + throw_error("!continue not within a loop."); + return ENSURE_EOS; +} + +// return from innermost macro +static enum eos po_return(void) // now GotByte = illegal char +{ + boolean allowed; + + // if ACME ever gets real functions, this is the place to parse the + // return value and store it via some innermost_function->result + // pointer. + allowed = parser_allow_return(FALSE); + parser_allow_return(allowed); + if (allowed) + parser_set_shortcut(SHORTCUT_RETURN); + else + throw_error("!return not within a macro."); + return ENSURE_EOS; +} + + // variables static STRUCT_DYNABUF_REF(user_message, 80); // for !debug/info/warn/error/serious @@ -1542,6 +1591,9 @@ static struct ronode pseudo_opcode_tree[] = { PREDEFNODE("addr", po_address), PREDEFNODE("address", po_address), PREDEFNODE("nowarn", po_nowarn), + PREDEFNODE("break", po_break), + PREDEFNODE("continue", po_continue), + PREDEFNODE("return", po_return), PREDEFNODE("debug", po_debug), PREDEFNODE("info", po_info), PREDEFNODE("warn", po_warn), diff --git a/src/version.h b/src/version.h index 91cb082..ec38f9c 100644 --- a/src/version.h +++ b/src/version.h @@ -9,7 +9,7 @@ #define RELEASE "0.97" // update before release FIXME #define CODENAME "Zem" // update before release -#define CHANGE_DATE "11 Jul" // update before release FIXME +#define CHANGE_DATE "26 Jul" // update before release FIXME #define CHANGE_YEAR "2025" // update before release //#define HOME_PAGE "http://home.pages.de/~mac_bacon/smorbrod/acme/" #define HOME_PAGE "http://sourceforge.net/p/acme-crossass/" // FIXME diff --git a/testing/auto/breakcontinuereturn.a b/testing/auto/breakcontinuereturn.a new file mode 100644 index 0000000..23bb3bc --- /dev/null +++ b/testing/auto/breakcontinuereturn.a @@ -0,0 +1,26 @@ + + !for @n in [2, 3] { + !if @n == 3 { + !error "!break does not leave the loop" + } + !if 1 { + !break + } + !error "!break is ignored" + } + !for @n in [2, 3] { + !if 1 { + !continue + } + !error "!continue does not skip the remainder of the block" + } + !macro testmacro { + !for @n in [2, 3] { + !if 1 { + !return + } + !error "!return does not leave the block" + } + !error "!return does not leave all blocks" + } + +testmacro diff --git a/testing/errors/breaknotwithinaloop1.a b/testing/errors/breaknotwithinaloop1.a new file mode 100644 index 0000000..331149e --- /dev/null +++ b/testing/errors/breaknotwithinaloop1.a @@ -0,0 +1,4 @@ +; (this file also gets included by breaknotwithinaloop2.a) + +; "!break" only works inside a loop: + !break ; -> "!break not within a loop" diff --git a/testing/errors/breaknotwithinaloop2.a b/testing/errors/breaknotwithinaloop2.a new file mode 100644 index 0000000..42a06de --- /dev/null +++ b/testing/errors/breaknotwithinaloop2.a @@ -0,0 +1,5 @@ + +; "!break" in a file is not supposed to break from a loop outside the file: + !for @i in "ab" { + !src "breaknotwithinaloop1.a" ; -> "!break not within a loop" + } diff --git a/testing/errors/breaknotwithinaloop3.a b/testing/errors/breaknotwithinaloop3.a new file mode 100644 index 0000000..670d8d3 --- /dev/null +++ b/testing/errors/breaknotwithinaloop3.a @@ -0,0 +1,9 @@ + +; "!break" inside a macro is not supposed to break from a loop outside the macro: + !macro testmacro { + !break ; -> "!break not within a loop" + } + + !for @i, 0, 2 { + +testmacro + } diff --git a/testing/errors/continuenotwithinaloop1.a b/testing/errors/continuenotwithinaloop1.a new file mode 100644 index 0000000..cfba0f6 --- /dev/null +++ b/testing/errors/continuenotwithinaloop1.a @@ -0,0 +1,4 @@ +; (this file also gets included by continuenotwithinaloop2.a) + +; "!continue" only works inside a loop: + !continue ; -> "!continue not within a loop" diff --git a/testing/errors/continuenotwithinaloop2.a b/testing/errors/continuenotwithinaloop2.a new file mode 100644 index 0000000..c7ea0a8 --- /dev/null +++ b/testing/errors/continuenotwithinaloop2.a @@ -0,0 +1,5 @@ + +; "!continue" in a file is not supposed to continue a loop outside the file: + !do { + !src "continuenotwithinaloop1.a" ; -> "!continue not within a loop" + } while 0 diff --git a/testing/errors/continuenotwithinaloop3.a b/testing/errors/continuenotwithinaloop3.a new file mode 100644 index 0000000..481c69c --- /dev/null +++ b/testing/errors/continuenotwithinaloop3.a @@ -0,0 +1,10 @@ + +; "!continue" inside a macro is not supposed to continue a loop outside the macro: + !macro testmacro { + !continue ; -> "!continue not within a loop" + } + + !while 1 { + +testmacro + !break ; inhibit infinite loop :) + } diff --git a/testing/errors/returnnotwithinamacro1.a b/testing/errors/returnnotwithinamacro1.a new file mode 100644 index 0000000..84665da --- /dev/null +++ b/testing/errors/returnnotwithinamacro1.a @@ -0,0 +1,4 @@ +; (this file also gets included by returnnotwithinamacro2.a) + +; "!return" only works inside a macro: + !return ; -> "!return not within a macro" diff --git a/testing/errors/returnnotwithinamacro2.a b/testing/errors/returnnotwithinamacro2.a new file mode 100644 index 0000000..4021224 --- /dev/null +++ b/testing/errors/returnnotwithinamacro2.a @@ -0,0 +1,7 @@ + +; "!return" in a file is not supposed to return from a macro outside the file: + !macro testmacro { + !src "returnnotwithinamacro1.a" ; -> "!return not within a macro" + } + + +testmacro