diff --git a/include/asm/asm.h b/include/asm/asm.h index cbe8fde2..5571a7a6 100644 --- a/include/asm/asm.h +++ b/include/asm/asm.h @@ -39,6 +39,7 @@ extern struct sSymbol *tHashedSymbols[HASHSIZE]; extern struct sSymbol *pPCSymbol; extern bool oDontExpandStrings; -size_t symvaluetostring(char *dest, size_t maxLength, char *sym); +size_t symvaluetostring(char *dest, size_t maxLength, char *sym, + const char *mode); #endif /* RGBDS_ASM_ASM_H */ diff --git a/src/asm/asmy.y b/src/asm/asmy.y index f13cb9be..7defba56 100644 --- a/src/asm/asmy.y +++ b/src/asm/asmy.y @@ -1,7 +1,7 @@ /* * This file is part of RGBDS. * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. + * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors. * * SPDX-License-Identifier: MIT */ @@ -75,7 +75,8 @@ static void bankrangecheck(char *name, uint32_t secttype, int32_t org, out_NewAbsSection(name, secttype, org, bank); } -size_t symvaluetostring(char *dest, size_t maxLength, char *sym) +size_t symvaluetostring(char *dest, size_t maxLength, char *sym, + const char *mode) { size_t length; @@ -83,6 +84,9 @@ size_t symvaluetostring(char *dest, size_t maxLength, char *sym) char *src = sym_GetStringValue(sym); size_t i; + if (mode) + yyerror("Print types are only allowed for numbers"); + for (i = 0; src[i] != 0; i++) { if (i >= maxLength) fatalerror("Symbol value too long to fit buffer"); @@ -94,8 +98,25 @@ size_t symvaluetostring(char *dest, size_t maxLength, char *sym) } else { uint32_t value = sym_GetConstantValue(sym); - int32_t fullLength = snprintf(dest, maxLength + 1, "$%X", - value); + int32_t fullLength; + + /* Special cheat for binary */ + if (mode && !mode[0]) { + char binary[33]; /* 32 bits + 1 terminator */ + char *write_ptr = binary + 32; + fullLength = 0; + binary[32] = 0; + do { + *(--write_ptr) = (value & 1) + '0'; + value >>= 1; + fullLength++; + } while(value); + strncpy(dest, write_ptr, maxLength + 1); + } else { + fullLength = snprintf(dest, maxLength + 1, + mode ? : "$%X", + value); + } if (fullLength < 0) { fatalerror("snprintf encoding error"); diff --git a/src/asm/lexer.c b/src/asm/lexer.c index 18009de8..66d0d33d 100644 --- a/src/asm/lexer.c +++ b/src/asm/lexer.c @@ -1,7 +1,7 @@ /* * This file is part of RGBDS. * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. + * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors. * * SPDX-License-Identifier: MIT */ @@ -599,6 +599,7 @@ size_t yylex_ReadBracketedSymbol(char *dest, size_t index) char ch; size_t i = 0; size_t length, maxLength; + const char *mode = NULL; for (ch = *pLexBuffer; ch != '}' && ch != '"' && ch != '\n'; @@ -612,16 +613,42 @@ size_t yylex_ReadBracketedSymbol(char *dest, size_t index) i += length; else fatalerror("Illegal character escape '%c'", ch); + } else if (ch == ':' && !mode) { /* Only grab 1st colon */ + /* Use a whitelist of modes, which does prevent the + * use of some features such as precision, + * but also avoids a security flaw + */ + const char *acceptedModes = "bxXd"; + /* Binary isn't natively supported, + * so it's handled differently + */ + static const char * const formatSpecifiers[] = { + "", "%x", "%X", "%d" + }; + /* Prevent reading out of bounds! */ + const char *designatedMode; + + if (i != 1) + fatalerror("Print types are exactly 1 character long"); + + designatedMode = strchr(acceptedModes, sym[i - 1]); + if (!designatedMode) + fatalerror("Illegal print type '%c'", + sym[i - 1]); + mode = formatSpecifiers[designatedMode - acceptedModes]; + /* Begin writing the symbol again */ + i = 0; } else { yylex_SymbolWriteChar(sym, i++, ch); } } + /* Properly terminate the string */ yylex_SymbolWriteChar(sym, i, 0); /* It's assumed we're writing to a T_STRING */ maxLength = MAXSTRLEN - index; - length = symvaluetostring(&dest[index], maxLength, sym); + length = symvaluetostring(&dest[index], maxLength, sym, mode); if (*pLexBuffer == '}') pLexBuffer++; diff --git a/src/asm/rgbasm.5 b/src/asm/rgbasm.5 index 9c91664c..babe827c 100644 --- a/src/asm/rgbasm.5 +++ b/src/asm/rgbasm.5 @@ -1079,7 +1079,20 @@ within a string. This will examine the type of the symbol and insert its value accordingly. If symbol is a string symbol, the symbols value is simply copied. If it's a numeric symbol, the value is converted to hexadecimal notation and -inserted as a string. +inserted as a string with a dollar prepended. +.Pp +It's possible to change the way numeric symbols are converted by specifying +a print type like so: +.Sy {d:symbol} +Valid print types are: +.Bl -column -offset indent +.It Sy Print type Ta Sy Format Ta Sy Example +.It Li d Ta Decimal Ta 42 +.It Li x Ta Lowercase hexadecimal Ta 2a +.It Li X Ta Uppercase hexadecimal Ta 2A +.It Li b Ta Binary Ta 101010 +.Pp +Note that print types should only be used with numeric values, not strings. .Pp HINT: The .Sy {symbol} diff --git a/test/asm/bracketed-symbols.asm b/test/asm/bracketed-symbols.asm new file mode 100644 index 00000000..33e7c1e3 --- /dev/null +++ b/test/asm/bracketed-symbols.asm @@ -0,0 +1,20 @@ +X = 42 +PRINTT "{X}\n" +PRINTT "{x:X}\n" +PRINTT "{X:X}\n" +PRINTT "{d:X}\n" +PRINTT "{b:X}\n" + +Y equ 1337 +PRINTT "{b:Y}\n" + +rsreset +R rb 0 +PRINTT "{d:R}\n" + +S equs "You can't format me!" +PRINTT "{X:S}\n" + +SECTION "Test", ROM0 +Label: +PRINTT "{x:Label}\n" diff --git a/test/asm/bracketed-symbols.out b/test/asm/bracketed-symbols.out new file mode 100644 index 00000000..5ebcd6d0 --- /dev/null +++ b/test/asm/bracketed-symbols.out @@ -0,0 +1,12 @@ +ERROR: bracketed-symbols.asm(16): + Print types are only allowed for numbers +ERROR: bracketed-symbols.asm(20): + Expression must have a constant value +$2A +2a +2A +42 +101010 +10100111001 +0 +You can't format me! diff --git a/test/asm/bracketed-symbols.out.pipe b/test/asm/bracketed-symbols.out.pipe new file mode 100644 index 00000000..c4ad9d95 --- /dev/null +++ b/test/asm/bracketed-symbols.out.pipe @@ -0,0 +1,12 @@ +ERROR: -(16): + Print types are only allowed for numbers +ERROR: -(20): + Expression must have a constant value +$2A +2a +2A +42 +101010 +10100111001 +0 +You can't format me!