mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Implement STRFMT and more printf-like format specifiers for string interpolation (#646)
Fixes #570 Fixes #178 Use errors for inapplicable format spec flags instead of -Wstring-format
This commit is contained in:
1
Makefile
1
Makefile
@@ -54,6 +54,7 @@ all: rgbasm rgblink rgbfix rgbgfx
|
|||||||
|
|
||||||
rgbasm_obj := \
|
rgbasm_obj := \
|
||||||
src/asm/charmap.o \
|
src/asm/charmap.o \
|
||||||
|
src/asm/format.o \
|
||||||
src/asm/fstack.o \
|
src/asm/fstack.o \
|
||||||
src/asm/lexer.o \
|
src/asm/lexer.o \
|
||||||
src/asm/macro.o \
|
src/asm/macro.o \
|
||||||
|
|||||||
63
include/asm/format.h
Normal file
63
include/asm/format.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RGBDS_FORMAT_SPEC_H
|
||||||
|
#define RGBDS_FORMAT_SPEC_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
enum FormatState {
|
||||||
|
FORMAT_SIGN, // expects '+' or ' ' (optional)
|
||||||
|
FORMAT_PREFIX, // expects '#' (optional)
|
||||||
|
FORMAT_ALIGN, // expects '-' (optional)
|
||||||
|
FORMAT_WIDTH, // expects '0'-'9', max 255 (optional) (leading '0' indicates pad)
|
||||||
|
FORMAT_FRAC, // got '.', expects '0'-'9', max 255 (optional)
|
||||||
|
FORMAT_DONE, // got [duXxbofs] (required)
|
||||||
|
FORMAT_INVALID, // got unexpected character
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FormatSpec {
|
||||||
|
enum FormatState state;
|
||||||
|
int sign;
|
||||||
|
bool prefix;
|
||||||
|
bool alignLeft;
|
||||||
|
bool padZero;
|
||||||
|
uint8_t width;
|
||||||
|
bool hasFrac;
|
||||||
|
uint8_t fracWidth;
|
||||||
|
int type;
|
||||||
|
bool valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StrFmtArg {
|
||||||
|
union {
|
||||||
|
uint32_t number;
|
||||||
|
char *string;
|
||||||
|
};
|
||||||
|
bool isNumeric;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define INITIAL_STRFMT_ARG_SIZE 4
|
||||||
|
struct StrFmtArgList {
|
||||||
|
char *format;
|
||||||
|
size_t nbArgs;
|
||||||
|
size_t capacity;
|
||||||
|
struct StrFmtArg *args;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FormatSpec fmt_NewSpec(void);
|
||||||
|
bool fmt_IsEmpty(struct FormatSpec const *fmt);
|
||||||
|
bool fmt_IsValid(struct FormatSpec const *fmt);
|
||||||
|
bool fmt_IsFinished(struct FormatSpec const *fmt);
|
||||||
|
void fmt_UseCharacter(struct FormatSpec *fmt, int c);
|
||||||
|
void fmt_FinishCharacters(struct FormatSpec *fmt);
|
||||||
|
void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, char const *value);
|
||||||
|
void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uint32_t value);
|
||||||
|
|
||||||
|
#endif /* RGBDS_FORMAT_SPEC_H */
|
||||||
@@ -22,7 +22,7 @@ enum WarningID {
|
|||||||
WARNING_EMPTY_ENTRY, /* Empty entry in `db`, `dw` or `dl` */
|
WARNING_EMPTY_ENTRY, /* Empty entry in `db`, `dw` or `dl` */
|
||||||
WARNING_LARGE_CONSTANT, /* Constants too large */
|
WARNING_LARGE_CONSTANT, /* Constants too large */
|
||||||
WARNING_LONG_STR, /* String too long for internal buffers */
|
WARNING_LONG_STR, /* String too long for internal buffers */
|
||||||
WARNING_NESTED_COMMENT, /* Comment-start delimeter in a block comment */
|
WARNING_NESTED_COMMENT, /* Comment-start delimiter in a block comment */
|
||||||
WARNING_OBSOLETE, /* Obsolete things */
|
WARNING_OBSOLETE, /* Obsolete things */
|
||||||
WARNING_SHIFT, /* Shifting undefined behavior */
|
WARNING_SHIFT, /* Shifting undefined behavior */
|
||||||
WARNING_SHIFT_AMOUNT, /* Strange shift amount */
|
WARNING_SHIFT_AMOUNT, /* Strange shift amount */
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ BISON_TARGET(PARSER "asm/parser.y"
|
|||||||
set(rgbasm_src
|
set(rgbasm_src
|
||||||
"${BISON_PARSER_OUTPUT_SOURCE}"
|
"${BISON_PARSER_OUTPUT_SOURCE}"
|
||||||
"asm/charmap.c"
|
"asm/charmap.c"
|
||||||
|
"asm/format.c"
|
||||||
"asm/fstack.c"
|
"asm/fstack.c"
|
||||||
"asm/lexer.c"
|
"asm/lexer.c"
|
||||||
"asm/macro.c"
|
"asm/macro.c"
|
||||||
|
|||||||
296
src/asm/format.c
Normal file
296
src/asm/format.c
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "asm/format.h"
|
||||||
|
#include "asm/warning.h"
|
||||||
|
|
||||||
|
struct FormatSpec fmt_NewSpec(void)
|
||||||
|
{
|
||||||
|
struct FormatSpec fmt = {0};
|
||||||
|
|
||||||
|
return fmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fmt_IsEmpty(struct FormatSpec const *fmt)
|
||||||
|
{
|
||||||
|
return !fmt->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fmt_IsValid(struct FormatSpec const *fmt)
|
||||||
|
{
|
||||||
|
return fmt->valid || fmt->state == FORMAT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fmt_IsFinished(struct FormatSpec const *fmt)
|
||||||
|
{
|
||||||
|
return fmt->state >= FORMAT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fmt_UseCharacter(struct FormatSpec *fmt, int c)
|
||||||
|
{
|
||||||
|
if (fmt->state == FORMAT_INVALID)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
/* sign */
|
||||||
|
case ' ':
|
||||||
|
case '+':
|
||||||
|
if (fmt->state > FORMAT_SIGN)
|
||||||
|
goto invalid;
|
||||||
|
fmt->state = FORMAT_PREFIX;
|
||||||
|
fmt->sign = c;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* prefix */
|
||||||
|
case '#':
|
||||||
|
if (fmt->state > FORMAT_PREFIX)
|
||||||
|
goto invalid;
|
||||||
|
fmt->state = FORMAT_ALIGN;
|
||||||
|
fmt->prefix = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* align */
|
||||||
|
case '-':
|
||||||
|
if (fmt->state > FORMAT_ALIGN)
|
||||||
|
goto invalid;
|
||||||
|
fmt->state = FORMAT_WIDTH;
|
||||||
|
fmt->alignLeft = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* pad and width */
|
||||||
|
case '0':
|
||||||
|
if (fmt->state < FORMAT_WIDTH)
|
||||||
|
fmt->padZero = true;
|
||||||
|
/* fallthrough */
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
if (fmt->state < FORMAT_WIDTH) {
|
||||||
|
fmt->state = FORMAT_WIDTH;
|
||||||
|
fmt->width = c - '0';
|
||||||
|
} else if (fmt->state == FORMAT_WIDTH) {
|
||||||
|
fmt->width = fmt->width * 10 + (c - '0');
|
||||||
|
} else if (fmt->state == FORMAT_FRAC) {
|
||||||
|
fmt->fracWidth = fmt->fracWidth * 10 + (c - '0');
|
||||||
|
} else {
|
||||||
|
goto invalid;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '.':
|
||||||
|
if (fmt->state > FORMAT_WIDTH)
|
||||||
|
goto invalid;
|
||||||
|
fmt->state = FORMAT_FRAC;
|
||||||
|
fmt->hasFrac = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* type */
|
||||||
|
case 'd':
|
||||||
|
case 'u':
|
||||||
|
case 'X':
|
||||||
|
case 'x':
|
||||||
|
case 'b':
|
||||||
|
case 'o':
|
||||||
|
case 'f':
|
||||||
|
case 's':
|
||||||
|
if (fmt->state >= FORMAT_DONE)
|
||||||
|
goto invalid;
|
||||||
|
fmt->state = FORMAT_DONE;
|
||||||
|
fmt->valid = true;
|
||||||
|
fmt->type = c;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
invalid:
|
||||||
|
fmt->state = FORMAT_INVALID;
|
||||||
|
fmt->valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fmt_FinishCharacters(struct FormatSpec *fmt)
|
||||||
|
{
|
||||||
|
if (!fmt_IsValid(fmt))
|
||||||
|
fmt->state = FORMAT_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, char const *value)
|
||||||
|
{
|
||||||
|
if (fmt->sign)
|
||||||
|
error("Formatting string with sign flag '%c'\n", fmt->sign);
|
||||||
|
if (fmt->prefix)
|
||||||
|
error("Formatting string with prefix flag '#'\n");
|
||||||
|
if (fmt->padZero)
|
||||||
|
error("Formatting string with padding flag '0'\n");
|
||||||
|
if (fmt->hasFrac)
|
||||||
|
error("Formatting string with fractional width\n");
|
||||||
|
if (fmt->type != 's')
|
||||||
|
error("Formatting string as type '%c'\n", fmt->type);
|
||||||
|
|
||||||
|
size_t len = strlen(value);
|
||||||
|
size_t totalLen = fmt->width > len ? fmt->width : len;
|
||||||
|
|
||||||
|
if (totalLen + 1 > bufLen) /* bufLen includes terminator */
|
||||||
|
error("Formatted string value too long\n");
|
||||||
|
|
||||||
|
size_t padLen = fmt->width > len ? fmt->width - len : 0;
|
||||||
|
|
||||||
|
if (fmt->alignLeft) {
|
||||||
|
strncpy(buf, value, len < bufLen ? len : bufLen);
|
||||||
|
for (size_t i = 0; i < totalLen && len + i < bufLen; i++)
|
||||||
|
buf[len + i] = ' ';
|
||||||
|
} else {
|
||||||
|
for (size_t i = 0; i < padLen && i < bufLen; i++)
|
||||||
|
buf[i] = ' ';
|
||||||
|
if (bufLen > padLen)
|
||||||
|
strncpy(buf + padLen, value, bufLen - padLen - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[totalLen] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uint32_t value)
|
||||||
|
{
|
||||||
|
if (fmt->type != 'X' && fmt->type != 'x' && fmt->type != 'b' && fmt->type != 'o'
|
||||||
|
&& fmt->prefix)
|
||||||
|
error("Formatting type '%c' with prefix flag '#'\n", fmt->type);
|
||||||
|
if (fmt->type != 'f' && fmt->hasFrac)
|
||||||
|
error("Formatting type '%c' with fractional width\n", fmt->type);
|
||||||
|
if (fmt->type == 's')
|
||||||
|
error("Formatting number as type 's'\n");
|
||||||
|
|
||||||
|
char sign = fmt->sign; /* 0 or ' ' or '+' */
|
||||||
|
|
||||||
|
if (fmt->type == 'd' || fmt->type == 'f') {
|
||||||
|
int32_t v = value;
|
||||||
|
|
||||||
|
if (v < 0) {
|
||||||
|
sign = '-';
|
||||||
|
if (v != INT32_MIN)
|
||||||
|
value = -v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char prefix = !fmt->prefix ? 0
|
||||||
|
: fmt->type == 'X' ? '$'
|
||||||
|
: fmt->type == 'x' ? '$'
|
||||||
|
: fmt->type == 'b' ? '%'
|
||||||
|
: fmt->type == 'o' ? '&'
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
char valueBuf[262]; /* Max 5 digits + decimal + 255 fraction digits + terminator */
|
||||||
|
|
||||||
|
if (fmt->type == 'b') {
|
||||||
|
/* Special case for binary */
|
||||||
|
char *ptr = valueBuf;
|
||||||
|
|
||||||
|
do {
|
||||||
|
*ptr++ = (value & 1) + '0';
|
||||||
|
value >>= 1;
|
||||||
|
} while (value);
|
||||||
|
|
||||||
|
*ptr = '\0';
|
||||||
|
|
||||||
|
/* Reverse the digits */
|
||||||
|
size_t valueLen = ptr - valueBuf;
|
||||||
|
|
||||||
|
for (size_t i = 0, j = valueLen - 1; i < j; i++, j--) {
|
||||||
|
char c = valueBuf[i];
|
||||||
|
|
||||||
|
valueBuf[i] = valueBuf[j];
|
||||||
|
valueBuf[j] = c;
|
||||||
|
}
|
||||||
|
} else if (fmt->type == 'f') {
|
||||||
|
/* Special case for fixed-point */
|
||||||
|
if (fmt->fracWidth) {
|
||||||
|
char spec[16]; /* Max "%" + 5-char PRIu32 + ".%0255.f" + terminator */
|
||||||
|
|
||||||
|
snprintf(spec, sizeof(spec), "%%" PRIu32 ".%%0%d.f", fmt->fracWidth);
|
||||||
|
snprintf(valueBuf, sizeof(valueBuf), spec, value >> 16,
|
||||||
|
(value % 65536) / 65536.0 * pow(10, fmt->fracWidth) + 0.5);
|
||||||
|
} else {
|
||||||
|
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, value >> 16);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
char const *spec = fmt->type == 'd' ? "%" PRId32
|
||||||
|
: fmt->type == 'u' ? "%" PRIu32
|
||||||
|
: fmt->type == 'X' ? "%" PRIX32
|
||||||
|
: fmt->type == 'x' ? "%" PRIx32
|
||||||
|
: fmt->type == 'o' ? "%" PRIo32
|
||||||
|
: "%" PRId32;
|
||||||
|
|
||||||
|
snprintf(valueBuf, sizeof(valueBuf), spec, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = strlen(valueBuf);
|
||||||
|
size_t numLen = len;
|
||||||
|
|
||||||
|
if (sign)
|
||||||
|
numLen++;
|
||||||
|
if (prefix)
|
||||||
|
numLen++;
|
||||||
|
|
||||||
|
size_t totalLen = fmt->width > numLen ? fmt->width : numLen;
|
||||||
|
|
||||||
|
if (totalLen + 1 > bufLen) /* bufLen includes terminator */
|
||||||
|
error("Formatted numeric value too long\n");
|
||||||
|
|
||||||
|
size_t padLen = fmt->width > numLen ? fmt->width - numLen : 0;
|
||||||
|
|
||||||
|
if (fmt->alignLeft) {
|
||||||
|
size_t pos = 0;
|
||||||
|
|
||||||
|
if (sign && pos < bufLen)
|
||||||
|
buf[pos++] = sign;
|
||||||
|
if (prefix && pos < bufLen)
|
||||||
|
buf[pos++] = prefix;
|
||||||
|
|
||||||
|
strcpy(buf + pos, valueBuf);
|
||||||
|
pos += len;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < totalLen && pos + i < bufLen; i++)
|
||||||
|
buf[pos + i] = ' ';
|
||||||
|
} else {
|
||||||
|
size_t pos = 0;
|
||||||
|
|
||||||
|
if (fmt->padZero) {
|
||||||
|
/* sign, then prefix, then zero padding */
|
||||||
|
if (sign && pos < bufLen)
|
||||||
|
buf[pos++] = sign;
|
||||||
|
if (prefix && pos < bufLen)
|
||||||
|
buf[pos++] = prefix;
|
||||||
|
for (size_t i = 0; i < padLen && pos < bufLen; i++)
|
||||||
|
buf[pos++] = '0';
|
||||||
|
} else {
|
||||||
|
/* space padding, then sign, then prefix */
|
||||||
|
for (size_t i = 0; i < padLen && pos < bufLen; i++)
|
||||||
|
buf[pos++] = ' ';
|
||||||
|
if (sign && pos < bufLen)
|
||||||
|
buf[pos++] = sign;
|
||||||
|
if (prefix && pos < bufLen)
|
||||||
|
buf[pos++] = prefix;
|
||||||
|
}
|
||||||
|
if (bufLen > pos)
|
||||||
|
strcpy(buf + pos, valueBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[totalLen] = '\0';
|
||||||
|
}
|
||||||
108
src/asm/lexer.c
108
src/asm/lexer.c
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#include "asm/asm.h"
|
#include "asm/asm.h"
|
||||||
#include "asm/lexer.h"
|
#include "asm/lexer.h"
|
||||||
|
#include "asm/format.h"
|
||||||
#include "asm/fstack.h"
|
#include "asm/fstack.h"
|
||||||
#include "asm/macro.h"
|
#include "asm/macro.h"
|
||||||
#include "asm/main.h"
|
#include "asm/main.h"
|
||||||
@@ -201,6 +202,7 @@ static struct KeywordMapping {
|
|||||||
{"STRCAT", T_OP_STRCAT},
|
{"STRCAT", T_OP_STRCAT},
|
||||||
{"STRUPR", T_OP_STRUPR},
|
{"STRUPR", T_OP_STRUPR},
|
||||||
{"STRLWR", T_OP_STRLWR},
|
{"STRLWR", T_OP_STRLWR},
|
||||||
|
{"STRFMT", T_OP_STRFMT},
|
||||||
|
|
||||||
{"INCLUDE", T_POP_INCLUDE},
|
{"INCLUDE", T_POP_INCLUDE},
|
||||||
{"PRINTT", T_POP_PRINTT},
|
{"PRINTT", T_POP_PRINTT},
|
||||||
@@ -480,7 +482,7 @@ struct KeywordDictNode {
|
|||||||
uint16_t children[0x60 - ' '];
|
uint16_t children[0x60 - ' '];
|
||||||
struct KeywordMapping const *keyword;
|
struct KeywordMapping const *keyword;
|
||||||
/* Since the keyword structure is invariant, the min number of nodes is known at compile time */
|
/* Since the keyword structure is invariant, the min number of nodes is known at compile time */
|
||||||
} keywordDict[347] = {0}; /* Make sure to keep this correct when adding keywords! */
|
} keywordDict[350] = {0}; /* Make sure to keep this correct when adding keywords! */
|
||||||
|
|
||||||
/* Convert a char into its index into the dict */
|
/* Convert a char into its index into the dict */
|
||||||
static inline uint8_t dictIndex(char c)
|
static inline uint8_t dictIndex(char c)
|
||||||
@@ -1273,57 +1275,11 @@ static int readIdentifier(char firstChar)
|
|||||||
|
|
||||||
/* Functions to read strings */
|
/* Functions to read strings */
|
||||||
|
|
||||||
enum PrintType {
|
|
||||||
TYPE_NONE,
|
|
||||||
TYPE_DECIMAL, /* d */
|
|
||||||
TYPE_UPPERHEX, /* X */
|
|
||||||
TYPE_LOWERHEX, /* x */
|
|
||||||
TYPE_BINARY, /* b */
|
|
||||||
};
|
|
||||||
|
|
||||||
static void intToString(char *dest, size_t bufSize, struct Symbol const *sym, enum PrintType type)
|
|
||||||
{
|
|
||||||
uint32_t value = sym_GetConstantSymValue(sym);
|
|
||||||
int fullLength;
|
|
||||||
|
|
||||||
/* Special cheat for binary */
|
|
||||||
if (type == TYPE_BINARY) {
|
|
||||||
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, bufSize - 1);
|
|
||||||
} else {
|
|
||||||
static char const * const formats[] = {
|
|
||||||
[TYPE_NONE] = "$%" PRIX32,
|
|
||||||
[TYPE_DECIMAL] = "%" PRId32,
|
|
||||||
[TYPE_UPPERHEX] = "%" PRIX32,
|
|
||||||
[TYPE_LOWERHEX] = "%" PRIx32
|
|
||||||
};
|
|
||||||
|
|
||||||
fullLength = snprintf(dest, bufSize, formats[type], value);
|
|
||||||
if (fullLength < 0) {
|
|
||||||
error("snprintf encoding error: %s\n", strerror(errno));
|
|
||||||
dest[0] = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((size_t)fullLength >= bufSize)
|
|
||||||
warning(WARNING_LONG_STR, "Interpolated symbol %s too long to fit buffer\n",
|
|
||||||
sym->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static char const *readInterpolation(void)
|
static char const *readInterpolation(void)
|
||||||
{
|
{
|
||||||
char symName[MAXSYMLEN + 1];
|
char symName[MAXSYMLEN + 1];
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
enum PrintType type = TYPE_NONE;
|
struct FormatSpec fmt = fmt_NewSpec();
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int c = peek(0);
|
int c = peek(0);
|
||||||
@@ -1342,33 +1298,24 @@ static char const *readInterpolation(void)
|
|||||||
} else if (c == '}') {
|
} else if (c == '}') {
|
||||||
shiftChars(1);
|
shiftChars(1);
|
||||||
break;
|
break;
|
||||||
} else if (c == ':' && type == TYPE_NONE) { /* Print type, only once */
|
} else if (c == ':' && !fmt_IsFinished(&fmt)) { /* Format spec, only once */
|
||||||
if (i != 1) {
|
|
||||||
error("Print types are exactly 1 character long\n");
|
|
||||||
} else {
|
|
||||||
switch (symName[0]) {
|
|
||||||
case 'b':
|
|
||||||
type = TYPE_BINARY;
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
type = TYPE_DECIMAL;
|
|
||||||
break;
|
|
||||||
case 'X':
|
|
||||||
type = TYPE_UPPERHEX;
|
|
||||||
break;
|
|
||||||
case 'x':
|
|
||||||
type = TYPE_LOWERHEX;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
error("Invalid print type '%s'\n", print(symName[0]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i = 0; /* Now that type has been set, restart at beginning of string */
|
|
||||||
shiftChars(1);
|
shiftChars(1);
|
||||||
|
for (size_t j = 0; j < i; j++)
|
||||||
|
fmt_UseCharacter(&fmt, symName[j]);
|
||||||
|
fmt_FinishCharacters(&fmt);
|
||||||
|
symName[i] = '\0';
|
||||||
|
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 {
|
} else {
|
||||||
|
shiftChars(1);
|
||||||
if (i < sizeof(symName)) /* Allow writing an extra char to flag overflow */
|
if (i < sizeof(symName)) /* Allow writing an extra char to flag overflow */
|
||||||
symName[i++] = c;
|
symName[i++] = c;
|
||||||
shiftChars(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1378,18 +1325,25 @@ static char const *readInterpolation(void)
|
|||||||
}
|
}
|
||||||
symName[i] = '\0';
|
symName[i] = '\0';
|
||||||
|
|
||||||
|
static char buf[MAXSTRLEN + 1];
|
||||||
|
|
||||||
struct Symbol const *sym = sym_FindScopedSymbol(symName);
|
struct Symbol const *sym = sym_FindScopedSymbol(symName);
|
||||||
|
|
||||||
if (!sym) {
|
if (!sym) {
|
||||||
error("Interpolated symbol \"%s\" does not exist\n", symName);
|
error("Interpolated symbol \"%s\" does not exist\n", symName);
|
||||||
} else if (sym->type == SYM_EQUS) {
|
} else if (sym->type == SYM_EQUS) {
|
||||||
if (type != TYPE_NONE)
|
if (fmt_IsEmpty(&fmt))
|
||||||
error("Print types are only allowed for numbers\n");
|
/* No format was specified */
|
||||||
return sym_GetStringValue(sym);
|
fmt.type = 's';
|
||||||
|
fmt_PrintString(buf, sizeof(buf), &fmt, sym_GetStringValue(sym));
|
||||||
|
return buf;
|
||||||
} else if (sym_IsNumeric(sym)) {
|
} else if (sym_IsNumeric(sym)) {
|
||||||
static char buf[33]; /* Worst case of 32 digits + terminator */
|
if (fmt_IsEmpty(&fmt)) {
|
||||||
|
/* No format was specified; default to uppercase $hex */
|
||||||
intToString(buf, sizeof(buf), sym, type);
|
fmt.type = 'X';
|
||||||
|
fmt.prefix = true;
|
||||||
|
}
|
||||||
|
fmt_PrintNumber(buf, sizeof(buf), &fmt, sym_GetConstantSymValue(sym));
|
||||||
return buf;
|
return buf;
|
||||||
} else {
|
} else {
|
||||||
error("Only numerical and string symbols can be interpolated\n");
|
error("Only numerical and string symbols can be interpolated\n");
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "asm/charmap.h"
|
#include "asm/charmap.h"
|
||||||
|
#include "asm/format.h"
|
||||||
#include "asm/fstack.h"
|
#include "asm/fstack.h"
|
||||||
#include "asm/lexer.h"
|
#include "asm/lexer.h"
|
||||||
#include "asm/main.h"
|
#include "asm/main.h"
|
||||||
|
|||||||
146
src/asm/parser.y
146
src/asm/parser.y
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include "asm/asm.h"
|
#include "asm/asm.h"
|
||||||
#include "asm/charmap.h"
|
#include "asm/charmap.h"
|
||||||
|
#include "asm/format.h"
|
||||||
#include "asm/fstack.h"
|
#include "asm/fstack.h"
|
||||||
#include "asm/lexer.h"
|
#include "asm/lexer.h"
|
||||||
#include "asm/macro.h"
|
#include "asm/macro.h"
|
||||||
@@ -163,6 +164,106 @@ static void strsubUTF8(char *dest, const char *src, uint32_t pos, uint32_t len)
|
|||||||
dest[destIndex] = 0;
|
dest[destIndex] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void initStrFmtArgList(struct StrFmtArgList *args) {
|
||||||
|
args->nbArgs = 0;
|
||||||
|
args->capacity = INITIAL_STRFMT_ARG_SIZE;
|
||||||
|
args->args = malloc(args->capacity * sizeof(*args->args));
|
||||||
|
if (!args->args)
|
||||||
|
fatalerror("Failed to allocate memory for STRFMT arg list: %s\n",
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t nextStrFmtArgListIndex(struct StrFmtArgList *args) {
|
||||||
|
if (args->nbArgs == args->capacity) {
|
||||||
|
args->capacity = (args->capacity + 1) * 2;
|
||||||
|
args->args = realloc(args->args, args->capacity * sizeof(*args->args));
|
||||||
|
if (!args->args)
|
||||||
|
fatalerror("realloc error while resizing STRFMT arg list: %s\n",
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
return args->nbArgs++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void freeStrFmtArgList(struct StrFmtArgList *args) {
|
||||||
|
free(args->format);
|
||||||
|
for (size_t i = 0; i < args->nbArgs; i++)
|
||||||
|
if (!args->args[i].isNumeric)
|
||||||
|
free(args->args[i].string);
|
||||||
|
free(args->args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void strfmt(char *dest, size_t destLen, char const *fmt, size_t nbArgs, struct StrFmtArg *args) {
|
||||||
|
size_t a = 0;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < destLen;) {
|
||||||
|
int c = *fmt++;
|
||||||
|
|
||||||
|
if (c == '\0') {
|
||||||
|
break;
|
||||||
|
} else if (c != '%') {
|
||||||
|
dest[i++] = c;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = *fmt++;
|
||||||
|
|
||||||
|
if (c == '%') {
|
||||||
|
dest[i++] = c;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FormatSpec spec = fmt_NewSpec();
|
||||||
|
|
||||||
|
while (c != '\0') {
|
||||||
|
fmt_UseCharacter(&spec, c);
|
||||||
|
if (fmt_IsFinished(&spec))
|
||||||
|
break;
|
||||||
|
c = *fmt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fmt_IsEmpty(&spec)) {
|
||||||
|
error("STRFMT: Illegal '%%' at end of format string\n");
|
||||||
|
dest[i++] = '%';
|
||||||
|
break;
|
||||||
|
} else if (!fmt_IsValid(&spec)) {
|
||||||
|
error("STRFMT: Invalid format spec for argument %zu\n", a + 1);
|
||||||
|
dest[i++] = '%';
|
||||||
|
a++;
|
||||||
|
continue;
|
||||||
|
} else if (a == nbArgs) {
|
||||||
|
error("STRFMT: Not enough arguments for format spec\n", a + 1);
|
||||||
|
dest[i++] = '%';
|
||||||
|
a++;
|
||||||
|
continue;
|
||||||
|
} else if (a > nbArgs) {
|
||||||
|
// already warned for a == nbArgs
|
||||||
|
dest[i++] = '%';
|
||||||
|
a++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StrFmtArg *arg = &args[a++];
|
||||||
|
static char buf[MAXSTRLEN + 1];
|
||||||
|
|
||||||
|
if (arg->isNumeric)
|
||||||
|
fmt_PrintNumber(buf, sizeof(buf), &spec, arg->number);
|
||||||
|
else
|
||||||
|
fmt_PrintString(buf, sizeof(buf), &spec, arg->string);
|
||||||
|
|
||||||
|
i += snprintf(&dest[i], destLen - i, "%s", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > destLen - 1) {
|
||||||
|
warning(WARNING_LONG_STR, "STRFMT: String too long, got truncated\n");
|
||||||
|
i = destLen - 1;
|
||||||
|
}
|
||||||
|
dest[i] = '\0';
|
||||||
|
|
||||||
|
if (a < nbArgs)
|
||||||
|
error("STRFMT: %zu unformatted argument(s)\n", nbArgs - a);
|
||||||
|
}
|
||||||
|
|
||||||
static inline void failAssert(enum AssertionType type)
|
static inline void failAssert(enum AssertionType type)
|
||||||
{
|
{
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -210,6 +311,7 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
|
|||||||
int32_t stop;
|
int32_t stop;
|
||||||
int32_t step;
|
int32_t step;
|
||||||
} foreachArgs;
|
} foreachArgs;
|
||||||
|
struct StrFmtArgList strfmtArgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
%type <sVal> relocexpr
|
%type <sVal> relocexpr
|
||||||
@@ -226,6 +328,8 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
|
|||||||
|
|
||||||
%type <tzString> string
|
%type <tzString> string
|
||||||
%type <tzString> strcat_args
|
%type <tzString> strcat_args
|
||||||
|
%type <strfmtArgs> strfmt_args
|
||||||
|
%type <strfmtArgs> strfmt_va_args
|
||||||
|
|
||||||
%type <nConstValue> sectorg
|
%type <nConstValue> sectorg
|
||||||
%type <sectSpec> sectattrs
|
%type <sectSpec> sectattrs
|
||||||
@@ -275,6 +379,7 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
|
|||||||
%left T_OP_STRCAT
|
%left T_OP_STRCAT
|
||||||
%left T_OP_STRUPR
|
%left T_OP_STRUPR
|
||||||
%left T_OP_STRLWR
|
%left T_OP_STRLWR
|
||||||
|
%left T_OP_STRFMT
|
||||||
|
|
||||||
%left NEG /* negation -- unary minus */
|
%left NEG /* negation -- unary minus */
|
||||||
|
|
||||||
@@ -1136,6 +1241,10 @@ string : T_STRING
|
|||||||
| T_OP_STRLWR T_LPAREN string T_RPAREN {
|
| T_OP_STRLWR T_LPAREN string T_RPAREN {
|
||||||
lowerstring($$, $3);
|
lowerstring($$, $3);
|
||||||
}
|
}
|
||||||
|
| T_OP_STRFMT T_LPAREN strfmt_args T_RPAREN {
|
||||||
|
strfmt($$, MAXSTRLEN + 1, $3.format, $3.nbArgs, $3.args);
|
||||||
|
freeStrFmtArgList(&$3);
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
strcat_args : string
|
strcat_args : string
|
||||||
@@ -1146,6 +1255,43 @@ strcat_args : string
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
strfmt_args : string strfmt_va_args {
|
||||||
|
$$.format = strdup($1);
|
||||||
|
$$.capacity = $2.capacity;
|
||||||
|
$$.nbArgs = $2.nbArgs;
|
||||||
|
$$.args = $2.args;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
strfmt_va_args : /* empty */ {
|
||||||
|
initStrFmtArgList(&$$);
|
||||||
|
}
|
||||||
|
| strfmt_va_args T_COMMA relocexpr_no_str {
|
||||||
|
int32_t value;
|
||||||
|
|
||||||
|
if (!rpn_isKnown(&$3)) {
|
||||||
|
error("Expected constant expression: %s\n",
|
||||||
|
$3.reason);
|
||||||
|
value = 0;
|
||||||
|
} else {
|
||||||
|
value = $3.nVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t i = nextStrFmtArgListIndex(&$1);
|
||||||
|
|
||||||
|
$1.args[i].number = value;
|
||||||
|
$1.args[i].isNumeric = true;
|
||||||
|
$$ = $1;
|
||||||
|
}
|
||||||
|
| strfmt_va_args T_COMMA string {
|
||||||
|
size_t i = nextStrFmtArgListIndex(&$1);
|
||||||
|
|
||||||
|
$1.args[i].string = strdup($3);
|
||||||
|
$1.args[i].isNumeric = false;
|
||||||
|
$$ = $1;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
section : T_POP_SECTION sectmod string T_COMMA sectiontype sectorg sectattrs {
|
section : T_POP_SECTION sectmod string T_COMMA sectiontype sectorg sectattrs {
|
||||||
out_NewSection($3, $5, $6, &$7, $2);
|
out_NewSection($3, $5, $6, &$7, $2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,18 +268,72 @@ PRINTT "The answer to {TOPIC} is {ANSWER}\[rs]n"
|
|||||||
.Pp
|
.Pp
|
||||||
Symbol interpolations can be nested, too!
|
Symbol interpolations can be nested, too!
|
||||||
.Pp
|
.Pp
|
||||||
It's possible to change the way numeric symbols are converted by specifying a print type like so:
|
It's possible to change the way symbols are converted by specifying a print format like so:
|
||||||
.Ql {d:symbol} .
|
.Ql {fmt:symbol} .
|
||||||
|
The
|
||||||
|
.Ql fmt
|
||||||
|
specifier consists of parts
|
||||||
|
.Ql <sign><prefix><align><pad><width><frac><type> .
|
||||||
|
These parts are:
|
||||||
|
.Bl -column "<prefix>"
|
||||||
|
.It Sy Part Ta Sy Meaning
|
||||||
|
.It Ql <sign> Ta May be
|
||||||
|
.Ql +
|
||||||
|
or
|
||||||
|
.Ql \ .
|
||||||
|
If specified, prints this character in front of non-negative numbers.
|
||||||
|
.It Ql <prefix> Ta May be
|
||||||
|
.Ql # .
|
||||||
|
If specified, prints the appropriate prefix for numbers,
|
||||||
|
.Ql $ ,
|
||||||
|
.Ql & ,
|
||||||
|
or
|
||||||
|
.Ql % .
|
||||||
|
.It Ql <align> Ta May be
|
||||||
|
.Ql - .
|
||||||
|
If specified, aligns left instead of right.
|
||||||
|
.It Ql <pad> Ta May be
|
||||||
|
.Ql 0 .
|
||||||
|
If specified, pads right-aligned numbers with zeros instead of spaces.
|
||||||
|
.It Ql <width> Ta May be one or more
|
||||||
|
.Ql 0
|
||||||
|
\[en]
|
||||||
|
.Ql 9 .
|
||||||
|
If specified, pads the value to this width, right-aligned with spaces by default.
|
||||||
|
.It Ql <frac> Ta May be
|
||||||
|
.Ql \&.
|
||||||
|
followed by one or more
|
||||||
|
.Ql 0
|
||||||
|
\[en]
|
||||||
|
.Ql 9 .
|
||||||
|
If specified, prints this many digits of a fixed-point fraction.
|
||||||
|
.It Ql <type> Ta Specifies the type of value.
|
||||||
|
.El
|
||||||
|
.Pp
|
||||||
|
All the format specifier parts are optional except the
|
||||||
|
.Ql <type> .
|
||||||
Valid print types are:
|
Valid print types are:
|
||||||
.Bl -column -offset indent "Print type" "Lowercase hexadecimal" "Example"
|
.Bl -column -offset indent "Print type" "Lowercase hexadecimal" "Example"
|
||||||
.It Sy Print type Ta Sy Format Ta Sy Example
|
.It Sy Print type Ta Sy Format Ta Sy Example
|
||||||
.It Ql d Ta Decimal Ta 42
|
.It Ql d Ta Signed decimal Ta -42
|
||||||
|
.It Ql u Ta Unsigned decimal Ta 42
|
||||||
.It Ql x Ta Lowercase hexadecimal Ta 2a
|
.It Ql x Ta Lowercase hexadecimal Ta 2a
|
||||||
.It Ql X Ta Uppercase hexadecimal Ta 2A
|
.It Ql X Ta Uppercase hexadecimal Ta 2A
|
||||||
.It Ql b Ta Binary Ta 101010
|
.It Ql b Ta Binary Ta 101010
|
||||||
|
.It Ql o Ta Octal Ta 52
|
||||||
|
.It Ql f Ta Fixed-point Ta 1234.56789
|
||||||
|
.It Ql s Ta String Ta \&"example\&"
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Note that print types should only be used with numeric values, not strings.
|
Examples:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
; Prints "%0010 + $3 == 5"
|
||||||
|
PRINTT STRFMT("%#05b + %#x == %d\n", 2, 3, 2+3)
|
||||||
|
; Prints "32% of 20 = 6.40"
|
||||||
|
PRINTT STRFMT("%d%% of %d = %.2f\n", 32, 20, MUL(20.0, 0.32))
|
||||||
|
; Prints "Hello world!"
|
||||||
|
PRINTT STRFMT("Hello %s!\n", STRLWR("WORLD"))
|
||||||
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
HINT: The
|
HINT: The
|
||||||
.Ic {symbol}
|
.Ic {symbol}
|
||||||
@@ -304,7 +358,7 @@ The following functions operate on string expressions.
|
|||||||
Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression!
|
Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression!
|
||||||
.Bl -column "STRSUB(str, pos, len)"
|
.Bl -column "STRSUB(str, pos, len)"
|
||||||
.It Sy Name Ta Sy Operation
|
.It Sy Name Ta Sy Operation
|
||||||
.It Fn STRLEN string Ta Returns the number of characters in Ar string .
|
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
||||||
.It Fn STRCAT str1 str2 Ta Appends Ar str2 No to Ar str1 .
|
.It Fn STRCAT str1 str2 Ta Appends Ar str2 No to Ar str1 .
|
||||||
.It Fn STRCMP str1 str2 Ta Returns -1 if Ar str1 No is alphabetically lower than Ar str2 No , zero if they match, 1 if Ar str1 No is greater than Ar str2 .
|
.It Fn STRCMP str1 str2 Ta Returns -1 if Ar str1 No is alphabetically lower than Ar str2 No , zero if they match, 1 if Ar str1 No is greater than Ar str2 .
|
||||||
.It Fn STRIN str1 str2 Ta Returns the first position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
|
.It Fn STRIN str1 str2 Ta Returns the first position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
|
||||||
@@ -312,6 +366,13 @@ Most of them return a string, however some of these functions actually return an
|
|||||||
.It Fn STRSUB str pos len Ta Returns a substring from Ar str No starting at Ar pos Po first character is position 1 Pc and Ar len No characters long.
|
.It Fn STRSUB str pos len Ta Returns a substring from Ar str No starting at Ar pos Po first character is position 1 Pc and Ar len No characters long.
|
||||||
.It Fn STRUPR str Ta Converts all characters in Ar str No to capitals and returns the new string.
|
.It Fn STRUPR str Ta Converts all characters in Ar str No to capitals and returns the new string.
|
||||||
.It Fn STRLWR str Ta Converts all characters in Ar str No to lower case and returns the new string.
|
.It Fn STRLWR str Ta Converts all characters in Ar str No to lower case and returns the new string.
|
||||||
|
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
|
||||||
|
.Ql %spec
|
||||||
|
pattern replaced by interpolating the format
|
||||||
|
.Ar spec
|
||||||
|
with its corresponding argument in
|
||||||
|
.Ar args
|
||||||
|
.Pq So %% Sc is replaced by the So % Sc character .
|
||||||
.El
|
.El
|
||||||
.Ss Character maps
|
.Ss Character maps
|
||||||
When writing text that is meant to be displayed in the Game Boy, the characters used in the source code may have a different encoding than the default of ASCII.
|
When writing text that is meant to be displayed in the Game Boy, the characters used in the source code may have a different encoding than the default of ASCII.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
ERROR: bracketed-symbols.asm(16):
|
ERROR: bracketed-symbols.asm(16):
|
||||||
Print types are only allowed for numbers
|
Formatting string as type 'X'
|
||||||
ERROR: bracketed-symbols.asm(20):
|
ERROR: bracketed-symbols.asm(20):
|
||||||
"Label" does not have a constant value
|
"Label" does not have a constant value
|
||||||
ERROR: bracketed-symbols.asm(21):
|
ERROR: bracketed-symbols.asm(21):
|
||||||
|
|||||||
24
test/asm/strfmt.asm
Normal file
24
test/asm/strfmt.asm
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
VAL EQUS STRFMT("Hello %s! I am %d years old today!", "world", $f)
|
||||||
|
PRINTT "{VAL}\n"
|
||||||
|
|
||||||
|
N = -42
|
||||||
|
PRINTT STRFMT("signed %010d == unsigned %010u\n", N, N)
|
||||||
|
|
||||||
|
N = 112
|
||||||
|
FMT EQUS "X"
|
||||||
|
PRINTT STRFMT("\tdb %#03{s:FMT} %% 26\t; %#03{FMT}\n", N, N % 26)
|
||||||
|
|
||||||
|
TEMPLATE EQUS "\"%s are %s\\n\""
|
||||||
|
PRINTT STRFMT(TEMPLATE, "roses", "red")
|
||||||
|
PRINTT STRFMT(TEMPLATE, "violets", "blue")
|
||||||
|
PRINTT STRFMT(TEMPLATE, "void", 0, "extra")
|
||||||
|
|
||||||
|
PRINTT STRCAT(STRFMT(STRFMT("%%%s.%d%s", "", 9, "f"), _PI), \
|
||||||
|
STRFMT(" ~ %s\n", STRFMT("%s%x", "thr", 238)))
|
||||||
|
|
||||||
|
PRINTT STRFMT("%d eol %", 1)
|
||||||
|
PRINTT "\n"
|
||||||
|
|
||||||
|
PRINTT STRFMT("invalid %w spec\n", 42)
|
||||||
|
|
||||||
|
PRINTT STRFMT("one=%d two=%d three=%d\n", 1)
|
||||||
11
test/asm/strfmt.err
Normal file
11
test/asm/strfmt.err
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
ERROR: strfmt.asm(14):
|
||||||
|
Formatting number as type 's'
|
||||||
|
ERROR: strfmt.asm(14):
|
||||||
|
STRFMT: 1 unformatted argument(s)
|
||||||
|
ERROR: strfmt.asm(19):
|
||||||
|
STRFMT: Illegal '%' at end of format string
|
||||||
|
ERROR: strfmt.asm(22):
|
||||||
|
STRFMT: Invalid format spec for argument 1
|
||||||
|
ERROR: strfmt.asm(24):
|
||||||
|
STRFMT: Not enough arguments for format spec
|
||||||
|
error: Assembly aborted (5 errors)!
|
||||||
10
test/asm/strfmt.out
Normal file
10
test/asm/strfmt.out
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Hello world! I am 15 years old today!
|
||||||
|
signed -000000042 == unsigned 4294967254
|
||||||
|
db $70 % 26 ; $08
|
||||||
|
roses are red
|
||||||
|
violets are blue
|
||||||
|
void are 0
|
||||||
|
3.141586304 ~ three
|
||||||
|
1 eol %
|
||||||
|
invalid % spec
|
||||||
|
one=1 two=% three=%
|
||||||
15
test/asm/string-formatting.asm
Normal file
15
test/asm/string-formatting.asm
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
n equ 300
|
||||||
|
m equ -42
|
||||||
|
f equ -123.0456
|
||||||
|
s equs "hello"
|
||||||
|
|
||||||
|
printt "<{ -6d:n}> <{+06u:n}> <{5x:n}> <{#16b:n}>\n"
|
||||||
|
printt "<{u:m}> <{+3d:m}> <{#016o:m}>\n"
|
||||||
|
printt "<{f:_PI}> <{06f:f}> <{.10f:f}>\n"
|
||||||
|
printt "<{#-10s:s}> <{10s:s}>\n"
|
||||||
|
|
||||||
|
foo: macro
|
||||||
|
printt "<{\1}>\n"
|
||||||
|
endm
|
||||||
|
|
||||||
|
foo -6d:n ; space is trimmed
|
||||||
3
test/asm/string-formatting.err
Normal file
3
test/asm/string-formatting.err
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ERROR: string-formatting.asm(9):
|
||||||
|
Formatting string with prefix flag '#'
|
||||||
|
error: Assembly aborted (1 errors)!
|
||||||
5
test/asm/string-formatting.out
Normal file
5
test/asm/string-formatting.out
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
< 300 > <+00300> < 12c> < %100101100>
|
||||||
|
<4294967254> <-42> <&000037777777726>
|
||||||
|
<3.14159> <-00123> <-123.0455932618>
|
||||||
|
<hello > < hello>
|
||||||
|
<300 >
|
||||||
Reference in New Issue
Block a user