From 637bbbdf4312ca6efb6eb4ad6ae4ad7f06d9357e Mon Sep 17 00:00:00 2001 From: Rangi Date: Sun, 18 Apr 2021 23:26:03 -0400 Subject: [PATCH] Support multi-digit macro arguments in parentheses This allows access to arguments past \9 without using 'shift' --- src/asm/lexer.c | 94 +++++++++++++++++------- src/asm/rgbasm.5 | 37 +++++++--- test/asm/invalid-empty-macro-arg.asm | 1 + test/asm/invalid-empty-macro-arg.err | 2 + test/asm/invalid-empty-macro-arg.out | 0 test/asm/invalid-macro-arg-character.asm | 1 + test/asm/invalid-macro-arg-character.err | 2 + test/asm/invalid-macro-arg-character.out | 0 test/asm/parenthetic-macro-args.asm | 17 +++++ test/asm/parenthetic-macro-args.err | 0 test/asm/parenthetic-macro-args.out | 6 ++ 11 files changed, 125 insertions(+), 35 deletions(-) create mode 100644 test/asm/invalid-empty-macro-arg.asm create mode 100644 test/asm/invalid-empty-macro-arg.err create mode 100644 test/asm/invalid-empty-macro-arg.out create mode 100644 test/asm/invalid-macro-arg-character.asm create mode 100644 test/asm/invalid-macro-arg-character.err create mode 100644 test/asm/invalid-macro-arg-character.out create mode 100644 test/asm/parenthetic-macro-args.asm create mode 100644 test/asm/parenthetic-macro-args.err create mode 100644 test/asm/parenthetic-macro-args.out diff --git a/src/asm/lexer.c b/src/asm/lexer.c index c8c11cec..a6f78236 100644 --- a/src/asm/lexer.c +++ b/src/asm/lexer.c @@ -717,21 +717,65 @@ static void freeExpansion(struct Expansion *expansion) static bool isMacroChar(char c) { - return c == '@' || c == '#' || (c >= '0' && c <= '9'); + return c == '@' || c == '#' || c == '(' || (c >= '0' && c <= '9'); +} + +/* forward declarations for readParentheticMacroArgNum */ +static int peek(void); +static void shiftChar(void); +static uint32_t readNumber(int radix, uint32_t baseValue); + +static uint32_t readParentheticMacroArgNum(void) +{ + dbgPrint("Reading parenthetic macro arg\n"); + bool disableMacroArgs = lexerState->disableMacroArgs; + bool disableInterpolation = lexerState->disableInterpolation; + + lexerState->disableMacroArgs = false; + lexerState->disableInterpolation = false; + + uint32_t num = 0; + int c = peek(); + bool hasDigit = c >= '0' && c <= '9'; + + if (hasDigit) + num = readNumber(10, 0); + + c = peek(); + if (c != ')') + fatalerror("Invalid character in parenthetic macro argument %s\n", printChar(c)); + else if (!hasDigit) + fatalerror("Empty parenthetic macro argument\n"); + else if (num == 0) + fatalerror("Invalid parenthetic macro argument '\\(0)'\n"); + + shiftChar(); + + lexerState->disableMacroArgs = disableMacroArgs; + lexerState->disableInterpolation = disableInterpolation; + return num; } static char const *readMacroArg(char name) { char const *str; - if (name == '@') + if (name == '@') { str = macro_GetUniqueIDStr(); - else if (name == '#') + } else if (name == '#') { str = macro_GetAllArgs(); - else if (name == '0') + } else if (name == '(') { + uint32_t num = readParentheticMacroArgNum(); + + str = macro_GetArg(num); + if (!str) + fatalerror("Macro argument '\\(%" PRIu32 ")' not defined\n", num); + } else if (name == '0') { fatalerror("Invalid macro argument '\\0'\n"); - else + } else { str = macro_GetArg(name - '0'); + } + if (!str) fatalerror("Macro argument '\\%c' not defined\n", name); @@ -1061,7 +1105,7 @@ static void readAnonLabelRef(char c) /* Functions to lex numbers of various radixes */ -static void readNumber(int radix, int32_t baseValue) +static uint32_t readNumber(int radix, uint32_t baseValue) { uint32_t value = baseValue; @@ -1077,10 +1121,10 @@ static void readNumber(int radix, int32_t baseValue) value = value * radix + (c - '0'); } - yylval.constValue = value; + return value; } -static void readFractionalPart(void) +static uint32_t readFractionalPart(int32_t integer) { uint32_t value = 0, divisor = 1; @@ -1105,20 +1149,20 @@ static void readFractionalPart(void) divisor *= 10; } - if (yylval.constValue > INT16_MAX || yylval.constValue < INT16_MIN) + if (integer > INT16_MAX || integer < INT16_MIN) warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n"); /* Cast to unsigned avoids UB if shifting discards bits */ - yylval.constValue = (uint32_t)yylval.constValue << 16; + integer = (uint32_t)integer << 16; /* Cast to unsigned avoids undefined overflow behavior */ uint16_t fractional = (uint16_t)round(value * 65536.0 / divisor); - yylval.constValue |= fractional * (yylval.constValue >= 0 ? 1 : -1); + return (uint32_t)integer | (fractional * (integer >= 0 ? 1 : -1)); } char binDigits[2]; -static void readBinaryNumber(void) +static uint32_t readBinaryNumber(void) { uint32_t value = 0; @@ -1140,10 +1184,10 @@ static void readBinaryNumber(void) value = value * 2 + bit; } - yylval.constValue = value; + return value; } -static void readHexNumber(void) +static uint32_t readHexNumber(void) { uint32_t value = 0; bool empty = true; @@ -1173,12 +1217,12 @@ static void readHexNumber(void) if (empty) error("Invalid integer constant, no digits after '$'\n"); - yylval.constValue = value; + return value; } char gfxDigits[4]; -static void readGfxConstant(void) +static uint32_t readGfxConstant(void) { uint32_t bp0 = 0, bp1 = 0; uint8_t width = 0; @@ -1215,7 +1259,7 @@ static void readGfxConstant(void) warning(WARNING_LARGE_CONSTANT, "Graphics constant is too long, only 8 first pixels considered\n"); - yylval.constValue = bp1 << 8 | bp0; + return bp1 << 8 | bp0; } /* Functions to read identifiers & keywords */ @@ -1496,6 +1540,7 @@ static void readString(void) case '7': case '8': case '9': + case '(': shiftChar(); char const *str = readMacroArg(c); @@ -1641,6 +1686,7 @@ static size_t appendStringLiteral(size_t i) case '7': case '8': case '9': + case '(': shiftChar(); char const *str = readMacroArg(c); @@ -1828,8 +1874,7 @@ static int yylex_NORMAL(void) /* Handle numbers */ case '$': - yylval.constValue = 0; - readHexNumber(); + yylval.constValue = readHexNumber(); /* Attempt to match `$ff00+c` */ if (yylval.constValue == 0xff00) { /* Whitespace is ignored anyways */ @@ -1859,10 +1904,10 @@ static int yylex_NORMAL(void) case '7': case '8': case '9': - readNumber(10, c - '0'); + yylval.constValue = readNumber(10, c - '0'); if (peek() == '.') { shiftChar(); - readFractionalPart(); + yylval.constValue = readFractionalPart(yylval.constValue); } return T_NUMBER; @@ -1872,7 +1917,7 @@ static int yylex_NORMAL(void) shiftChar(); return T_OP_LOGICAND; } else if (secondChar >= '0' && secondChar <= '7') { - readNumber(8, 0); + yylval.constValue = readNumber(8, 0); return T_NUMBER; } return T_OP_AND; @@ -1882,12 +1927,11 @@ static int yylex_NORMAL(void) if (secondChar != binDigits[0] && secondChar != binDigits[1]) return T_OP_MOD; - yylval.constValue = 0; - readBinaryNumber(); + yylval.constValue = readBinaryNumber(); return T_NUMBER; case '`': /* Gfx constant */ - readGfxConstant(); + yylval.constValue = readGfxConstant(); return T_NUMBER; /* Handle strings */ diff --git a/src/asm/rgbasm.5 b/src/asm/rgbasm.5 index 171d87c0..9c6e6917 100644 --- a/src/asm/rgbasm.5 +++ b/src/asm/rgbasm.5 @@ -1566,16 +1566,30 @@ The backslash in .Ql \[rs]n does not need to be escaped because string literals also work as usual inside macro arguments. .Pp -In reality, up to 256 arguments can be passed to a macro, but you can only use the first 9 like this. -If you want to use the rest, you need to use the -.Ic SHIFT -command. +Since there are only nine digits, you can only access the first nine macro arguments like this. +To use the rest, you need to put the multi-digit argument number in parentheses, like +.Ql \[rs](10) . +This parenthetic syntax only supports decimal numbers. .Pp +Other macro arguments and symbol interpolations will be expanded inside the parentheses. +For example, if +.Ql \[rs]1 +is +.Ql 13 , +then +.Ql \[rs](\[rs]1) +will expand to +.Ql \[rs](13) . +And if +.Ql x = 42 , +then +.Ql \[rs]({d:x}) +will expand to +.Ql \[rs](42) . +.Pp +Another way to access more than nine macro arguments is the .Ic SHIFT -is a special command only available in macros. -Very useful in -.Ic REPT -blocks. +command, a special command only available in macros. It will shift the arguments by one to the left, and decrease .Dv _NARG by 1. @@ -1586,11 +1600,14 @@ will get the value of .Ic \[rs]3 , and so forth. .Pp -This is the only way of accessing the value of arguments from 10 to 256. -.Pp .Ic SHIFT can optionally be given an integer parameter, and will apply the above shifting that number of times. A negative parameter will shift the arguments in reverse. +.Pp +.Ic SHIFT +is useful in +.Ic REPT +blocks to repeat the same commands with multiple arguments. .Ss Printing things during assembly The .Ic PRINT diff --git a/test/asm/invalid-empty-macro-arg.asm b/test/asm/invalid-empty-macro-arg.asm new file mode 100644 index 00000000..d334ed44 --- /dev/null +++ b/test/asm/invalid-empty-macro-arg.asm @@ -0,0 +1 @@ +\() diff --git a/test/asm/invalid-empty-macro-arg.err b/test/asm/invalid-empty-macro-arg.err new file mode 100644 index 00000000..0ba77403 --- /dev/null +++ b/test/asm/invalid-empty-macro-arg.err @@ -0,0 +1,2 @@ +FATAL: invalid-empty-macro-arg.asm(1): + Empty parenthetic macro argument diff --git a/test/asm/invalid-empty-macro-arg.out b/test/asm/invalid-empty-macro-arg.out new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/invalid-macro-arg-character.asm b/test/asm/invalid-macro-arg-character.asm new file mode 100644 index 00000000..0b43ea90 --- /dev/null +++ b/test/asm/invalid-macro-arg-character.asm @@ -0,0 +1 @@ +\(10!) diff --git a/test/asm/invalid-macro-arg-character.err b/test/asm/invalid-macro-arg-character.err new file mode 100644 index 00000000..1153f5c8 --- /dev/null +++ b/test/asm/invalid-macro-arg-character.err @@ -0,0 +1,2 @@ +FATAL: invalid-macro-arg-character.asm(1): + Invalid character in parenthetic macro argument '!' diff --git a/test/asm/invalid-macro-arg-character.out b/test/asm/invalid-macro-arg-character.out new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/parenthetic-macro-args.asm b/test/asm/parenthetic-macro-args.asm new file mode 100644 index 00000000..7f4d30ba --- /dev/null +++ b/test/asm/parenthetic-macro-args.asm @@ -0,0 +1,17 @@ +MACRO printargs + PRINTLN "first = \(1)" + FOR I, 2, _NARG + PRINTLN "next = \({d:I})" + ENDR + PRINTLN "last = \({d:_NARG})" +ENDM + + printargs A, B, C, D + +MACRO mac + println \(2__) + \(1_2) + \(\1) +x = 2 + println \({d:x}) + \(1_{d:x}) + \(\(\(13))) +ENDM + + mac 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 1 diff --git a/test/asm/parenthetic-macro-args.err b/test/asm/parenthetic-macro-args.err new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/parenthetic-macro-args.out b/test/asm/parenthetic-macro-args.out new file mode 100644 index 00000000..e5bb3394 --- /dev/null +++ b/test/asm/parenthetic-macro-args.out @@ -0,0 +1,6 @@ +first = A +next = B +next = C +last = D +$F0 +$F0