diff --git a/lib/std/std.zig b/lib/std/std.zig index 98a1bba0dd..0781c877cf 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -193,7 +193,9 @@ pub const valgrind = @import("valgrind.zig"); /// Constants and types representing the Wasm binary format. pub const wasm = @import("wasm.zig"); -/// Tokenizing and parsing of Zig code and other Zig-specific language tooling. +/// Builds of the Zig compiler are distributed partly in source form. That +/// source lives here. These APIs are provided as-is and have absolutely no API +/// guarantees whatsoever. pub const zig = @import("zig.zig"); pub const start = @import("start.zig"); diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 38cad68c21..c5da47d5d8 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -1,3 +1,4 @@ +/// Implementation of `zig fmt`. pub const fmt = @import("zig/fmt.zig"); pub const ErrorBundle = @import("zig/ErrorBundle.zig"); @@ -5,9 +6,6 @@ pub const Server = @import("zig/Server.zig"); pub const Client = @import("zig/Client.zig"); pub const Token = tokenizer.Token; pub const Tokenizer = tokenizer.Tokenizer; -pub const fmtId = fmt.fmtId; -pub const fmtEscapes = fmt.fmtEscapes; -pub const isValidId = fmt.isValidId; pub const string_literal = @import("zig/string_literal.zig"); pub const number_literal = @import("zig/number_literal.zig"); pub const primitives = @import("zig/primitives.zig"); @@ -694,6 +692,124 @@ const tokenizer = @import("zig/tokenizer.zig"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -test { - @import("std").testing.refAllDecls(@This()); +/// Return a Formatter for a Zig identifier +pub fn fmtId(bytes: []const u8) std.fmt.Formatter(formatId) { + return .{ .data = bytes }; +} + +/// Print the string as a Zig identifier escaping it with @"" syntax if needed. +fn formatId( + bytes: []const u8, + comptime unused_format_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = unused_format_string; + if (isValidId(bytes)) { + return writer.writeAll(bytes); + } + try writer.writeAll("@\""); + try stringEscape(bytes, "", options, writer); + try writer.writeByte('"'); +} + +/// Return a Formatter for Zig Escapes of a double quoted string. +/// The format specifier must be one of: +/// * `{}` treats contents as a double-quoted string. +/// * `{'}` treats contents as a single-quoted string. +pub fn fmtEscapes(bytes: []const u8) std.fmt.Formatter(stringEscape) { + return .{ .data = bytes }; +} + +test "escape invalid identifiers" { + const expectFmt = std.testing.expectFmt; + try expectFmt("@\"while\"", "{}", .{fmtId("while")}); + try expectFmt("hello", "{}", .{fmtId("hello")}); + try expectFmt("@\"11\\\"23\"", "{}", .{fmtId("11\"23")}); + try expectFmt("@\"11\\x0f23\"", "{}", .{fmtId("11\x0F23")}); + try expectFmt("\\x0f", "{}", .{fmtEscapes("\x0f")}); + try expectFmt( + \\" \\ hi \x07 \x11 " derp \'" + , "\"{'}\"", .{fmtEscapes(" \\ hi \x07 \x11 \" derp '")}); + try expectFmt( + \\" \\ hi \x07 \x11 \" derp '" + , "\"{}\"", .{fmtEscapes(" \\ hi \x07 \x11 \" derp '")}); +} + +/// Print the string as escaped contents of a double quoted or single-quoted string. +/// Format `{}` treats contents as a double-quoted string. +/// Format `{'}` treats contents as a single-quoted string. +pub fn stringEscape( + bytes: []const u8, + comptime f: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = options; + for (bytes) |byte| switch (byte) { + '\n' => try writer.writeAll("\\n"), + '\r' => try writer.writeAll("\\r"), + '\t' => try writer.writeAll("\\t"), + '\\' => try writer.writeAll("\\\\"), + '"' => { + if (f.len == 1 and f[0] == '\'') { + try writer.writeByte('"'); + } else if (f.len == 0) { + try writer.writeAll("\\\""); + } else { + @compileError("expected {} or {'}, found {" ++ f ++ "}"); + } + }, + '\'' => { + if (f.len == 1 and f[0] == '\'') { + try writer.writeAll("\\'"); + } else if (f.len == 0) { + try writer.writeByte('\''); + } else { + @compileError("expected {} or {'}, found {" ++ f ++ "}"); + } + }, + ' ', '!', '#'...'&', '('...'[', ']'...'~' => try writer.writeByte(byte), + // Use hex escapes for rest any unprintable characters. + else => { + try writer.writeAll("\\x"); + try std.fmt.formatInt(byte, 16, .lower, .{ .width = 2, .fill = '0' }, writer); + }, + }; +} + +pub fn isValidId(bytes: []const u8) bool { + if (bytes.len == 0) return false; + if (std.mem.eql(u8, bytes, "_")) return false; + for (bytes, 0..) |c, i| { + switch (c) { + '_', 'a'...'z', 'A'...'Z' => {}, + '0'...'9' => if (i == 0) return false, + else => return false, + } + } + return std.zig.Token.getKeyword(bytes) == null; +} + +test isValidId { + try std.testing.expect(!isValidId("")); + try std.testing.expect(isValidId("foobar")); + try std.testing.expect(!isValidId("a b c")); + try std.testing.expect(!isValidId("3d")); + try std.testing.expect(!isValidId("enum")); + try std.testing.expect(isValidId("i386")); +} + +test { + _ = Ast; + _ = AstRlAnnotate; + _ = BuiltinFn; + _ = Client; + _ = ErrorBundle; + _ = Server; + _ = fmt; + _ = number_literal; + _ = primitives; + _ = string_literal; + _ = system; } diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index e358ee80d6..d4e393bdf0 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -105,9 +105,7 @@ pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!A }; } -/// `gpa` is used for allocating the resulting formatted source code, as well as -/// for allocating extra stack memory if needed, because this function utilizes recursion. -/// Note: that's not actually true yet, see https://github.com/ziglang/zig/issues/1006. +/// `gpa` is used for allocating the resulting formatted source code. /// Caller owns the returned slice of bytes, allocated with `gpa`. pub fn render(tree: Ast, gpa: Allocator) RenderError![]u8 { var buffer = std.ArrayList(u8).init(gpa); diff --git a/lib/std/zig/fmt.zig b/lib/std/zig/fmt.zig index 5375b93025..f8841bfb5b 100644 --- a/lib/std/zig/fmt.zig +++ b/lib/std/zig/fmt.zig @@ -1,110 +1 @@ -const std = @import("std"); -const mem = std.mem; - -/// Print the string as a Zig identifier escaping it with @"" syntax if needed. -fn formatId( - bytes: []const u8, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, -) !void { - _ = fmt; - if (isValidId(bytes)) { - return writer.writeAll(bytes); - } - try writer.writeAll("@\""); - try stringEscape(bytes, "", options, writer); - try writer.writeByte('"'); -} - -/// Return a Formatter for a Zig identifier -pub fn fmtId(bytes: []const u8) std.fmt.Formatter(formatId) { - return .{ .data = bytes }; -} - -pub fn isValidId(bytes: []const u8) bool { - if (bytes.len == 0) return false; - if (mem.eql(u8, bytes, "_")) return false; - for (bytes, 0..) |c, i| { - switch (c) { - '_', 'a'...'z', 'A'...'Z' => {}, - '0'...'9' => if (i == 0) return false, - else => return false, - } - } - return std.zig.Token.getKeyword(bytes) == null; -} - -test "isValidId" { - try std.testing.expect(!isValidId("")); - try std.testing.expect(isValidId("foobar")); - try std.testing.expect(!isValidId("a b c")); - try std.testing.expect(!isValidId("3d")); - try std.testing.expect(!isValidId("enum")); - try std.testing.expect(isValidId("i386")); -} - -/// Print the string as escaped contents of a double quoted or single-quoted string. -/// Format `{}` treats contents as a double-quoted string. -/// Format `{'}` treats contents as a single-quoted string. -pub fn stringEscape( - bytes: []const u8, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, -) !void { - _ = options; - for (bytes) |byte| switch (byte) { - '\n' => try writer.writeAll("\\n"), - '\r' => try writer.writeAll("\\r"), - '\t' => try writer.writeAll("\\t"), - '\\' => try writer.writeAll("\\\\"), - '"' => { - if (fmt.len == 1 and fmt[0] == '\'') { - try writer.writeByte('"'); - } else if (fmt.len == 0) { - try writer.writeAll("\\\""); - } else { - @compileError("expected {} or {'}, found {" ++ fmt ++ "}"); - } - }, - '\'' => { - if (fmt.len == 1 and fmt[0] == '\'') { - try writer.writeAll("\\'"); - } else if (fmt.len == 0) { - try writer.writeByte('\''); - } else { - @compileError("expected {} or {'}, found {" ++ fmt ++ "}"); - } - }, - ' ', '!', '#'...'&', '('...'[', ']'...'~' => try writer.writeByte(byte), - // Use hex escapes for rest any unprintable characters. - else => { - try writer.writeAll("\\x"); - try std.fmt.formatInt(byte, 16, .lower, .{ .width = 2, .fill = '0' }, writer); - }, - }; -} - -/// Return a Formatter for Zig Escapes of a double quoted string. -/// The format specifier must be one of: -/// * `{}` treats contents as a double-quoted string. -/// * `{'}` treats contents as a single-quoted string. -pub fn fmtEscapes(bytes: []const u8) std.fmt.Formatter(stringEscape) { - return .{ .data = bytes }; -} - -test "escape invalid identifiers" { - const expectFmt = std.testing.expectFmt; - try expectFmt("@\"while\"", "{}", .{fmtId("while")}); - try expectFmt("hello", "{}", .{fmtId("hello")}); - try expectFmt("@\"11\\\"23\"", "{}", .{fmtId("11\"23")}); - try expectFmt("@\"11\\x0f23\"", "{}", .{fmtId("11\x0F23")}); - try expectFmt("\\x0f", "{}", .{fmtEscapes("\x0f")}); - try expectFmt( - \\" \\ hi \x07 \x11 " derp \'" - , "\"{'}\"", .{fmtEscapes(" \\ hi \x07 \x11 \" derp '")}); - try expectFmt( - \\" \\ hi \x07 \x11 \" derp '" - , "\"{}\"", .{fmtEscapes(" \\ hi \x07 \x11 \" derp '")}); -} +const std = @import("../std.zig"); diff --git a/src/Package.zig b/src/Package.zig index da2e214154..1bb02c5a5a 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -126,7 +126,7 @@ pub const Path = struct { ) !void { if (fmt_string.len == 1) { // Quote-escape the string. - const stringEscape = std.zig.fmt.stringEscape; + const stringEscape = std.zig.stringEscape; const f = switch (fmt_string[0]) { 'q' => "", '\'' => '\'',