diff --git a/include/asm/macro.hpp b/include/asm/macro.hpp index fb804176..c18b9de3 100644 --- a/include/asm/macro.hpp +++ b/include/asm/macro.hpp @@ -9,11 +9,11 @@ #include struct MacroArgs { - unsigned int shift; + uint32_t shift; std::vector> args; uint32_t nbArgs() const { return args.size() - shift; } - std::shared_ptr getArg(uint32_t i) const; + std::shared_ptr getArg(int32_t i) const; std::shared_ptr getAllArgs() const; void appendArg(std::shared_ptr arg); diff --git a/man/rgbasm.5 b/man/rgbasm.5 index 9be4296f..5bfd8fbd 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -1877,9 +1877,11 @@ being the second, and so on. Since there are only nine digits, you can only use To use the rest, you put the argument number in angle brackets, like .Ic \e<10> . .Pp -This bracketed syntax supports decimal numbers and numeric symbols. +This bracketed syntax supports decimal numbers and numeric symbols, where negative values count from the last argument. For example, .Ql \e<_NARG> +or +.Ql \e<-1> will get the last argument. .Pp Other macro arguments and symbol interpolations will also be expanded inside the angle brackets. diff --git a/src/asm/lexer.cpp b/src/asm/lexer.cpp index 58337b9a..cc999fed 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -625,13 +625,24 @@ static uint32_t readBracketedMacroArgNum() { lexerState->disableInterpolation = disableInterpolation; }}; - uint32_t num = 0; + int32_t num = 0; int c = peek(); bool empty = false; bool symbolError = false; + bool negative = c == '-'; + + if (negative) { + shiftChar(); + c = peek(); + } if (c >= '0' && c <= '9') { - num = readNumber(10, 0); + uint32_t n = readNumber(10, 0); + if (n > INT32_MAX) { + error("Number in bracketed macro argument is too large\n"); + return 0; + } + num = negative ? -n : static_cast(n); } else if (startsIdentifier(c) || c == '#') { if (c == '#') { shiftChar(); @@ -664,7 +675,7 @@ static uint32_t readBracketedMacroArgNum() { num = 0; symbolError = true; } else { - num = sym->getConstantValue(); + num = static_cast(sym->getConstantValue()); } } else { empty = true; @@ -704,7 +715,7 @@ static std::shared_ptr readMacroArg(char name) { assume(str); // '\#' should always be defined (at least as an empty string) return str; } else if (name == '<') { - uint32_t num = readBracketedMacroArgNum(); + int32_t num = readBracketedMacroArgNum(); if (num == 0) { // The error was already reported by `readBracketedMacroArgNum`. return nullptr; @@ -718,7 +729,7 @@ static std::shared_ptr readMacroArg(char name) { auto str = macroArgs->getArg(num); if (!str) { - error("Macro argument '\\<%" PRIu32 ">' not defined\n", num); + error("Macro argument '\\<%" PRId32 ">' not defined\n", num); } return str; } else { diff --git a/src/asm/macro.cpp b/src/asm/macro.cpp index dc5f3a46..db214785 100644 --- a/src/asm/macro.cpp +++ b/src/asm/macro.cpp @@ -8,10 +8,16 @@ #include "asm/warning.hpp" -std::shared_ptr MacroArgs::getArg(uint32_t i) const { - uint32_t realIndex = i + shift - 1; +std::shared_ptr MacroArgs::getArg(int32_t i) const { + // Bracketed macro arguments adjust negative indexes such that -1 is the last argument. + if (i < 0) { + i += args.size() + 1; + } - return realIndex >= args.size() ? nullptr : args[realIndex]; + int32_t realIndex = i + shift - 1; + + return realIndex < 0 || static_cast(realIndex) >= args.size() ? nullptr + : args[realIndex]; } std::shared_ptr MacroArgs::getAllArgs() const { diff --git a/test/asm/negative-macro-args.asm b/test/asm/negative-macro-args.asm new file mode 100644 index 00000000..c524381a --- /dev/null +++ b/test/asm/negative-macro-args.asm @@ -0,0 +1,21 @@ +MACRO mac + for i, -1, -_NARG - 1, -1 + println "{d:i}: \ == \<{d:i}>" + endr + ; error cases + def i = 0 + println "{d:i}: \ == \<{d:i}>" + def i = -_NARG - 1 + println "{d:i}: \ == \<{d:i}>" + def i = $7fff_ffff + println "{d:i}: \ == \<{d:i}>" + ; signed/unsigned difference error cases + def i = $8000_0000 + println "{d:i}: \ == \<{d:i}>" + println "{u:i}: \ == \<{u:i}>" + def i = $ffff_ffff + println "{d:i}: \ == \<{d:i}>" + println "{u:i}: \ == \<{u:i}>" +ENDM + +mac A, B, C, D, E, F, G diff --git a/test/asm/negative-macro-args.err b/test/asm/negative-macro-args.err new file mode 100644 index 00000000..827e342f --- /dev/null +++ b/test/asm/negative-macro-args.err @@ -0,0 +1,26 @@ +error: negative-macro-args.asm(21) -> negative-macro-args.asm::mac(7): + Invalid bracketed macro argument '\<0>' +error: negative-macro-args.asm(21) -> negative-macro-args.asm::mac(7): + Invalid bracketed macro argument '\<0>' +error: negative-macro-args.asm(21) -> negative-macro-args.asm::mac(9): + Macro argument '\<-8>' not defined +error: negative-macro-args.asm(21) -> negative-macro-args.asm::mac(9): + Macro argument '\<-8>' not defined +error: negative-macro-args.asm(21) -> negative-macro-args.asm::mac(11): + Macro argument '\<2147483647>' not defined +error: negative-macro-args.asm(21) -> negative-macro-args.asm::mac(11): + Macro argument '\<2147483647>' not defined +error: negative-macro-args.asm(21) -> negative-macro-args.asm::mac(14): + Macro argument '\<-2147483648>' not defined +error: negative-macro-args.asm(21) -> negative-macro-args.asm::mac(14): + Number in bracketed macro argument is too large +while expanding symbol "-2147483648" +error: negative-macro-args.asm(21) -> negative-macro-args.asm::mac(15): + Macro argument '\<-2147483648>' not defined +error: negative-macro-args.asm(21) -> negative-macro-args.asm::mac(15): + Number in bracketed macro argument is too large +while expanding symbol "2147483648" +error: negative-macro-args.asm(21) -> negative-macro-args.asm::mac(18): + Number in bracketed macro argument is too large +while expanding symbol "4294967295" +error: Assembly aborted (11 errors)! diff --git a/test/asm/negative-macro-args.out b/test/asm/negative-macro-args.out new file mode 100644 index 00000000..48c32e6b --- /dev/null +++ b/test/asm/negative-macro-args.out @@ -0,0 +1,14 @@ +-1: G == G +-2: F == F +-3: E == E +-4: D == D +-5: C == C +-6: B == B +-7: A == A +0: == +-8: == +2147483647: == +-2147483648: == > +2147483648: == > +-1: G == G +4294967295: G == >