diff --git a/docs/AllPOs.txt b/docs/AllPOs.txt index 39c1cd5..3d175d2 100644 --- a/docs/AllPOs.txt +++ b/docs/AllPOs.txt @@ -457,11 +457,17 @@ Examples: ; this was taken from <6502/std.a>: Call: !for SYMBOL, START, END { BLOCK } +or: !for SYMBOL in ITERABLE { BLOCK } Purpose: Looping assembly. The block of statements will be parsed a fixed number of times, as specified by the - values of START and END. For more flexible - possibilities, have a look at "!do" and "!while" - below. + arguments: + When using the first syntax, SYMBOL will simply count + from START to END. + When using the second syntax, SYMBOL will iterate over + the contents of the ITERABLE, which must be a string + or a list. + For more flexible loop constructs, have a look at + "!do" and "!while" below. Parameters: SYMBOL: Any valid symbol name. START: Any formula the value parser accepts, but it must be solvable even in the first pass. SYMBOL will @@ -469,6 +475,15 @@ Parameters: SYMBOL: Any valid symbol name. END: Any formula the value parser accepts, but it must be solvable even in the first pass. SYMBOL will have this value during the last loop cycle. + ITERABLE: This must be a string or a list, but its + length must be defined even in the first pass (and of + course it should stay the same during all subsequent + passes). + If ITERABLE is a list, its _items_ are allowed + to be undefined. + If ITERABLE is a string, SYMBOL will be set to + each of its character codes in turn, using the + currently chosen conversion table. BLOCK: A block of assembler statements. If START or END are floats, they will be converted to integers (never use floats for loop counters). If @@ -508,8 +523,25 @@ Examples: sta $0400 + i } -Miscellaneous: The old syntax ("!for SYMBOL, END { BLOCK }" where - START was always implied to be 1) is still fully + split_table_lo ; generate two tables from one list + !for h in my_handler_list { + !by h + } + + hidden_string ; "encrypt" a string by XORing with address + !ct scr { ; use screen codes + !for c in "very secret message" { + !by c XOR <* + } + } + +Miscellaneous: The old syntax + !for SYMBOL, END { BLOCK } + where START was always implied to be 1 is still fully supported, but gives a warning to get people to change to the new syntax. You can disable this warning using the "--dialect" or @@ -778,6 +810,11 @@ or type (call-by-value vs. call-by-reference). So !macro process_bytes b1, b2, ~b3 {...whatever...} can *all* be used at the same time without any name clash. +Since release 0.97, lists are supported. This is useful for macros if +you want an arbitrary number of arguments: Just define the macro with +a single argument, then pass a list and have the macro iterate over +its contents. + ---------------------------------------------------------------------- Section: Segment assembly diff --git a/docs/Errors.txt b/docs/Errors.txt index 783f9ec..632e2ef 100644 --- a/docs/Errors.txt +++ b/docs/Errors.txt @@ -264,6 +264,12 @@ File name quotes not found ("" or <>). File names have to be given in quotes. Either "" quoting for files located in the current directory or <> quoting for library files. +Force bits can only be given to counters, not when iterating over string/list contents. + You used a force bit with a "!for" loop counter, but then used the + "iterate over string/list contents" syntax. This does not work, + because lists could contain other lists, and then a force bit does + not make any sense. + Force bits can only be given to numbers. You tried to give a force bit to a symbol and then assign a string or list to it. @@ -276,6 +282,10 @@ Garbage data at end of statement (unexpected 'CHAR'). There are still arguments when there should not be any more. The given character is the one where end-of-line was expected. +Given object is not iterable. + You used "!for VAR in ITERABLE", but the iterable was neither a + string nor a list (likely a number). + Hex digits are not given in pairs. The two digits of a hex byte are separated by another character, or there is an odd number of digits. @@ -289,6 +299,12 @@ Index is undefined. Index out of range. The value for an indexing operation wasn't in the allowed range. +Loop var must be followed by either "in" keyword or comma. + You made a syntax error when using "!for": After the loop counter + symbol there can either be a comma (for a simple counting loop) or + the "in" keyword (when iterating over string or list contents). + Anything else will give this error. + Macro already defined. Macros can only be defined once. If you define a macro twice, ACME will help you find the definitions by giving a warning for the @@ -554,6 +570,9 @@ IllegalImmediateMode IllegalInputSource Input is taken neither from a file nor from a RAM block. +IllegalLoopAlgo + The "!for" function was told to use an unknown algorithm. + IllegalNumberTypeX A number was neither INT nor FLOAT nor UNDEFINED. diff --git a/src/flow.c b/src/flow.c index be11be3..dcdfe93 100644 --- a/src/flow.c +++ b/src/flow.c @@ -63,24 +63,11 @@ static void parse_ram_block(struct block *block) } -// back end function for "!for" pseudo opcode -void flow_forloop(struct for_loop *loop) +// function for "!for" with counter variable +static void counting_for(struct for_loop *loop) { - struct input loop_input, - *outer_input; struct object loop_var; - // switching input makes us lose GotByte. But we know it's '}' anyway! - // set up new input - loop_input = *Input_now; // copy current input structure into new - loop_input.source = INPUTSRC_RAM; // set new byte source - // remember old input - outer_input = Input_now; - // activate new input - // (not yet useable; pointer and line number are still missing) - Input_now = &loop_input; - // fix line number (not for block, but in case symbol handling throws errors) - Input_now->line_number = loop->block.start; // init counter loop_var.type = &type_number; loop_var.u.number.ntype = NUMTYPE_INT; @@ -98,12 +85,11 @@ void flow_forloop(struct for_loop *loop) // cases like !set N=N+1 worked, because the force bit was // taken from result. // maybe support this behaviour via --dialect? - if (loop->force_bit) - symbol_set_force_bit(loop->symbol, loop->force_bit); + if (loop->u.counter.force_bit) + symbol_set_force_bit(loop->symbol, loop->u.counter.force_bit); loop_var = loop->symbol->object; // update local copy with force bit loop->symbol->has_been_read = TRUE; // lock force bit loop_var.u.number.val.intval = loop->u.counter.first; // SEE ABOVE - this may be nonzero, but has not yet been copied to user symbol! - while (loop->iterations_left) { loop->symbol->object = loop_var; // overwrite whole struct, in case some joker has re-assigned loop counter var parse_ram_block(&loop->block); @@ -111,15 +97,53 @@ void flow_forloop(struct for_loop *loop) loop->iterations_left--; } // new algo wants illegal value in loop counter after block: - if (loop->algorithm == FORALGO_NEW) + if (loop->algorithm == FORALGO_NEWCOUNT) loop->symbol->object = loop_var; // overwrite whole struct, in case some joker has re-assigned loop counter var +} -// case FORALGO_ITER: // iterate over string/list contents: -// FIXME -// break; -// default: -// Bug_found("IllegalLoopAlgo", loop->algorithm); // FIXME - add to docs! -// } +// function for "!for" with iterating variable +static void iterating_for(struct for_loop *loop) +{ + intval_t index = 0; + struct object obj; + + 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--; + } +} + + +// back end function for "!for" pseudo opcode +void flow_forloop(struct for_loop *loop) +{ + struct input loop_input, + *outer_input; + + // switching input makes us lose GotByte. But we know it's '}' anyway! + // set up new input + loop_input = *Input_now; // copy current input structure into new + loop_input.source = INPUTSRC_RAM; // set new byte source + // remember old input + outer_input = Input_now; + // activate new input + // (not yet useable; pointer and line number are still missing) + Input_now = &loop_input; + // fix line number (not for block, but in case symbol handling throws errors) + Input_now->line_number = loop->block.start; + switch (loop->algorithm) { + case FORALGO_OLDCOUNT: + case FORALGO_NEWCOUNT: + counting_for(loop); + break; + case FORALGO_ITERATE: + iterating_for(loop); + break; + default: + Bug_found("IllegalLoopAlgo", loop->algorithm); + } // restore previous input: Input_now = outer_input; } diff --git a/src/flow.h b/src/flow.h index 40358c8..04d288a 100644 --- a/src/flow.h +++ b/src/flow.h @@ -18,26 +18,24 @@ struct block { // struct to pass "!for" loop stuff from pseudoopcodes.c to flow.c enum foralgo { - FORALGO_OLD, // block can be skipped by passing zero, counter keeps value after block - FORALGO_NEW, // first and last value are given, counter is out of range after block - //FORALGO_ITER, // iterate over string/list contents (old algo could be changed to use this!) + FORALGO_OLDCOUNT, // block can be skipped by passing zero, counter keeps value after block + FORALGO_NEWCOUNT, // first and last value are given, counter is out of range after block + FORALGO_ITERATE // iterate over string/list contents }; struct for_loop { struct symbol *symbol; enum foralgo algorithm; - bits force_bit; // TODO - move to counter struct? illegal for iter algo! intval_t iterations_left; union { struct { intval_t first, increment; // 1 or -1 + bits force_bit; int addr_refs; // address reference count } counter; -/* struct { - struct symbol *iterable; - int index; - add a "last" value here? or check len() in every iteration? - } iter;*/ + struct { + struct object obj; // string or list + } iter; } u; struct block block; }; diff --git a/src/pseudoopcodes.c b/src/pseudoopcodes.c index 049a8ef..4647d8b 100644 --- a/src/pseudoopcodes.c +++ b/src/pseudoopcodes.c @@ -1009,12 +1009,13 @@ static enum eos po_ifndef(void) // now GotByte = illegal char // looping assembly ("!for"). has to be re-entrant. -// old syntax: !for VAR, END { BLOCK } VAR counts from 1 to END -// new syntax: !for VAR, START, END { BLOCK } VAR counts from START to END -// maybe future alternative: !for VAR in ITERABLE { BLOCK } VAR iterates over string/list contents +// old counter syntax: !for VAR, END { BLOCK } VAR counts from 1 to END +// new counter syntax: !for VAR, START, END { BLOCK } VAR counts from START to END +// iterating syntax: !for VAR in ITERABLE { BLOCK } VAR iterates over string/list contents static enum eos po_for(void) // now GotByte = illegal char { scope_t scope; + bits force_bit; struct for_loop loop; struct number intresult; @@ -1022,63 +1023,80 @@ static enum eos po_for(void) // now GotByte = illegal char return SKIP_REMAINDER; // zero length // now GotByte = illegal char - loop.force_bit = Input_get_force_bit(); // skips spaces after + force_bit = Input_get_force_bit(); // skips spaces after loop.symbol = symbol_find(scope); // if not number, error will be reported on first assignment - if (!Input_accept_comma()) { -#if 1 - Throw_error(exception_syntax); - return SKIP_REMAINDER; -#else - // check for "in" keyword - if (Input_read_and_lower_keyword() == 0) - return SKIP_REMAINDER; - - if (strcmp(GlobalDynaBuf->buffer, "in") != 0) { - Throw_error("Loop var must be followed by either \"in\" keyword or comma."); // TODO - add to docs! - return SKIP_REMAINDER; - } - if (loop.force_bit) { - Throw_error("Force bits can only be given to counters, not when iterating over string/list contents."); // TODO - add to docs! - return SKIP_REMAINDER; - } - loop.algorithm = FORALGO_ITER; - FIXME -#endif - } - - ALU_defined_int(&intresult); // read first argument - loop.u.counter.addr_refs = intresult.addr_refs; if (Input_accept_comma()) { - // new format - yay! - loop.algorithm = FORALGO_NEW; - if (config.wanted_version < VER_NEWFORSYNTAX) - Throw_first_pass_warning("Found new \"!for\" syntax."); - loop.u.counter.first = intresult.val.intval; // use first argument - ALU_defined_int(&intresult); // read second argument - // compare addr_ref counts and complain if not equal! - if (config.warn_on_type_mismatch - && (intresult.addr_refs != loop.u.counter.addr_refs)) { - Throw_first_pass_warning("Wrong type for loop's END value - must match type of START value."); - } - // setup direction and total - if (loop.u.counter.first <= intresult.val.intval) { - loop.iterations_left = 1 + intresult.val.intval - loop.u.counter.first; - loop.u.counter.increment = 1; + // counter syntax (old or new) + loop.u.counter.force_bit = force_bit; + ALU_defined_int(&intresult); // read first argument + loop.u.counter.addr_refs = intresult.addr_refs; + if (Input_accept_comma()) { + // new counter syntax + loop.algorithm = FORALGO_NEWCOUNT; + if (config.wanted_version < VER_NEWFORSYNTAX) + Throw_first_pass_warning("Found new \"!for\" syntax."); + loop.u.counter.first = intresult.val.intval; // use first argument + ALU_defined_int(&intresult); // read second argument + // compare addr_ref counts and complain if not equal! + if (config.warn_on_type_mismatch + && (intresult.addr_refs != loop.u.counter.addr_refs)) { + Throw_first_pass_warning("Wrong type for loop's END value - must match type of START value."); + } + // setup direction and total + if (loop.u.counter.first <= intresult.val.intval) { + // count up + loop.iterations_left = 1 + intresult.val.intval - loop.u.counter.first; + loop.u.counter.increment = 1; + } else { + // count down + loop.iterations_left = 1 + loop.u.counter.first - intresult.val.intval; + loop.u.counter.increment = -1; + } } else { - loop.iterations_left = 1 + loop.u.counter.first - intresult.val.intval; - loop.u.counter.increment = -1; + // old counter syntax + loop.algorithm = FORALGO_OLDCOUNT; + if (config.wanted_version >= VER_NEWFORSYNTAX) + Throw_first_pass_warning("Found old \"!for\" syntax."); + if (intresult.val.intval < 0) + Throw_serious_error("Loop count is negative."); + // count up + loop.u.counter.first = 1; + loop.iterations_left = intresult.val.intval; // use given argument + loop.u.counter.increment = 1; } } else { - // old format - booo! - loop.algorithm = FORALGO_OLD; - if (config.wanted_version >= VER_NEWFORSYNTAX) - Throw_first_pass_warning("Found old \"!for\" syntax."); - if (intresult.val.intval < 0) - Throw_serious_error("Loop count is negative."); - loop.u.counter.first = 1; - loop.iterations_left = intresult.val.intval; // use given argument - loop.u.counter.increment = 1; + // iterator syntax + loop.algorithm = FORALGO_ITERATE; + // check for "in" keyword + if ((GotByte != 'i') && (GotByte != 'I')) { + Throw_error(exception_syntax); + return SKIP_REMAINDER; // FIXME - this ignores '{' and will then complain about '}' + } +/* checking for the first character explicitly here looks dumb, but actually +solves a purpose: we're here because the check for comma failed, but maybe that +was just a typo. if the current byte is '.' or '-' or whatever, then trying to +read a keyword will result in "No string given" - which is confusing for the +user if they did not even want to put a string there. +so if the current byte is not the start of "in" we just throw a syntax error. +knowing there is an "i" also makes sure that Input_read_and_lower_keyword() +does not fail. */ + Input_read_and_lower_keyword(); + if (strcmp(GlobalDynaBuf->buffer, "in") != 0) { + Throw_error("Loop var must be followed by either \"in\" keyword or comma."); + return SKIP_REMAINDER; // FIXME - this ignores '{' and will then complain about '}' + } + if (force_bit) { + Throw_error("Force bits can only be given to counters, not when iterating over string/list contents."); + return SKIP_REMAINDER; // FIXME - this ignores '{' and will then complain about '}' + } + ALU_any_result(&loop.u.iter.obj); // get iterable + loop.iterations_left = loop.u.iter.obj.type->length(&loop.u.iter.obj); + if (loop.iterations_left < 0) { + Throw_error("Given object is not iterable."); + return SKIP_REMAINDER; // FIXME - this ignores '{' and will then complain about '}' + } } + if (GotByte != CHAR_SOB) Throw_serious_error(exception_no_left_brace); diff --git a/src/version.h b/src/version.h index 470b927..d613404 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 "23 Oct" // update before release FIXME +#define CHANGE_DATE "24 Oct" // update before release FIXME #define CHANGE_YEAR "2020" // update before release //#define HOME_PAGE "http://home.pages.de/~mac_bacon/smorbrod/acme/" #define HOME_PAGE "http://sourceforge.net/p/acme-crossass/" // FIXME