Implement BREAK to exit REPT and FOR loops

Fixes #684
This commit is contained in:
Rangi
2021-01-03 22:00:02 -05:00
committed by Eldred Habert
parent 7ce5cf1595
commit 62bea23c49
13 changed files with 204 additions and 7 deletions

View File

@@ -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_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, void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
int32_t reptLineNo, char *body, size_t size); int32_t reptLineNo, char *body, size_t size);
bool fstk_Break(void);
void fstk_Init(char const *mainPath, size_t maxRecursionDepth); void fstk_Init(char const *mainPath, size_t maxRecursionDepth);

View File

@@ -56,7 +56,8 @@ enum LexerMode {
LEXER_NORMAL, LEXER_NORMAL,
LEXER_RAW, LEXER_RAW,
LEXER_SKIP_TO_ELIF, LEXER_SKIP_TO_ELIF,
LEXER_SKIP_TO_ENDC LEXER_SKIP_TO_ENDC,
LEXER_SKIP_TO_ENDR
}; };
void lexer_SetMode(enum LexerMode mode); void lexer_SetMode(enum LexerMode mode);

View File

@@ -219,16 +219,18 @@ bool yywrap(void)
contextStack->fileInfo = (struct FileStackNode *)fileInfo; contextStack->fileInfo = (struct FileStackNode *)fileInfo;
} }
fileInfo->iters[0]++;
/* If this is a FOR, update the symbol value */ /* If this is a FOR, update the symbol value */
if (contextStack->forName) { if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) {
contextStack->forValue += contextStack->forStep; contextStack->forValue += contextStack->forStep;
struct Symbol *sym = sym_AddSet(contextStack->forName, struct Symbol *sym = sym_AddSet(contextStack->forName,
contextStack->forValue); contextStack->forValue);
/* This error message will refer to the current iteration */
if (sym->type != SYM_SET) if (sym->type != SYM_SET)
fatalerror("Failed to update FOR symbol value\n"); 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 this wasn't the last iteration, wrap instead of popping */
if (fileInfo->iters[0] <= contextStack->nbReptIters) { if (fileInfo->iters[0] <= contextStack->nbReptIters) {
lexer_RestartRept(contextStack->fileInfo->lineNo); 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)); 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) void fstk_Init(char const *mainPath, size_t maxRecursionDepth)
{ {
struct LexerState *state = lexer_OpenFile(mainPath); struct LexerState *state = lexer_OpenFile(mainPath);

View File

@@ -251,6 +251,7 @@ static struct KeywordMapping {
{"REPT", T_POP_REPT}, {"REPT", T_POP_REPT},
{"FOR", T_POP_FOR}, {"FOR", T_POP_FOR},
{"ENDR", T_POP_ENDR}, {"ENDR", T_POP_ENDR},
{"BREAK", T_POP_BREAK},
{"LOAD", T_POP_LOAD}, {"LOAD", T_POP_LOAD},
{"ENDL", T_POP_ENDL}, {"ENDL", T_POP_ENDL},
@@ -498,7 +499,7 @@ struct KeywordDictNode {
uint16_t children[0x60 - ' ']; uint16_t children[0x60 - ' '];
struct KeywordMapping const *keyword; struct KeywordMapping const *keyword;
/* Since the keyword structure is invariant, the min number of nodes is known at compile time */ /* 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 */ /* Convert a char into its index into the dict */
static inline uint8_t dictIndex(char c) static inline uint8_t dictIndex(char c)
@@ -2049,6 +2050,85 @@ static int yylex_SKIP_TO_ENDC(void)
return skipIfBlock(true); 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) int yylex(void)
{ {
restart: restart:
@@ -2066,7 +2146,8 @@ restart:
[LEXER_NORMAL] = yylex_NORMAL, [LEXER_NORMAL] = yylex_NORMAL,
[LEXER_RAW] = yylex_RAW, [LEXER_RAW] = yylex_RAW,
[LEXER_SKIP_TO_ELIF] = yylex_SKIP_TO_ELIF, [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](); int token = lexerModeFuncs[lexerState->mode]();

View File

@@ -463,6 +463,7 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
%token T_POP_POPC %token T_POP_POPC
%token T_POP_SHIFT %token T_POP_SHIFT
%token T_POP_ENDR %token T_POP_ENDR
%token T_POP_BREAK
%token T_POP_LOAD T_POP_ENDL %token T_POP_LOAD T_POP_ENDL
%token T_POP_FAIL %token T_POP_FAIL
%token T_POP_WARN %token T_POP_WARN
@@ -684,6 +685,7 @@ simple_pseudoop : include
| rept | rept
| for | for
| shift | shift
| break
| fail | fail
| warn | warn
| assert | 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 { macrodef : T_LABEL T_COLON T_POP_MACRO {
int32_t nDefinitionLineNo = lexer_GetLineNo(); int32_t nDefinitionLineNo = lexer_GetLineNo();
char *body; char *body;

View File

@@ -1616,6 +1616,35 @@ blocks, you can use the escape sequence
inside of inside of
.Ic FOR .Ic FOR
blocks, and they can be nested. 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 .Ss Aborting the assembly process
.Ic FAIL .Ic FAIL
and and

22
test/asm/break.asm Normal file
View File

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

5
test/asm/break.err Normal file
View File

@@ -0,0 +1,5 @@
warning: break.asm(9): [-Wuser]
done 5
warning: break.asm(17): [-Wuser]
OK
error: Unterminated IF construct (1 levels)!

10
test/asm/break.out Normal file
View File

@@ -0,0 +1,10 @@
- 1
cont
- 2
cont
- 3
cont
- 4
cont
- 5
stop

View File

@@ -1,6 +1,6 @@
ERROR: for.asm(16): ERROR: for.asm(16):
FOR cannot have a step value of 0 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) '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 Failed to update FOR symbol value

17
test/asm/nested-break.asm Normal file
View File

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

View File

View File

@@ -0,0 +1,7 @@
AazaZ
AazazaZ
AazazazaZ
AazazazazaZ
AazazazazazaZ
Aazazazazazaza
n=6 x=6