added "!for VAR in ITERABLE { BLOCK }" possibility.

git-svn-id: https://svn.code.sf.net/p/acme-crossass/code-0/trunk@306 4df02467-bbd4-4a76-a152-e7ce94205b78
This commit is contained in:
marcobaye 2020-10-23 23:13:26 +00:00
parent cc82e17cda
commit c9d148d345
6 changed files with 190 additions and 94 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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