From 071d890391c6249217ea4d36b91a1179beb59d37 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 3 Jun 2023 14:04:42 +0000 Subject: [PATCH] Fix Kym error messages --- source/coral/io.zig | 22 +- source/coral/utf8.zig | 421 +++++++++++++++++++------------------- source/ona/kym/Ast.zig | 4 +- source/ona/kym/Chunk.zig | 22 +- source/ona/kym/tokens.zig | 7 +- source/runner.zig | 5 - 6 files changed, 233 insertions(+), 248 deletions(-) diff --git a/source/coral/io.zig b/source/coral/io.zig index 7a347f3..90cbb23 100755 --- a/source/coral/io.zig +++ b/source/coral/io.zig @@ -25,18 +25,19 @@ pub fn Functor(comptime Output: type, comptime Input: type) type { const Self = @This(); pub fn bind(comptime State: type, state: *const State, comptime invoker: fn (capture: *const State, input: Input) Output) Self { + const alignment = @alignOf(State); + const is_zero_aligned = alignment == 0; + return .{ - .context = state, + .context = if (is_zero_aligned) state else @ptrCast(*const anyopaque, state), .invoker = struct { fn invoke_opaque(context: *const anyopaque, input: Input) Output { - const state_alignment = @alignOf(State); - - if (state_alignment == 0) { + if (is_zero_aligned) { return invoker(@ptrCast(*const State, context), input); } - return invoker(@ptrCast(*const State, @alignCast(state_alignment, context)), input); + return invoker(@ptrCast(*const State, @alignCast(alignment, context)), input); } }.invoke_opaque, }; @@ -60,18 +61,19 @@ pub fn Generator(comptime Output: type, comptime Input: type) type { const Self = @This(); pub fn bind(comptime State: type, state: *State, comptime invoker: fn (capture: *State, input: Input) Output) Self { + const alignment = @alignOf(State); + const is_zero_aligned = alignment == 0; + return .{ - .context = state, + .context = if (is_zero_aligned) state else @ptrCast(*anyopaque, state), .invoker = struct { fn invoke_opaque(context: *anyopaque, input: Input) Output { - const state_alignment = @alignOf(State); - - if (state_alignment == 0) { + if (is_zero_aligned) { return invoker(@ptrCast(*State, context), input); } - return invoker(@ptrCast(*State, @alignCast(state_alignment, context)), input); + return invoker(@ptrCast(*State, @alignCast(alignment, context)), input); } }.invoke_opaque, }; diff --git a/source/coral/utf8.zig b/source/coral/utf8.zig index b6214a8..4b448ba 100755 --- a/source/coral/utf8.zig +++ b/source/coral/utf8.zig @@ -9,26 +9,18 @@ const std = @import("std"); /// /// /// -pub const ArgsContext = struct { - writer: io.Writer, - index: usize, +pub const DecimalFormat = struct { + delimiter: []const u8 = "", + positive_prefix: enum {none, plus, space} = .none, }; /// /// /// -pub const ArgsFormatter = io.Functor(PrintError!void, ArgsContext); - -/// -/// Errors that may occur during utf8-encoded int parsing. -/// -pub const IntParseError = math.CheckedArithmeticError || ParseError; - -/// -/// Optional rules for int parsing logic to consider during parsing. -/// -pub const IntParseOptions = struct { +pub const HexadecimalFormat = struct { delimiter: []const u8 = "", + positive_prefix: enum {none, plus, space} = .none, + casing: enum {lower, upper} = .lower, }; /// @@ -46,119 +38,105 @@ pub const PrintError = error { PrintIncomplete, }; -pub fn format_args(args: anytype) ArgsFormatter { - const Args = @TypeOf(args); - - return ArgsFormatter.bind(Args, &args, struct { - fn get(typed_args: *const Args, context: ArgsContext) PrintError!void { - _ = typed_args; - _ = context; - } - }.get); -} - /// -/// Attempts to parse a float value of type described by `float` from `utf8`. /// -/// The function returns a [ParseError] if `utf8` does not conform to the syntax of a float. /// -pub fn parse_float(comptime float: std.builtin.Type.Float, utf8: []const u8) ParseError!math.Float(float) { - // "" +pub fn parse_decimal(comptime Decimal: type, utf8: []const u8, format: DecimalFormat) !Decimal { if (utf8.len == 0) { return error.BadSyntax; } - const is_negative = utf8[0] == '-'; + switch (@typeInfo(Decimal)) { + .Int => |int| { + var has_sign = switch (utf8[0]) { + '-', '+', ' ' => true, + else => false, + }; - // "-" - if (is_negative and (utf8.len == 1)) { - return error.BadSyntax; - } + var result = @as(Decimal, 0); - const negative_offset = @boolToInt(is_negative); - var has_decimal = utf8[negative_offset] == '.'; + for (@boolToInt(has_sign) .. utf8.len) |index| { + const radix = 10; + const code = utf8[index]; - // "-." - if (has_decimal and (utf8.len == 2)) { - return error.BadSyntax; - } + switch (code) { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { + result = try math.checked_add( + try math.checked_mul(result, radix), + try math.checked_sub(code, '0')); + }, - const Float = math.Float(float); - var result: Float = 0; - var factor: Float = 1; + else => { + if (format.delimiter.len == 0 or !io.equals(format.delimiter, utf8[index ..])) { + return error.BadSyntax; + } + }, + } + } - for (utf8[0 .. negative_offset + @boolToInt(has_decimal)]) |code| switch (code) { - '.' => { - if (has_decimal) return error.BadSyntax; + switch (int.signedness) { + .signed => { + return result * @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1); + }, - has_decimal = true; + .unsigned => { + if (has_sign and utf8[0] == '-') { + return error.OutOfMemory; + } + + return result; + }, + } }, - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { - if (has_decimal) factor /= 10.0; + .Float => { + // "" + if (utf8.len == 0) { + return error.BadSyntax; + } - result = ((result * 10.0) + @intToFloat(Float, code - '0')); + var has_sign = switch (utf8[0]) { + '-', '+', ' ' => true, + else => false, + }; + + // "-" + if (has_sign and utf8.len == 1) { + return error.BadSyntax; + } + + const sign_offset = @boolToInt(has_sign); + var has_decimal = utf8[sign_offset] == '.'; + + // "-." + if (has_decimal and (utf8.len == 2)) { + return error.BadSyntax; + } + + var result = @as(Decimal, 0); + var factor = @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1); + + for (utf8[0 .. (sign_offset + @boolToInt(has_decimal))]) |code| switch (code) { + '.' => { + if (has_decimal) return error.BadSyntax; + + has_decimal = true; + }, + + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { + if (has_decimal) factor /= 10.0; + + result = ((result * 10.0) + @intToFloat(Decimal, code - '0')); + }, + + else => return error.BadSyntax, + }; + + return result * factor; }, - else => return error.BadSyntax, - }; - - return result * factor; -} - -/// -/// Attempts to parse an int value of type described by `int` from `utf8`, with `options` as additional rules for the -/// parsing logic to consider. -/// -/// The function returns a [IntParseError] if `utf8` does not conform to the syntax of a float, does not match the rules -/// specified in `option`, or exceeds the maximum size of the int described by `int`. -/// -pub fn parse_int( - comptime int: std.builtin.Type.Int, - utf8: []const u8, - options: IntParseOptions) IntParseError!math.Int(int) { - - if (utf8.len == 0) { - return error.BadSyntax; + else => @compileError("`" ++ @typeName(Decimal) ++ "` cannot be formatted as a decimal string"), } - - { - const is_negative = utf8[0] == '-'; - - switch (int.signedness) { - .signed => { - if (is_negative and utf8.len == 1) { - return error.BadSyntax; - } - }, - - .unsigned => { - if (is_negative) { - return error.BadSyntax; - } - }, - } - } - - var result = @as(math.Int(int), 0); - - for (0 .. utf8.len) |index| { - const code = utf8[index]; - - switch (code) { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { - result = try math.checked_add(try math.checked_mul(result, 10), try math.checked_sub(code, '0')); - }, - - else => { - if (options.delimiter.len == 0 or !io.equals(options.delimiter, utf8[index ..])) { - return error.BadSyntax; - } - }, - } - } - - return result; } /// @@ -175,123 +153,146 @@ pub fn print(writer: io.Writer, utf8: []const u8) PrintError!void { /// /// /// -pub fn print_float(comptime float: std.builtin.Type.Float, writer: io.Writer, value: @Type(float)) PrintError!void { - _ = writer; - _ = value; -} +pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments: anytype) PrintError!void { + switch (@typeInfo(@TypeOf(arguments))) { + .Struct => |arguments_struct| { + comptime var arg_index = 0; + comptime var head = 0; + comptime var tail = 0; -/// -/// Prints the format string `format` with all `{...}` placeholders substituted with the a value in `args` corresponding -/// to the ordinality of each substitution. -/// -/// Specifiers may be placed inside `{}` to augment the behavior of the corresponding [FormatArg]. For example, to print -/// a float formatted with an integer component padding of `3`, a decimal padding of `2`, and a prefix for positives as -/// well as negatives, you may specify it as `{+3.2}`. -/// -/// To prevent braces from being interpreted as format placeholders, simply double-brace them (`"{{"`) and the format -/// string will substitute them as a single brace. -/// -/// *Note* the function assumes that, for every specifier in `format`, there is a corresponding [FormatArg] in `args`. -/// Further, any instance of `{` is assumed to be a placeholder and must be terminated with a corresponding `}` before -/// the end of string. Failure to meet any of these requirements will result in safety-checked runtime behavior. -/// -pub fn print_formatted(writer: io.Writer, format: []const u8, args_formatter: ArgsFormatter) PrintError!void { - const usize_int = @typeInfo(usize).Int; - var head = @as(usize, 0); - var tail = @as(usize, 0); - var arg_index = @as(usize, 0); - - while (tail < format.len) : (tail += 1) { - if (format[tail] == '{') { - debug.assert(tail < format.len); - - tail += 1; - - switch (format[tail]) { - '{' => { - try print(writer, format[head .. tail]); - - tail += 1; - head = tail; - }, - - '}' => { - try print(writer, format[head .. tail]); - - try args_formatter.invoke(.{ - .index = arg_index, - .writer = writer, - }); - - arg_index += 1; - tail += 1; - head = tail; - }, - - else => { - try print(writer, format[head .. tail]); - - tail += 1; - head = tail; - - debug.assert(tail < format.len); - - while (format[tail] != '}') { - tail += 1; - - debug.assert(tail < format.len); + inline while (tail < format.len) : (tail += 1) { + if (format[tail] == '{') { + if (tail > format.len) { + @compileError("expected an idenifier after opening `{`"); } - const arg_index_name = format[head .. tail]; - - try args_formatter.invoke(.{ - .index = parse_int(usize_int, arg_index_name, .{}) catch @panic("invalid arg index value"), - .writer = writer, - }); - tail += 1; - head = tail; + + switch (format[tail]) { + '{' => { + try print(writer, format[head .. (tail - 1)]); + + tail += 1; + head = tail; + }, + + '}' => { + if (!arguments_struct.is_tuple) { + @compileError("all format specifiers must be named when using a named struct"); + } + + try print(writer, arguments[arg_index]); + + arg_index += 1; + tail += 1; + head = tail; + }, + + else => { + if (arguments_struct.is_tuple) { + @compileError("format specifiers cannot be named when using a tuple struct"); + } + + try print(writer, format[head .. (tail - 1)]); + + head = tail; + tail += 1; + + if (tail >= format.len) { + @compileError("expected closing `}` or another `{` after opening `{`"); + } + + debug.assert(tail < format.len); + + inline while (format[tail] != '}') { + tail += 1; + + debug.assert(tail < format.len); + } + + try print_value(writer, @field(arguments, format[head .. tail])); + + tail += 1; + head = tail; + } + } } } - } + }, + + else => @compileError("`arguments` must be a struct type"), } } /// -/// Attempts to print the int `value` described by `int` to `writer`. /// -/// The function returns [PrintError] if the write failed to complete partially or entirely. /// -pub fn print_int(comptime int: std.builtin.Type.Int, writer: io.Writer, value: math.Int(int)) PrintError!void { +pub fn print_decimal(writer: io.Writer, value: anytype, format: DecimalFormat) PrintError!void { if (value == 0) { - return try print(writer, "0"); + return print(writer, switch (format.positive_prefix) { + .none => "0", + .plus => "+0", + .space => " 0", + }); } - // TODO: Don't make this buffer arbitrarily size cause big int types WILL overflow. - var buffer = [_]u8{0} ** 40; - var buffer_count: usize = 0; - var split_value = value; + switch (@typeInfo(@TypeOf(value))) { + .Int => |int| { + const radix = 10; + var buffer = [_]u8{0} ** (1 + math.max(int.bits, 1)); + var buffer_start = buffer.len - 1; - if ((int.signedness == .unsigned) and (value < 0)) { - buffer[0] = '-'; - buffer_count += 1; + { + var decomposable_value = value; + + while (decomposable_value != 0) : (buffer_start -= 1) { + buffer[buffer_start] = @intCast(u8, (decomposable_value % radix) + '0'); + decomposable_value = (decomposable_value / radix); + } + } + + if (int.signedness == .unsigned and value < 0) { + buffer[buffer_start] = '-'; + } else { + switch (format.positive_prefix) { + .none => buffer_start += 1, + .plus => buffer[buffer_start] = '+', + .space => buffer[buffer_start] = ' ', + } + } + + try print(writer, buffer[buffer_start ..]); + }, + + else => @compileError("`arguments` must be a struct type"), } - - while (split_value != 0) : (buffer_count += 1) { - const radix = 10; - - buffer[buffer_count] = @intCast(u8, (split_value % radix) + '0'); - split_value = (split_value / radix); - } - - { - const half_buffer_count = buffer_count / 2; - var index: usize = 0; - - while (index < half_buffer_count) : (index += 1) { - io.swap(u8, &buffer[index], &buffer[buffer_count - index - 1]); - } - } - - try print(writer, buffer[0 .. buffer_count]); +} + +pub fn print_hexadecimal(writer: io.Writer, value: anytype, format: HexadecimalFormat) PrintError!void { + // TODO: Implement. + _ = writer; + _ = value; + _ = format; + + unreachable; +} + +noinline fn print_value(writer: io.Writer, value: anytype) PrintError!void { + const Value = @TypeOf(value); + + return switch (@typeInfo(Value)) { + .Int => print_decimal(writer, value, .{}), + .Float => print_decimal(writer, value, .{}), + + .Pointer => |pointer| switch (pointer.size) { + .One, .Many, .C => print_hexadecimal(writer, @ptrToInt(value), .{}), + .Slice => if (pointer.child == u8) print(writer, value) else @compileError(unformattableMessage(Value)), + }, + + else => @compileError(unformattableMessage(Value)), + }; +} + +fn unformattableMessage(comptime Value: type) []const u8 { + return "`" ++ @typeName(Value) ++ "` are not formattable"; } diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig index 280f715..ae7d466 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -189,7 +189,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expr _ = tokenizer.step(.{.include_newlines = false}); return Expression{ - .integer_literal = coral.utf8.parse_int(@typeInfo(types.Integer).Int, value, .{}) catch |parse_error| { + .integer_literal = coral.utf8.parse_decimal(types.Integer, value, .{}) catch |parse_error| { return self.fail_syntax(switch (parse_error) { error.BadSyntax => "invalid integer literal", error.IntOverflow => "integer literal is too big", @@ -202,7 +202,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expr _ = tokenizer.step(.{.include_newlines = false}); return Expression{ - .float_literal = coral.utf8.parse_float(@typeInfo(types.Float).Float, value) catch |parse_error| { + .float_literal = coral.utf8.parse_decimal(types.Float, value, .{}) catch |parse_error| { return self.fail_syntax(switch (parse_error) { error.BadSyntax => "invalid float literal", }); diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 6eea305..208eae0 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -59,25 +59,15 @@ pub fn compile(self: *Self, data: []const u8) types.RuntimeError!void { var tokenizer = tokens.Tokenizer{.source = data}; ast.parse(&tokenizer) catch |init_error| { - if (init_error == error.OutOfMemory) { + if (init_error == error.BadSyntax) { self.clear_error_details(); - try self.message_data.push_all(self.env.allocator, "@("); - var message_buffer = self.message_data.as_buffer(self.env.allocator); - const message_writer = message_buffer.as_writer(); - coral.utf8.print_int(@typeInfo(usize).Int, message_writer, tokenizer.lines_stepped) catch { - return error.OutOfMemory; - }; - - coral.utf8.print(message_writer, "): ") catch { - return error.OutOfMemory; - }; - - coral.utf8.print(message_writer, ast.error_message) catch { - return error.OutOfMemory; - }; + coral.utf8.print_formatted(message_buffer.as_writer(), "@({line}): {name}", .{ + .line = tokenizer.lines_stepped, + .name = ast.error_message, + }) catch return error.OutOfMemory; } return init_error; @@ -216,7 +206,7 @@ pub fn emit_opcode(self: *Self, opcode: Opcode) coral.io.AllocationError!void { pub fn error_details(self: Self) []const u8 { coral.debug.assert(self.message_data.values.len >= self.message_name_len); - return self.message_data.values[self.message_name_len .. ]; + return self.message_data.values; } pub fn init(env: *Environment, chunk_name: []const u8) coral.io.AllocationError!Self { diff --git a/source/ona/kym/tokens.zig b/source/ona/kym/tokens.zig index a3ef7af..3925817 100755 --- a/source/ona/kym/tokens.zig +++ b/source/ona/kym/tokens.zig @@ -91,8 +91,6 @@ pub const Tokenizer = struct { include_newlines: bool, }; - const default_token = Token{.unknown = 0}; - pub fn step(self: *Tokenizer, options: StepOptions) bool { var cursor = @as(usize, 0); @@ -114,11 +112,10 @@ pub const Tokenizer = struct { '\n' => { cursor += 1; + self.current_token = .newline; + self.lines_stepped += 1; if (options.include_newlines) { - self.lines_stepped += 1; - self.current_token = .newline; - return true; } }, diff --git a/source/runner.zig b/source/runner.zig index e0ee81c..215069b 100755 --- a/source/runner.zig +++ b/source/runner.zig @@ -3,10 +3,5 @@ const coral = @import("coral"); const ona = @import("ona"); pub fn main() !void { - var data = [_]u8{0} ** 32; - var fixed_buffer = coral.io.FixedBuffer{.slice = &data}; - - try coral.utf8.print_formatted(fixed_buffer.as_writer(), "hello, {}", coral.utf8.format_args(.{"world"})); - ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd}); }