authorgravatar for tgschultz@gmail.comtgschultz <tgschultz@gmail.com> 2018-05-30 10:18:11-05:00
committergravatar for tgschultz@gmail.comtgschultz <tgschultz@gmail.com> 2018-05-30 10:18:11-05:00
log8fc52a94f46295ee821708c44a165803207e85a6
tree383d1652ef5dd4cf0d0508082a5e893e48bf6df8
parent8174f972a779384b287528e46ea086c714ce5553

Added custom formatter support, refactored fmt.format


1 files changed, 201 insertions(+), 203 deletions(-)

std/fmt/index.zig+201-203
...@@ -16,27 +16,12 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),...@@ -16,27 +16,12 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
16 Start,16 Start,
17 OpenBrace,17 OpenBrace,
18 CloseBrace,18 CloseBrace,
19 Integer,19 FormatString,
20 IntegerWidth,
21 Float,
22 FloatWidth,
23 FloatScientific,
24 FloatScientificWidth,
25 Character,
26 Buf,
27 BufWidth,
28 Bytes,
29 BytesBase,
30 BytesWidth,
31 };20 };
3221
33 comptime var start_index = 0;22 comptime var start_index = 0;
34 comptime var state = State.Start;23 comptime var state = State.Start;
35 comptime var next_arg = 0;24 comptime var next_arg = 0;
36 comptime var radix = 0;
37 comptime var uppercase = false;
38 comptime var width = 0;
39 comptime var width_start = 0;
4025
41 inline for (fmt) |c, i| {26 inline for (fmt) |c, i| {
42 switch (state) {27 switch (state) {
...@@ -45,8 +30,10 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),...@@ -45,8 +30,10 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
45 if (start_index < i) {30 if (start_index < i) {
46 try output(context, fmt[start_index..i]);31 try output(context, fmt[start_index..i]);
47 }32 }
33 start_index = i;
48 state = State.OpenBrace;34 state = State.OpenBrace;
49 },35 },
36
50 '}' => {37 '}' => {
51 if (start_index < i) {38 if (start_index < i) {
52 try output(context, fmt[start_index..i]);39 try output(context, fmt[start_index..i]);
...@@ -61,57 +48,14 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),...@@ -61,57 +48,14 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
61 start_index = i;48 start_index = i;
62 },49 },
63 '}' => {50 '}' => {
64 try formatValue(args[next_arg], context, Errors, output);51 try formatType(args[next_arg], fmt[0..0], context, Errors, output);
65 next_arg += 1;52 next_arg += 1;
66 state = State.Start;53 state = State.Start;
67 start_index = i + 1;54 start_index = i + 1;
68 },55 },
69 'd' => {56 else => {
70 radix = 10;57 state = State.FormatString;
71 uppercase = false;
72 width = 0;
73 state = State.Integer;
74 },
75 'x' => {
76 radix = 16;
77 uppercase = false;
78 width = 0;
79 state = State.Integer;
80 },
81 'X' => {
82 radix = 16;
83 uppercase = true;
84 width = 0;
85 state = State.Integer;
86 },
87 'c' => {
88 state = State.Character;
89 },
90 's' => {
91 state = State.Buf;
92 },
93 'e' => {
94 state = State.FloatScientific;
95 },58 },
96 '.' => {
97 state = State.Float;
98 },
99 'B' => {
100 width = 0;
101 radix = 1000;
102 state = State.Bytes;
103 },
104 else => @compileError("Unknown format character: " ++ []u8{c}),
105 },
106 State.Buf => switch (c) {
107 '}' => {
108 return output(context, args[next_arg]);
109 },
110 '0'...'9' => {
111 width_start = i;
112 state = State.BufWidth;
113 },
114 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
115 },59 },
116 State.CloseBrace => switch (c) {60 State.CloseBrace => switch (c) {
117 '}' => {61 '}' => {
...@@ -120,139 +64,16 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),...@@ -120,139 +64,16 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
120 },64 },
121 else => @compileError("Single '}' encountered in format string"),65 else => @compileError("Single '}' encountered in format string"),
122 },66 },
123 State.Integer => switch (c) {67 State.FormatString => switch(c) {
124 '}' => {68 '}' => {
125 try formatInt(args[next_arg], radix, uppercase, width, context, Errors, output);69 const s = start_index + 1;
70 try formatType(args[next_arg], fmt[s..i], context, Errors, output);
126 next_arg += 1;71 next_arg += 1;
127 state = State.Start;72 state = State.Start;
128 start_index = i + 1;73 start_index = i + 1;
129 },74 },
130 '0'...'9' => {75 else => {},
131 width_start = i;76 }
132 state = State.IntegerWidth;
133 },
134 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
135 },
136 State.IntegerWidth => switch (c) {
137 '}' => {
138 width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable);
139 try formatInt(args[next_arg], radix, uppercase, width, context, Errors, output);
140 next_arg += 1;
141 state = State.Start;
142 start_index = i + 1;
143 },
144 '0'...'9' => {},
145 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
146 },
147 State.FloatScientific => switch (c) {
148 '}' => {
149 try formatFloatScientific(args[next_arg], null, context, Errors, output);
150 next_arg += 1;
151 state = State.Start;
152 start_index = i + 1;
153 },
154 '0'...'9' => {
155 width_start = i;
156 state = State.FloatScientificWidth;
157 },
158 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
159 },
160 State.FloatScientificWidth => switch (c) {
161 '}' => {
162 width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable);
163 try formatFloatScientific(args[next_arg], width, context, Errors, output);
164 next_arg += 1;
165 state = State.Start;
166 start_index = i + 1;
167 },
168 '0'...'9' => {},
169 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
170 },
171 State.Float => switch (c) {
172 '}' => {
173 try formatFloatDecimal(args[next_arg], null, context, Errors, output);
174 next_arg += 1;
175 state = State.Start;
176 start_index = i + 1;
177 },
178 '0'...'9' => {
179 width_start = i;
180 state = State.FloatWidth;
181 },
182 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
183 },
184 State.FloatWidth => switch (c) {
185 '}' => {
186 width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable);
187 try formatFloatDecimal(args[next_arg], width, context, Errors, output);
188 next_arg += 1;
189 state = State.Start;
190 start_index = i + 1;
191 },
192 '0'...'9' => {},
193 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
194 },
195 State.BufWidth => switch (c) {
196 '}' => {
197 width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable);
198 try formatBuf(args[next_arg], width, context, Errors, output);
199 next_arg += 1;
200 state = State.Start;
201 start_index = i + 1;
202 },
203 '0'...'9' => {},
204 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
205 },
206 State.Character => switch (c) {
207 '}' => {
208 try formatAsciiChar(args[next_arg], context, Errors, output);
209 next_arg += 1;
210 state = State.Start;
211 start_index = i + 1;
212 },
213 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
214 },
215 State.Bytes => switch (c) {
216 '}' => {
217 try formatBytes(args[next_arg], 0, radix, context, Errors, output);
218 next_arg += 1;
219 state = State.Start;
220 start_index = i + 1;
221 },
222 'i' => {
223 radix = 1024;
224 state = State.BytesBase;
225 },
226 '0'...'9' => {
227 width_start = i;
228 state = State.BytesWidth;
229 },
230 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
231 },
232 State.BytesBase => switch (c) {
233 '}' => {
234 try formatBytes(args[next_arg], 0, radix, context, Errors, output);
235 next_arg += 1;
236 state = State.Start;
237 start_index = i + 1;
238 },
239 '0'...'9' => {
240 width_start = i;
241 state = State.BytesWidth;
242 },
243 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
244 },
245 State.BytesWidth => switch (c) {
246 '}' => {
247 width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable);
248 try formatBytes(args[next_arg], width, radix, context, Errors, output);
249 next_arg += 1;
250 state = State.Start;
251 start_index = i + 1;
252 },
253 '0'...'9' => {},
254 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
255 },
256 }77 }
257 }78 }
258 comptime {79 comptime {
...@@ -268,14 +89,14 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),...@@ -268,14 +89,14 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
268 }89 }
269}90}
27091
271pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8) Errors!void) Errors!void {92pub fn formatType(value: var, comptime fmt: []const u8, context: var, comptime Errors: type,
93 output: fn(@typeOf(context), []const u8)Errors!void) Errors!void
94{
272 const T = @typeOf(value);95 const T = @typeOf(value);
273 switch (@typeId(T)) {96 switch (@typeId(T)) {
274 builtin.TypeId.Int => {97 builtin.TypeId.Int,
275 return formatInt(value, 10, false, 0, context, Errors, output);
276 },
277 builtin.TypeId.Float => {98 builtin.TypeId.Float => {
278 return formatFloatScientific(value, null, context, Errors, output);99 return formatValue(value, fmt, context, Errors, output);
279 },100 },
280 builtin.TypeId.Void => {101 builtin.TypeId.Void => {
281 return output(context, "void");102 return output(context, "void");
...@@ -285,16 +106,16 @@ pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@...@@ -285,16 +106,16 @@ pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@
285 },106 },
286 builtin.TypeId.Nullable => {107 builtin.TypeId.Nullable => {
287 if (value) |payload| {108 if (value) |payload| {
288 return formatValue(payload, context, Errors, output);109 return formatType(payload, fmt, context, Errors, output);
289 } else {110 } else {
290 return output(context, "null");111 return output(context, "null");
291 }112 }
292 },113 },
293 builtin.TypeId.ErrorUnion => {114 builtin.TypeId.ErrorUnion => {
294 if (value) |payload| {115 if (value) |payload| {
295 return formatValue(payload, context, Errors, output);116 return formatType(payload, fmt, context, Errors, output);
296 } else |err| {117 } else |err| {
297 return formatValue(err, context, Errors, output);118 return formatType(err, fmt, context, Errors, output);
298 }119 }
299 },120 },
300 builtin.TypeId.ErrorSet => {121 builtin.TypeId.ErrorSet => {
...@@ -302,10 +123,60 @@ pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@...@@ -302,10 +123,60 @@ pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@
302 return output(context, @errorName(value));123 return output(context, @errorName(value));
303 },124 },
304 builtin.TypeId.Pointer => {125 builtin.TypeId.Pointer => {
305 if (@typeId(T.Child) == builtin.TypeId.Array and T.Child.Child == u8) {126 switch(@typeId(T.Child)) {
306 return output(context, (value.*)[0..]);127 builtin.TypeId.Array => {
307 } else {128 if(T.Child.Child == u8) {
308 return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value));129 return formatText(value, fmt, context, Errors, output);
130 }
131 },
132 builtin.TypeId.Enum,
133 builtin.TypeId.Union,
134 builtin.TypeId.Struct => {
135 const has_cust_fmt = comptime cf: {
136 const info = @typeInfo(T.Child);
137 const defs = switch (info) {
138 builtin.TypeId.Struct => |s| s.defs,
139 builtin.TypeId.Union => |u| u.defs,
140 builtin.TypeId.Enum => |e| e.defs,
141 else => unreachable,
142 };
143
144 for (defs) |def| {
145 if (mem.eql(u8, def.name, "format") and def.is_pub) {
146 const data = def.data;
147 switch (data) {
148 builtin.TypeInfo.Definition.Data.Type,
149 builtin.TypeInfo.Definition.Data.Var => continue,
150 builtin.TypeInfo.Definition.Data.Fn => |*fn_def| {
151 //const FmtType = fn(@typeOf(context), []const u8)Errors!void;
152 //// for some reason, fn_type sees the arg `comptime []const u8` as `var`
153 //const TargetType = fn(T, var, var, type, FmtType) Errors!void;
154
155 // This hack is because fn_def.fn_type != TargetType
156 // for reasons I have yet to determine.
157
158 const fn_type_name = @typeName(@typeOf(value.format));
159 const value_type_name = @typeName(@typeOf(value));
160 const target_type_name = "(bound fn("
161 ++ value_type_name ++ ",var,var,var,var)var)";
162 if (mem.eql(u8, fn_type_name, target_type_name))
163 {
164 break :cf true;
165 }
166
167 },
168 }
169 }
170 }
171 break :cf false;
172 };
173
174 if (has_cust_fmt) return value.format(fmt, context, Errors, output);
175 return format(context, Errors, output, "{}@{x}", @typeName(T.Child),
176 @ptrToInt(value));
177 },
178 else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child),
179 @ptrToInt(value)),
309 }180 }
310 },181 },
311 else => if (@canImplicitCast([]const u8, value)) {182 else => if (@canImplicitCast([]const u8, value)) {
...@@ -317,11 +188,106 @@ pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@...@@ -317,11 +188,106 @@ pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@
317 }188 }
318}189}
319190
191fn formatValue(value: var, comptime fmt: []const u8, context: var, comptime Errors: type,
192 output: fn(@typeOf(context), []const u8)Errors!void) Errors!void
193{
194 if (fmt.len > 0) {
195 if (fmt[0] == 'B') {
196 comptime var width: ?usize = null;
197 if (fmt.len > 1) {
198 if (fmt[1] == 'i') {
199 if (fmt.len > 2) width = comptime (parseUnsigned(usize, fmt[2..], 10) catch unreachable);
200 return formatBytes(value, width, 1024, context, Errors, output);
201 }
202 width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable);
203 }
204 return formatBytes(value, width, 1000, context, Errors, output);
205 }
206 }
207
208 comptime var T = @typeOf(value);
209 switch (@typeId(T)) {
210 builtin.TypeId.Float => return formatFloatValue(value, fmt, context, Errors, output),
211 builtin.TypeId.Int => return formatIntValue(value, fmt, context, Errors, output),
212 else => unreachable,
213 }
214}
215
216pub fn formatIntValue(value: var, comptime fmt: []const u8, context: var, comptime Errors: type,
217 output: fn(@typeOf(context), []const u8)Errors!void) Errors!void
218{
219 comptime var radix = 10;
220 comptime var uppercase = false;
221 comptime var width = 0;
222 if (fmt.len > 0) {
223 switch (fmt[0]) {
224 'c' => {
225 if(@typeOf(value) == u8) {
226 if(fmt.len > 1) @compileError("Unknown format character: " ++ []u8{fmt[1]});
227 return formatAsciiChar(fmt[0], context, Errors, output);
228 }
229 },
230 'd' => {
231 radix = 10;
232 uppercase = false;
233 width = 0;
234 },
235 'x' => {
236 radix = 16;
237 uppercase = false;
238 width = 0;
239 },
240 'X' => {
241 radix = 16;
242 uppercase = true;
243 width = 0;
244 },
245 else => @compileError("Unknown format character: " ++ []u8{fmt[0]}),
246 }
247 if (fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable);
248 }
249 return formatInt(value, radix, uppercase, width, context, Errors, output);
250}
251
252fn formatFloatValue(value: var, comptime fmt: []const u8, context: var, comptime Errors: type,
253 output: fn(@typeOf(context), []const u8)Errors!void) Errors!void
254{
255 comptime var width: ?usize = null;
256 comptime var float_fmt = 'e';
257 if (fmt.len > 0) {
258 float_fmt = fmt[0];
259 if(fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable);
260 }
261
262 switch (float_fmt) {
263 'e' => try formatFloatScientific(value, width, context, Errors, output),
264 '.' => try formatFloatDecimal(value, width, context, Errors, output),
265 else => @compileError("Unknown format character: " ++ []u8{float_fmt}),
266 }
267
268}
269
270pub fn formatText(bytes: []const u8, comptime fmt: []const u8, context: var,
271 comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void
272{
273 if (fmt.len > 0) {
274 if (fmt[0] == 's') {
275 comptime var width = 0;
276 if(fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable);
277 return formatBuf(bytes, width, context, Errors, output);
278 }
279 else @compileError("Unknown format character: " ++ []u8{fmt[0]});
280 }
281 return output(context, bytes);
282}
283
320pub fn formatAsciiChar(c: u8, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8) Errors!void) Errors!void {284pub fn formatAsciiChar(c: u8, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8) Errors!void) Errors!void {
321 return output(context, (&c)[0..1]);285 return output(context, (&c)[0..1]);
322}286}
323287
324pub fn formatBuf(buf: []const u8, width: usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8) Errors!void) Errors!void {288pub fn formatBuf(buf: []const u8, width: usize, context: var,
289 comptime Errors: type, output: fn(@typeOf(context), []const u8) Errors!void) Errors!void
290{
325 try output(context, buf);291 try output(context, buf);
326292
327 var leftover_padding = if (width > buf.len) (width - buf.len) else return;293 var leftover_padding = if (width > buf.len) (width - buf.len) else return;
...@@ -1048,6 +1014,38 @@ test "fmt.format" {...@@ -1048,6 +1014,38 @@ test "fmt.format" {
1048 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);1014 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
1049 assert(mem.eql(u8, result, "f64: 18014400656965630.00000\n"));1015 assert(mem.eql(u8, result, "f64: 18014400656965630.00000\n"));
1050 }1016 }
1017 //custom type format
1018 {
1019 const Vec2 = struct {
1020 const SelfType = this;
1021 x: f32,
1022 y: f32,
1023
1024 pub fn format(self: &SelfType, comptime fmt: []const u8, context: var,
1025 comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void)
1026 Errors!void
1027 {
1028 if (fmt.len > 0) {
1029 if (fmt.len > 1) unreachable;
1030 switch (fmt[0]) {
1031 //point format
1032 'p' => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y),
1033 //dimension format
1034 'd' => return std.fmt.format(context, Errors, output, "{.3}x{.3}", self.x, self.y),
1035 else => unreachable,
1036 }
1037 }
1038 return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y);
1039 }
1040 };
1041
1042 var buf1: [32]u8 = undefined;
1043 var value = Vec2{.x = 10.2, .y = 2.22,};
1044 const point_result = try bufPrint(buf1[0..], "point: {}\n", &value);
1045 assert(mem.eql(u8, point_result, "point: (10.200,2.220)\n"));
1046 const dim_result = try bufPrint(buf1[0..], "dim: {d}\n", &value);
1047 assert(mem.eql(u8, dim_result, "dim: 10.200x2.220\n"));
1048 }
1051}1049}
10521050
1053fn testFmt(expected: []const u8, comptime template: []const u8, args: ...) !void {1051fn testFmt(expected: []const u8, comptime template: []const u8, args: ...) !void {