shithub: rgbds

Download patch

ref: 6874f694e5eadfdeb5ddfcf24dea57413c02bd46
parent: 3690546795dec9adf4a1bc36f8a8465d999ca1f1
author: Rangi <35663410+Rangi42@users.noreply.github.com>
date: Tue Dec 29 10:30:42 EST 2020

Implement `FOREACH` (#658)

This acts like `REPT` with a variable automatically
incremented across a range of values

Fixes #432

--- a/include/asm/fstack.h
+++ b/include/asm/fstack.h
@@ -73,6 +73,8 @@
 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);
 
--- a/src/asm/fstack.c
+++ b/src/asm/fstack.c
@@ -34,6 +34,9 @@
 	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 @@
 		}
 
 		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 @@
 	/* 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,6 +281,7 @@
 	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!!
@@ -273,7 +288,6 @@
 	 */
 	context->parent = contextStack;
 	contextStack = context;
-
 }
 
 void fstk_RunInclude(char const *path)
@@ -386,12 +400,8 @@
 	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 @@
 
 	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 @@
 		fatalerror("Failed to set up lexer for rept block\n");
 	lexer_SetStateAtEOL(contextStack->lexerState);
 	contextStack->uniqueID = macro_UseNewUniqueID();
+	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 @@
 	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;
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -238,6 +238,7 @@
 	{"SHIFT", T_POP_SHIFT},
 
 	{"REPT", T_POP_REPT},
+	{"FOREACH", T_POP_FOREACH},
 	{"ENDR", T_POP_ENDR},
 
 	{"LOAD", T_POP_LOAD},
@@ -453,7 +454,7 @@
 
 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 @@
 	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 @@
 		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 @@
 		/* 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 @@
 		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 */
--- a/src/asm/parser.y
+++ b/src/asm/parser.y
@@ -205,6 +205,11 @@
 	struct SectionSpec sectSpec;
 	struct MacroArgs *macroArg;
 	enum AssertionType assertType;
+	struct {
+		int32_t start;
+		int32_t stop;
+		int32_t step;
+	} foreachArgs;
 }
 
 %type	<sVal>		relocexpr
@@ -296,7 +301,7 @@
 %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 @@
 %type	<sectMod> sectmod
 %type	<macroArg> macroargs
 
+%type	<foreachArgs> 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 @@
 		| popc
 		| load
 		| rept
+		| foreach
 		| shift
 		| fail
 		| warn
@@ -642,6 +650,31 @@
 			size_t size;
 			lexer_CaptureRept(&body, &size);
 			fstk_RunRept($2, nDefinitionLineNo, body, size);
+		}
+;
+
+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;
 		}
 ;
 
--- a/src/asm/rgbasm.5
+++ b/src/asm/rgbasm.5
@@ -1470,6 +1470,73 @@
 .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
--- /dev/null
+++ b/test/asm/foreach.asm
@@ -1,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"
--- /dev/null
+++ b/test/asm/foreach.err
@@ -1,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
--- /dev/null
+++ b/test/asm/foreach.out
@@ -1,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