Support multi-digit macro arguments in parentheses

This allows access to arguments past \9 without using 'shift'
This commit is contained in:
Rangi
2021-04-18 23:26:03 -04:00
committed by Eldred Habert
parent 8230e8165c
commit 637bbbdf43
11 changed files with 125 additions and 35 deletions

View File

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

View File

@@ -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

View File

@@ -0,0 +1 @@
\()

View File

@@ -0,0 +1,2 @@
FATAL: invalid-empty-macro-arg.asm(1):
Empty parenthetic macro argument

View File

View File

@@ -0,0 +1 @@
\(10!)

View File

@@ -0,0 +1,2 @@
FATAL: invalid-macro-arg-character.asm(1):
Invalid character in parenthetic macro argument '!'

View File

View 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

View File

View File

@@ -0,0 +1,6 @@
first = A
next = B
next = C
last = D
$F0
$F0