shithub: rgbds

Download patch

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