macro expansion: Fixed bug that prevented nested macros from expanding master origin/HEAD origin/master
authorLukas Krickl <lukas@krickl.dev>
Sun, 26 Apr 2026 13:43:20 +0000 (15:43 +0200)
committerLukas Krickl <lukas@krickl.dev>
Sun, 26 Apr 2026 13:43:20 +0000 (15:43 +0200)
correctly.

The way nested macros were handeled previously was broken in many ways.
Now we tokenize the first expansion's lines and expand them
individually.
This fixed nested macros.
Previosuly it was also possible to get stuck in an infinite loop.

The current implementation uses a lot of ulas_str and malloc/free which
is not great, but in the end it should be ok.

makefile
src/test.c
src/ulas.c
src/ulas.h
tests/macroexpandnested.bin [new file with mode: 0644]
tests/macroexpandnested.s [new file with mode: 0644]

index 4fd7a4e0d98c9a9e75e6652539806f870b5486cb..c2e00f862e556bcc05dffc73f7441151b268d2af 100644 (file)
--- a/makefile
+++ b/makefile
@@ -59,3 +59,4 @@ buildtests: bin
        ./$(NAME) tests/t0.s -D ULAS_PREDEF -l - -o tests/t0.bin
        ./$(NAME) tests/multiline.s -D ULAS_PREDEF -l - -o tests/multiline.bin
        ./$(NAME) tests/t0.bin -d - -o tests/t0_dasm.s
+       ./$(NAME) tests/macroexpandnested.s  -o tests/macroexpandnested.bin
index 6b86dd07b425c1bc859758fdcd15e0a10b3cc9eb..043aabfdb88302176b0c511f8043e034e521d615 100644 (file)
@@ -125,19 +125,19 @@ void test_preproc(void) {
 
   /* macro */
   assert_preproc(
-      "  line p1 1 label01,2 3\n  line p2 2\n  line p3 3 p1, p2, p3\n", 0,
+      "\nline p1 1 label01,2 3\nline p2 2\nline p3 3 p1, p2, p3\n", 0,
       "#macro test\n  line $1 1 label$$$$,$$ $$\n  line $2 2\n  line $3 3 "
       "$0\n#endmacro\ntest p1, p2, p3");
-  assert_preproc("test macro with no args\n", 0,
+  assert_preproc("\ntest macro with no args\n", 0,
                  "#macro mtest\ntest macro with no args\n#endmacro\nmtest");
   assert_preproc("", -1, "#macro test\n not terminated\n");
   assert_preproc(
-      "\ncontent macro t1\nt1\nafter\ncontent n1\n", 0,
+      "\n\ncontent macro t1\n\nt1\nafter\ncontent n1\n", 0,
       "#macro test\nmnested macro $1\n$1\n#macro "
       "mnested\ncontent $1\n#endmacro\nafter\nmnested n1\n#endmacro\ntest t1");
 
   /* this macro caused a heap buffer overflow in production code */
-  assert_preproc("ld a, verylongmacroinput & 0xFF\nld [hl+], a\nld a, "
+  assert_preproc("\nld a, verylongmacroinput & 0xFF\nld [hl+], a\nld a, "
                  "(verylongmacroinput >> 8) & 0xFF\nld [hl+], a\n",
                  0,
                  "#macro testlonginput\nld a, $1 & 0xFF\nld [hl+], a\nld a, "
@@ -433,6 +433,8 @@ void test_full_asm(void) {
 
   ASSERT_FULL_ASM(0, "tests/t0.s", "tests/t0.bin");
   ASSERT_FULL_ASM(0, "tests/multiline.s", "tests/multiline.bin");
+  ASSERT_FULL_ASM(0, "tests/macroexpandnested.s", 
+                       "tests/macroexpandnested.bin");
 
   TESTEND("testfullasm");
 }
index 93fdf70f73c871a6e2d2156278e0736ef12db3ef..16f11ef84cdbd6dc348c1be79fbb6305186ef137 100644 (file)
@@ -976,32 +976,68 @@ void ulas_trimend(char c, char *buf, unsigned long n) {
   }
 }
 
-char *ulas_preprocexpand2(struct ulas_preproc *pp, const char *raw_line,
-                         unsigned long *n, int recursive); 
-void ulas_preprocexpand_rec(struct ulas_preproc *pp) {
+char *ulas_preprocexpand(struct ulas_preproc *pp, const char *raw_line,
+                         unsigned long *n);
+void ulas_preprocexpand_rec(struct ulas_preproc *pp, int append_newline) {
+       struct ulas_str line2 = ulas_str(1);
+       struct ulas_str next_line = ulas_str(1);
+       struct ulas_str new_ppline = ulas_str(1);
+       const char *linep;
+       int lines_expanded = 0;
+
        unsigned long n = 0;
   /* expand macro result again to allow
    * defines to appear in the macro
         */
-  ulas_strensr(&pp->line2, strlen(pp->line.buf) + 1);
-  sprintf(pp->line2.buf, "%s", pp->line.buf);
+  ulas_strensr(&line2, strlen(pp->line.buf) + 1);
+  sprintf(line2.buf, "%s", pp->line.buf);
   n = strlen(pp->line.buf);
   pp->exp_depth++;
   if (pp->exp_depth > ULAS_PREPROC_MAX_MACRO_DEPTH) {
     ULASERR("Max macro recursion depth reached\n");
+
+               ulas_strfree(&line2);
+               ulas_strfree(&next_line);
+               ulas_strfree(&new_ppline);
     return;
   }
-  ulas_preprocexpand2(pp, pp->line2.buf, &n, 1);
+       
+       linep = line2.buf;
+       new_ppline.buf[0] = '\0';
+       while (ulas_tokuntil(&next_line, '\n', &linep, strlen(linep))) {
+               n = strlen(next_line.buf);
+               ulas_preprocexpand(pp, next_line.buf, &n);
+
+               ulas_strensr(&new_ppline, 
+                               strlen(pp->line.buf) + strlen(new_ppline.buf) + 4);
+
+               if (lines_expanded == 0 && append_newline) {
+                       sprintf(new_ppline.buf, "\n%s", pp->line.buf);
+               } else if (lines_expanded == 0) {
+                       sprintf(new_ppline.buf, "%s", pp->line.buf);
+               } else {
+                       sprintf(new_ppline.buf, "%s\n%s", new_ppline.buf, pp->line.buf);
+               }
+
+               lines_expanded++;
+       }
+
+       ulas_strensr(&pp->line, strlen(new_ppline.buf) + 4);
+       
+       if (!append_newline) {
+               sprintf(pp->line.buf, "%s", new_ppline.buf);
+       } else {
+               sprintf(pp->line.buf, "%s\n", new_ppline.buf);
+       }
+
   pp->exp_depth--;
+       ulas_strfree(&line2);
+       ulas_strfree(&next_line);
+       ulas_strfree(&new_ppline);
 }
 
 char *ulas_preprocexpand(struct ulas_preproc *pp, const char *raw_line,
                          unsigned long *n) {
-  return ulas_preprocexpand2(pp, raw_line, n, 0);
-}
-
-char *ulas_preprocexpand2(struct ulas_preproc *pp, const char *raw_line,
-                         unsigned long *n, int recursive) {
        const char *macro_argname[ULAS_MACROPARAMMAX] = {
                        "$1", "$2",  "$3",  "$4",  "$5",  "$6",  "$7", "$8",
                        "$9", "$10", "$11", "$12", "$13", "$14", "$15"};
@@ -1071,7 +1107,7 @@ char *ulas_preprocexpand2(struct ulas_preproc *pp, const char *raw_line,
           } else {
             strncat(pp->line.buf, def->value, val_len);
           }
-          ulas_preprocexpand_rec(pp);
+          ulas_preprocexpand_rec(pp, 0);
         }
         break;
       }
@@ -1084,7 +1120,6 @@ char *ulas_preprocexpand2(struct ulas_preproc *pp, const char *raw_line,
                                int paramc;
                                
         /* TODO: i am sure we can optimize the resize of line buffers here...
-         * TODO: allow recursive macro calls
                                 */
 
         /* get 9 comma separated values.
@@ -1122,17 +1157,6 @@ char *ulas_preprocexpand2(struct ulas_preproc *pp, const char *raw_line,
                                 */
         tocat = NULL;
         tocatlen = 0;
-
-        /* always start an expanded macro with a new line
-         * to ensure expansion are separated if we are in a recursive call
-         * this fixes new lines in recursive macros being consumed
-                                */
-        if (recursive) {
-          int len = ulas_strnlen(pp->line.buf, pp->line.maxlen) + 1;
-          ulas_strensr(&pp->line,
-                       len + 1);
-          strncat(pp->line.buf, "\n", len);
-        }
         
 
         /* now tokenize the macro's value and look for $0-$9
@@ -1196,7 +1220,7 @@ char *ulas_preprocexpand2(struct ulas_preproc *pp, const char *raw_line,
             strncat(pp->line.buf, tocat, tocatlen);
           }
         }
-        ulas_preprocexpand_rec(pp);
+        ulas_preprocexpand_rec(pp, 1);
         goto end;
       }
       }
@@ -1544,7 +1568,6 @@ struct ulas_preproc ulas_preprocinit(void) {
        pp.defslen = 0;
        pp.tok = ulas_str(1);
        pp.line = ulas_str(1);
-       pp.line2 = ulas_str(1);
 
   for (i = 0; i < ULAS_MACROPARAMMAX; i++) {
     pp.macroparam[i] = ulas_str(8);
@@ -1581,7 +1604,6 @@ void ulas_preprocclear(struct ulas_preproc *pp) {
 void ulas_preprocfree(struct ulas_preproc *pp) {
        unsigned long i;
   ulas_strfree(&pp->line);
-  ulas_strfree(&pp->line2);
   ulas_strfree(&pp->tok);
 
   ulas_preprocclear(pp);
index fcc4ae04bdce753438e9f6a6cfb429a0de67346e..1636b7b5430d4009fdada9dd7176eb76c694c918 100644 (file)
@@ -277,7 +277,6 @@ struct ulas_preproc {
 
   struct ulas_str tok;
   struct ulas_str line;
-  struct ulas_str line2;
 
   int exp_depth;
 
diff --git a/tests/macroexpandnested.bin b/tests/macroexpandnested.bin
new file mode 100644 (file)
index 0000000..6665bb9
--- /dev/null
@@ -0,0 +1 @@
+Ȼ\ 2Ȼ
\ No newline at end of file
diff --git a/tests/macroexpandnested.s b/tests/macroexpandnested.s
new file mode 100644 (file)
index 0000000..110e8f7
--- /dev/null
@@ -0,0 +1,16 @@
+
+
+#macro nested_def
+       dw $1
+
+       .db $2
+
+       dw $1
+#endmacro
+
+#macro dw 
+.db $1 & 0xFF
+.db ($1 >> 8) & 0xFF 
+#endmacro
+
+nested_def 0xAABB, 2