Landin library reference source

core/text/format.ldn

1--  Formatting: a template whose placeholders take values of any type that
2--  can write itself into a builder.  Nothing runs at compile time, so the
3--  template is checked against its values when it is formatted.
4
5--- The template does not fit its values: its `{}` placeholders and the
6--- values differ in number, or a brace is neither part of a `{}` nor
7--- doubled.
8public invalid_format: atom
9
10--- A value that writes its own text into a builder, which `format` fills
11--- a placeholder with.  The library supplies it for the ten integer types
12--- in decimal, `bool` as `true` or `false`, `utf8`, `[]u8` and host
13--- strings as their bytes.  A type of the program's own conforms with a
14--- function that appends its text; a different reading of a supplied type,
15--- hexadecimal for one, is a typed `append` call, or a `distinct` wrapper
16--- with its own conformance [1280].
17public formattable: type = concept (item: type)
18    --- Append the value's text, or report `no_space` and leave the
19    --- builder as it was.
20    append: (self: ptr item, inout into: builder) -> none ! no_space
21end formattable
22
23--  Check the template against the number of values: every `{` begins `{}`
24--  or `{{`, every `}` not closing a placeholder is doubled, and there are
25--  as many placeholders as values.
26checked: (source: []u8, count: usize) -> none ! invalid_format =
27    mut wanted: usize = 0
28    mut at: usize = 0
29    while at < lenof source do
30        current: u8 = source[at]
31        if current == 123 or current == 125 then
32            fail invalid_format when at + 1 >= lenof source
33            following: u8 = source[at + 1]
34            if current == 123 and following == 125 then
35                inc wanted
36            elsif following <> current then
37                fail invalid_format
38            end if
39            at += 2
40        else
41            inc at
42        end if
43    end while
44    fail invalid_format when wanted <> count
45end checked
46
47--  Write a checked template: each run of plain bytes, one brace of each
48--  doubled pair, and each value where its placeholder stands.
49written: (inout into: builder, source: []u8, values: []any formattable)
50         -> none ! no_space =
51    mut run: usize = 0
52    mut at: usize = 0
53    mut next: usize = 0
54    while at < lenof source do
55        current: u8 = source[at]
56        if current == 123 or current == 125 then
57            if current == 123 and source[at + 1] == 125 then
58                try append(into, source[run..<at])
59                value: any formattable = values[next]
60                try value.append(into)
61                inc next
62            else
63                try append(into, source[run..<at + 1])
64            end if
65            at += 2
66            run = at
67        else
68            inc at
69        end if
70    end while
71    try append(into, source[run..<at])
72end written
73
74--- Append `template` with each `{}` replaced by the next of `values`, in
75--- order, written by its own `formattable` conformance; `{{` writes `{`
76--- and `}}` writes `}`.  A placeholder takes nothing else: width, padding
77--- and hexadecimal are the typed `append` and `align` calls.  The template
78--- is checked first, so `invalid_format` writes nothing, and a builder that
79--- fills partway is put back as it was before `no_space` is reported.  No
80--- locale is consulted: the text is the same everywhere.
81---
82--- The values are an array literal of erased addresses, which the call
83--- lends as a slice: `text.format(line, "{} {}\n", [any(addr count),
84--- any(addr name)])`.
85public format: (inout into: builder, template: utf8,
86                values: []any formattable)
87               -> none ! no_space | invalid_format =
88    source: []u8 = bytes(template)
89    try checked(source, lenof values)
90    start: usize = into.used
91    written(into, source, values) else (problem)
92        into.used = start
93        fail problem
94    end
95end format
96
97append_u8_value: (self: ptr u8, inout into: builder) -> none ! no_space =
98    try append_u32(into, u32(self.val))
99end append_u8_value
100append_u16_value: (self: ptr u16, inout into: builder) -> none ! no_space =
101    try append_u32(into, u32(self.val))
102end append_u16_value
103append_u32_value: (self: ptr u32, inout into: builder) -> none ! no_space =
104    try append_u32(into, self.val)
105end append_u32_value
106append_u64_value: (self: ptr u64, inout into: builder) -> none ! no_space =
107    try append_u64(into, self.val)
108end append_u64_value
109append_usize_value: (self: ptr usize, inout into: builder)
110                    -> none ! no_space =
111    try append_usize(into, self.val)
112end append_usize_value
113append_i8_value: (self: ptr i8, inout into: builder) -> none ! no_space =
114    try append_i32(into, i32(self.val))
115end append_i8_value
116append_i16_value: (self: ptr i16, inout into: builder) -> none ! no_space =
117    try append_i32(into, i32(self.val))
118end append_i16_value
119append_i32_value: (self: ptr i32, inout into: builder) -> none ! no_space =
120    try append_i32(into, self.val)
121end append_i32_value
122append_i64_value: (self: ptr i64, inout into: builder) -> none ! no_space =
123    try append_i64(into, self.val)
124end append_i64_value
125append_isize_value: (self: ptr isize, inout into: builder)
126                    -> none ! no_space =
127    try append_isize(into, self.val)
128end append_isize_value
129append_bool_value: (self: ptr bool, inout into: builder) -> none ! no_space =
130    if self.val then
131        try append(into, "true")
132    else
133        try append(into, "false")
134    end if
135end append_bool_value
136append_text_value: (self: ptr utf8, inout into: builder) -> none ! no_space =
137    try append_text(into, self.val)
138end append_text_value
139append_bytes_value: (self: ptr []u8, inout into: builder) -> none ! no_space =
140    try append(into, self.val)
141end append_bytes_value
142append_host_value: (self: ptr host_string, inout into: builder)
143                   -> none ! no_space =
144    try append_host(into, self.val)
145end append_host_value
146
147u8 is formattable (append: append_u8_value)
148u16 is formattable (append: append_u16_value)
149u32 is formattable (append: append_u32_value)
150u64 is formattable (append: append_u64_value)
151usize is formattable (append: append_usize_value)
152i8 is formattable (append: append_i8_value)
153i16 is formattable (append: append_i16_value)
154i32 is formattable (append: append_i32_value)
155i64 is formattable (append: append_i64_value)
156isize is formattable (append: append_isize_value)
157bool is formattable (append: append_bool_value)
158utf8 is formattable (append: append_text_value)
159[]u8 is formattable (append: append_bytes_value)
160host_value is formattable (append: append_host_value)