added "!break", "!continue" and "!return" (for loops and macros, respectively)

git-svn-id: https://svn.code.sf.net/p/acme-crossass/code-0/trunk@447 4df02467-bbd4-4a76-a152-e7ce94205b78
This commit is contained in:
marcobaye
2025-07-29 20:33:10 +00:00
parent 4b25464181
commit 68c20bbc8e
15 changed files with 275 additions and 11 deletions

View File

@@ -1,5 +1,5 @@
// ACME - a crossassembler for producing 6502/65c02/65816/65ce02 code. // 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 // Have a look at "acme.c" for further info
// //
// Flow control stuff (loops, conditional assembly etc.) // 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) static void counting_for(struct for_loop *loop)
{ {
struct object loop_var; struct object loop_var;
enum shortcut shortcut;
// init counter // init counter
loop_var.type = &type_number; loop_var.type = &type_number;
@@ -95,6 +96,15 @@ static void counting_for(struct for_loop *loop)
parse_ram_block(&loop->block); parse_ram_block(&loop->block);
loop_var.u.number.val.intval += loop->u.counter.increment; loop_var.u.number.val.intval += loop->u.counter.increment;
loop->iterations_left--; 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: // new algo wants illegal value in loop counter after block:
if (loop->algorithm == FORALGO_NEWCOUNT) if (loop->algorithm == FORALGO_NEWCOUNT)
@@ -106,12 +116,22 @@ static void iterating_for(struct for_loop *loop)
{ {
intval_t index = 0; intval_t index = 0;
struct object obj; struct object obj;
enum shortcut shortcut;
while (loop->iterations_left) { while (loop->iterations_left) {
loop->u.iter.obj.type->at(&loop->u.iter.obj, &obj, index++); loop->u.iter.obj.type->at(&loop->u.iter.obj, &obj, index++);
symbol_set_object(loop->symbol, &obj, POWER_CHANGE_VALUE | POWER_CHANGE_OBJTYPE); symbol_set_object(loop->symbol, &obj, POWER_CHANGE_VALUE | POWER_CHANGE_OBJTYPE);
parse_ram_block(&loop->block); parse_ram_block(&loop->block);
loop->iterations_left--; 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) void flow_forloop(struct for_loop *loop)
{ {
struct inputchange_buf icb; struct inputchange_buf icb;
boolean break_cont_buf;
// remember input and set up new one: // remember input and set up new one:
inputchange_new_ram(&icb); inputchange_new_ram(&icb);
// fix line number (not for block, but in case symbol handling throws errors) // fix line number (not for block, but in case symbol handling throws errors)
inputchange_set_ram(loop->block.line_number, NULL); 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) { switch (loop->algorithm) {
case FORALGO_OLDCOUNT: case FORALGO_OLDCOUNT:
@@ -138,6 +161,8 @@ void flow_forloop(struct for_loop *loop)
BUG("IllegalLoopAlgo", loop->algorithm); BUG("IllegalLoopAlgo", loop->algorithm);
} }
// restore outer break/cont state:
parser_allow_break_cont(break_cont_buf);
// restore outer input // restore outer input
inputchange_back(&icb); inputchange_back(&icb);
} }
@@ -223,19 +248,37 @@ static boolean check_condition(struct condition *condition)
void flow_do_while(struct do_while *loop) void flow_do_while(struct do_while *loop)
{ {
struct inputchange_buf icb; struct inputchange_buf icb;
boolean break_cont_buf;
enum shortcut shortcut;
// remember input and prepare new one: // remember input and prepare new one:
inputchange_new_ram(&icb); inputchange_new_ram(&icb);
// remember break/cont state and allow:
break_cont_buf = parser_allow_break_cont(TRUE);
for (;;) { for (;;) {
// check head condition // check head condition
if (!check_condition(&loop->head_cond)) if (!check_condition(&loop->head_cond))
break; break;
parse_ram_block(&loop->block); 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 // check tail condition
if (!check_condition(&loop->tail_cond)) if (!check_condition(&loop->tail_cond))
break; break;
} }
// restore outer break/cont state:
parser_allow_break_cont(break_cont_buf);
// restore outer input // restore outer input
inputchange_back(&icb); inputchange_back(&icb);
} }

View File

@@ -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. // status var to tell mainloop (actually "statement loop") to exit.
// this is better than the error handler exiting directly, because // this is better than the error handler exiting directly, because
// there are cases where an error message is followed by an info message // 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? // did the error handler decide to give up?
if (too_many_errors) if (too_many_errors)
exit(ACME_finalize(EXIT_FAILURE)); exit(ACME_finalize(EXIT_FAILURE));
// go on with next byte // was any of the shortcut POs used?
GetByte(); //NEXTANDSKIPSPACE(); 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; struct inputchange_buf icb;
const char *ppb; // path buffer in platform format const char *ppb; // path buffer in platform format
boolean break_cont_allowed;
boolean return_allowed;
// be verbose // be verbose
if (config.process_verbosity >= 3) 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; input_plat_pathref_filename = eternal_plat_filename;
// remember input and set up new one: // remember input and set up new one:
inputchange_new_file(&icb, fd, eternal_plat_filename); 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 block and check end reason
parse_until_eob_or_eof(); parse_until_eob_or_eof();
if (GotByte != CHAR_EOF) if (GotByte != CHAR_EOF)
throw_error("Expected EOF, found '}' instead." ); 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 // restore outer input
inputchange_back(&icb); inputchange_back(&icb);
// restore outer base for relative paths // restore outer base for relative paths
@@ -493,6 +535,27 @@ bits parser_get_force_bit(void)
return force_bit; 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 // Error handling

View File

@@ -106,6 +106,12 @@ struct config {
}; };
extern struct config 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 { struct pass {
int number; // counts up from one int number; // counts up from one
struct { struct {
@@ -215,6 +221,18 @@ extern void parse_source_code_file(FILE *fd, const char *eternal_plat_filename);
// read optional info about parameter length // read optional info about parameter length
extern bits parser_get_force_bit(void); 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 // generate a debug/info/warning/error message
// if the "optional alternative location" given is NULL, the current location is used // 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); extern void throw_message(enum debuglevel level, const char msg[], struct location *opt_alt_loc);

View File

@@ -1,5 +1,5 @@
// ACME - a crossassembler for producing 6502/65c02/65816/65ce02 code. // 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 // Have a look at "acme.c" for further info
// //
// Macro stuff // Macro stuff
@@ -189,6 +189,8 @@ void macro_parse_call(void) // Now GotByte = first char of macro name
symbol_scope; symbol_scope;
int arg_count = 0; int arg_count = 0;
int outer_msg_sum; int outer_msg_sum;
boolean break_cont_allowed;
boolean return_allowed;
// make sure arg_table is ready (if not yet initialised, do it now) // make sure arg_table is ready (if not yet initialised, do it now)
if (arg_table == NULL) if (arg_table == NULL)
@@ -285,12 +287,24 @@ void macro_parse_call(void) // Now GotByte = first char of macro name
} while (parser_accept_comma()); } 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 // and now, finally, parse the actual macro body
// maybe call parse_ram_block(actual_macro->definition.line_number, actual_macro->body) // maybe call parse_ram_block(actual_macro->definition.line_number, actual_macro->body)
inputchange_macro2_body(actual_macro->body.body); inputchange_macro2_body(actual_macro->body.body);
parse_until_eob_or_eof(); parse_until_eob_or_eof();
if (GotByte != CHAR_EOB) if (GotByte != CHAR_EOB)
BUG("IllegalBlockTerminator", GotByte); 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) // end section (free title memory, if needed)
section_finalize(&new_section); section_finalize(&new_section);
// restore previous section // restore previous section

View File

@@ -1,5 +1,5 @@
// ACME - a crossassembler for producing 6502/65c02/65816/65ce02 code. // 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 // Have a look at "acme.c" for further info
// //
// pseudo opcode stuff // pseudo opcode stuff
@@ -28,6 +28,10 @@
static boolean line_uses_keyword_arguments(void) static boolean line_uses_keyword_arguments(void)
{ {
// to be on the safe side, return FALSE if dialect < 0.98! // 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]+=' // read line to buffer, check if it begins with '[a-zA-Z]+='
// change input to RAM, return result // change input to RAM, return result
// ...and the caller needs to pass us some struct so it can change input back later on! // ...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; FILE *stream;
const char *eternal_plat_filename; 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 // read file name and convert from UNIX style to platform style
if (input_read_input_filename(&flags)) if (input_read_input_filename(&flags))
return SKIP_REMAINDER; // if missing or unterminated, give up 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 // if file could be opened, parse it. otherwise, complain
stream = includepaths_open_ro(&flags); stream = includepaths_open_ro(&flags);
if (stream) { 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 // variables
static STRUCT_DYNABUF_REF(user_message, 80); // for !debug/info/warn/error/serious 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("addr", po_address),
PREDEFNODE("address", po_address), PREDEFNODE("address", po_address),
PREDEFNODE("nowarn", po_nowarn), PREDEFNODE("nowarn", po_nowarn),
PREDEFNODE("break", po_break),
PREDEFNODE("continue", po_continue),
PREDEFNODE("return", po_return),
PREDEFNODE("debug", po_debug), PREDEFNODE("debug", po_debug),
PREDEFNODE("info", po_info), PREDEFNODE("info", po_info),
PREDEFNODE("warn", po_warn), PREDEFNODE("warn", po_warn),

View File

@@ -9,7 +9,7 @@
#define RELEASE "0.97" // update before release FIXME #define RELEASE "0.97" // update before release FIXME
#define CODENAME "Zem" // update before release #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 CHANGE_YEAR "2025" // update before release
//#define HOME_PAGE "http://home.pages.de/~mac_bacon/smorbrod/acme/" //#define HOME_PAGE "http://home.pages.de/~mac_bacon/smorbrod/acme/"
#define HOME_PAGE "http://sourceforge.net/p/acme-crossass/" // FIXME #define HOME_PAGE "http://sourceforge.net/p/acme-crossass/" // FIXME

View File

@@ -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

View File

@@ -0,0 +1,4 @@
; (this file also gets included by breaknotwithinaloop2.a)
; "!break" only works inside a loop:
!break ; -> "!break not within a loop"

View File

@@ -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"
}

View File

@@ -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
}

View File

@@ -0,0 +1,4 @@
; (this file also gets included by continuenotwithinaloop2.a)
; "!continue" only works inside a loop:
!continue ; -> "!continue not within a loop"

View File

@@ -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

View File

@@ -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 :)
}

View File

@@ -0,0 +1,4 @@
; (this file also gets included by returnnotwithinamacro2.a)
; "!return" only works inside a macro:
!return ; -> "!return not within a macro"

View File

@@ -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