From 62bea23c49cc74d8a894b88002da2290dbc60f82 Mon Sep 17 00:00:00 2001 From: Rangi Date: Sun, 3 Jan 2021 22:00:02 -0500 Subject: [PATCH] Implement `BREAK` to exit `REPT` and `FOR` loops Fixes #684 --- include/asm/fstack.h | 1 + include/asm/lexer.h | 3 +- src/asm/fstack.c | 20 ++++++++- src/asm/lexer.c | 85 ++++++++++++++++++++++++++++++++++++++- src/asm/parser.y | 8 ++++ src/asm/rgbasm.5 | 29 +++++++++++++ test/asm/break.asm | 22 ++++++++++ test/asm/break.err | 5 +++ test/asm/break.out | 10 +++++ test/asm/for.err | 4 +- test/asm/nested-break.asm | 17 ++++++++ test/asm/nested-break.err | 0 test/asm/nested-break.out | 7 ++++ 13 files changed, 204 insertions(+), 7 deletions(-) create mode 100644 test/asm/break.asm create mode 100644 test/asm/break.err create mode 100644 test/asm/break.out create mode 100644 test/asm/nested-break.asm create mode 100644 test/asm/nested-break.err create mode 100644 test/asm/nested-break.out diff --git a/include/asm/fstack.h b/include/asm/fstack.h index 8a029eb1..c05e73e6 100644 --- a/include/asm/fstack.h +++ b/include/asm/fstack.h @@ -75,6 +75,7 @@ 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_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); diff --git a/include/asm/lexer.h b/include/asm/lexer.h index 9494ab6f..1f497f0a 100644 --- a/include/asm/lexer.h +++ b/include/asm/lexer.h @@ -56,7 +56,8 @@ enum LexerMode { 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); diff --git a/src/asm/fstack.c b/src/asm/fstack.c index 6e0fcac2..b3aabc2d 100644 --- a/src/asm/fstack.c +++ b/src/asm/fstack.c @@ -219,16 +219,18 @@ bool yywrap(void) 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); @@ -479,6 +481,20 @@ void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step, 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) { struct LexerState *state = lexer_OpenFile(mainPath); diff --git a/src/asm/lexer.c b/src/asm/lexer.c index 7820c985..a131c08f 100644 --- a/src/asm/lexer.c +++ b/src/asm/lexer.c @@ -251,6 +251,7 @@ static struct KeywordMapping { {"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 @@ 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[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 @@ static int yylex_SKIP_TO_ENDC(void) 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 @@ restart: [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](); diff --git a/src/asm/parser.y b/src/asm/parser.y index 9c6a66b9..f25cdb6b 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -463,6 +463,7 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg) %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 @@ simple_pseudoop : include | rept | for | shift + | break | fail | warn | assert @@ -832,6 +834,12 @@ for_args : const { } ; +break : T_POP_BREAK { + if (fstk_Break()) + lexer_SetMode(LEXER_SKIP_TO_ENDR); + } +; + 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 f805e96b..dcdd6db1 100644 --- a/src/asm/rgbasm.5 +++ b/src/asm/rgbasm.5 @@ -1616,6 +1616,35 @@ blocks, you can use the escape sequence 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 diff --git a/test/asm/break.asm b/test/asm/break.asm new file mode 100644 index 00000000..5cf295d8 --- /dev/null +++ b/test/asm/break.asm @@ -0,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 diff --git a/test/asm/break.err b/test/asm/break.err new file mode 100644 index 00000000..c4e97d4e --- /dev/null +++ b/test/asm/break.err @@ -0,0 +1,5 @@ +warning: break.asm(9): [-Wuser] + done 5 +warning: break.asm(17): [-Wuser] + OK +error: Unterminated IF construct (1 levels)! diff --git a/test/asm/break.out b/test/asm/break.out new file mode 100644 index 00000000..d697346c --- /dev/null +++ b/test/asm/break.out @@ -0,0 +1,10 @@ +- 1 +cont +- 2 +cont +- 3 +cont +- 4 +cont +- 5 +stop diff --git a/test/asm/for.err b/test/asm/for.err index 4a48d9d5..f3d7f2a8 100644 --- 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 diff --git a/test/asm/nested-break.asm b/test/asm/nested-break.asm new file mode 100644 index 00000000..fbf3ab32 --- /dev/null +++ b/test/asm/nested-break.asm @@ -0,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}" diff --git a/test/asm/nested-break.err b/test/asm/nested-break.err new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/nested-break.out b/test/asm/nested-break.out new file mode 100644 index 00000000..cc6119e9 --- /dev/null +++ b/test/asm/nested-break.out @@ -0,0 +1,7 @@ +AazaZ +AazazaZ +AazazazaZ +AazazazazaZ +AazazazazazaZ +Aazazazazazaza +n=6 x=6