difftastic/vendored_parsers/tree-sitter-hare/example/fmt.ha

433 lines
13 KiB
Plaintext

// A format string consists of a string of literal characters, to be printed
// verbatim, and format sequences, which describe how to format arguments from
// a set of variadic parameters for printing.
//
// A format sequence is enclosed in curly braces '{}'. An empty sequence takes
// the next argument from the parameter list, in order. A specific parameter may
// be selected by indexing it from zero: '{0}', '{1}', and so on. To print '{',
// use '{{', and for '}', use '}}'.
//
// You may use a colon to add format modifiers; for example, '{:x}' will format
// an argument in hexadecimal, and '{3:-10}' will left-align the 3rd argument to
// at least 10 characters.
//
// The format modifiers takes the form of an optional flag character:
//
// 0: Numeric values are zero-padded up to the required width.
// -: The value shall be left-aligned, and spaces inserted on the right to meet
// the required width. '-' takes precedence over '0' if both are used.
// : (a space) insert a space before positive numbers, where '-' would be if it
// were negative.
// +: insert a '+' before positive numbers, where '-' would be if it were
// negative. '+' takes precedence over ' ' if both are used.
//
// Following the flag, an optional decimal number shall specify the minimum
// width of this field. If '0' or '-' were not given, the default behavior shall
// be to pad with spaces to achieve the necessary width.
//
// Following the width, an optional precision may be given as a decimal number
// following a '.' character. For integer types, this gives the minimum number
// of digits to include. For floating types, this gives the number of digits
// following the radix to include.
//
// Following the precision, an optional character controls the output format:
//
// x, X: print in lowercase or uppercase hexadecimal
// o, b: print in octal or binary
//
// TODO: Expand this with more format modifiers
use ascii;
use bufio;
use encoding::utf8;
use io;
use os;
use strconv;
use strings;
use types;
// Tagged union of all types which are formattable.
export type formattable =
(...types::numeric | uintptr | str | rune | bool | nullable *void);
// Formats text for printing and writes it to [os::stdout].
export fn printf(fmt: str, args: formattable...) (io::error | size) =
fprintf(os::stdout, fmt, args...);
// Formats text for printing and writes it to [os::stdout], followed by a line
// feed.
export fn printfln(fmt: str, args: formattable...) (io::error | size) =
fprintfln(os::stdout, fmt, args...);
// Formats text for printing and writes it to [os::stderr].
export fn errorf(fmt: str, args: formattable...) (io::error | size) =
fprintf(os::stderr, fmt, args...);
// Formats text for printing and writes it to [os::stderr], followed by a line
// feed.
export fn errorfln(fmt: str, args: formattable...) (io::error | size) =
fprintfln(os::stderr, fmt, args...);
// Formats text for printing and writes it into a heap-allocated string. The
// caller must free the return value.
export fn asprintf(fmt: str, args: formattable...) str = {
let buf = bufio::dynamic(io::mode::WRITE);
assert(fprintf(buf, fmt, args...) is size);
return strings::fromutf8_unsafe(bufio::finish(buf));
};
// Formats text for printing and writes it into a caller supplied buffer. The
// returned string is borrowed from this buffer.
export fn bsprintf(buf: []u8, fmt: str, args: formattable...) str = {
let sink = bufio::fixed(buf, io::mode::WRITE);
let l = fprintf(sink, fmt, args...) as size;
return strings::fromutf8_unsafe(buf[..l]);
};
// Formats text for printing and writes it to [os::stderr], followed by a line
// feed, then exits the program with an error status.
export @noreturn fn fatal(fmt: str, args: formattable...) void = {
fprintfln(os::stderr, fmt, args...);
os::exit(1);
};
// Formats text for printing and writes it to an [io::stream], followed by a
// line feed.
export fn fprintfln(
s: *io::stream,
fmt: str,
args: formattable...
) (io::error | size) = {
return fprintf(s, fmt, args...)? + io::write(s, ['\n': u32: u8])?;
};
// Formats values for printing using the default format modifiers and writes
// them to [os::stdout] separated by spaces
export fn print(args: formattable...) (io::error | size) =
fprint(os::stdout, args...);
// Formats values for printing using the default format modifiers and writes
// them to [os::stdout] separated by spaces and followed by a line feed
export fn println(args: formattable...) (io::error | size) =
fprintln(os::stdout, args...);
// Formats values for printing using the default format modifiers and writes
// them to [os::stderr] separated by spaces
export fn error(args: formattable...) (io::error | size) =
fprint(os::stderr, args...);
// Formats values for printing using the default format modifiers and writes
// them to [os::stderr] separated by spaces and followed by a line feed
export fn errorln(args: formattable...) (io::error | size) =
fprintln(os::stderr, args...);
// Formats values for printing using the default format modifiers and writes
// them into a heap-allocated string separated by spaces. The caller must free
// the return value.
export fn asprint(args: formattable...) str = {
let buf = bufio::dynamic(io::mode::WRITE);
assert(fprint(buf, args...) is size);
return strings::fromutf8_unsafe(bufio::finish(buf));
};
// Formats values for printing using the default format modifiers and writes
// them into a caller supplied buffer separated by spaces. The returned string
// is borrowed from this buffer.
export fn bsprint(buf: []u8, args: formattable...) str = {
let sink = bufio::fixed(buf, io::mode::WRITE);
assert(fprint(sink, args...) is size);
return strings::fromutf8_unsafe(buf);
};
// Formats values for printing using the default format modifiers and writes
// them to an [io::stream] separated by spaces and followed by a line feed
export fn fprintln(s: *io::stream, args: formattable...) (io::error | size) = {
return fprint(s, args...)? + io::write(s, ['\n': u32: u8])?;
};
// Formats values for printing using the default format modifiers and writes
// them to an [io::stream] separated by spaces
export fn fprint(s: *io::stream, args: formattable...) (io::error | size) = {
let mod = modifiers { base = strconv::base::DEC, ... };
let n = 0z;
for (let i = 0z; i < len(args); i += 1) {
n += format(s, args[i], &mod)?;
if (i != len(args) - 1) {
n += io::write(s, [' ': u32: u8])?;
};
};
return n;
};
type negation = enum {
NONE,
SPACE,
PLUS,
};
type padding = enum {
ALIGN_RIGHT,
ALIGN_LEFT,
ZEROES,
};
type modifiers = struct {
padding: padding,
negation: negation,
width: uint,
precision: uint,
base: strconv::base,
};
type modflags = enum uint {
NONE = 0,
ZERO = 1 << 0,
MINUS = 1 << 1,
SPACE = 1 << 2,
PLUS = 1 << 3,
};
// Formats text for printing and writes it to an [io::stream].
export fn fprintf(
s: *io::stream,
fmt: str,
args: formattable...
) (io::error | size) = {
let n = 0z, i = 0z;
let iter = strings::iter(fmt);
for (true) {
let r: rune = match (strings::next(&iter)) {
void => break,
r: rune => r,
};
if (r == '{') {
r = match (strings::next(&iter)) {
void => abort("Invalid format string (unterminated '{')"),
r: rune => r,
};
const arg = if (r == '{') {
n += io::write(s, utf8::encoderune('{'))?;
continue;
} else if (ascii::isdigit(r)) {
strings::push(&iter, r);
args[scan_uint(&iter)];
} else {
strings::push(&iter, r);
i += 1;
args[i - 1];
};
let mod = modifiers { base = strconv::base::DEC, ... };
r = match (strings::next(&iter)) {
void => abort("Invalid format string (unterminated '{')"),
r: rune => r,
};
switch (r) {
':' => scan_modifiers(&iter, &mod),
'}' => void,
* => abort("Invalid format string"),
};
n += format(s, arg, &mod)?;
} else if (r == '}') {
match (strings::next(&iter)) {
void => abort("Invalid format string (hanging '}')"),
r: rune => assert(r == '}', "Invalid format string (hanging '}')"),
};
n += io::write(s, utf8::encoderune('}'))?;
} else {
n += io::write(s, utf8::encoderune(r))?;
};
};
return n;
};
fn format(out: *io::stream, arg: formattable, mod: *modifiers) (size | io::error) = {
let z = format_raw(io::empty, arg, mod)?;
let pad: []u8 = [];
if (z < mod.width: size) {
pad = utf8::encoderune(switch (mod.padding) {
padding::ZEROES => '0',
* => ' ',
});
};
if (mod.padding == padding::ALIGN_LEFT) {
format_raw(out, arg, mod);
};
for (z < mod.width: size) {
z += io::write(out, pad)?;
};
if (mod.padding != padding::ALIGN_LEFT) {
format_raw(out, arg, mod);
};
return z;
};
fn format_raw(
out: *io::stream,
arg: formattable,
mod: *modifiers,
) (size | io::error) = match (arg) {
s: str => io::write(out, strings::toutf8(s)),
r: rune => io::write(out, utf8::encoderune(r)),
b: bool => io::write(out, strings::toutf8(if (b) "true" else "false")),
n: types::numeric => {
let s = strconv::numerictosb(n, mod.base);
io::write(out, strings::toutf8(s));
},
p: uintptr => {
let s = strconv::uptrtosb(p, mod.base);
io::write(out, strings::toutf8(s));
},
v: nullable *void => match (v) {
v: *void => {
let s = strconv::uptrtosb(v: uintptr,
strconv::base::HEX_LOWER);
let n = io::write(out, strings::toutf8("0x"))?;
n += io::write(out, strings::toutf8(s))?;
n;
},
null => format(out, "(null)", mod),
},
};
fn scan_uint(iter: *strings::iterator) uint = {
let num: []u8 = [];
defer free(num);
for (true) {
let r = match (strings::next(iter)) {
void => abort("Invalid format string (unterminated '{')"),
r: rune => r,
};
if (ascii::isdigit(r)) {
append(num, r: u32: u8);
} else {
strings::push(iter, r);
match (strconv::stou(strings::fromutf8(num))) {
(strconv::invalid | strconv::overflow) =>
abort("Invalid format string (invalid index)"),
u: uint => return u,
};
};
};
abort("unreachable");
};
fn scan_modifier_flags(iter: *strings::iterator, mod: *modifiers) void = {
let flags = modflags::NONE;
for (true) {
let r = match (strings::next(iter)) {
void => abort("Invalid format string (unterminated '{')"),
r: rune => r,
};
switch (r) {
'0' => flags |= modflags::ZERO,
'-' => flags |= modflags::MINUS,
' ' => flags |= modflags::SPACE,
'+' => flags |= modflags::PLUS,
* => {
strings::push(iter, r);
break;
},
};
};
mod.padding = if (flags & modflags::MINUS != 0)
padding::ALIGN_LEFT
else if (flags & modflags::ZERO != 0)
padding::ZEROES
else
padding::ALIGN_RIGHT;
mod.negation = if (flags & modflags::PLUS != 0)
negation::PLUS
else if (flags & modflags::SPACE != 0)
negation::SPACE
else
negation::NONE;
};
fn scan_modifier_width(iter: *strings::iterator, mod: *modifiers) void = {
let r = match (strings::next(iter)) {
void => abort("Invalid format string (unterminated '{')"),
r: rune => r,
};
let is_digit = ascii::isdigit(r);
strings::push(iter, r);
if (is_digit) {
mod.width = scan_uint(iter);
};
};
fn scan_modifier_precision(iter: *strings::iterator, mod: *modifiers) void = {
let r = match (strings::next(iter)) {
void => abort("Invalid format string (unterminated '{')"),
r: rune => r,
};
if (r == '.') {
mod.precision = scan_uint(iter);
} else {
strings::push(iter, r);
};
};
fn scan_modifier_base(iter: *strings::iterator, mod: *modifiers) void = {
let r = match (strings::next(iter)) {
void => abort("Invalid format string (unterminated '{')"),
r: rune => r,
};
switch (r) {
'x' => mod.base = strconv::base::HEX_LOWER,
'X' => mod.base = strconv::base::HEX_UPPER,
'o' => mod.base = strconv::base::OCT,
'b' => mod.base = strconv::base::BIN,
* => strings::push(iter, r),
};
};
fn scan_modifiers(iter: *strings::iterator, mod: *modifiers) void = {
scan_modifier_flags(iter, mod);
scan_modifier_width(iter, mod);
scan_modifier_precision(iter, mod);
scan_modifier_base(iter, mod);
// eat '}'
let terminated = match (strings::next(iter)) {
void => false,
r: rune => r == '}',
};
assert(terminated, "Invalid format string (unterminated '{')");
};
@test fn fmt() void = {
let buf: [1024]u8 = [0...];
assert(bsprintf(buf, "hello world") == "hello world");
assert(bsprintf(buf, "{} {}", "hello", "world") == "hello world");
assert(bsprintf(buf, "{0} {1}", "hello", "world") == "hello world");
assert(bsprintf(buf, "{0} {0}", "hello", "world") == "hello hello");
assert(bsprintf(buf, "{1} {0} {1}", "hello", "world") == "world hello world");
assert(bsprintf(buf, "x: {:08x}", 0xBEEF) == "x: 0000beef");
assert(bsprintf(buf, "x: {:8X}", 0xBEEF) == "x: BEEF");
assert(bsprintf(buf, "x: {:-8X}", 0xBEEF) == "x: BEEF ");
assert(bsprintf(buf, "x: {:o}", 0o755) == "x: 755");
assert(bsprintf(buf, "x: {:b}", 0b11011) == "x: 11011");
assert(bsprintf(buf, "{} {} {} {}", true, false, null, 'x')
== "true false (null) x");
};