mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-22 03:02:06 +00:00
Implement PRINT and PRINTLN (#672)
Fixes #669 Closes #368 Closes #624 Deprecate PRINTT, PRINTV, PRINTI, and PRINTF Default STRFMT("%f") to 5 fractional digits like "{f:}" Any use of string formatting will share this default
This commit is contained in:
@@ -220,12 +220,16 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
||||
}
|
||||
} else if (fmt->type == 'f') {
|
||||
/* Special case for fixed-point */
|
||||
if (fmt->fracWidth) {
|
||||
|
||||
/* Default fractional width (C's is 6 for "%f"; here 5 is enough) */
|
||||
uint8_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
|
||||
|
||||
if (fracWidth) {
|
||||
char spec[16]; /* Max "%" + 5-char PRIu32 + ".%0255.f" + terminator */
|
||||
|
||||
snprintf(spec, sizeof(spec), "%%" PRIu32 ".%%0%d.f", fmt->fracWidth);
|
||||
snprintf(spec, sizeof(spec), "%%" PRIu32 ".%%0%d.f", fracWidth);
|
||||
snprintf(valueBuf, sizeof(valueBuf), spec, value >> 16,
|
||||
(value % 65536) / 65536.0 * pow(10, fmt->fracWidth) + 0.5);
|
||||
(value % 65536) / 65536.0 * pow(10, fracWidth) + 0.5);
|
||||
} else {
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, value >> 16);
|
||||
}
|
||||
|
||||
@@ -207,6 +207,8 @@ static struct KeywordMapping {
|
||||
{"STRFMT", T_OP_STRFMT},
|
||||
|
||||
{"INCLUDE", T_POP_INCLUDE},
|
||||
{"PRINT", T_POP_PRINT},
|
||||
{"PRINTLN", T_POP_PRINTLN},
|
||||
{"PRINTT", T_POP_PRINTT},
|
||||
{"PRINTI", T_POP_PRINTI},
|
||||
{"PRINTV", T_POP_PRINTV},
|
||||
@@ -491,7 +493,7 @@ struct KeywordDictNode {
|
||||
uint16_t children[0x60 - ' '];
|
||||
struct KeywordMapping const *keyword;
|
||||
/* Since the keyword structure is invariant, the min number of nodes is known at compile time */
|
||||
} keywordDict[356] = {0}; /* Make sure to keep this correct when adding keywords! */
|
||||
} keywordDict[353] = {0}; /* Make sure to keep this correct when adding keywords! */
|
||||
|
||||
/* Convert a char into its index into the dict */
|
||||
static inline uint8_t dictIndex(char c)
|
||||
@@ -1315,13 +1317,8 @@ static char const *readInterpolation(void)
|
||||
fmt_UseCharacter(&fmt, symName[j]);
|
||||
fmt_FinishCharacters(&fmt);
|
||||
symName[i] = '\0';
|
||||
if (!fmt_IsValid(&fmt)) {
|
||||
if (!fmt_IsValid(&fmt))
|
||||
error("Invalid format spec '%s'\n", symName);
|
||||
} else if (!strcmp(symName, "f")) {
|
||||
/* Format 'f' defaults to '.5f' like PRINTF */
|
||||
fmt.hasFrac = true;
|
||||
fmt.fracWidth = 5;
|
||||
}
|
||||
i = 0; /* Now that format has been set, restart at beginning of string */
|
||||
} else {
|
||||
shiftChars(1);
|
||||
|
||||
@@ -317,6 +317,7 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
|
||||
%type <sVal> relocexpr
|
||||
%type <sVal> relocexpr_no_str
|
||||
%type <nConstValue> const
|
||||
%type <nConstValue> const_no_str
|
||||
%type <nConstValue> uconst
|
||||
%type <nConstValue> rs_uconst
|
||||
%type <nConstValue> const_3bit
|
||||
@@ -399,7 +400,7 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
|
||||
%token T_POP_EQUAL
|
||||
%token T_POP_EQUS
|
||||
|
||||
%token T_POP_INCLUDE T_POP_PRINTF T_POP_PRINTT T_POP_PRINTV T_POP_PRINTI
|
||||
%token T_POP_INCLUDE T_POP_PRINT T_POP_PRINTLN T_POP_PRINTF T_POP_PRINTT T_POP_PRINTV T_POP_PRINTI
|
||||
%token T_POP_IF T_POP_ELIF T_POP_ELSE T_POP_ENDC
|
||||
%token T_POP_EXPORT T_POP_GLOBAL T_POP_XDEF
|
||||
%token T_POP_DB T_POP_DS T_POP_DW T_POP_DL
|
||||
@@ -617,6 +618,8 @@ pseudoop : equ
|
||||
;
|
||||
|
||||
simple_pseudoop : include
|
||||
| print
|
||||
| println
|
||||
| printf
|
||||
| printt
|
||||
| printv
|
||||
@@ -966,16 +969,43 @@ pushc : T_POP_PUSHC { charmap_Push(); }
|
||||
popc : T_POP_POPC { charmap_Pop(); }
|
||||
;
|
||||
|
||||
printt : T_POP_PRINTT string { printf("%s", $2); }
|
||||
print : T_POP_PRINT print_exprs
|
||||
;
|
||||
|
||||
printv : T_POP_PRINTV const { printf("$%" PRIX32, $2); }
|
||||
println : T_POP_PRINTLN { putchar('\n'); }
|
||||
| T_POP_PRINTLN print_exprs { putchar('\n'); }
|
||||
;
|
||||
|
||||
printi : T_POP_PRINTI const { printf("%" PRId32, $2); }
|
||||
print_exprs : print_expr
|
||||
| print_exprs T_COMMA print_expr
|
||||
;
|
||||
|
||||
printf : T_POP_PRINTF const { math_Print($2); }
|
||||
print_expr : const_no_str { printf("$%" PRIX32, $1); }
|
||||
| string { printf("%s", $1); }
|
||||
;
|
||||
|
||||
printt : T_POP_PRINTT string {
|
||||
warning(WARNING_OBSOLETE, "`PRINTT` is deprecated; use `PRINT`\n");
|
||||
printf("%s", $2);
|
||||
}
|
||||
;
|
||||
|
||||
printv : T_POP_PRINTV const {
|
||||
warning(WARNING_OBSOLETE, "`PRINTV` is deprecated; use `PRINT`\n");
|
||||
printf("$%" PRIX32, $2);
|
||||
}
|
||||
;
|
||||
|
||||
printi : T_POP_PRINTI const {
|
||||
warning(WARNING_OBSOLETE, "`PRINTI` is deprecated; use `PRINT` with `STRFMT`\n");
|
||||
printf("%" PRId32, $2);
|
||||
}
|
||||
;
|
||||
|
||||
printf : T_POP_PRINTF const {
|
||||
warning(WARNING_OBSOLETE, "`PRINTF` is deprecated; use `PRINT` with `STRFMT`\n");
|
||||
math_Print($2);
|
||||
}
|
||||
;
|
||||
|
||||
const_3bit : const {
|
||||
@@ -1250,6 +1280,17 @@ const : relocexpr {
|
||||
}
|
||||
;
|
||||
|
||||
const_no_str : relocexpr_no_str {
|
||||
if (!rpn_isKnown(&$1)) {
|
||||
error("Expected constant expression: %s\n",
|
||||
$1.reason);
|
||||
$$ = 0;
|
||||
} else {
|
||||
$$ = $1.nVal;
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
string : T_STRING
|
||||
| T_OP_STRSUB T_LPAREN string T_COMMA uconst T_COMMA uconst T_RPAREN {
|
||||
strsubUTF8($$, $3, $5, $7);
|
||||
|
||||
@@ -269,7 +269,7 @@ prepended.
|
||||
TOPIC equs "life, the universe, and \[rs]"everything\[rs]""
|
||||
ANSWER = 42
|
||||
;\ Prints "The answer to life, the universe, and "everything" is $2A"
|
||||
PRINTT "The answer to {TOPIC} is {ANSWER}\[rs]n"
|
||||
PRINTLN "The answer to {TOPIC} is {ANSWER}"
|
||||
.Ed
|
||||
.Pp
|
||||
Symbol interpolations can be nested, too!
|
||||
@@ -313,6 +313,7 @@ followed by one or more
|
||||
\[en]
|
||||
.Ql 9 .
|
||||
If specified, prints this many digits of a fixed-point fraction.
|
||||
Defaults to 5 digits.
|
||||
.It Ql <type> Ta Specifies the type of value.
|
||||
.El
|
||||
.Pp
|
||||
@@ -334,11 +335,11 @@ Valid print types are:
|
||||
Examples:
|
||||
.Bd -literal -offset indent
|
||||
; Prints "%0010 + $3 == 5"
|
||||
PRINTT STRFMT("%#05b + %#x == %d\n", 2, 3, 2+3)
|
||||
PRINTLN STRFMT("%#05b + %#x == %d", 2, 3, 2+3)
|
||||
; Prints "32% of 20 = 6.40"
|
||||
PRINTT STRFMT("%d%% of %d = %.2f\n", 32, 20, MUL(20.0, 0.32))
|
||||
PRINTLN STRFMT("%d%% of %d = %.2f", 32, 20, MUL(20.0, 0.32))
|
||||
; Prints "Hello world!"
|
||||
PRINTT STRFMT("Hello %s!\n", STRLWR("WORLD"))
|
||||
PRINTLN STRFMT("Hello %s!", STRLWR("WORLD"))
|
||||
.Ed
|
||||
.Pp
|
||||
HINT: The
|
||||
@@ -355,7 +356,7 @@ INDEX = 1{ZERO_STR}{{FMT}:ZERO_NUM}
|
||||
;\ Defines ITEM_100 as "\[rs]"hundredth\[rs]""
|
||||
{NAME}_{d:INDEX} equs "\[rs]"hundredth\[rs]""
|
||||
;\ Prints "ITEM_100 is hundredth"
|
||||
PRINTT STRCAT("{NAME}_{d:INDEX} is ", {NAME}_{d:INDEX})
|
||||
PRINTLN STRCAT("{NAME}_{d:INDEX} is ", {NAME}_{d:INDEX})
|
||||
;\ Purges ITEM_100
|
||||
PURGE {NAME}_{d:INDEX}
|
||||
.Ed
|
||||
@@ -1065,7 +1066,7 @@ This won't work:
|
||||
.Bd -literal -offset indent
|
||||
outer: MACRO
|
||||
inner: MACRO
|
||||
PRINTT "Hello!\[rs]n"
|
||||
PRINTLN "Hello!"
|
||||
ENDM
|
||||
ENDM
|
||||
.Ed
|
||||
@@ -1073,7 +1074,7 @@ ENDM
|
||||
But this will:
|
||||
.Bd -literal -offset indent
|
||||
outer: MACRO
|
||||
definition equs "inner: MACRO\[rs]nPRINTT \[rs]"Hello!\[rs]\[rs]n\[rs]"\[rs]nENDM"
|
||||
definition equs "inner: MACRO\[rs]nPRINTLN \[rs]"Hello!\[rs]"\[rs]nENDM"
|
||||
definition
|
||||
PURGE definition
|
||||
ENDM
|
||||
@@ -1435,22 +1436,22 @@ if you perform further calculations on them.
|
||||
For instance, consider the following:
|
||||
.Bd -literal -offset indent
|
||||
print_double: MACRO
|
||||
PRINTV \[rs]1 * 2
|
||||
PRINTLN \[rs]1 * 2
|
||||
ENDM
|
||||
print_double 1 + 2
|
||||
.Ed
|
||||
.Pp
|
||||
The
|
||||
.Ic PRINTV
|
||||
.Ic PRINTLN
|
||||
statement will expand to
|
||||
.Ql PRINTV 1 + 2 * 2 ,
|
||||
.Ql PRINTLN 1 + 2 * 2 ,
|
||||
which will print 5 and not 6 as you might have expected.
|
||||
.Pp
|
||||
Line continuations work as usual inside macros or lists of macro arguments.
|
||||
However, some characters need to be escaped, as in the following example:
|
||||
.Bd -literal -offset indent
|
||||
PrintMacro: MACRO
|
||||
PRINTT \[rs]1
|
||||
PRINT \[rs]1
|
||||
ENDM
|
||||
|
||||
PrintMacro STRCAT("Hello "\[rs], \[rs]
|
||||
@@ -1490,31 +1491,31 @@ This is the only way of accessing the value of arguments from 10 to 256.
|
||||
.Ic SHIFT
|
||||
can optionally be given an integer parameter, and will apply the above shifting that number of times.
|
||||
.Ss Printing things during assembly
|
||||
The next four commands print text and values to the standard output.
|
||||
The
|
||||
.Ic PRINT
|
||||
and
|
||||
.Ic PRINTLN
|
||||
commands print text and values to the standard output.
|
||||
Useful for debugging macros, or wherever you may feel the need to tell yourself some important information.
|
||||
.Bd -literal -offset indent
|
||||
PRINTT "I'm the greatest programmer in the whole wide world\[rs]n"
|
||||
PRINTI (2 + 3) / 5
|
||||
PRINTV $FF00 + $F0
|
||||
PRINTF MUL(3.14, 3987.0)
|
||||
PRINT "Hello world!\[rs]n"
|
||||
PRINTLN "Hello world!"
|
||||
PRINT _NARG, " arguments\[rs]n"
|
||||
PRINTLN "sum: ", 2+3, " product: ", 2*3
|
||||
PRINTLN "Line #", __LINE__
|
||||
PRINTLN STRFMT("E = %f", 2.718)
|
||||
.Ed
|
||||
.Bl -inset
|
||||
.It Ic PRINTT
|
||||
prints out a string.
|
||||
Be careful to add a line feed
|
||||
.Pq Qq \[rs]n
|
||||
at the end, as it is not added automatically.
|
||||
.It Ic PRINTV
|
||||
prints out an integer value in hexadecimal or, as in the example, the result of a calculation.
|
||||
Unsurprisingly, you can also print out a constant symbol's value.
|
||||
.It Ic PRINTI
|
||||
prints out a signed integer value.
|
||||
.It Ic PRINTF
|
||||
prints out a fixed point value.
|
||||
.It Ic PRINT
|
||||
prints out each of its comma-separated arguments.
|
||||
Numbers are printed as unsigned uppercase hexadecimal with a leading
|
||||
.Ic $ .
|
||||
For different formats, use
|
||||
.Ic STRFMT .
|
||||
.It Ic PRINTLN
|
||||
prints out each of its comma-separated arguments, if any, followed by a line feed
|
||||
.Pq Ql \[rs]n .
|
||||
.El
|
||||
.Pp
|
||||
Be careful that none of those automatically print a line feed; if you need one, use
|
||||
.Ic PRINTT "\[rs]n" .
|
||||
.Ss Automatically repeating blocks of code
|
||||
Suppose you want to unroll a time consuming loop without copy-pasting it.
|
||||
.Ic REPT
|
||||
@@ -1537,14 +1538,12 @@ You can also use
|
||||
.Ic REPT
|
||||
to generate tables on the fly:
|
||||
.Bd -literal -offset indent
|
||||
;\ --
|
||||
;\ -- Generate a 256 byte sine table with values between 0 and 128
|
||||
;\ --
|
||||
ANGLE = 0.0
|
||||
REPT 256
|
||||
db (MUL(64.0, SIN(ANGLE)) + 64.0) >> 16
|
||||
ANGLE = ANGLE+256.0
|
||||
ENDR
|
||||
; Generate a 256-byte sine table with values between 0 and 128
|
||||
ANGLE = 0.0
|
||||
REPT 256
|
||||
db (MUL(64.0, SIN(ANGLE)) + 64.0) >> 16
|
||||
ANGLE = ANGLE + 256.0
|
||||
ENDR
|
||||
.Ed
|
||||
.Pp
|
||||
As in macros, you can also use the escape sequence
|
||||
@@ -1602,9 +1601,9 @@ until it reaches or exceeds
|
||||
For example:
|
||||
.Bd -literal -offset indent
|
||||
FOR V, 4, 25, 5
|
||||
PRINTT "{d:V} "
|
||||
PRINT "{d:V} "
|
||||
ENDR
|
||||
PRINTT "done {d:V}\n"
|
||||
PRINTLN "done {d:V}"
|
||||
.Ed
|
||||
This will print:
|
||||
.Bd -literal -offset indent
|
||||
@@ -1714,11 +1713,11 @@ skip over parts of your code depending on a condition.
|
||||
This is a powerful feature commonly used in macros.
|
||||
.Bd -literal -offset indent
|
||||
IF NUM < 0
|
||||
PRINTT "NUM < 0\[rs]n"
|
||||
PRINTLN "NUM < 0"
|
||||
ELIF NUM == 0
|
||||
PRINTT "NUM == 0\[rs]n"
|
||||
PRINTLN "NUM == 0"
|
||||
ELSE
|
||||
PRINTT "NUM > 0\[rs]n"
|
||||
PRINTLN "NUM > 0"
|
||||
ENDC
|
||||
.Ed
|
||||
.Pp
|
||||
|
||||
Reference in New Issue
Block a user