From: Lukas Krickl Date: Sun, 26 Apr 2026 13:43:20 +0000 (+0200) Subject: macro expansion: Fixed bug that prevented nested macros from expanding X-Git-Url: https://git.krickl.dev/?a=commitdiff_plain;p=ulas%2F.git macro expansion: Fixed bug that prevented nested macros from expanding 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. --- diff --git a/makefile b/makefile index 4fd7a4e..c2e00f8 100644 --- 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 diff --git a/src/test.c b/src/test.c index 6b86dd0..043aabf 100644 --- a/src/test.c +++ b/src/test.c @@ -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"); } diff --git a/src/ulas.c b/src/ulas.c index 93fdf70..16f11ef 100644 --- a/src/ulas.c +++ b/src/ulas.c @@ -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); diff --git a/src/ulas.h b/src/ulas.h index fcc4ae0..1636b7b 100644 --- a/src/ulas.h +++ b/src/ulas.h @@ -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 index 0000000..6665bb9 --- /dev/null +++ b/tests/macroexpandnested.bin @@ -0,0 +1 @@ +ȻȻ \ No newline at end of file diff --git a/tests/macroexpandnested.s b/tests/macroexpandnested.s new file mode 100644 index 0000000..110e8f7 --- /dev/null +++ b/tests/macroexpandnested.s @@ -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