| ... | ... | @@ -10,6 +10,7 @@ pub const Tokenizer = tokenizer.Tokenizer; |
| 10 | 10 | pub const string_literal = @import("zig/string_literal.zig"); |
| 11 | 11 | pub const number_literal = @import("zig/number_literal.zig"); |
| 12 | 12 | pub const primitives = @import("zig/primitives.zig"); |
| 13 | pub const isPrimitive = primitives.isPrimitive; |
| 13 | 14 | pub const Ast = @import("zig/Ast.zig"); |
| 14 | 15 | pub const AstGen = @import("zig/AstGen.zig"); |
| 15 | 16 | pub const Zir = @import("zig/Zir.zig"); |
| ... | ... | @@ -728,20 +729,87 @@ const tokenizer = @import("zig/tokenizer.zig"); |
| 728 | 729 | const assert = std.debug.assert; |
| 729 | 730 | const Allocator = std.mem.Allocator; |
| 730 | 731 | |
| 731 | | /// Return a Formatter for a Zig identifier |
| 732 | /// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed. |
| 733 | /// |
| 734 | /// - An empty `{}` format specifier escapes invalid identifiers, identifiers that shadow primitives |
| 735 | /// and the reserved `_` identifier. |
| 736 | /// - Add `p` to the specifier to render identifiers that shadow primitives unescaped. |
| 737 | /// - Add `_` to the specifier to render the reserved `_` identifier unescaped. |
| 738 | /// - `p` and `_` can be combined, e.g. `{p_}`. |
| 739 | /// |
| 732 | 740 | pub fn fmtId(bytes: []const u8) std.fmt.Formatter(formatId) { |
| 733 | 741 | return .{ .data = bytes }; |
| 734 | 742 | } |
| 735 | 743 | |
| 736 | | /// Print the string as a Zig identifier escaping it with @"" syntax if needed. |
| 744 | test fmtId { |
| 745 | const expectFmt = std.testing.expectFmt; |
| 746 | try expectFmt("@\"while\"", "{}", .{fmtId("while")}); |
| 747 | try expectFmt("@\"while\"", "{p}", .{fmtId("while")}); |
| 748 | try expectFmt("@\"while\"", "{_}", .{fmtId("while")}); |
| 749 | try expectFmt("@\"while\"", "{p_}", .{fmtId("while")}); |
| 750 | try expectFmt("@\"while\"", "{_p}", .{fmtId("while")}); |
| 751 | |
| 752 | try expectFmt("hello", "{}", .{fmtId("hello")}); |
| 753 | try expectFmt("hello", "{p}", .{fmtId("hello")}); |
| 754 | try expectFmt("hello", "{_}", .{fmtId("hello")}); |
| 755 | try expectFmt("hello", "{p_}", .{fmtId("hello")}); |
| 756 | try expectFmt("hello", "{_p}", .{fmtId("hello")}); |
| 757 | |
| 758 | try expectFmt("@\"type\"", "{}", .{fmtId("type")}); |
| 759 | try expectFmt("type", "{p}", .{fmtId("type")}); |
| 760 | try expectFmt("@\"type\"", "{_}", .{fmtId("type")}); |
| 761 | try expectFmt("type", "{p_}", .{fmtId("type")}); |
| 762 | try expectFmt("type", "{_p}", .{fmtId("type")}); |
| 763 | |
| 764 | try expectFmt("@\"_\"", "{}", .{fmtId("_")}); |
| 765 | try expectFmt("@\"_\"", "{p}", .{fmtId("_")}); |
| 766 | try expectFmt("_", "{_}", .{fmtId("_")}); |
| 767 | try expectFmt("_", "{p_}", .{fmtId("_")}); |
| 768 | try expectFmt("_", "{_p}", .{fmtId("_")}); |
| 769 | |
| 770 | try expectFmt("@\"i123\"", "{}", .{fmtId("i123")}); |
| 771 | try expectFmt("i123", "{p}", .{fmtId("i123")}); |
| 772 | try expectFmt("@\"4four\"", "{}", .{fmtId("4four")}); |
| 773 | try expectFmt("_underscore", "{}", .{fmtId("_underscore")}); |
| 774 | try expectFmt("@\"11\\\"23\"", "{}", .{fmtId("11\"23")}); |
| 775 | try expectFmt("@\"11\\x0f23\"", "{}", .{fmtId("11\x0F23")}); |
| 776 | |
| 777 | // These are technically not currently legal in Zig. |
| 778 | try expectFmt("@\"\"", "{}", .{fmtId("")}); |
| 779 | try expectFmt("@\"\\x00\"", "{}", .{fmtId("\x00")}); |
| 780 | } |
| 781 | |
| 782 | /// Print the string as a Zig identifier, escaping it with `@""` syntax if needed. |
| 737 | 783 | fn formatId( |
| 738 | 784 | bytes: []const u8, |
| 739 | | comptime unused_format_string: []const u8, |
| 785 | comptime fmt: []const u8, |
| 740 | 786 | options: std.fmt.FormatOptions, |
| 741 | 787 | writer: anytype, |
| 742 | 788 | ) !void { |
| 743 | | _ = unused_format_string; |
| 744 | | if (isValidId(bytes)) { |
| 789 | const allow_primitive, const allow_underscore = comptime parse_fmt: { |
| 790 | var allow_primitive = false; |
| 791 | var allow_underscore = false; |
| 792 | for (fmt) |char| { |
| 793 | switch (char) { |
| 794 | 'p' => if (!allow_primitive) { |
| 795 | allow_primitive = true; |
| 796 | continue; |
| 797 | }, |
| 798 | '_' => if (!allow_underscore) { |
| 799 | allow_underscore = true; |
| 800 | continue; |
| 801 | }, |
| 802 | else => {}, |
| 803 | } |
| 804 | @compileError("expected {}, {p}, {_}, {p_} or {_p}, found {" ++ fmt ++ "}"); |
| 805 | } |
| 806 | break :parse_fmt .{ allow_primitive, allow_underscore }; |
| 807 | }; |
| 808 | |
| 809 | if (isValidId(bytes) and |
| 810 | (allow_primitive or !std.zig.isPrimitive(bytes)) and |
| 811 | (allow_underscore or !isUnderscore(bytes))) |
| 812 | { |
| 745 | 813 | return writer.writeAll(bytes); |
| 746 | 814 | } |
| 747 | 815 | try writer.writeAll("@\""); |
| ... | ... | @@ -757,12 +825,8 @@ pub fn fmtEscapes(bytes: []const u8) std.fmt.Formatter(stringEscape) { |
| 757 | 825 | return .{ .data = bytes }; |
| 758 | 826 | } |
| 759 | 827 | |
| 760 | | test "escape invalid identifiers" { |
| 828 | test fmtEscapes { |
| 761 | 829 | const expectFmt = std.testing.expectFmt; |
| 762 | | try expectFmt("@\"while\"", "{}", .{fmtId("while")}); |
| 763 | | try expectFmt("hello", "{}", .{fmtId("hello")}); |
| 764 | | try expectFmt("@\"11\\\"23\"", "{}", .{fmtId("11\"23")}); |
| 765 | | try expectFmt("@\"11\\x0f23\"", "{}", .{fmtId("11\x0F23")}); |
| 766 | 830 | try expectFmt("\\x0f", "{}", .{fmtEscapes("\x0f")}); |
| 767 | 831 | try expectFmt( |
| 768 | 832 | \\" \\ hi \x07 \x11 " derp \'" |
| ... | ... | @@ -816,7 +880,6 @@ pub fn stringEscape( |
| 816 | 880 | |
| 817 | 881 | pub fn isValidId(bytes: []const u8) bool { |
| 818 | 882 | if (bytes.len == 0) return false; |
| 819 | | if (std.mem.eql(u8, bytes, "_")) return false; |
| 820 | 883 | for (bytes, 0..) |c, i| { |
| 821 | 884 | switch (c) { |
| 822 | 885 | '_', 'a'...'z', 'A'...'Z' => {}, |
| ... | ... | @@ -836,6 +899,18 @@ test isValidId { |
| 836 | 899 | try std.testing.expect(isValidId("i386")); |
| 837 | 900 | } |
| 838 | 901 | |
| 902 | pub fn isUnderscore(bytes: []const u8) bool { |
| 903 | return bytes.len == 1 and bytes[0] == '_'; |
| 904 | } |
| 905 | |
| 906 | test isUnderscore { |
| 907 | try std.testing.expect(isUnderscore("_")); |
| 908 | try std.testing.expect(!isUnderscore("__")); |
| 909 | try std.testing.expect(!isUnderscore("_foo")); |
| 910 | try std.testing.expect(isUnderscore("\x5f")); |
| 911 | try std.testing.expect(!isUnderscore("\\x5f")); |
| 912 | } |
| 913 | |
| 839 | 914 | pub fn readSourceFileToEndAlloc( |
| 840 | 915 | allocator: Allocator, |
| 841 | 916 | input: std.fs.File, |