Files
nunuhara_xsystem4/src/format.c
T
Nunuhara Cabbage 842d712a31 Fix bug in S_MOD
The 3rd argument to the instruction indicates the type of the second
argument. If there are multiple specifiers in the format string, the
first specifier matching the given type is substituted (NOT the first
specifier encountered when processing the format string).
2023-01-10 18:47:03 -08:00

175 lines
3.7 KiB
C

/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <string.h>
#include "system4.h"
#include "system4/ain.h"
#include "system4/string.h"
#include "vm.h"
#include "vm/heap.h"
#define DIGIT_MAX 512
enum fmt_type {
FMT_VOID,
FMT_INT,
FMT_FLOAT,
FMT_STRING,
FMT_CHAR,
FMT_BOOL
};
struct fmt_spec {
enum fmt_type type;
int precision;
int padding;
bool zero_pad;
bool zenkaku;
};
static int read_number(const char **_fmt)
{
int n = 0;
const char *fmt = *_fmt;
while (*fmt && *fmt >= '0' && *fmt <= '9') {
n *= 10;
n += *fmt - '0';
fmt++;
}
*_fmt = fmt;
return n;
}
static bool parse_fmt_spec(const char **_fmt, struct fmt_spec *spec, enum ain_data_type target)
{
bool r = false;
const char *fmt = (*_fmt) + 1;
memset(spec, 0, sizeof(struct fmt_spec));
spec->precision = 6;
while (*fmt) {
switch (*fmt) {
case 'd':
spec->type = FMT_INT;
r = target == AIN_INT;
goto end;
case 'D':
spec->type = FMT_INT;
spec->zenkaku = true;
r = target == AIN_INT;
goto end;
case 'f':
spec->type = FMT_FLOAT;
r = target == AIN_FLOAT;
goto end;
case 'F':
spec->type = FMT_FLOAT;
spec->zenkaku = true;
r = target == AIN_FLOAT;
goto end;
case 's':
spec->type = FMT_STRING;
r = target == AIN_STRING;
goto end;
case 'c':
spec->type = FMT_CHAR;
r = target == AIN_INT;
goto end;
case 'b':
spec->type = FMT_BOOL;
r = target == AIN_INT;
goto end;
case '0':
spec->zero_pad = true;
fmt++;
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
spec->padding = read_number(&fmt);
break;
case '.':
fmt++;
spec->precision = read_number(&fmt);
break;
default:
goto warn;
}
}
warn:
WARNING("Invalid format specifier: %s", *_fmt);
end:
if (r)
*_fmt = ++fmt;
return r;
}
static void append_fmt(struct string **s, struct fmt_spec *spec, union vm_value arg)
{
int len;
char buf[DIGIT_MAX] = { [DIGIT_MAX-1] = '\0' };
switch (spec->type) {
case FMT_VOID:
return;
case FMT_INT:
len = int_to_cstr(buf, DIGIT_MAX, arg.i, spec->padding, spec->zero_pad, spec->zenkaku);
string_append_cstr(s, buf, len);
break;
case FMT_FLOAT:
len = float_to_cstr(buf, DIGIT_MAX, arg.f, spec->padding, spec->zero_pad, spec->precision, spec->zenkaku);
string_append_cstr(s, buf, len);
break;
case FMT_STRING:
string_append(s, heap_get_string(arg.i));
heap_unref(arg.i);
break;
case FMT_CHAR:
string_push_back(s, arg.i);
break;
case FMT_BOOL:
if (arg.i)
string_append_cstr(s, "true", 4);
else
string_append_cstr(s, "false", 5);
break;
}
}
struct string *string_format(struct string *fmt, union vm_value arg, enum ain_data_type type)
{
struct string *out = string_ref(&EMPTY_STRING);
for (const char *s = fmt->text; *s; s++) {
if (*s != '%')
continue;
size_t size = s - fmt->text;
struct fmt_spec spec;
if (parse_fmt_spec(&s, &spec, type)) {
string_append_cstr(&out, fmt->text, size);
append_fmt(&out, &spec, arg);
string_append_cstr(&out, s, strlen(s));
break;
}
}
return out;
}