ref: 62bea23c49cc74d8a894b88002da2290dbc60f82
parent: 7ce5cf1595f2fb3a508e920d8e4929381e298e59
author: Rangi <remy.oukaour+rangi42@gmail.com>
date: Sun Jan 3 17:00:02 EST 2021
Implement `BREAK` to exit `REPT` and `FOR` loops Fixes #684
--- a/include/asm/fstack.h
+++ b/include/asm/fstack.h
@@ -75,6 +75,7 @@
void fstk_RunRept(uint32_t count, int32_t nReptLineNo, char *body, size_t size);
void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
int32_t reptLineNo, char *body, size_t size);
+bool fstk_Break(void);
void fstk_Init(char const *mainPath, size_t maxRecursionDepth);
--- a/include/asm/lexer.h
+++ b/include/asm/lexer.h
@@ -56,7 +56,8 @@
LEXER_NORMAL,
LEXER_RAW,
LEXER_SKIP_TO_ELIF,
- LEXER_SKIP_TO_ENDC
+ LEXER_SKIP_TO_ENDC,
+ LEXER_SKIP_TO_ENDR
};
void lexer_SetMode(enum LexerMode mode);
--- a/src/asm/fstack.c
+++ b/src/asm/fstack.c
@@ -219,16 +219,18 @@
contextStack->fileInfo = (struct FileStackNode *)fileInfo;
}
- fileInfo->iters[0]++;
/* If this is a FOR, update the symbol value */
- if (contextStack->forName) {
+ if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) {
contextStack->forValue += contextStack->forStep;
struct Symbol *sym = sym_AddSet(contextStack->forName,
contextStack->forValue);
+ /* This error message will refer to the current iteration */
if (sym->type != SYM_SET)
fatalerror("Failed to update FOR symbol value\n");
}
+ /* Advance to the next iteration */
+ fileInfo->iters[0]++;
/* If this wasn't the last iteration, wrap instead of popping */
if (fileInfo->iters[0] <= contextStack->nbReptIters) {
lexer_RestartRept(contextStack->fileInfo->lineNo);
@@ -477,6 +479,20 @@
contextStack->forName = strdup(symName);
if (!contextStack->forName)
fatalerror("Not enough memory for FOR symbol name: %s\n", strerror(errno));
+}
+
+bool fstk_Break(void)
+{
+ dbgPrint("Breaking out of REPT/FOR\n");
+
+ if (contextStack->fileInfo->type != NODE_REPT) {
+ error("BREAK can only be used inside a REPT/FOR block\n");
+ return false;
+ }
+
+ /* Prevent more iterations */
+ contextStack->nbReptIters = 0;
+ return true;
}
void fstk_Init(char const *mainPath, size_t maxRecursionDepth)
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -251,6 +251,7 @@
{"REPT", T_POP_REPT},
{"FOR", T_POP_FOR},
{"ENDR", T_POP_ENDR},
+ {"BREAK", T_POP_BREAK},
{"LOAD", T_POP_LOAD},
{"ENDL", T_POP_ENDL},
@@ -498,7 +499,7 @@
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[355] = {0}; /* Make sure to keep this correct when adding keywords! */
+} keywordDict[359] = {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)
@@ -2049,6 +2050,85 @@
return skipIfBlock(true);
}
+static int yylex_SKIP_TO_ENDR(void)
+{
+ dbgPrint("Skipping remainder of REPT/FOR block\n");
+ lexer_SetMode(LEXER_NORMAL);
+ int depth = 1;
+ bool atLineStart = lexerState->atLineStart;
+
+ /* Prevent expanding macro args and symbol interpolation in this state */
+ lexerState->disableMacroArgs = true;
+ lexerState->disableInterpolation = true;
+
+ for (;;) {
+ if (atLineStart) {
+ int c;
+
+ for (;;) {
+ c = peek(0);
+ if (!isWhitespace(c))
+ break;
+ shiftChars(1);
+ }
+
+ if (startsIdentifier(c)) {
+ shiftChars(1);
+ switch (readIdentifier(c)) {
+ case T_POP_FOR:
+ case T_POP_REPT:
+ depth++;
+ break;
+
+ case T_POP_ENDR:
+ depth--;
+ if (!depth)
+ goto finish;
+ break;
+
+ case T_POP_IF:
+ nIFDepth++;
+ break;
+
+ case T_POP_ENDC:
+ nIFDepth--;
+ }
+ }
+ atLineStart = false;
+ }
+
+ /* Read chars until EOL */
+ do {
+ int c = nextChar();
+
+ if (c == EOF) {
+ goto finish;
+ } else if (c == '\\') {
+ /* Unconditionally skip the next char, including line conts */
+ c = nextChar();
+ } else if (c == '\r' || c == '\n') {
+ atLineStart = true;
+ }
+
+ if (c == '\r' || c == '\n') {
+ /* Handle CRLF before nextLine() since shiftChars updates colNo */
+ if (c == '\r' && peek(0) == '\n')
+ shiftChars(1);
+ /* Do this both on line continuations and plain EOLs */
+ nextLine();
+ }
+ } while (!atLineStart);
+ }
+finish:
+
+ lexerState->disableMacroArgs = false;
+ lexerState->disableInterpolation = false;
+ lexerState->atLineStart = false;
+
+ /* yywrap() will finish the REPT/FOR loop */
+ return 0;
+}
+
int yylex(void)
{
restart:
@@ -2066,7 +2146,8 @@
[LEXER_NORMAL] = yylex_NORMAL,
[LEXER_RAW] = yylex_RAW,
[LEXER_SKIP_TO_ELIF] = yylex_SKIP_TO_ELIF,
- [LEXER_SKIP_TO_ENDC] = yylex_SKIP_TO_ENDC
+ [LEXER_SKIP_TO_ENDC] = yylex_SKIP_TO_ENDC,
+ [LEXER_SKIP_TO_ENDR] = yylex_SKIP_TO_ENDR
};
int token = lexerModeFuncs[lexerState->mode]();
--- a/src/asm/parser.y
+++ b/src/asm/parser.y
@@ -463,6 +463,7 @@
%token T_POP_POPC
%token T_POP_SHIFT
%token T_POP_ENDR
+%token T_POP_BREAK
%token T_POP_LOAD T_POP_ENDL
%token T_POP_FAIL
%token T_POP_WARN
@@ -684,6 +685,7 @@
| rept
| for
| shift
+ | break
| fail
| warn
| assert
@@ -829,6 +831,12 @@
$$.start = $1;
$$.stop = $3;
$$.step = $5;
+ }
+;
+
+break : T_POP_BREAK {
+ if (fstk_Break())
+ lexer_SetMode(LEXER_SKIP_TO_ENDR);
}
;
--- a/src/asm/rgbasm.5
+++ b/src/asm/rgbasm.5
@@ -1616,6 +1616,35 @@
inside of
.Ic FOR
blocks, and they can be nested.
+.Pp
+You can stop a repeating block with the
+.Ic BREAK
+command.
+A
+.Ic BREAK
+inside of a
+.Ic REPT
+or
+.Ic FOR
+block will interrupt the current iteration and not repeat any more.
+It will continue running code after the block's
+.Ic ENDR .
+For example:
+.Bd -literal -offset indent
+FOR V, 1, 100
+ PRINT "{d:V}"
+ IF V == 5
+ PRINT " stop! "
+ BREAK
+ ENDC
+ PRINT ", "
+ENDR
+ PRINTLN "done {d:V}"
+.Ed
+This will print:
+.Bd -literal -offset indent
+1, 2, 3, 4, 5 stop! done 5
+.Ed
.Ss Aborting the assembly process
.Ic FAIL
and
--- /dev/null
+++ b/test/asm/break.asm
@@ -1,0 +1,22 @@
+FOR V, 1, 100
+ PRINTLN "- {d:V}"
+ IF V == 5
+ PRINTLN "stop"
+ BREAK
+ ENDC
+ PRINTLN "cont"
+ENDR
+WARN "done {d:V}"
+rept 1
+ break
+ ; skips invalid code
+ !@#$%
+elif: macro
+ invalid
+endr
+warn "OK"
+rept 1
+ if 1
+ break
+ no endc
+endr
--- /dev/null
+++ b/test/asm/break.err
@@ -1,0 +1,5 @@
+warning: break.asm(9): [-Wuser]
+ done 5
+warning: break.asm(17): [-Wuser]
+ OK
+error: Unterminated IF construct (1 levels)!
--- /dev/null
+++ b/test/asm/break.out
@@ -1,0 +1,10 @@
+- 1
+cont
+- 2
+cont
+- 3
+cont
+- 4
+cont
+- 5
+stop
--- a/test/asm/for.err
+++ b/test/asm/for.err
@@ -1,6 +1,6 @@
ERROR: for.asm(16):
FOR cannot have a step value of 0
-ERROR: for.asm(41) -> for.asm::REPT~5(47):
+ERROR: for.asm(41) -> for.asm::REPT~4(47):
'v' already defined as constant at for.asm(41) -> for.asm::REPT~4(45)
-FATAL: for.asm(41) -> for.asm::REPT~5(47):
+FATAL: for.asm(41) -> for.asm::REPT~4(47):
Failed to update FOR symbol value
--- /dev/null
+++ b/test/asm/nested-break.asm
@@ -1,0 +1,17 @@
+n=1
+rept 10
+ print "A"
+ for x, 10
+ print "a"
+ if x==n
+ break
+ endc
+ print "z"
+ endr
+ if n==6
+ break
+ endc
+ println "Z"
+n=n+1
+endr
+println "\nn={d:n} x={d:x}"
--- /dev/null
+++ b/test/asm/nested-break.out
@@ -1,0 +1,7 @@
+AazaZ
+AazazaZ
+AazazazaZ
+AazazazazaZ
+AazazazazazaZ
+Aazazazazazaza
+n=6 x=6