mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
114 lines
3.8 KiB
Zig
114 lines
3.8 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
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" {
|
|
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
|
|
|
|
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 '")});
|
|
}
|