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)
|
||||
{
|
||||
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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
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