diff --git a/include/asm/fstack.h b/include/asm/fstack.h index 80e2096d..cef562ff 100644 --- a/include/asm/fstack.h +++ b/include/asm/fstack.h @@ -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); diff --git a/src/asm/fstack.c b/src/asm/fstack.c index a9b6fe57..5d5f1512 100644 --- a/src/asm/fstack.c +++ b/src/asm/fstack.c @@ -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; diff --git a/src/asm/lexer.c b/src/asm/lexer.c index a7cd139f..7eaf4aba 100644 --- a/src/asm/lexer.c +++ b/src/asm/lexer.c @@ -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 */ diff --git a/src/asm/parser.y b/src/asm/parser.y index 2c89cd23..face1782 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -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 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 %type macroargs +%type 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; diff --git a/src/asm/rgbasm.5 b/src/asm/rgbasm.5 index fddb12c2..36817b8c 100644 --- a/src/asm/rgbasm.5 +++ b/src/asm/rgbasm.5 @@ -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 diff --git a/test/asm/foreach.asm b/test/asm/foreach.asm new file mode 100644 index 00000000..d4028b1e --- /dev/null +++ b/test/asm/foreach.asm @@ -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" diff --git a/test/asm/foreach.err b/test/asm/foreach.err new file mode 100644 index 00000000..5f27ecda --- /dev/null +++ b/test/asm/foreach.err @@ -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 diff --git a/test/asm/foreach.out b/test/asm/foreach.out new file mode 100644 index 00000000..2ae1355b --- /dev/null +++ b/test/asm/foreach.out @@ -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