Extract Ona IO writer into reusable "Function" type
This commit is contained in:
		
							parent
							
								
									32bb049f73
								
							
						
					
					
						commit
						ef2c6c3a3c
					
				
							
								
								
									
										70
									
								
								src/ona/fmt.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/ona/fmt.zig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | |||||||
|  | const io = @import("io.zig"); | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// Number formatting modes supported by [writeInt]. | ||||||
|  | /// | ||||||
|  | pub const Radix = enum { | ||||||
|  |     binary, | ||||||
|  |     tinary, | ||||||
|  |     quaternary, | ||||||
|  |     quinary, | ||||||
|  |     senary, | ||||||
|  |     septenary, | ||||||
|  |     octal, | ||||||
|  |     nonary, | ||||||
|  |     decimal, | ||||||
|  |     undecimal, | ||||||
|  |     duodecimal, | ||||||
|  |     tridecimal, | ||||||
|  |     tetradecimal, | ||||||
|  |     pentadecimal, | ||||||
|  |     hexadecimal, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// Writes `value` as a ASCII / UTF-8 encoded integer to `writer`, returning `true` if the full | ||||||
|  | /// sequence was successfully written, otherwise `false`. | ||||||
|  | /// | ||||||
|  | /// The `radix` argument identifies which base system to format `value` as. | ||||||
|  | /// | ||||||
|  | pub fn printInt(writer: io.Writer, radix: Radix, value: anytype) bool { | ||||||
|  |     const Int = @TypeOf(value); | ||||||
|  |     const type_info = @typeInfo(Int); | ||||||
|  | 
 | ||||||
|  |     switch (type_info) { | ||||||
|  |         .Int => { | ||||||
|  |             if (value == 0) return writer.writeByte('0'); | ||||||
|  | 
 | ||||||
|  |             // TODO: Unhardcode this as it will break with large ints. | ||||||
|  |             var buffer = std.mem.zeroes([28]u8); | ||||||
|  |             var buffer_count = @as(usize, 0); | ||||||
|  |             var n1 = value; | ||||||
|  | 
 | ||||||
|  |             if ((type_info.Int.signedness == .signed) and (value < 0)) { | ||||||
|  |                 // Negative value. | ||||||
|  |                 n1 = -value; | ||||||
|  |                 buffer[0] = '-'; | ||||||
|  |                 buffer_count += 1; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             while (n1 != 0) { | ||||||
|  |                 const base = @enumToInt(radix); | ||||||
|  | 
 | ||||||
|  |                 buffer[buffer_count] = @intCast(u8, (n1 % base) + '0'); | ||||||
|  |                 n1 = (n1 / base); | ||||||
|  |                 buffer_count += 1; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             for (buffer[0 .. (buffer_count / 2)]) |_, i| | ||||||
|  |                 std.mem.swap(u8, &buffer[i], &buffer[buffer_count - i - 1]); | ||||||
|  | 
 | ||||||
|  |             return (writer.call(.{buffer[0 .. buffer_count]}) == buffer_count); | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         // Cast comptime int into known-size integer and try again. | ||||||
|  |         .ComptimeInt => return writer. | ||||||
|  |             writeInt(radix, @intCast(std.math.IntFittingRange(value, value), value)), | ||||||
|  | 
 | ||||||
|  |         else => @compileError("value must be of type int"), | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										146
									
								
								src/ona/io.zig
									
									
									
									
									
								
							
							
						
						
									
										146
									
								
								src/ona/io.zig
									
									
									
									
									
								
							| @ -1,3 +1,4 @@ | |||||||
|  | const meta = @import("./meta.zig"); | ||||||
| const stack = @import("./stack.zig"); | const stack = @import("./stack.zig"); | ||||||
| const std = @import("std"); | const std = @import("std"); | ||||||
| 
 | 
 | ||||||
| @ -170,121 +171,9 @@ test "Spliterating text" { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// Opaque interface to a "writable" resource, such as a block device, memory buffer, or network | /// Opaque interface to a "writable" resource like a block device, memory buffer, or network socket. | ||||||
| /// socket. |  | ||||||
| /// | /// | ||||||
| pub const Writer = struct { | pub const Writer = meta.Function([]const u8, usize); | ||||||
|     context: *anyopaque, |  | ||||||
|     writeContext: fn (*anyopaque, []const u8) usize, |  | ||||||
| 
 |  | ||||||
|     /// |  | ||||||
|     /// Radices supported by [writeInt]. |  | ||||||
|     /// |  | ||||||
|     pub const Radix = enum { |  | ||||||
|         binary, |  | ||||||
|         tinary, |  | ||||||
|         quaternary, |  | ||||||
|         quinary, |  | ||||||
|         senary, |  | ||||||
|         septenary, |  | ||||||
|         octal, |  | ||||||
|         nonary, |  | ||||||
|         decimal, |  | ||||||
|         undecimal, |  | ||||||
|         duodecimal, |  | ||||||
|         tridecimal, |  | ||||||
|         tetradecimal, |  | ||||||
|         pentadecimal, |  | ||||||
|         hexadecimal, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     /// |  | ||||||
|     /// Wraps and returns a reference to `write_context` of type `WriteContext` and its associated |  | ||||||
|     /// `writeContext` writing operation in a [Writer]. |  | ||||||
|     /// |  | ||||||
|     pub fn wrap( |  | ||||||
|         comptime WriteContext: type, |  | ||||||
|         write_context: *WriteContext, |  | ||||||
|         comptime writeContext: fn (*WriteContext, []const u8) usize |  | ||||||
|     ) Writer { |  | ||||||
|         return .{ |  | ||||||
|             .context = write_context, |  | ||||||
| 
 |  | ||||||
|             .writeContext = struct { |  | ||||||
|                 fn write(context: *anyopaque, buffer: []const u8) usize { |  | ||||||
|                     return writeContext(@ptrCast(*WriteContext, |  | ||||||
|                         @alignCast(@alignOf(WriteContext), context)), buffer); |  | ||||||
|                 } |  | ||||||
|             }.write, |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// |  | ||||||
|     /// Attempts to write `buffer` to `writer`, returning the number of bytes from `buffer` that |  | ||||||
|     /// were successfully written. |  | ||||||
|     /// |  | ||||||
|     pub fn write(writer: Writer, buffer: []const u8) usize { |  | ||||||
|         return writer.writeContext(writer.context, buffer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// |  | ||||||
|     /// Writes the singular `byte` to `writer`, returning `true` if it was successfully written, |  | ||||||
|     /// otherwise `false`. |  | ||||||
|     /// |  | ||||||
|     pub fn writeByte(writer: Writer, byte: u8) bool { |  | ||||||
|         return (writer.writeContext(writer.context, |  | ||||||
|             @ptrCast([*]const u8, &byte)[0 .. 1]) != 0); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// |  | ||||||
|     /// Writes `value` as a ASCII / UTF-8 encoded integer to `writer`, returning `true` if the full |  | ||||||
|     /// sequence was successfully written, otherwise `false`. |  | ||||||
|     /// |  | ||||||
|     /// The `radix` argument identifies which base system to encode `value` as, with `10` being |  | ||||||
|     /// decimal, `16` being hexadecimal, `8` being octal`, so on and so forth. |  | ||||||
|     /// |  | ||||||
|     pub fn writeInt(writer: Writer, radix: Radix, value: anytype) bool { |  | ||||||
|         const Int = @TypeOf(value); |  | ||||||
|         const type_info = @typeInfo(Int); |  | ||||||
| 
 |  | ||||||
|         switch (type_info) { |  | ||||||
|             .Int => { |  | ||||||
|                 if (value == 0) return writer.writeByte('0'); |  | ||||||
| 
 |  | ||||||
|                 // TODO: Unhardcode this as it will break with large ints. |  | ||||||
|                 var buffer = std.mem.zeroes([28]u8); |  | ||||||
|                 var buffer_count = @as(usize, 0); |  | ||||||
|                 var n1 = value; |  | ||||||
| 
 |  | ||||||
|                 if ((type_info.Int.signedness == .signed) and (value < 0)) { |  | ||||||
|                     // Negative value. |  | ||||||
|                     n1 = -value; |  | ||||||
|                     buffer[0] = '-'; |  | ||||||
|                     buffer_count += 1; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 while (n1 != 0) { |  | ||||||
|                     const base = @enumToInt(radix); |  | ||||||
| 
 |  | ||||||
|                     buffer[buffer_count] = @intCast(u8, (n1 % base) + '0'); |  | ||||||
|                     n1 = (n1 / base); |  | ||||||
|                     buffer_count += 1; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 for (buffer[0 .. (buffer_count / 2)]) |_, i| |  | ||||||
|                     std.mem.swap(u8, &buffer[i], &buffer[buffer_count - i - 1]); |  | ||||||
| 
 |  | ||||||
|                 return (writer.write(buffer[0 .. buffer_count]) == buffer_count); |  | ||||||
|             }, |  | ||||||
| 
 |  | ||||||
|             // Cast comptime int into known-size integer and try again. |  | ||||||
|             .ComptimeInt => return writer. |  | ||||||
|                 writeInt(radix, @intCast(std.math.IntFittingRange(value, value), value)), |  | ||||||
| 
 |  | ||||||
|             else => @compileError("value must be of type int"), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// Returns `true` if `this_bytes` is the same length and contains the same data as `that_bytes`, | /// Returns `true` if `this_bytes` is the same length and contains the same data as `that_bytes`, | ||||||
| @ -323,21 +212,19 @@ test "Hashing bytes" { | |||||||
|     try testing.expect(hashBytes(bytes_sequence) != hashBytes(&.{69, 42})); |     try testing.expect(hashBytes(bytes_sequence) != hashBytes(&.{69, 42})); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var null_context: usize = undefined; | ||||||
|  | 
 | ||||||
| /// | /// | ||||||
| /// Writer that silently throws consumed data away and never fails. | /// Writer that silently throws consumed data away and never fails. | ||||||
| /// | /// | ||||||
| /// This is commonly used for testing or redirected otherwise unwanted output data that can't not be | /// This is commonly used for testing or redirected otherwise unwanted output data that can't not be | ||||||
| /// sent somewhere for whatever reason. | /// sent somewhere for whatever reason. | ||||||
| /// | /// | ||||||
| pub const null_writer = Writer{ | pub const null_writer = Writer.wrap(&null_context, struct { | ||||||
|     .context = undefined, |     fn write(_: *@TypeOf(null_context), buffer: []const u8) usize { | ||||||
| 
 |         return buffer.len; | ||||||
|     .writeContext = struct { |     } | ||||||
|         fn write(_: *anyopaque, buffer: []const u8) usize { | }.write); | ||||||
|             return buffer.len; |  | ||||||
|         } |  | ||||||
|     }.write, |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| test "Null writing" { | test "Null writing" { | ||||||
|     const testing = std.testing; |     const testing = std.testing; | ||||||
| @ -345,9 +232,14 @@ test "Null writing" { | |||||||
|     { |     { | ||||||
|         const sequence = "foo"; |         const sequence = "foo"; | ||||||
| 
 | 
 | ||||||
|         try testing.expectEqual(null_writer.write(sequence), sequence.len); |         try testing.expectEqual(null_writer.apply(sequence), sequence.len); | ||||||
|     } |     } | ||||||
| 
 | } | ||||||
|     try testing.expect(null_writer.writeByte(0)); | 
 | ||||||
|     try testing.expect(null_writer.writeInt(.decimal, 420)); | /// | ||||||
|  | /// Writes the singular `byte` to `writer`, returning `true` if it was successfully written, | ||||||
|  | /// otherwise `false`. | ||||||
|  | /// | ||||||
|  | pub fn writeByte(writer: Writer, byte: u8) bool { | ||||||
|  |     return (writer.call(.{@ptrCast([*]const u8, &byte)[0 .. 1]}) != 0); | ||||||
| } | } | ||||||
|  | |||||||
| @ -8,3 +8,54 @@ pub fn FnReturn(comptime Fn: type) type { | |||||||
| 
 | 
 | ||||||
|     return type_info.Fn.return_type orelse void; |     return type_info.Fn.return_type orelse void; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// Returns single-input closure type where `Input` is the input type and `Output` is the output | ||||||
|  | /// type. | ||||||
|  | /// | ||||||
|  | pub fn Function(comptime Input: type, comptime Output: type) type { | ||||||
|  |     return struct { | ||||||
|  |         context: *anyopaque, | ||||||
|  |         contextualApply: fn (*anyopaque, Input) Output, | ||||||
|  | 
 | ||||||
|  |         /// | ||||||
|  |         /// Function type. | ||||||
|  |         /// | ||||||
|  |         const Self = @This(); | ||||||
|  | 
 | ||||||
|  |         /// | ||||||
|  |         /// Applies `input` to `self`, producing a result according to the type-erased | ||||||
|  |         /// implementation. | ||||||
|  |         /// | ||||||
|  |         pub fn apply(self: Self, input: Input) Output { | ||||||
|  |             return self.contextualApply(self.context, input); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// | ||||||
|  |         /// Creates a new [Self] by wrapping `concrete_context` as a pointer to the implementation | ||||||
|  |         /// and `contextualApply` as the behavior executed when [apply] is called. | ||||||
|  |         /// | ||||||
|  |         /// The newly created [Self] is returned. | ||||||
|  |         /// | ||||||
|  |         pub fn wrap( | ||||||
|  |             concrete_context: anytype, | ||||||
|  |             comptime contextualApply: fn (@TypeOf(concrete_context), Input) Output | ||||||
|  |         ) Self { | ||||||
|  |             const ConcreteContext = @TypeOf(concrete_context); | ||||||
|  | 
 | ||||||
|  |             if (@typeInfo(ConcreteContext) != .Pointer) | ||||||
|  |                 @compileError("`concrete_context` must be a pointer type"); | ||||||
|  | 
 | ||||||
|  |             return .{ | ||||||
|  |                 .context = concrete_context, | ||||||
|  | 
 | ||||||
|  |                 .contextualApply = struct { | ||||||
|  |                     fn call(erased_context: *anyopaque, input: Input) Output { | ||||||
|  |                         return contextualApply(@ptrCast(ConcreteContext, @alignCast( | ||||||
|  |                             @alignOf(ConcreteContext), erased_context)), input); | ||||||
|  |                     } | ||||||
|  |                 }.call, | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | |||||||
| @ -23,7 +23,7 @@ pub fn Fixed(comptime Element: type) type { | |||||||
|             if (Element != u8) @compileError("Cannot coerce fixed stack of type " ++ |             if (Element != u8) @compileError("Cannot coerce fixed stack of type " ++ | ||||||
|                 @typeName(Element) ++ " into a Writer"); |                 @typeName(Element) ++ " into a Writer"); | ||||||
| 
 | 
 | ||||||
|             return io.Writer.wrap(Self, self, struct { |             return io.Writer.wrap(self, struct { | ||||||
|                 fn write(stack: *Self, buffer: []const u8) usize { |                 fn write(stack: *Self, buffer: []const u8) usize { | ||||||
|                     stack.pushAll(buffer) catch |err| switch (err) { |                     stack.pushAll(buffer) catch |err| switch (err) { | ||||||
|                         error.OutOfMemory => return 0, |                         error.OutOfMemory => return 0, | ||||||
| @ -112,6 +112,5 @@ test "Fixed stack manipulation" { | |||||||
| 
 | 
 | ||||||
|     const writer = stack.writer(); |     const writer = stack.writer(); | ||||||
| 
 | 
 | ||||||
|     try testing.expectEqual(writer.write(&.{0, 0, 0, 0}), 4); |     try testing.expectEqual(writer.apply(&.{0, 0, 0, 0}), 4); | ||||||
|     try testing.expectEqual(writer.writeByte(0), false); |  | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user