mirror of
https://github.com/uffejakobsen/acme.git
synced 2025-11-23 22:17:33 +00:00
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:
45
src/flow.c
45
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);
|
||||
}
|
||||
|
||||
67
src/global.c
67
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
|
||||
|
||||
|
||||
18
src/global.h
18
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);
|
||||
|
||||
16
src/macro.c
16
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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
26
testing/auto/breakcontinuereturn.a
Normal file
26
testing/auto/breakcontinuereturn.a
Normal 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
|
||||
4
testing/errors/breaknotwithinaloop1.a
Normal file
4
testing/errors/breaknotwithinaloop1.a
Normal 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"
|
||||
5
testing/errors/breaknotwithinaloop2.a
Normal file
5
testing/errors/breaknotwithinaloop2.a
Normal 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"
|
||||
}
|
||||
9
testing/errors/breaknotwithinaloop3.a
Normal file
9
testing/errors/breaknotwithinaloop3.a
Normal 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
|
||||
}
|
||||
4
testing/errors/continuenotwithinaloop1.a
Normal file
4
testing/errors/continuenotwithinaloop1.a
Normal 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"
|
||||
5
testing/errors/continuenotwithinaloop2.a
Normal file
5
testing/errors/continuenotwithinaloop2.a
Normal 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
|
||||
10
testing/errors/continuenotwithinaloop3.a
Normal file
10
testing/errors/continuenotwithinaloop3.a
Normal 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 :)
|
||||
}
|
||||
4
testing/errors/returnnotwithinamacro1.a
Normal file
4
testing/errors/returnnotwithinamacro1.a
Normal 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"
|
||||
7
testing/errors/returnnotwithinamacro2.a
Normal file
7
testing/errors/returnnotwithinamacro2.a
Normal 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
|
||||
Reference in New Issue
Block a user