mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Support multi-digit macro arguments in parentheses
This allows access to arguments past \9 without using 'shift'
This commit is contained in:
@@ -717,21 +717,65 @@ static void freeExpansion(struct Expansion *expansion)
|
|||||||
|
|
||||||
static bool isMacroChar(char c)
|
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)
|
static char const *readMacroArg(char name)
|
||||||
{
|
{
|
||||||
char const *str;
|
char const *str;
|
||||||
|
|
||||||
if (name == '@')
|
if (name == '@') {
|
||||||
str = macro_GetUniqueIDStr();
|
str = macro_GetUniqueIDStr();
|
||||||
else if (name == '#')
|
} else if (name == '#') {
|
||||||
str = macro_GetAllArgs();
|
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");
|
fatalerror("Invalid macro argument '\\0'\n");
|
||||||
else
|
} else {
|
||||||
str = macro_GetArg(name - '0');
|
str = macro_GetArg(name - '0');
|
||||||
|
}
|
||||||
|
|
||||||
if (!str)
|
if (!str)
|
||||||
fatalerror("Macro argument '\\%c' not defined\n", name);
|
fatalerror("Macro argument '\\%c' not defined\n", name);
|
||||||
|
|
||||||
@@ -1061,7 +1105,7 @@ static void readAnonLabelRef(char c)
|
|||||||
|
|
||||||
/* Functions to lex numbers of various radixes */
|
/* 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;
|
uint32_t value = baseValue;
|
||||||
|
|
||||||
@@ -1077,10 +1121,10 @@ static void readNumber(int radix, int32_t baseValue)
|
|||||||
value = value * radix + (c - '0');
|
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;
|
uint32_t value = 0, divisor = 1;
|
||||||
|
|
||||||
@@ -1105,20 +1149,20 @@ static void readFractionalPart(void)
|
|||||||
divisor *= 10;
|
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");
|
warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n");
|
||||||
|
|
||||||
/* Cast to unsigned avoids UB if shifting discards bits */
|
/* 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 */
|
/* Cast to unsigned avoids undefined overflow behavior */
|
||||||
uint16_t fractional = (uint16_t)round(value * 65536.0 / divisor);
|
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];
|
char binDigits[2];
|
||||||
|
|
||||||
static void readBinaryNumber(void)
|
static uint32_t readBinaryNumber(void)
|
||||||
{
|
{
|
||||||
uint32_t value = 0;
|
uint32_t value = 0;
|
||||||
|
|
||||||
@@ -1140,10 +1184,10 @@ static void readBinaryNumber(void)
|
|||||||
value = value * 2 + bit;
|
value = value * 2 + bit;
|
||||||
}
|
}
|
||||||
|
|
||||||
yylval.constValue = value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void readHexNumber(void)
|
static uint32_t readHexNumber(void)
|
||||||
{
|
{
|
||||||
uint32_t value = 0;
|
uint32_t value = 0;
|
||||||
bool empty = true;
|
bool empty = true;
|
||||||
@@ -1173,12 +1217,12 @@ static void readHexNumber(void)
|
|||||||
if (empty)
|
if (empty)
|
||||||
error("Invalid integer constant, no digits after '$'\n");
|
error("Invalid integer constant, no digits after '$'\n");
|
||||||
|
|
||||||
yylval.constValue = value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
char gfxDigits[4];
|
char gfxDigits[4];
|
||||||
|
|
||||||
static void readGfxConstant(void)
|
static uint32_t readGfxConstant(void)
|
||||||
{
|
{
|
||||||
uint32_t bp0 = 0, bp1 = 0;
|
uint32_t bp0 = 0, bp1 = 0;
|
||||||
uint8_t width = 0;
|
uint8_t width = 0;
|
||||||
@@ -1215,7 +1259,7 @@ static void readGfxConstant(void)
|
|||||||
warning(WARNING_LARGE_CONSTANT,
|
warning(WARNING_LARGE_CONSTANT,
|
||||||
"Graphics constant is too long, only 8 first pixels considered\n");
|
"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 */
|
/* Functions to read identifiers & keywords */
|
||||||
@@ -1496,6 +1540,7 @@ static void readString(void)
|
|||||||
case '7':
|
case '7':
|
||||||
case '8':
|
case '8':
|
||||||
case '9':
|
case '9':
|
||||||
|
case '(':
|
||||||
shiftChar();
|
shiftChar();
|
||||||
char const *str = readMacroArg(c);
|
char const *str = readMacroArg(c);
|
||||||
|
|
||||||
@@ -1641,6 +1686,7 @@ static size_t appendStringLiteral(size_t i)
|
|||||||
case '7':
|
case '7':
|
||||||
case '8':
|
case '8':
|
||||||
case '9':
|
case '9':
|
||||||
|
case '(':
|
||||||
shiftChar();
|
shiftChar();
|
||||||
char const *str = readMacroArg(c);
|
char const *str = readMacroArg(c);
|
||||||
|
|
||||||
@@ -1828,8 +1874,7 @@ static int yylex_NORMAL(void)
|
|||||||
/* Handle numbers */
|
/* Handle numbers */
|
||||||
|
|
||||||
case '$':
|
case '$':
|
||||||
yylval.constValue = 0;
|
yylval.constValue = readHexNumber();
|
||||||
readHexNumber();
|
|
||||||
/* Attempt to match `$ff00+c` */
|
/* Attempt to match `$ff00+c` */
|
||||||
if (yylval.constValue == 0xff00) {
|
if (yylval.constValue == 0xff00) {
|
||||||
/* Whitespace is ignored anyways */
|
/* Whitespace is ignored anyways */
|
||||||
@@ -1859,10 +1904,10 @@ static int yylex_NORMAL(void)
|
|||||||
case '7':
|
case '7':
|
||||||
case '8':
|
case '8':
|
||||||
case '9':
|
case '9':
|
||||||
readNumber(10, c - '0');
|
yylval.constValue = readNumber(10, c - '0');
|
||||||
if (peek() == '.') {
|
if (peek() == '.') {
|
||||||
shiftChar();
|
shiftChar();
|
||||||
readFractionalPart();
|
yylval.constValue = readFractionalPart(yylval.constValue);
|
||||||
}
|
}
|
||||||
return T_NUMBER;
|
return T_NUMBER;
|
||||||
|
|
||||||
@@ -1872,7 +1917,7 @@ static int yylex_NORMAL(void)
|
|||||||
shiftChar();
|
shiftChar();
|
||||||
return T_OP_LOGICAND;
|
return T_OP_LOGICAND;
|
||||||
} else if (secondChar >= '0' && secondChar <= '7') {
|
} else if (secondChar >= '0' && secondChar <= '7') {
|
||||||
readNumber(8, 0);
|
yylval.constValue = readNumber(8, 0);
|
||||||
return T_NUMBER;
|
return T_NUMBER;
|
||||||
}
|
}
|
||||||
return T_OP_AND;
|
return T_OP_AND;
|
||||||
@@ -1882,12 +1927,11 @@ static int yylex_NORMAL(void)
|
|||||||
if (secondChar != binDigits[0] && secondChar != binDigits[1])
|
if (secondChar != binDigits[0] && secondChar != binDigits[1])
|
||||||
return T_OP_MOD;
|
return T_OP_MOD;
|
||||||
|
|
||||||
yylval.constValue = 0;
|
yylval.constValue = readBinaryNumber();
|
||||||
readBinaryNumber();
|
|
||||||
return T_NUMBER;
|
return T_NUMBER;
|
||||||
|
|
||||||
case '`': /* Gfx constant */
|
case '`': /* Gfx constant */
|
||||||
readGfxConstant();
|
yylval.constValue = readGfxConstant();
|
||||||
return T_NUMBER;
|
return T_NUMBER;
|
||||||
|
|
||||||
/* Handle strings */
|
/* Handle strings */
|
||||||
|
|||||||
@@ -1566,16 +1566,30 @@ The backslash in
|
|||||||
.Ql \[rs]n
|
.Ql \[rs]n
|
||||||
does not need to be escaped because string literals also work as usual inside macro arguments.
|
does not need to be escaped because string literals also work as usual inside macro arguments.
|
||||||
.Pp
|
.Pp
|
||||||
In reality, up to 256 arguments can be passed to a macro, but you can only use the first 9 like this.
|
Since there are only nine digits, you can only access the first nine macro arguments like this.
|
||||||
If you want to use the rest, you need to use the
|
To use the rest, you need to put the multi-digit argument number in parentheses, like
|
||||||
.Ic SHIFT
|
.Ql \[rs](10) .
|
||||||
command.
|
This parenthetic syntax only supports decimal numbers.
|
||||||
.Pp
|
.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
|
.Ic SHIFT
|
||||||
is a special command only available in macros.
|
command, a special command only available in macros.
|
||||||
Very useful in
|
|
||||||
.Ic REPT
|
|
||||||
blocks.
|
|
||||||
It will shift the arguments by one to the left, and decrease
|
It will shift the arguments by one to the left, and decrease
|
||||||
.Dv _NARG
|
.Dv _NARG
|
||||||
by 1.
|
by 1.
|
||||||
@@ -1586,11 +1600,14 @@ will get the value of
|
|||||||
.Ic \[rs]3 ,
|
.Ic \[rs]3 ,
|
||||||
and so forth.
|
and so forth.
|
||||||
.Pp
|
.Pp
|
||||||
This is the only way of accessing the value of arguments from 10 to 256.
|
|
||||||
.Pp
|
|
||||||
.Ic SHIFT
|
.Ic SHIFT
|
||||||
can optionally be given an integer parameter, and will apply the above shifting that number of times.
|
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.
|
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
|
.Ss Printing things during assembly
|
||||||
The
|
The
|
||||||
.Ic PRINT
|
.Ic PRINT
|
||||||
|
|||||||
1
test/asm/invalid-empty-macro-arg.asm
Normal file
1
test/asm/invalid-empty-macro-arg.asm
Normal file
@@ -0,0 +1 @@
|
|||||||
|
\()
|
||||||
2
test/asm/invalid-empty-macro-arg.err
Normal file
2
test/asm/invalid-empty-macro-arg.err
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
FATAL: invalid-empty-macro-arg.asm(1):
|
||||||
|
Empty parenthetic macro argument
|
||||||
0
test/asm/invalid-empty-macro-arg.out
Normal file
0
test/asm/invalid-empty-macro-arg.out
Normal file
1
test/asm/invalid-macro-arg-character.asm
Normal file
1
test/asm/invalid-macro-arg-character.asm
Normal file
@@ -0,0 +1 @@
|
|||||||
|
\(10!)
|
||||||
2
test/asm/invalid-macro-arg-character.err
Normal file
2
test/asm/invalid-macro-arg-character.err
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
FATAL: invalid-macro-arg-character.asm(1):
|
||||||
|
Invalid character in parenthetic macro argument '!'
|
||||||
0
test/asm/invalid-macro-arg-character.out
Normal file
0
test/asm/invalid-macro-arg-character.out
Normal file
17
test/asm/parenthetic-macro-args.asm
Normal file
17
test/asm/parenthetic-macro-args.asm
Normal file
@@ -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
|
||||||
0
test/asm/parenthetic-macro-args.err
Normal file
0
test/asm/parenthetic-macro-args.err
Normal file
6
test/asm/parenthetic-macro-args.out
Normal file
6
test/asm/parenthetic-macro-args.out
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
first = A
|
||||||
|
next = B
|
||||||
|
next = C
|
||||||
|
last = D
|
||||||
|
$F0
|
||||||
|
$F0
|
||||||
Reference in New Issue
Block a user