diff --git a/build.zig b/build.zig index a4dbce3..82ba653 100644 --- a/build.zig +++ b/build.zig @@ -81,11 +81,12 @@ pub fn build(b: *Builder) !void { // End External dependencies // Private modules/dependencies - const mod_json = b.createModule(.{ - .root_source_file = b.path("codegen/src/json.zig"), + const dep_json = b.dependency("json", .{ .target = target, .optimize = optimize, }); + const mod_json = dep_json.module("json"); + mod_exe.addImport("json", mod_json); const dep_date = b.dependency("date", .{ .target = target, @@ -114,6 +115,7 @@ pub fn build(b: *Builder) !void { }); cg_mod.addImport("smithy", mod_smithy); cg_mod.addImport("date", mod_date); + cg_mod.addImport("json", mod_json); const cg_exe = b.addExecutable(.{ .name = "codegen", @@ -163,7 +165,7 @@ pub fn build(b: *Builder) !void { service_manifest_module.addImport("json", mod_json); service_manifest_module.addImport("zeit", mod_zeit); - exe.root_module.addImport("service_manifest", service_manifest_module); + mod_exe.addImport("service_manifest", service_manifest_module); // Expose module to others const mod_aws = b.addModule("aws", .{ @@ -174,6 +176,7 @@ pub fn build(b: *Builder) !void { mod_aws.addImport("smithy", mod_smithy); mod_aws.addImport("service_manifest", service_manifest_module); mod_aws.addImport("date", mod_date); + mod_aws.addImport("json", mod_json); mod_aws.addImport("zeit", mod_zeit); // Expose module to others @@ -182,6 +185,7 @@ pub fn build(b: *Builder) !void { }); mod_aws_signing.addImport("date", mod_date); mod_aws_signing.addImport("smithy", mod_smithy); + mod_aws_signing.addImport("json", mod_json); // Similar to creating the run step earlier, this exposes a `test` step to // the `zig build --help` menu, providing a way for the user to request @@ -214,6 +218,7 @@ pub fn build(b: *Builder) !void { mod_unit_tests.addImport("service_manifest", service_manifest_module); mod_unit_tests.addImport("date", mod_date); mod_unit_tests.addImport("zeit", mod_zeit); + mod_unit_tests.addImport("json", mod_json); // Creates a step for unit testing. This only builds the test executable // but does not run it. diff --git a/build.zig.zon b/build.zig.zon index 6f3c913..b0be3a9 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -28,5 +28,8 @@ .date = .{ .path = "lib/date", }, + .json = .{ + .path = "lib/json", + }, }, } diff --git a/codegen/src/json.zig b/codegen/src/json.zig deleted file mode 100644 index 3fe93ec..0000000 --- a/codegen/src/json.zig +++ /dev/null @@ -1,150 +0,0 @@ -const std = @import("std"); -// options is a json.Options, but since we're using our hacked json.zig we don't want to -// specifically call this out -pub fn serializeMap(map: anytype, key: []const u8, options: anytype, out_stream: anytype) !bool { - if (@typeInfo(@TypeOf(map)) == .optional) { - if (map == null) - return false - else - return serializeMapInternal(map.?, key, options, out_stream); - } - return serializeMapInternal(map, key, options, out_stream); -} - -fn serializeMapInternal(map: anytype, key: []const u8, options: anytype, out_stream: anytype) !bool { - if (map.len == 0) { - var child_options = options; - if (child_options.whitespace) |*child_ws| - child_ws.indent_level += 1; - - try out_stream.writeByte('"'); - try out_stream.writeAll(key); - _ = try out_stream.write("\":"); - if (options.whitespace) |ws| { - if (ws.separator) { - try out_stream.writeByte(' '); - } - } - try out_stream.writeByte('{'); - try out_stream.writeByte('}'); - return true; - } - // TODO: Map might be [][]struct{key, value} rather than []struct{key, value} - var child_options = options; - if (child_options.whitespace) |*child_ws| - child_ws.indent_level += 1; - - try out_stream.writeByte('"'); - try out_stream.writeAll(key); - _ = try out_stream.write("\":"); - if (options.whitespace) |ws| { - if (ws.separator) { - try out_stream.writeByte(' '); - } - } - try out_stream.writeByte('{'); - if (options.whitespace) |_| - try out_stream.writeByte('\n'); - for (map, 0..) |tag, i| { - if (tag.key == null or tag.value == null) continue; - // TODO: Deal with escaping and general "json.stringify" the values... - if (child_options.whitespace) |ws| - try ws.outputIndent(out_stream); - try out_stream.writeByte('"'); - try jsonEscape(tag.key.?, child_options, out_stream); - _ = try out_stream.write("\":"); - if (child_options.whitespace) |ws| { - if (ws.separator) { - try out_stream.writeByte(' '); - } - } - try out_stream.writeByte('"'); - try jsonEscape(tag.value.?, child_options, out_stream); - try out_stream.writeByte('"'); - if (i < map.len - 1) { - try out_stream.writeByte(','); - } - if (child_options.whitespace) |_| - try out_stream.writeByte('\n'); - } - if (options.whitespace) |ws| - try ws.outputIndent(out_stream); - try out_stream.writeByte('}'); - return true; -} -// code within jsonEscape lifted from json.zig in stdlib -fn jsonEscape(value: []const u8, options: anytype, out_stream: anytype) !void { - var i: usize = 0; - while (i < value.len) : (i += 1) { - switch (value[i]) { - // normal ascii character - 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try out_stream.writeByte(c), - // only 2 characters that *must* be escaped - '\\' => try out_stream.writeAll("\\\\"), - '\"' => try out_stream.writeAll("\\\""), - // solidus is optional to escape - '/' => { - if (options.string.String.escape_solidus) { - try out_stream.writeAll("\\/"); - } else { - try out_stream.writeByte('/'); - } - }, - // control characters with short escapes - // TODO: option to switch between unicode and 'short' forms? - 0x8 => try out_stream.writeAll("\\b"), - 0xC => try out_stream.writeAll("\\f"), - '\n' => try out_stream.writeAll("\\n"), - '\r' => try out_stream.writeAll("\\r"), - '\t' => try out_stream.writeAll("\\t"), - else => { - const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable; - // control characters (only things left with 1 byte length) should always be printed as unicode escapes - if (ulen == 1 or options.string.String.escape_unicode) { - const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable; - try outputUnicodeEscape(codepoint, out_stream); - } else { - try out_stream.writeAll(value[i .. i + ulen]); - } - i += ulen - 1; - }, - } - } -} -// outputUnicodeEscape and assert lifted from json.zig in stdlib -fn outputUnicodeEscape( - codepoint: u21, - out_stream: anytype, -) !void { - if (codepoint <= 0xFFFF) { - // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), - // then it may be represented as a six-character sequence: a reverse solidus, followed - // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point. - try out_stream.writeAll("\\u"); - try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); - } else { - assert(codepoint <= 0x10FFFF); - // To escape an extended character that is not in the Basic Multilingual Plane, - // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair. - const high = @as(u16, @intCast((codepoint - 0x10000) >> 10)) + 0xD800; - const low = @as(u16, @intCast(codepoint & 0x3FF)) + 0xDC00; - try out_stream.writeAll("\\u"); - try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); - try out_stream.writeAll("\\u"); - try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); - } -} - -/// This function invokes undefined behavior when `ok` is `false`. -/// In Debug and ReleaseSafe modes, calls to this function are always -/// generated, and the `unreachable` statement triggers a panic. -/// In ReleaseFast and ReleaseSmall modes, calls to this function are -/// optimized away, and in fact the optimizer is able to use the assertion -/// in its heuristics. -/// Inside a test block, it is best to use the `std.testing` module rather -/// than this function, because this function may not detect a test failure -/// in ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert -/// function is the correct function to use. -pub fn assert(ok: bool) void { - if (!ok) unreachable; // assertion failure -} diff --git a/lib/json/build.zig b/lib/json/build.zig new file mode 100644 index 0000000..b37239a --- /dev/null +++ b/lib/json/build.zig @@ -0,0 +1,29 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const lib_mod = b.addModule("json", .{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const lib = b.addLibrary(.{ + .linkage = .static, + .name = "json", + .root_module = lib_mod, + }); + + b.installArtifact(lib); + + const lib_unit_tests = b.addTest(.{ + .root_module = lib_mod, + }); + + const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_lib_unit_tests.step); +} diff --git a/lib/json/build.zig.zon b/lib/json/build.zig.zon new file mode 100644 index 0000000..280c75c --- /dev/null +++ b/lib/json/build.zig.zon @@ -0,0 +1,12 @@ +.{ + .name = .json, + .version = "0.0.0", + .fingerprint = 0x6b0725452065211c, // Changing this has security and trust implications. + .minimum_zig_version = "0.14.0", + .dependencies = .{}, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/src/json.zig b/lib/json/src/json.zig similarity index 96% rename from src/json.zig rename to lib/json/src/json.zig index eeb4c05..d307e9e 100644 --- a/src/json.zig +++ b/lib/json/src/json.zig @@ -14,8 +14,116 @@ const testing = std.testing; const mem = std.mem; const maxInt = std.math.maxInt; -// pub const WriteStream = @import("json/write_stream.zig").WriteStream; -// pub const writeStream = @import("json/write_stream.zig").writeStream; +pub fn serializeMap(map: anytype, key: []const u8, options: anytype, out_stream: anytype) !bool { + if (@typeInfo(@TypeOf(map)) == .optional) { + if (map == null) + return false + else + return serializeMapInternal(map.?, key, options, out_stream); + } + return serializeMapInternal(map, key, options, out_stream); +} + +fn serializeMapInternal(map: anytype, key: []const u8, options: anytype, out_stream: anytype) !bool { + if (map.len == 0) { + var child_options = options; + if (child_options.whitespace) |*child_ws| + child_ws.indent_level += 1; + + try out_stream.writeByte('"'); + try out_stream.writeAll(key); + _ = try out_stream.write("\":"); + if (options.whitespace) |ws| { + if (ws.separator) { + try out_stream.writeByte(' '); + } + } + try out_stream.writeByte('{'); + try out_stream.writeByte('}'); + return true; + } + // TODO: Map might be [][]struct{key, value} rather than []struct{key, value} + var child_options = options; + if (child_options.whitespace) |*child_ws| + child_ws.indent_level += 1; + + try out_stream.writeByte('"'); + try out_stream.writeAll(key); + _ = try out_stream.write("\":"); + if (options.whitespace) |ws| { + if (ws.separator) { + try out_stream.writeByte(' '); + } + } + try out_stream.writeByte('{'); + if (options.whitespace) |_| + try out_stream.writeByte('\n'); + for (map, 0..) |tag, i| { + if (tag.key == null or tag.value == null) continue; + // TODO: Deal with escaping and general "json.stringify" the values... + if (child_options.whitespace) |ws| + try ws.outputIndent(out_stream); + try out_stream.writeByte('"'); + try jsonEscape(tag.key.?, child_options, out_stream); + _ = try out_stream.write("\":"); + if (child_options.whitespace) |ws| { + if (ws.separator) { + try out_stream.writeByte(' '); + } + } + try out_stream.writeByte('"'); + try jsonEscape(tag.value.?, child_options, out_stream); + try out_stream.writeByte('"'); + if (i < map.len - 1) { + try out_stream.writeByte(','); + } + if (child_options.whitespace) |_| + try out_stream.writeByte('\n'); + } + if (options.whitespace) |ws| + try ws.outputIndent(out_stream); + try out_stream.writeByte('}'); + return true; +} +// code within jsonEscape lifted from json.zig in stdlib +fn jsonEscape(value: []const u8, options: anytype, out_stream: anytype) !void { + var i: usize = 0; + while (i < value.len) : (i += 1) { + switch (value[i]) { + // normal ascii character + 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try out_stream.writeByte(c), + // only 2 characters that *must* be escaped + '\\' => try out_stream.writeAll("\\\\"), + '\"' => try out_stream.writeAll("\\\""), + // solidus is optional to escape + '/' => { + if (options.string.String.escape_solidus) { + try out_stream.writeAll("\\/"); + } else { + try out_stream.writeByte('/'); + } + }, + // control characters with short escapes + // TODO: option to switch between unicode and 'short' forms? + 0x8 => try out_stream.writeAll("\\b"), + 0xC => try out_stream.writeAll("\\f"), + '\n' => try out_stream.writeAll("\\n"), + '\r' => try out_stream.writeAll("\\r"), + '\t' => try out_stream.writeAll("\\t"), + else => { + const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable; + // control characters (only things left with 1 byte length) should always be printed as unicode escapes + if (ulen == 1 or options.string.String.escape_unicode) { + const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable; + try outputUnicodeEscape(codepoint, out_stream); + } else { + try out_stream.writeAll(value[i .. i + ulen]); + } + i += ulen - 1; + }, + } + } +} const StringEscapes = union(enum) { None, @@ -1316,8 +1424,8 @@ pub const Value = union(enum) { } pub fn dump(self: Value) void { - var held = std.debug.getStderrMutex().acquire(); - defer held.release(); + std.debug.lockStdErr(); + defer std.debug.unlockStdErr(); const stderr = std.io.getStdErr().writer(); stringify(self, StringifyOptions{ .whitespace = null }, stderr) catch return; diff --git a/lib/json/src/root.zig b/lib/json/src/root.zig new file mode 100644 index 0000000..8ad6008 --- /dev/null +++ b/lib/json/src/root.zig @@ -0,0 +1,4 @@ +const std = @import("std"); +const testing = std.testing; + +pub usingnamespace @import("json.zig"); diff --git a/src/aws.zig b/src/aws.zig index e259731..a44423b 100644 --- a/src/aws.zig +++ b/src/aws.zig @@ -3,7 +3,7 @@ const std = @import("std"); const zeit = @import("zeit"); const awshttp = @import("aws_http.zig"); -const json = @import("json.zig"); +const json = @import("json"); const url = @import("url.zig"); const case = @import("case.zig"); const date = @import("date"); diff --git a/src/main.zig b/src/main.zig index ed3024a..1a7bfaf 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,6 @@ const std = @import("std"); const aws = @import("aws.zig"); -const json = @import("json.zig"); +const json = @import("json"); var verbose: u8 = 0;