Implement FOREACH (#658)

This acts like `REPT` with a variable automatically
incremented across a range of values

Fixes #432
This commit is contained in:
Rangi
2020-12-29 15:30:42 -05:00
committed by GitHub
parent 3690546795
commit 6874f694e5
8 changed files with 239 additions and 14 deletions

View File

@@ -73,6 +73,8 @@ bool yywrap(void);
void fstk_RunInclude(char const *path);
void fstk_RunMacro(char const *macroName, struct MacroArgs *args);
void fstk_RunRept(uint32_t count, int32_t nReptLineNo, char *body, size_t size);
void fstk_RunForeach(char const *symName, int32_t start, int32_t stop, int32_t step,
int32_t reptLineNo, char *body, size_t size);
void fstk_Init(char const *mainPath, size_t maxRecursionDepth);

View File

@@ -34,6 +34,9 @@ struct Context {
uint32_t uniqueID;
struct MacroArgs *macroArgs; /* Macro args are *saved* here */
uint32_t nbReptIters;
int32_t foreachValue;
int32_t foreachStep;
char *foreachName;
};
static struct Context *contextStack;
@@ -217,6 +220,15 @@ bool yywrap(void)
}
fileInfo->iters[0]++;
/* If this is a FOREACH, update the symbol value */
if (contextStack->foreachName) {
contextStack->foreachValue += contextStack->foreachStep;
struct Symbol *sym = sym_AddSet(contextStack->foreachName,
contextStack->foreachValue);
if (sym->type != SYM_SET)
fatalerror("Failed to update FOREACH symbol value\n");
}
/* If this wasn't the last iteration, wrap instead of popping */
if (fileInfo->iters[0] <= contextStack->nbReptIters) {
lexer_RestartRept(contextStack->fileInfo->lineNo);
@@ -242,6 +254,8 @@ bool yywrap(void)
/* Free the file stack node */
if (!context->fileInfo->referenced)
free(context->fileInfo);
/* Free the FOREACH symbol name */
free(context->foreachName);
/* Free the entry and make its parent the current entry */
free(context);
@@ -267,13 +281,13 @@ static void newContext(struct FileStackNode *fileInfo)
fileInfo->referenced = false;
fileInfo->lineNo = lexer_GetLineNo();
context->fileInfo = fileInfo;
context->foreachName = NULL;
/*
* Link new entry to its parent so it's reachable later
* ERRORS SHOULD NOT OCCUR AFTER THIS!!
*/
context->parent = contextStack;
contextStack = context;
}
void fstk_RunInclude(char const *path)
@@ -386,12 +400,8 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
macro_UseNewArgs(args);
}
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
static bool newReptContext(int32_t reptLineNo, char *body, size_t size)
{
dbgPrint("Running REPT(%" PRIu32 ")\n", count);
if (count == 0)
return;
uint32_t reptDepth = contextStack->fileInfo->type == NODE_REPT
? ((struct FileStackReptNode *)contextStack->fileInfo)->reptDepth
: 0;
@@ -400,7 +410,7 @@ void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
if (!fileInfo) {
error("Failed to alloc file info for REPT: %s\n", strerror(errno));
return;
return false;
}
fileInfo->node.type = NODE_REPT;
fileInfo->reptDepth = reptDepth + 1;
@@ -420,8 +430,53 @@ void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
fatalerror("Failed to set up lexer for rept block\n");
lexer_SetStateAtEOL(contextStack->lexerState);
contextStack->uniqueID = macro_UseNewUniqueID();
contextStack->nbReptIters = count;
return true;
}
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
{
dbgPrint("Running REPT(%" PRIu32 ")\n", count);
if (count == 0)
return;
if (!newReptContext(reptLineNo, body, size))
return;
contextStack->nbReptIters = count;
contextStack->foreachName = NULL;
}
void fstk_RunForeach(char const *symName, int32_t start, int32_t stop, int32_t step,
int32_t reptLineNo, char *body, size_t size)
{
dbgPrint("Running FOREACH(\"%s\", %" PRId32 ", %" PRId32 ", %" PRId32 ")\n",
symName, start, stop, step);
struct Symbol *sym = sym_AddSet(symName, start);
if (sym->type != SYM_SET)
return;
uint32_t count = 0;
if (step > 0 && start < stop)
count = (stop - start - 1) / step + 1;
else if (step < 0 && stop < start)
count = (start - stop - 1) / -step + 1;
else if (step == 0)
error("FOREACH cannot have a step value of 0\n");
if (count == 0)
return;
if (!newReptContext(reptLineNo, body, size))
return;
contextStack->nbReptIters = count;
contextStack->foreachValue = start;
contextStack->foreachStep = step;
contextStack->foreachName = strdup(symName);
if (!contextStack->foreachName)
fatalerror("Not enough memory for FOREACH name: %s\n", strerror(errno));
}
void fstk_Init(char const *mainPath, size_t maxRecursionDepth)
@@ -453,6 +508,9 @@ void fstk_Init(char const *mainPath, size_t maxRecursionDepth)
context->uniqueID = 0;
macro_SetUniqueID(0);
context->nbReptIters = 0;
context->foreachValue = 0;
context->foreachStep = 0;
context->foreachName = NULL;
/* Now that it's set up properly, register the context */
contextStack = context;

View File

@@ -238,6 +238,7 @@ static struct KeywordMapping {
{"SHIFT", T_POP_SHIFT},
{"REPT", T_POP_REPT},
{"FOREACH", T_POP_FOREACH},
{"ENDR", T_POP_ENDR},
{"LOAD", T_POP_LOAD},
@@ -453,7 +454,7 @@ struct LexerState *lexer_OpenFileView(char *buf, size_t size, uint32_t lineNo)
void lexer_RestartRept(uint32_t lineNo)
{
dbgPrint("Restarting REPT\n");
dbgPrint("Restarting REPT/FOREACH\n");
lexerState->offset = 0;
initState(lexerState);
lexerState->lineNo = lineNo;
@@ -479,7 +480,7 @@ struct KeywordDictNode {
uint16_t children[0x60 - ' '];
struct KeywordMapping const *keyword;
/* Since the keyword structure is invariant, the min number of nodes is known at compile time */
} keywordDict[341] = {0}; /* Make sure to keep this correct when adding keywords! */
} keywordDict[347] = {0}; /* Make sure to keep this correct when adding keywords! */
/* Convert a char into its index into the dict */
static inline uint8_t dictIndex(char c)
@@ -2161,10 +2162,11 @@ void lexer_CaptureRept(char **capture, size_t *size)
do { /* Discard initial whitespace */
c = nextChar();
} while (isWhitespace(c));
/* Now, try to match either `REPT` or `ENDR` as a **whole** identifier */
/* Now, try to match `REPT`, `FOREACH` or `ENDR` as a **whole** identifier */
if (startsIdentifier(c)) {
switch (readIdentifier(c)) {
case T_POP_REPT:
case T_POP_FOREACH:
level++;
/* Ignore the rest of that line */
break;
@@ -2188,7 +2190,7 @@ void lexer_CaptureRept(char **capture, size_t *size)
/* Just consume characters until EOL or EOF */
for (;;) {
if (c == EOF) {
error("Unterminated REPT block\n");
error("Unterminated REPT/FOREACH block\n");
lexerState->capturing = false;
goto finish;
} else if (c == '\n' || c == '\r') {
@@ -2246,7 +2248,7 @@ void lexer_CaptureMacroBody(char **capture, size_t *size)
do { /* Discard initial whitespace */
c = nextChar();
} while (isWhitespace(c));
/* Now, try to match either `REPT` or `ENDR` as a **whole** identifier */
/* Now, try to match `ENDM` as a **whole** identifier */
if (startsIdentifier(c)) {
if (readIdentifier(c) == T_POP_ENDM) {
/* Read (but don't capture) until EOL or EOF */

View File

@@ -205,6 +205,11 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
struct SectionSpec sectSpec;
struct MacroArgs *macroArg;
enum AssertionType assertType;
struct {
int32_t start;
int32_t stop;
int32_t step;
} foreachArgs;
}
%type <sVal> relocexpr
@@ -296,7 +301,7 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
%token T_POP_ENDM
%token T_POP_RSRESET T_POP_RSSET
%token T_POP_UNION T_POP_NEXTU T_POP_ENDU
%token T_POP_INCBIN T_POP_REPT
%token T_POP_INCBIN T_POP_REPT T_POP_FOREACH
%token T_POP_CHARMAP
%token T_POP_NEWCHARMAP
%token T_POP_SETCHARMAP
@@ -321,6 +326,8 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
%type <sectMod> sectmod
%type <macroArg> macroargs
%type <foreachArgs> foreach_args
%token T_Z80_ADC T_Z80_ADD T_Z80_AND
%token T_Z80_BIT
%token T_Z80_CALL T_Z80_CCF T_Z80_CP T_Z80_CPL
@@ -522,6 +529,7 @@ simple_pseudoop : include
| popc
| load
| rept
| foreach
| shift
| fail
| warn
@@ -645,6 +653,31 @@ rept : T_POP_REPT uconst {
}
;
foreach : T_POP_FOREACH T_ID T_COMMA foreach_args {
uint32_t nDefinitionLineNo = lexer_GetLineNo();
char *body;
size_t size;
lexer_CaptureRept(&body, &size);
fstk_RunForeach($2, $4.start, $4.stop, $4.step, nDefinitionLineNo, body, size);
}
foreach_args : const {
$$.start = 0;
$$.stop = $1;
$$.step = 1;
}
| const T_COMMA const {
$$.start = $1;
$$.stop = $3;
$$.step = 1;
}
| const T_COMMA const T_COMMA const {
$$.start = $1;
$$.stop = $3;
$$.step = $5;
}
;
macrodef : T_LABEL T_COLON T_POP_MACRO {
int32_t nDefinitionLineNo = lexer_GetLineNo();
char *body;

View File

@@ -1470,6 +1470,73 @@ As in macros, you can also use the escape sequence
.Ic \[rs]@ .
.Ic REPT
blocks can be nested.
.Pp
A common pattern is to repeat a block for each value in some range.
.Ic FOREACH
is simpler than
.Ic REPT
for that purpose.
Everything between
.Ic FOREACH
and the matching
.Ic ENDR
will be repeated for each value of a given symbol.
For example, this code will produce a table of squared values from 0 to 255:
.Bd -literal -offset indent
FOREACH N, 256
dw N * N
ENDR
.Ed
.Pp
It acts just as if you had done:
.Bd -literal -offset ident
N = 0
dw N * N
N = 1
dw N * N
N = 2
dw N * N
; ...
N = 255
dw N * N
N = 256
.Ed
.Pp
You can customize the range of
.Ic FOREACH
values:
.Bl -column "FOREACH V, start, stop, step"
.It Sy Code Ta Sy Range
.It Ic FOREACH Ar V , stop Ta Ar V No increments from 0 to Ar stop No
.It Ic FOREACH Ar V , start , stop Ta Ar V No increments from Ar start No to Ar stop No
.It Ic FOREACH Ar V , start , stop , step Ta Ar V No goes from Ar start No to Ar stop No by Ar step No
.El
.Pp
The
.Ic FOREACH
value will be updated by
.Ar step
until it reaches or exceeds
.Ar stop.
For example:
.Bd -literal -offset indent
FOREACH V, 4, 25, 5
PRINTT "{d:V} "
ENDR
PRINTT "done {d:V}\n"
.Ed
This will print:
.Bd -literal -offset indent
4 9 14 19 24 done 29
.Ed
.Pp
Just like with
.Ic REPT
blocks, you can use the escape sequence
.Ic \[rs]@
inside of
.Ic FOREACH
blocks, and they can be nested.
.Ss Aborting the assembly process
.Ic FAIL
and

48
test/asm/foreach.asm Normal file
View File

@@ -0,0 +1,48 @@
foreach n, 10
printt "{d:n} "
endr
printt "-> {d:n}\n"
foreach v, 0
printt "unreached"
endr
foreach v, 2, 1
printt "unreached"
endr
foreach v, 1, 2, 0
printt "unreached"
endr
foreach x, 1, 5+1
printt "{d:x} "
endr
printt "-> {d:x}\n"
foreach v, 10, -1, -1
printt "{d:v} "
v = 42
endr
printt "-> {d:v}\n"
foreach q, 5, 21, 5
printt "{d:q} "
purge q
endr
printt "-> {d:q}\n"
s EQUS "x"
foreach s, 3, 30, 3
printt "{d:x} "
endr
printt "-> {d:x}\n"
foreach v, 10
printt "{d:v}\n"
if v == 3
purge v
v equ 42 ; causes a fatal error
endc
endr
printt "-> {d:v}\n"

6
test/asm/foreach.err Normal file
View File

@@ -0,0 +1,6 @@
ERROR: foreach.asm(16):
FOREACH cannot have a step value of 0
ERROR: foreach.asm(41) -> foreach.asm::REPT~5(47):
'v' already defined as constant at foreach.asm(41) -> foreach.asm::REPT~4(45)
FATAL: foreach.asm(41) -> foreach.asm::REPT~5(47):
Failed to update FOREACH symbol value

9
test/asm/foreach.out Normal file
View File

@@ -0,0 +1,9 @@
0 1 2 3 4 5 6 7 8 9 -> 10
1 2 3 4 5 -> 6
10 9 8 7 6 5 4 3 2 1 0 -> -1
5 10 15 20 -> 25
3 6 9 12 15 18 21 24 27 -> 30
0
1
2
3