| ... | @@ -11,6 +11,7 @@ const unicode = std.unicode; | ... | @@ -11,6 +11,7 @@ const unicode = std.unicode; |
| 11 | const meta = std.meta; | 11 | const meta = std.meta; |
| 12 | const lossyCast = math.lossyCast; | 12 | const lossyCast = math.lossyCast; |
| 13 | const expectFmt = std.testing.expectFmt; | 13 | const expectFmt = std.testing.expectFmt; |
| | 14 | const testing = std.testing; |
| 14 | | 15 | |
| 15 | pub const default_max_depth = 3; | 16 | pub const default_max_depth = 3; |
| 16 | | 17 | |
| ... | @@ -319,7 +320,6 @@ pub const Specifier = union(enum) { | ... | @@ -319,7 +320,6 @@ pub const Specifier = union(enum) { |
| 319 | /// Allows to implement formatters compatible with std.fmt without replicating | 320 | /// Allows to implement formatters compatible with std.fmt without replicating |
| 320 | /// the standard library behavior. | 321 | /// the standard library behavior. |
| 321 | pub const Parser = struct { | 322 | pub const Parser = struct { |
| 322 | pos: usize = 0, | | |
| 323 | iter: std.unicode.Utf8Iterator, | 323 | iter: std.unicode.Utf8Iterator, |
| 324 | | 324 | |
| 325 | // Returns a decimal number or null if the current character is not a | 325 | // Returns a decimal number or null if the current character is not a |
| ... | @@ -345,16 +345,17 @@ pub const Parser = struct { | ... | @@ -345,16 +345,17 @@ pub const Parser = struct { |
| 345 | // Returns a substring of the input starting from the current position | 345 | // Returns a substring of the input starting from the current position |
| 346 | // and ending where `ch` is found or until the end if not found | 346 | // and ending where `ch` is found or until the end if not found |
| 347 | pub fn until(self: *@This(), ch: u21) []const u8 { | 347 | pub fn until(self: *@This(), ch: u21) []const u8 { |
| 348 | var result: []const u8 = &[_]u8{}; | 348 | const start = self.iter.i; |
| 349 | while (self.peek(0)) |code_point| { | 349 | while (self.peek(0)) |code_point| { |
| 350 | if (code_point == ch) | 350 | if (code_point == ch) |
| 351 | break; | 351 | break; |
| 352 | result = result ++ (self.iter.nextCodepointSlice() orelse &[_]u8{}); | 352 | _ = self.iter.nextCodepoint(); |
| 353 | } | 353 | } |
| 354 | return result; | 354 | return self.iter.bytes[start..self.iter.i]; |
| 355 | } | 355 | } |
| 356 | | 356 | |
| 357 | // Returns one character, if available | 357 | // Returns the character pointed to by the iterator if available, or |
| | 358 | // null otherwise |
| 358 | pub fn char(self: *@This()) ?u21 { | 359 | pub fn char(self: *@This()) ?u21 { |
| 359 | if (self.iter.nextCodepoint()) |code_point| { | 360 | if (self.iter.nextCodepoint()) |code_point| { |
| 360 | return code_point; | 361 | return code_point; |
| ... | @@ -362,6 +363,8 @@ pub const Parser = struct { | ... | @@ -362,6 +363,8 @@ pub const Parser = struct { |
| 362 | return null; | 363 | return null; |
| 363 | } | 364 | } |
| 364 | | 365 | |
| | 366 | // Returns true if the iterator points to an existing character and |
| | 367 | // false otherwise |
| 365 | pub fn maybe(self: *@This(), val: u21) bool { | 368 | pub fn maybe(self: *@This(), val: u21) bool { |
| 366 | if (self.peek(0) == val) { | 369 | if (self.peek(0) == val) { |
| 367 | _ = self.iter.nextCodepoint(); | 370 | _ = self.iter.nextCodepoint(); |
| ... | @@ -2779,3 +2782,239 @@ test hex { | ... | @@ -2779,3 +2782,239 @@ test hex { |
| 2779 | try std.testing.expectEqualStrings("[00efcdab78563412]", s); | 2782 | try std.testing.expectEqualStrings("[00efcdab78563412]", s); |
| 2780 | } | 2783 | } |
| 2781 | } | 2784 | } |
| | 2785 | |
| | 2786 | test "parser until" { |
| | 2787 | { // return substring till ':' |
| | 2788 | var parser: Parser = .{ |
| | 2789 | .iter = .{ .bytes = "abc:1234", .i = 0 }, |
| | 2790 | }; |
| | 2791 | try testing.expectEqualStrings("abc", parser.until(':')); |
| | 2792 | } |
| | 2793 | |
| | 2794 | { // return the entire string - `ch` not found |
| | 2795 | var parser: Parser = .{ |
| | 2796 | .iter = .{ .bytes = "abc1234", .i = 0 }, |
| | 2797 | }; |
| | 2798 | try testing.expectEqualStrings("abc1234", parser.until(':')); |
| | 2799 | } |
| | 2800 | |
| | 2801 | { // substring is empty - `ch` is the only character |
| | 2802 | var parser: Parser = .{ |
| | 2803 | .iter = .{ .bytes = ":", .i = 0 }, |
| | 2804 | }; |
| | 2805 | try testing.expectEqualStrings("", parser.until(':')); |
| | 2806 | } |
| | 2807 | |
| | 2808 | { // empty string and `ch` not found |
| | 2809 | var parser: Parser = .{ |
| | 2810 | .iter = .{ .bytes = "", .i = 0 }, |
| | 2811 | }; |
| | 2812 | try testing.expectEqualStrings("", parser.until(':')); |
| | 2813 | } |
| | 2814 | |
| | 2815 | { // substring starts at index 2 and goes upto `ch` |
| | 2816 | var parser: Parser = .{ |
| | 2817 | .iter = .{ .bytes = "abc:1234", .i = 2 }, |
| | 2818 | }; |
| | 2819 | try testing.expectEqualStrings("c", parser.until(':')); |
| | 2820 | } |
| | 2821 | |
| | 2822 | { // substring starts at index 4 and goes upto the end - `ch` not found |
| | 2823 | var parser: Parser = .{ |
| | 2824 | .iter = .{ .bytes = "abc1234", .i = 4 }, |
| | 2825 | }; |
| | 2826 | try testing.expectEqualStrings("234", parser.until(':')); |
| | 2827 | } |
| | 2828 | } |
| | 2829 | |
| | 2830 | test "parser peek" { |
| | 2831 | { // start iteration from the first index |
| | 2832 | var parser: Parser = .{ |
| | 2833 | .iter = .{ .bytes = "hello world", .i = 0 }, |
| | 2834 | }; |
| | 2835 | |
| | 2836 | try testing.expectEqual('h', parser.peek(0)); |
| | 2837 | try testing.expectEqual('e', parser.peek(1)); |
| | 2838 | try testing.expectEqual(' ', parser.peek(5)); |
| | 2839 | try testing.expectEqual('d', parser.peek(10)); |
| | 2840 | try testing.expectEqual(null, parser.peek(11)); |
| | 2841 | } |
| | 2842 | |
| | 2843 | { // start iteration from the second last index |
| | 2844 | var parser: Parser = .{ |
| | 2845 | .iter = .{ .bytes = "hello world!", .i = 10 }, |
| | 2846 | }; |
| | 2847 | |
| | 2848 | try testing.expectEqual('d', parser.peek(0)); |
| | 2849 | try testing.expectEqual('!', parser.peek(1)); |
| | 2850 | try testing.expectEqual(null, parser.peek(5)); |
| | 2851 | } |
| | 2852 | |
| | 2853 | { // start iteration beyond the length of the string |
| | 2854 | var parser: Parser = .{ |
| | 2855 | .iter = .{ .bytes = "hello", .i = 5 }, |
| | 2856 | }; |
| | 2857 | |
| | 2858 | try testing.expectEqual(null, parser.peek(0)); |
| | 2859 | try testing.expectEqual(null, parser.peek(1)); |
| | 2860 | } |
| | 2861 | |
| | 2862 | { // empty string |
| | 2863 | var parser: Parser = .{ |
| | 2864 | .iter = .{ .bytes = "", .i = 0 }, |
| | 2865 | }; |
| | 2866 | |
| | 2867 | try testing.expectEqual(null, parser.peek(0)); |
| | 2868 | try testing.expectEqual(null, parser.peek(2)); |
| | 2869 | } |
| | 2870 | } |
| | 2871 | |
| | 2872 | test "parser char" { |
| | 2873 | // character exists - iterator at 0 |
| | 2874 | var parser: Parser = .{ .iter = .{ .bytes = "~~hello", .i = 0 } }; |
| | 2875 | try testing.expectEqual('~', parser.char()); |
| | 2876 | |
| | 2877 | // character exists - iterator in the middle |
| | 2878 | parser = .{ .iter = .{ .bytes = "~~hello", .i = 3 } }; |
| | 2879 | try testing.expectEqual('e', parser.char()); |
| | 2880 | |
| | 2881 | // character exists - iterator at the end |
| | 2882 | parser = .{ .iter = .{ .bytes = "~~hello", .i = 6 } }; |
| | 2883 | try testing.expectEqual('o', parser.char()); |
| | 2884 | |
| | 2885 | // character doesn't exist - iterator beyond the length of the string |
| | 2886 | parser = .{ .iter = .{ .bytes = "~~hello", .i = 7 } }; |
| | 2887 | try testing.expectEqual(null, parser.char()); |
| | 2888 | } |
| | 2889 | |
| | 2890 | test "parser maybe" { |
| | 2891 | // character exists - iterator at 0 |
| | 2892 | var parser: Parser = .{ .iter = .{ .bytes = "hello world", .i = 0 } }; |
| | 2893 | try testing.expect(parser.maybe('h')); |
| | 2894 | |
| | 2895 | // character exists - iterator at space |
| | 2896 | parser = .{ .iter = .{ .bytes = "hello world", .i = 5 } }; |
| | 2897 | try testing.expect(parser.maybe(' ')); |
| | 2898 | |
| | 2899 | // character exists - iterator at the end |
| | 2900 | parser = .{ .iter = .{ .bytes = "hello world", .i = 10 } }; |
| | 2901 | try testing.expect(parser.maybe('d')); |
| | 2902 | |
| | 2903 | // character doesn't exist - iterator beyond the length of the string |
| | 2904 | parser = .{ .iter = .{ .bytes = "hello world", .i = 11 } }; |
| | 2905 | try testing.expect(!parser.maybe('e')); |
| | 2906 | } |
| | 2907 | |
| | 2908 | test "parser number" { |
| | 2909 | // input is a single digit natural number - iterator at 0 |
| | 2910 | var parser: Parser = .{ .iter = .{ .bytes = "7", .i = 0 } }; |
| | 2911 | try testing.expect(7 == parser.number()); |
| | 2912 | |
| | 2913 | // input is a two digit natural number - iterator at 1 |
| | 2914 | parser = .{ .iter = .{ .bytes = "29", .i = 1 } }; |
| | 2915 | try testing.expect(9 == parser.number()); |
| | 2916 | |
| | 2917 | // input is a two digit natural number - iterator beyond the length of the string |
| | 2918 | parser = .{ .iter = .{ .bytes = "32", .i = 2 } }; |
| | 2919 | try testing.expectEqual(null, parser.number()); |
| | 2920 | |
| | 2921 | // input is an integer |
| | 2922 | parser = .{ .iter = .{ .bytes = "0", .i = 0 } }; |
| | 2923 | try testing.expect(0 == parser.number()); |
| | 2924 | |
| | 2925 | // input is a negative integer |
| | 2926 | parser = .{ .iter = .{ .bytes = "-2", .i = 0 } }; |
| | 2927 | try testing.expectEqual(null, parser.number()); |
| | 2928 | |
| | 2929 | // input is a string |
| | 2930 | parser = .{ .iter = .{ .bytes = "no_number", .i = 2 } }; |
| | 2931 | try testing.expectEqual(null, parser.number()); |
| | 2932 | |
| | 2933 | // input is a single character string |
| | 2934 | parser = .{ .iter = .{ .bytes = "n", .i = 0 } }; |
| | 2935 | try testing.expectEqual(null, parser.number()); |
| | 2936 | |
| | 2937 | // input is an empty string |
| | 2938 | parser = .{ .iter = .{ .bytes = "", .i = 0 } }; |
| | 2939 | try testing.expectEqual(null, parser.number()); |
| | 2940 | } |
| | 2941 | |
| | 2942 | test "parser specifier" { |
| | 2943 | { // input string is a digit; iterator at 0 |
| | 2944 | const expected: Specifier = Specifier{ .number = 1 }; |
| | 2945 | var parser: Parser = .{ .iter = .{ .bytes = "1", .i = 0 } }; |
| | 2946 | |
| | 2947 | const result = try parser.specifier(); |
| | 2948 | try testing.expect(expected.number == result.number); |
| | 2949 | } |
| | 2950 | |
| | 2951 | { // input string is a two digit number; iterator at 0 |
| | 2952 | const digit: Specifier = Specifier{ .number = 42 }; |
| | 2953 | var parser: Parser = .{ .iter = .{ .bytes = "42", .i = 0 } }; |
| | 2954 | |
| | 2955 | const result = try parser.specifier(); |
| | 2956 | try testing.expect(digit.number == result.number); |
| | 2957 | } |
| | 2958 | |
| | 2959 | { // input string is a two digit number digit; iterator at 1 |
| | 2960 | const digit: Specifier = Specifier{ .number = 8 }; |
| | 2961 | var parser: Parser = .{ .iter = .{ .bytes = "28", .i = 1 } }; |
| | 2962 | |
| | 2963 | const result = try parser.specifier(); |
| | 2964 | try testing.expect(digit.number == result.number); |
| | 2965 | } |
| | 2966 | |
| | 2967 | { // input string is a two digit number with square brackets; iterator at 0 |
| | 2968 | const digit: Specifier = Specifier{ .named = "15" }; |
| | 2969 | var parser: Parser = .{ .iter = .{ .bytes = "[15]", .i = 0 } }; |
| | 2970 | |
| | 2971 | const result = try parser.specifier(); |
| | 2972 | try testing.expectEqualStrings(digit.named, result.named); |
| | 2973 | } |
| | 2974 | |
| | 2975 | { // input string is not a number and contains square brackets; iterator at 0 |
| | 2976 | const digit: Specifier = Specifier{ .named = "hello" }; |
| | 2977 | var parser: Parser = .{ .iter = .{ .bytes = "[hello]", .i = 0 } }; |
| | 2978 | |
| | 2979 | const result = try parser.specifier(); |
| | 2980 | try testing.expectEqualStrings(digit.named, result.named); |
| | 2981 | } |
| | 2982 | |
| | 2983 | { // input string is not a number and doesn't contain closing square bracket; iterator at 0 |
| | 2984 | var parser: Parser = .{ .iter = .{ .bytes = "[hello", .i = 0 } }; |
| | 2985 | |
| | 2986 | const result = parser.specifier(); |
| | 2987 | try testing.expectError(@field(anyerror, "Expected closing ]"), result); |
| | 2988 | } |
| | 2989 | |
| | 2990 | { // input string is not a number and doesn't contain closing square bracket; iterator at 2 |
| | 2991 | var parser: Parser = .{ .iter = .{ .bytes = "[[[[hello", .i = 2 } }; |
| | 2992 | |
| | 2993 | const result = parser.specifier(); |
| | 2994 | try testing.expectError(@field(anyerror, "Expected closing ]"), result); |
| | 2995 | } |
| | 2996 | |
| | 2997 | { // input string is not a number and contains unbalanced square brackets; iterator at 0 |
| | 2998 | const digit: Specifier = Specifier{ .named = "[[hello" }; |
| | 2999 | var parser: Parser = .{ .iter = .{ .bytes = "[[[hello]", .i = 0 } }; |
| | 3000 | |
| | 3001 | const result = try parser.specifier(); |
| | 3002 | try testing.expectEqualStrings(digit.named, result.named); |
| | 3003 | } |
| | 3004 | |
| | 3005 | { // input string is not a number and contains unbalanced square brackets; iterator at 1 |
| | 3006 | const digit: Specifier = Specifier{ .named = "[[hello" }; |
| | 3007 | var parser: Parser = .{ .iter = .{ .bytes = "[[[[hello]]]]]", .i = 1 } }; |
| | 3008 | |
| | 3009 | const result = try parser.specifier(); |
| | 3010 | try testing.expectEqualStrings(digit.named, result.named); |
| | 3011 | } |
| | 3012 | |
| | 3013 | { // input string is neither a digit nor a named argument |
| | 3014 | const char: Specifier = Specifier{ .none = {} }; |
| | 3015 | var parser: Parser = .{ .iter = .{ .bytes = "hello", .i = 0 } }; |
| | 3016 | |
| | 3017 | const result = try parser.specifier(); |
| | 3018 | try testing.expectEqual(char.none, result.none); |
| | 3019 | } |
| | 3020 | } |