| ... | ... | @@ -11,6 +11,7 @@ const unicode = std.unicode; |
| 11 | 11 | const meta = std.meta; |
| 12 | 12 | const lossyCast = math.lossyCast; |
| 13 | 13 | const expectFmt = std.testing.expectFmt; |
| 14 | const testing = std.testing; |
| 14 | 15 | |
| 15 | 16 | pub const default_max_depth = 3; |
| 16 | 17 | |
| ... | ... | @@ -319,7 +320,6 @@ pub const Specifier = union(enum) { |
| 319 | 320 | /// Allows to implement formatters compatible with std.fmt without replicating |
| 320 | 321 | /// the standard library behavior. |
| 321 | 322 | pub const Parser = struct { |
| 322 | | pos: usize = 0, |
| 323 | 323 | iter: std.unicode.Utf8Iterator, |
| 324 | 324 | |
| 325 | 325 | // Returns a decimal number or null if the current character is not a |
| ... | ... | @@ -345,16 +345,17 @@ pub const Parser = struct { |
| 345 | 345 | // Returns a substring of the input starting from the current position |
| 346 | 346 | // and ending where `ch` is found or until the end if not found |
| 347 | 347 | pub fn until(self: *@This(), ch: u21) []const u8 { |
| 348 | | var result: []const u8 = &[_]u8{}; |
| 348 | const start = self.iter.i; |
| 349 | 349 | while (self.peek(0)) |code_point| { |
| 350 | 350 | if (code_point == ch) |
| 351 | 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 | 359 | pub fn char(self: *@This()) ?u21 { |
| 359 | 360 | if (self.iter.nextCodepoint()) |code_point| { |
| 360 | 361 | return code_point; |
| ... | ... | @@ -362,6 +363,8 @@ pub const Parser = struct { |
| 362 | 363 | return null; |
| 363 | 364 | } |
| 364 | 365 | |
| 366 | // Returns true if the iterator points to an existing character and |
| 367 | // false otherwise |
| 365 | 368 | pub fn maybe(self: *@This(), val: u21) bool { |
| 366 | 369 | if (self.peek(0) == val) { |
| 367 | 370 | _ = self.iter.nextCodepoint(); |
| ... | ... | @@ -2779,3 +2782,239 @@ test hex { |
| 2779 | 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 | } |