diff options
| author | Taylan Kammer <taylan.kammer@gmail.com> | 2025-03-28 18:02:38 +0100 |
|---|---|---|
| committer | Taylan Kammer <taylan.kammer@gmail.com> | 2025-03-28 18:02:38 +0100 |
| commit | 6eedf5394997b91467a392732cdb7fbb80a790b8 (patch) | |
| tree | 69ad43857ec17315c9a9d5547709eb6fd238502d | |
| parent | 00fd32b6c0d35140bdc160aa759bbac52242d7d0 (diff) | |
blub
| -rw-r--r-- | _tests/test.zig | 4 | ||||
| -rw-r--r-- | spec/parser.ebnf | 30 | ||||
| -rw-r--r-- | src/libzisp.zig | 16 | ||||
| -rw-r--r-- | src/libzisp/io/parser.zig | 250 | ||||
| -rw-r--r-- | src/libzisp/value/rune.zig | 4 |
5 files changed, 170 insertions, 134 deletions
diff --git a/_tests/test.zig b/_tests/test.zig index 5acb628..e746851 100644 --- a/_tests/test.zig +++ b/_tests/test.zig @@ -1,11 +1,13 @@ const std = @import("std"); -pub fn main() void { +pub fn main() u8 { // const y: [3]u64 = .{ 1, 2, 3 }; // const x: struct { u8, u64, u8 } = y; // @import("std").debug.print("{}\n", .{x[0] + x[1] + x[2]}); std.debug.print("{}\n", .{@sizeOf(struct { u64, ?u8 })}); + + return while (true) if (true) break 1; } // const x: ?u8 = 5; diff --git a/spec/parser.ebnf b/spec/parser.ebnf index 44b1967..60f7890 100644 --- a/spec/parser.ebnf +++ b/spec/parser.ebnf @@ -1,11 +1,14 @@ -unit : blank* ( datum blank? | EOF ) ; +unit : empty_unit | datum_unit ; -blank : 9...13 | comment ; +empty_unit : blank* EOF ; -datum : one_datum ( join_char? one_datum )* ; +datum_unit : blank* datum blank? ; -join_char : '.' | ':' | '|' ; + +blank : 9...13 | comment ; + +datum : join_data | dot_string ; comment : ';' ( skip_unit | skip_line ) ; @@ -15,9 +18,18 @@ skip_unit : '~' unit ; skip_line : ( ~10 )* 10? ; -one_datum : ( bare_str | clad_datum ) ; +join_data : one_datum ( join_char? one_datum )* + +join_char : '.' | ':' | '|' ; + +dot_string : '.'{2,} + -bare_str : bare_str_elt+ ; +one_datum : ( num_string | bare_string | clad_datum ) ; + +num_string : ( '+' | '-' )? digit ( bare_str_elt | '.' )* ; + +bare_string : bare_str_elt+ ; clad_datum : '\' bare_esc_str | '"' quoted_str '"' @@ -37,11 +49,13 @@ bare_esc_str : bare_esc bare_str_elt* ; quoted_str : ( quoted_char | '\' quoted_esc )* ; hash_expr : rune clad_datum? - | '%' label ( '%' | '=' unit ) + | '%' label ( '%' | '=' datum_unit ) | clad_datum ; -list : unit+ ( '.' blank+ unit )? blank* ; +list : datum_unit+ list_tail? blank* ; + +list_tail : '.' blank+ datum_unit quote_expr : ( "'" | "`" | "," ) datum ; diff --git a/src/libzisp.zig b/src/libzisp.zig index df8422b..ceee3f6 100644 --- a/src/libzisp.zig +++ b/src/libzisp.zig @@ -352,11 +352,17 @@ fn parseBench(path: []const u8, iters: usize) !void { var timer = try std.time.Timer.start(); for (0..iters) |i| { _ = i; - var br = std.io.bufferedReader(file.reader()); - const reader = br.reader().any(); + // var br = std.io.bufferedReader(file.reader()); + // const reader = br.reader().any(); + const reader = file.reader().any(); var v: Value = undefined; while (true) { - v = io.parser.parse(reader); + v = io.parser._parse(reader) catch |e| { + std.debug.print("\nfile pos: {}\n", .{ + try file.getPos(), + }); + return e; + }; if (value.eof.check(v)) { break; } @@ -374,7 +380,7 @@ fn parseBench(path: []const u8, iters: usize) !void { test "parse bench" { try parseBench("test-data/parser-test-1.scm", 1000); try parseBench("test-data/parser-test-2.scm", 1000); - // try parseBench("test-data/parser-torture.scm", 1); + try parseBench("test-data/parser-torture.scm", 1); } test "unparse" { @@ -423,7 +429,7 @@ test "unparse5" { test "unparse6" { const w = std.io.getStdErr().writer(); - const v = parseString("(foo .bar ... baz. bat.(qux))"); + const v = parseString("(foo bar ... baz bat.(qux))"); try io.unparser.unparse(w, v); try w.writeByte('\n'); } diff --git a/src/libzisp/io/parser.zig b/src/libzisp/io/parser.zig index 643f7e8..8093ffe 100644 --- a/src/libzisp/io/parser.zig +++ b/src/libzisp/io/parser.zig @@ -275,10 +275,10 @@ const GRAVE = value.rune.pack("GRAVE"); const COMMA = value.rune.pack("COMMA"); const SQUARE = value.rune.pack("SQUARE"); const BRACE = value.rune.pack("BRACE"); +const VOID = value.rune.packForced(""); +const LSTAIL = value.rune.packForced("."); // zig fmt: on -const S_DOT = value.sstr.pack("."); - const Context = struct { // What to do next. next: Fn = .parse_unit, @@ -288,7 +288,7 @@ const Context = struct { char: u8 = undefined, }; -const ParseError = error{ +const ParseError = enum { InvalidCharacter, UnclosedString, UnexpectedEof, @@ -314,7 +314,6 @@ const State = struct { result: Value = undefined, unused_char: ?u8 = null, - err_code: anyerror = undefined, err_msg: []const u8 = undefined, fn init( @@ -337,9 +336,13 @@ const State = struct { s.chars.deinit(s.chars_alloc); } - fn err(s: *State, e: ParseError, msg: []const u8) ParseError { - s.err_msg = msg; - return e; + fn err( + s: *State, + comptime e: ParseError, + comptime msg: []const u8, + ) error{ParseError} { + s.err_msg = @tagName(e) ++ " at: " ++ msg; + return error.ParseError; } fn read(s: *State) !?u8 { @@ -348,10 +351,7 @@ const State = struct { } const c = s.input.readByte() catch |e| switch (e) { error.EndOfStream => return null, - else => { - s.err_code = e; - return error.ReadError; - }, + else => return s.err(.ReadError, "???"), }; if (detailed_debug) { std.debug.print("{c}", .{c}); @@ -359,8 +359,8 @@ const State = struct { return c; } - fn readNoEof(s: *State, emsg: []const u8) !u8 { - return if (try s.read()) |c| c else s.err(error.UnexpectedEof, emsg); + fn readNoEof(s: *State, comptime emsg: []const u8) !u8 { + return if (try s.read()) |c| c else s.err(.UnexpectedEof, emsg); } fn getUnused(s: *State) ?u8 { @@ -371,10 +371,6 @@ const State = struct { return null; } - fn skipLine(s: *State) !void { - while (try s.read()) |c| if (c == '\n') break; - } - fn addChar(s: *State, c: u8) !void { try s.chars.append(s.chars_alloc, c); } @@ -423,7 +419,7 @@ const State = struct { } fn abort(s: *State, next: Fn, unused_c: u8) void { - s.result = value.undef; + s.result = VOID; s.unused_char = unused_c; s.context.next = next; } @@ -438,7 +434,7 @@ const State = struct { } }; -pub fn parse(input: std.io.AnyReader) Value { +pub fn _parse(input: std.io.AnyReader) !Value { var debug_alloc: std.heap.DebugAllocator(.{}) = undefined; if (!is_test and is_debug) { debug_alloc = .init; @@ -465,25 +461,28 @@ pub fn parse(input: std.io.AnyReader) Value { var s = State.init(input, stack_alloc, chars_alloc) catch @panic(""); defer s.deinit(); - while (s.context.next != .done) callNext(&s) catch { - if (s.unused_char) |c| { - std.debug.panic( - "Parse error: {} at: {s}, char: {c}\n", - .{ s.err_code, s.err_msg, c }, - ); - } else { - std.debug.panic( - "Parse error: {} at: {s}\n", - .{ s.err_code, s.err_msg }, - ); - } + while (s.context.next != .done) callNext(&s) catch |e| { + // _ = e; + // if (s.unused_char) |c| { + // std.debug.panic( + // "Parse error: {s}, unused_char: 0x{x}\n", + // .{ s.err_msg, c }, + // ); + // } else { + // std.debug.panic("Parse error: {s}\n", .{s.err_msg}); + // } + return e; }; if (s.unused_char) |c| { - std.debug.panic("Invalid character: {c}\n", .{c}); + std.debug.panic("Invalid trailing character: {c}\n", .{c}); } return s.result; } +pub fn parse(input: std.io.AnyReader) Value { + return _parse(input) catch @panic(""); +} + const Fn = enum { parse_unit, return_context, @@ -549,43 +548,74 @@ fn callNext(s: *State) !void { fn parseUnit(s: *State) !void { var c1 = s.getUnused() orelse try s.read(); while (c1) |c| : (c1 = try s.read()) { - switch (try checkBlank(s, c)) { + switch (try checkBlanks(s, c)) { .yes => {}, .skip_unit => try s.push(.parse_unit), - .skip_line => try s.skipLine(), .no => return parseDatum(s, c), } } return s.retval(value.eof.eof); } -fn checkBlank(s: *State, c: u8) !enum { yes, skip_unit, skip_line, no } { +fn checkBlanks(s: *State, c: u8) !enum { yes, skip_unit, no } { return switch (c) { '\t'...'\r', ' ' => .yes, ';' => switch (try s.read() orelse '\n') { '\n' => .yes, '~' => .skip_unit, - else => .skip_line, + else => while (try s.read() != '\n') {} else .yes, }, else => .no, }; } fn parseDatum(s: *State, c: u8) !void { + if (c == '.') { + return parseDotString(s); + } return parseOneDatum(s, c, .end_one_datum); } +fn parseDotString(s: *State) !void { + try s.addChar('.'); + while (try s.read()) |c| { + switch (try checkBlanks(s, c)) { + .yes => return dotString(s, false), + .skip_unit => return dotString(s, true), + .no => switch (c) { + '.' => try s.addChar('.'), + ')', ']', '}' => { + s.unused_char = c; + return dotString(s, false); + }, + else => return s.err(.InvalidCharacter, "dot string"), + }, + } + } + unreachable; +} + +fn dotString(s: *State, skip_unit: bool) !void { + const lstail = s.chars.items.len == 1; + const result = if (lstail) LSTAIL else s.getBareString(); + if (skip_unit) { + s.context.val = result; + return s.subr(.parse_unit, .return_context); + } else { + return s.retval(result); + } +} + fn endOneDatum(s: *State) !void { - if (s.result.eq(value.undef)) { - return s.retval(value.undef); + if (s.result.eq(VOID)) { + return s.retval(VOID); } const d = s.result; const c1 = s.getUnused() orelse try s.read(); if (c1) |c| { - switch (try checkBlank(s, c)) { + switch (try checkBlanks(s, c)) { .yes => {}, .skip_unit => return skipUnitAndReturn(s, d), - .skip_line => try s.skipLine(), .no => return parseJoin(s, d, c), } } @@ -629,11 +659,11 @@ fn joinData(s: *State) !void { const head = s.context.val; const join = s.context.char; const tail = s.result; - if (tail.eq(value.undef)) { + if (tail.eq(VOID)) { if (join == 0) { return s.retval(head); } else { - return s.err(error.InvalidCharacter, "join datum"); + return s.err(.InvalidCharacter, "join datum"); } } const rune = switch (join) { @@ -649,20 +679,17 @@ fn joinData(s: *State) !void { fn parseOneDatum(s: *State, c: u8, next: Fn) !void { if (isBareChar(c)) { - const d, s.unused_char = try parseBareString(s, c); - return s.jump(next, d); + return s.jump(next, try parseBareString(s, c)); } return parseCladDatum(s, c, next); } fn parseCladDatum(s: *State, c: u8, next: Fn) !void { if (c == '\\') { - const bs, s.unused_char = try parseBareEscString(s); - return s.jump(next, bs); + return s.jump(next, try parseBareEscString(s)); } if (c == '"') { - const qs = try parseQuotedString(s); - return s.jump(next, qs); + return s.jump(next, try parseQuotedString(s)); } return switch (c) { '#' => parseHashExpression(s, next), @@ -675,10 +702,8 @@ fn parseCladDatum(s: *State, c: u8, next: Fn) !void { fn isBareChar(c: u8) bool { return switch (c) { // zig fmt: off - 'a'...'z' , 'A'...'Z' , '0'...'9', - '!' , '$' , '%' , '&' , '*' , '+', - '-' , '/' , '<' , '=' , '>' , '?', - '@' , '^' , '_' , '~' , '.' => true, + 'a'...'z' , 'A'...'Z' , '0'...'9' , '!' , '$' , '%' , '&' , '*' , + '+' , '-' , '/' , '<' , '=' , '>' , '?' , '@' , '^' , '_' , '~' => true, // zig fmt: on else => false, }; @@ -691,27 +716,28 @@ fn isBareEsc(c: u8) bool { }; } -fn parseBareString(s: *State, c: u8) !struct { Value, ?u8 } { +fn parseBareString(s: *State, c: u8) !Value { try s.addChar(c); return parseBareStringRest(s); } -fn parseBareEscString(s: *State) !struct { Value, ?u8 } { +fn parseBareEscString(s: *State) !Value { try s.addChar(try parseBareEsc(s)); return parseBareStringRest(s); } -fn parseBareStringRest(s: *State) !struct { Value, ?u8 } { +fn parseBareStringRest(s: *State) !Value { while (try s.read()) |c| { if (isBareChar(c)) { try s.addChar(c); } else if (c == '\\') { try s.addChar(try parseBareEsc(s)); } else { - return .{ s.getBareString(), c }; + s.unused_char = c; + break; } } - return .{ s.getBareString(), null }; + return s.getBareString(); } fn parseBareEsc(s: *State) !u8 { @@ -719,7 +745,7 @@ fn parseBareEsc(s: *State) !u8 { if (isBareEsc(c)) { return c; } else { - return s.err(error.InvalidCharacter, "bare escape"); + return s.err(.InvalidCharacter, "bare escape"); } } @@ -754,17 +780,16 @@ fn parseQuotedEsc(s: *State) !void { 'r' => 13, 'e' => 27, 'x' => try parseHexByte(s, "hex escape"), - else => return s.err(error.InvalidCharacter, "quoted escape"), + else => return s.err(.InvalidCharacter, "quoted escape"), }); } fn parseUniHexHandleErrors(s: *State) !void { return parseUniHex(s) catch |err| switch (err) { - error.Utf8CannotEncodeSurrogateHalf => e: { - s.err_code = err; - s.err_msg = "unicode escape"; - break :e error.UnicodeError; - }, + error.Utf8CannotEncodeSurrogateHalf => s.err( + .UnicodeError, + "unicode escape", + ), else => |e| e, }; } @@ -773,16 +798,16 @@ fn parseUniHex(s: *State) !void { const msg = "unicode escape"; if (try s.readNoEof(msg) != '{') { - return s.err(error.InvalidCharacter, msg); + return s.err(.InvalidCharacter, msg); } const uc, const unused_c = try parseHex(s, u21, msg); if (unused_c) |c| { if (c != '}') { - return s.err(error.InvalidCharacter, msg); + return s.err(.InvalidCharacter, msg); } } else { - return s.err(error.UnexpectedEof, msg); + return s.err(.UnexpectedEof, msg); } const n = try std.unicode.utf8CodepointSequenceLength(uc); @@ -792,8 +817,8 @@ fn parseUniHex(s: *State) !void { fn parseHashExpression(s: *State, next: Fn) !void { const c = try s.readNoEof("hash expression"); - if (try checkBlank(s, c) != .no) { - return s.err(error.InvalidCharacter, "hash expression"); + if (try checkBlanks(s, c) != .no) { + return s.err(.InvalidCharacter, "hash expression"); } if (std.ascii.isAlphabetic(c)) { const r, const unused_c = try parseRune(s, c); @@ -805,16 +830,14 @@ fn parseHashExpression(s: *State, next: Fn) !void { } if (isBareChar(c)) { // Reserved for future extensions to syntax sugar. - return s.err(error.InvalidCharacter, "hash expression"); + return s.err(.InvalidCharacter, "hash expression"); } // fast-path to avoid subr if (c == '\\') { - const bs, s.unused_char = try parseBareEscString(s); - return s.jump(next, cons(HASH, bs)); + return s.jump(next, cons(HASH, try parseBareEscString(s))); } if (c == '"') { - const qs = try parseQuotedString(s); - return s.jump(next, cons(HASH, qs)); + return s.jump(next, cons(HASH, try parseQuotedString(s))); } s.unused_char = c; return s.subr(.parse_hash_datum, next); @@ -825,8 +848,8 @@ fn parseHashDatum(s: *State) !void { } fn endHashDatum(s: *State) !void { - if (s.result.eq(value.undef)) { - return s.err(error.InvalidCharacter, "hash datum"); + if (s.result.eq(VOID)) { + return s.err(.InvalidCharacter, "hash datum"); } return s.retval(cons(HASH, s.result)); } @@ -846,12 +869,10 @@ fn parseRune(s: *State, c1: u8) !struct { Value, ?u8 } { fn parseRuneEnd(s: *State, r: Value, c1: ?u8, next: Fn) !void { const c = c1 orelse return s.jump(next, r); if (c == '\\') { - const bs, s.unused_char = try parseBareString(s, c); - return s.jump(next, cons(r, bs)); + return s.jump(next, cons(r, try parseBareString(s, c))); } if (c == '"') { - const qs = try parseQuotedString(s); - return s.jump(next, cons(r, qs)); + return s.jump(next, cons(r, try parseQuotedString(s))); } s.unused_char = c; switch (c) { @@ -869,12 +890,10 @@ fn parseRuneDatum(s: *State) !void { } fn endRuneDatum(s: *State) !void { - const r = s.context.val; - const d = s.result; - if (d.eq(value.undef)) { - s.retval(r); + if (s.result.eq(VOID)) { + s.retval(s.context.val); } - return s.retval(cons(r, d)); + return s.retval(cons(s.context.val, s.result)); } fn parseLabel(s: *State) !struct { Value, ?u8 } { @@ -883,7 +902,7 @@ fn parseLabel(s: *State) !struct { Value, ?u8 } { } fn parseLabelEnd(s: *State, l: Value, c1: ?u8, next: Fn) !void { - const c = c1 orelse return s.err(error.UnexpectedEof, "datum label"); + const c = c1 orelse return s.err(.UnexpectedEof, "datum label"); if (c == '%') { return s.jump(next, cons(LABEL, l)); } @@ -892,16 +911,14 @@ fn parseLabelEnd(s: *State, l: Value, c1: ?u8, next: Fn) !void { s.context.val = l; return s.subr(.parse_unit, .end_label_datum); } - return s.err(error.InvalidCharacter, "datum label"); + return s.err(.InvalidCharacter, "datum label"); } fn endLabelDatum(s: *State) !void { - const l = s.context.val; - const d = s.result; - if (d.eq(value.undef)) { - return s.err(error.InvalidCharacter, "label datum"); + if (s.result.eq(VOID)) { + return s.err(.InvalidCharacter, "label datum"); } - return s.retval(cons(LABEL, cons(l, d))); + return s.retval(cons(LABEL, cons(s.context.val, s.result))); } fn parseList(s: *State, open: u8, next: Fn) !void { @@ -921,14 +938,13 @@ fn parseList(s: *State, open: u8, next: Fn) !void { if (c == close) { return s.jump(next, head); } - switch (try checkBlank(s, c)) { + switch (try checkBlanks(s, c)) { .yes => {}, .skip_unit => { try listParserSetup(s, head, close, next); // Parse twice in a row, ignoring the first result. return s.subr(.parse_unit, .parse_unit); }, - .skip_line => try s.skipLine(), .no => { try listParserSetup(s, head, close, next); s.unused_char = c; @@ -936,7 +952,7 @@ fn parseList(s: *State, open: u8, next: Fn) !void { }, } } - return s.err(error.UnexpectedEof, "list"); + return s.err(.UnexpectedEof, "list"); } fn listParserSetup(s: *State, head: Value, close: u8, next: Fn) !void { @@ -953,15 +969,15 @@ fn parseListElement(s: *State) !void { fn continueList(s: *State) !void { const close = s.context.char; - if (s.result.eq(value.undef)) { + if (s.result.eq(VOID)) { const c = s.getUnused().?; if (c == close) { return endList(s); } - return s.err(error.InvalidCharacter, "list"); + return s.err(.InvalidCharacter, "list"); } - if (s.result.eq(S_DOT)) { + if (s.result.eq(LSTAIL)) { return s.subr(.parse_unit, .end_improper_list); } @@ -972,20 +988,19 @@ fn continueList(s: *State) !void { if (c == close) { return endList(s); } - switch (try checkBlank(s, c)) { + switch (try checkBlanks(s, c)) { .yes => {}, .skip_unit => { try s.pushContext(.continue_list); return s.subr(.parse_unit, .parse_unit); }, - .skip_line => try s.skipLine(), .no => { s.unused_char = c; return s.subr(.parse_list_element, .continue_list); }, } } - return s.err(error.UnexpectedEof, "list"); + return s.err(.UnexpectedEof, "list"); } fn endList(s: *State) !void { @@ -993,11 +1008,10 @@ fn endList(s: *State) !void { } fn endImproperList(s: *State) !void { - const tail = s.result; - if (tail.eq(value.undef)) { - return s.err(error.InvalidCharacter, "list tail"); + if (s.result.eq(VOID)) { + return s.err(.InvalidCharacter, "list tail"); } - s.context.val = lib.list.reverseWithTail(s.context.val, tail); + s.context.val = lib.list.reverseWithTail(s.context.val, s.result); return closeImproperList(s); } @@ -1009,11 +1023,10 @@ fn closeImproperList(s: *State) !void { if (c == close) { return s.retval(result); } - switch (try checkBlank(s, c)) { + switch (try checkBlanks(s, c)) { .yes => {}, .skip_unit => return s.subr(.parse_unit, .close_improper_list), - .skip_line => try s.skipLine(), - .no => return s.err(error.InvalidCharacter, "after list tail"), + .no => return s.err(.InvalidCharacter, "after list tail"), } } unreachable; @@ -1030,8 +1043,7 @@ fn parseQuoteExpr(s: *State, c1: u8, next: Fn) !void { // fast-path to avoid subr const c = try s.readNoEof("quote expression"); if (isBareChar(c) or c == '\\') { - const bs, s.unused_char = try parseBareString(s, c); - return s.jump(next, cons(q, bs)); + return s.jump(next, cons(q, try parseBareString(s, c))); } s.context.val = q; @@ -1040,12 +1052,10 @@ fn parseQuoteExpr(s: *State, c1: u8, next: Fn) !void { } fn endQuoteExpr(s: *State) !void { - if (s.result.eq(value.undef)) { - return s.err(error.InvalidCharacter, "quote expression datum"); + if (s.result.eq(VOID)) { + return s.err(.InvalidCharacter, "quote expression datum"); } - const q = s.context.val; - const d = s.result; - return s.retval(cons(q, d)); + return s.retval(cons(s.context.val, s.result)); } // Helpers @@ -1053,7 +1063,7 @@ fn endQuoteExpr(s: *State) !void { fn parseHex( s: *State, u_type: type, - emsg: []const u8, + comptime emsg: []const u8, ) !struct { u_type, ?u8 } { var uc: u_type = undefined; @@ -1065,13 +1075,13 @@ fn parseHex( return .{ uc, c }; } const shl = std.math.shlExact; - uc = shl(u_type, uc, 4) catch return s.err(error.OutOfRange, emsg); + uc = shl(u_type, uc, 4) catch return s.err(.OutOfRange, emsg); uc |= try parseHexDigit(s, c, emsg); } return .{ uc, null }; } -fn parseHexByte(s: *State, emsg: []const u8) !u8 { +fn parseHexByte(s: *State, comptime emsg: []const u8) !u8 { const h1 = try s.readNoEof(emsg); const h2 = try s.readNoEof(emsg); const hi = try parseHexDigit(s, h1, emsg); @@ -1079,11 +1089,11 @@ fn parseHexByte(s: *State, emsg: []const u8) !u8 { return hi << 4 | lo; } -fn parseHexDigit(s: *State, c: u8, emsg: []const u8) !u8 { +fn parseHexDigit(s: *State, c: u8, comptime emsg: []const u8) !u8 { return switch (c) { '0'...'9' => c - '0', 'A'...'F' => c - 'A' + 10, 'a'...'f' => c - 'a' + 10, - else => s.err(error.InvalidCharacter, emsg), + else => s.err(.InvalidCharacter, emsg), }; } diff --git a/src/libzisp/value/rune.zig b/src/libzisp/value/rune.zig index 154ec13..195210e 100644 --- a/src/libzisp/value/rune.zig +++ b/src/libzisp/value/rune.zig @@ -44,6 +44,10 @@ fn assertValidRune(s: []const u8) void { pub fn pack(s: []const u8) Value { assertValidRune(s); + return packForced(s); +} + +pub fn packForced(s: []const u8) Value { var v = Value{ .rune = .{ .name = 0 } }; const dest: [*]u8 = @ptrCast(&v.rune.name); @memcpy(dest, s); |
