Add functions to print and to convert to string

This commit is contained in:
Cameron Reed 2024-11-02 09:26:16 -06:00
parent 5357d0a593
commit 438e800f2f
2 changed files with 258 additions and 36 deletions

View File

@ -1,4 +1,5 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
pub const JSONError = error{ pub const JSONError = error{
IncorrectType, IncorrectType,
@ -16,17 +17,43 @@ pub const JSONType = enum {
Null, Null,
}; };
pub const JSONFormat = struct {
indent_level: usize = 4,
indent_char: u8 = ' ',
one_line: bool = false,
};
fn printIndent(writer: std.fs.File.Writer, format: JSONFormat, level: usize) !void {
if (!format.one_line) {
try writer.writeByte('\n');
}
for (0..level) |_| {
try writer.writeByteNTimes(format.indent_char, format.indent_level);
}
}
fn indentString(allocator: std.mem.Allocator, format: JSONFormat, level: usize) ![]const u8 {
var str = std.ArrayList(u8).init(allocator);
if (!format.one_line) {
try str.append('\n');
}
for (0..level) |_| {
try str.appendNTimes(format.indent_char, format.indent_level);
}
return try str.toOwnedSlice();
}
pub const JSONObject = struct { pub const JSONObject = struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
children: MapType, children: MapType,
const MapType = std.StringHashMap(*JSONValue); const MapType = std.StringHashMap(*JSONValue);
pub fn get(self: *JSONObject, name: []const u8) ?*JSONValue { pub fn create(allocator: std.mem.Allocator) JSONError!*JSONObject {
return self.children.get(name);
}
fn init(allocator: std.mem.Allocator) JSONError!*JSONObject {
var self = try allocator.create(JSONObject); var self = try allocator.create(JSONObject);
self.allocator = allocator; self.allocator = allocator;
self.children = MapType.init(allocator); self.children = MapType.init(allocator);
@ -34,6 +61,59 @@ pub const JSONObject = struct {
return self; return self;
} }
pub fn get(self: *JSONObject, name: []const u8) ?*JSONValue {
return self.children.get(name);
}
pub fn print(self: *JSONObject, writer: std.fs.File.Writer, format: JSONFormat, indent_level: usize) std.fs.File.WriteError!void {
try writer.writeByte('{');
var iter = self.children.keyIterator();
var first = true;
while (iter.next()) |key| {
if (!first) {
try writer.writeAll(", ");
}
try printIndent(writer, format, indent_level + 1);
try writer.print("\"{s}\": ", .{key.*});
try self.children.get(key.*).?.print(writer, format, indent_level + 1);
first = false;
}
try printIndent(writer, format, indent_level);
try writer.writeByte('}');
}
pub fn toString(self: *JSONObject, allocator: std.mem.Allocator, format: JSONFormat, indent_level: usize) std.mem.Allocator.Error![]const u8 {
var str = std.ArrayList(u8).init(allocator);
try str.append('{');
var iter = self.children.keyIterator();
var first = true;
while (iter.next()) |key| {
if (!first) {
try str.appendSlice(", ");
}
try str.appendSlice(try indentString(allocator, format, indent_level + 1));
try str.append('"');
try str.appendSlice(key.*);
try str.appendSlice("\": ");
const valstr = try self.children.get(key.*).?.toString(allocator, format, indent_level + 1);
defer allocator.free(valstr);
try str.appendSlice(valstr);
first = false;
}
try str.appendSlice(try indentString(allocator, format, indent_level));
try str.append('}');
return try str.toOwnedSlice();
}
pub fn deinit(self: *JSONObject) void { pub fn deinit(self: *JSONObject) void {
var iter = self.children.valueIterator(); var iter = self.children.valueIterator();
while (iter.next()) |child| { while (iter.next()) |child| {
@ -41,9 +121,10 @@ pub const JSONObject = struct {
} }
var names = self.children.keyIterator(); var names = self.children.keyIterator();
while (names.next()) |name| { while (names.next()) |key| {
self.allocator.free(name.*); self.allocator.free(key.*);
} }
self.children.deinit(); self.children.deinit();
self.allocator.destroy(self); self.allocator.destroy(self);
} }
@ -63,6 +144,9 @@ pub const JSONValue = struct {
getBool: *const fn (self: *const JSONValue) JSONError!bool = incorrectValue(bool), getBool: *const fn (self: *const JSONValue) JSONError!bool = incorrectValue(bool),
deinit: *const fn (self: *JSONValue) void, deinit: *const fn (self: *JSONValue) void,
print: *const fn (self: *const JSONValue, writer: std.fs.File.Writer, format: JSONFormat, indent_level: usize) std.fs.File.WriteError!void,
toString: *const fn (self: *const JSONValue, allocator: std.mem.Allocator, format: JSONFormat, indent_level: usize) std.mem.Allocator.Error![]const u8,
}; };
fn incorrectValue(comptime T: type) *const fn (*const JSONValue) JSONError!T { fn incorrectValue(comptime T: type) *const fn (*const JSONValue) JSONError!T {
@ -97,34 +181,50 @@ pub const JSONValue = struct {
return self.vtable.getObject(self); return self.vtable.getObject(self);
} }
pub fn print(self: *const JSONValue, writer: std.fs.File.Writer, format: JSONFormat, indent_level: usize) std.fs.File.WriteError!void {
return self.vtable.print(self, writer, format, indent_level);
}
pub fn toString(self: *const JSONValue, allocator: std.mem.Allocator, format: JSONFormat, indent_level: usize) std.mem.Allocator.Error![]const u8 {
return self.vtable.toString(self, allocator, format, indent_level);
}
pub fn deinit(self: *JSONValue) void { pub fn deinit(self: *JSONValue) void {
self.vtable.deinit(self); self.vtable.deinit(self);
} }
fn createNull(allocator: std.mem.Allocator) JSONError!*JSONValue { pub fn createNull(allocator: std.mem.Allocator) JSONError!*JSONValue {
var self = try allocator.create(JSONValue); var self = try allocator.create(JSONValue);
self.type = .Null; self.type = .Null;
self.vtable = .{ .deinit = basicDeinit }; self.vtable = .{ .deinit = basicDeinit, .print = printNull, .toString = toStringNull };
self.allocator = allocator; self.allocator = allocator;
return self; return self;
} }
fn printNull(_: *const JSONValue, writer: std.fs.File.Writer, _: JSONFormat, _: usize) std.fs.File.WriteError!void {
return writer.writeAll("null");
}
fn toStringNull(_: *const JSONValue, allocator: std.mem.Allocator, _: JSONFormat, _: usize) ![]const u8 {
return allocator.dupe(u8, "null");
}
fn basicDeinit(self: *JSONValue) void { fn basicDeinit(self: *JSONValue) void {
self.allocator.destroy(self); self.allocator.destroy(self);
} }
}; };
const JSONArrayValue = struct { pub const JSONArrayValue = struct {
value: JSONValue, value: JSONValue,
array: []*JSONValue, array: []*JSONValue,
fn init(allocator: std.mem.Allocator, array: []*JSONValue) !*JSONValue { pub fn create(allocator: std.mem.Allocator, array: []*JSONValue) !*JSONValue {
var self = try allocator.create(JSONArrayValue); var self = try allocator.create(JSONArrayValue);
self.array = array; self.array = array;
self.value = .{ self.value = .{
.type = .Array, .type = .Array,
.vtable = .{ .getArray = getValue, .deinit = deinit }, .vtable = .{ .getArray = getValue, .print = print, .toString = toString, .deinit = deinit },
.allocator = allocator, .allocator = allocator,
}; };
@ -136,6 +236,47 @@ const JSONArrayValue = struct {
return self.array; return self.array;
} }
fn print(value: *const JSONValue, writer: std.fs.File.Writer, format: JSONFormat, indent_level: usize) std.fs.File.WriteError!void {
const self: *const JSONArrayValue = @fieldParentPtr("value", value);
try writer.writeAll("[ ");
var first = true;
for (self.array) |val| {
if (!first) {
try writer.writeAll(", ");
}
try val.print(writer, format, indent_level);
first = false;
}
try writer.writeAll(" ]");
}
fn toString(value: *const JSONValue, allocator: std.mem.Allocator, format: JSONFormat, indent_level: usize) std.mem.Allocator.Error![]const u8 {
const self: *const JSONArrayValue = @fieldParentPtr("value", value);
var str = std.ArrayList(u8).init(allocator);
try str.appendSlice("[ ");
var first = true;
for (self.array) |val| {
if (!first) {
try str.appendSlice(", ");
}
const valstr = try val.toString(allocator, format, indent_level);
defer allocator.free(valstr);
try str.appendSlice(valstr);
first = false;
}
try str.appendSlice(" ]");
return str.toOwnedSlice();
}
fn deinit(value: *JSONValue) void { fn deinit(value: *JSONValue) void {
var self: *JSONArrayValue = @fieldParentPtr("value", value); var self: *JSONArrayValue = @fieldParentPtr("value", value);
for (self.array) |val| { for (self.array) |val| {
@ -146,16 +287,16 @@ const JSONArrayValue = struct {
} }
}; };
const JSONObjectValue = struct { pub const JSONObjectValue = struct {
value: JSONValue, value: JSONValue,
object: *JSONObject, object: *JSONObject,
fn init(allocator: std.mem.Allocator, object: *JSONObject) !*JSONValue { pub fn create(allocator: std.mem.Allocator, object: *JSONObject) !*JSONValue {
var self = try allocator.create(JSONObjectValue); var self = try allocator.create(JSONObjectValue);
self.object = object; self.object = object;
self.value = .{ self.value = .{
.type = .Object, .type = .Object,
.vtable = .{ .getObject = getValue, .deinit = deinit }, .vtable = .{ .getObject = getValue, .print = print, .toString = toString, .deinit = deinit },
.allocator = allocator, .allocator = allocator,
}; };
@ -167,6 +308,16 @@ const JSONObjectValue = struct {
return self.object; return self.object;
} }
fn print(value: *const JSONValue, writer: std.fs.File.Writer, format: JSONFormat, indent_level: usize) std.fs.File.WriteError!void {
const self: *const JSONObjectValue = @fieldParentPtr("value", value);
try self.object.print(writer, format, indent_level);
}
fn toString(value: *const JSONValue, allocator: std.mem.Allocator, format: JSONFormat, indent_level: usize) std.mem.Allocator.Error![]const u8 {
const self: *const JSONObjectValue = @fieldParentPtr("value", value);
return self.object.toString(allocator, format, indent_level);
}
fn deinit(value: *JSONValue) void { fn deinit(value: *JSONValue) void {
var self: *const JSONObjectValue = @fieldParentPtr("value", value); var self: *const JSONObjectValue = @fieldParentPtr("value", value);
self.object.deinit(); self.object.deinit();
@ -174,16 +325,16 @@ const JSONObjectValue = struct {
} }
}; };
const JSONIntValue = struct { pub const JSONIntValue = struct {
value: JSONValue, value: JSONValue,
int: isize, int: isize,
fn init(allocator: std.mem.Allocator, value: isize) !*JSONValue { pub fn create(allocator: std.mem.Allocator, value: isize) !*JSONValue {
var self = try allocator.create(JSONIntValue); var self = try allocator.create(JSONIntValue);
self.int = value; self.int = value;
self.value = .{ self.value = .{
.type = .Int, .type = .Int,
.vtable = .{ .getInt = getValue, .deinit = deinit }, .vtable = .{ .getInt = getValue, .print = print, .toString = toString, .deinit = deinit },
.allocator = allocator, .allocator = allocator,
}; };
@ -195,22 +346,32 @@ const JSONIntValue = struct {
return self.int; return self.int;
} }
fn print(value: *const JSONValue, writer: std.fs.File.Writer, _: JSONFormat, _: usize) std.fs.File.WriteError!void {
const self: *const JSONIntValue = @fieldParentPtr("value", value);
try writer.print("{d}", .{self.int});
}
fn toString(value: *const JSONValue, allocator: std.mem.Allocator, _: JSONFormat, _: usize) std.mem.Allocator.Error![]const u8 {
const self: *const JSONIntValue = @fieldParentPtr("value", value);
return std.fmt.allocPrint(allocator, "{d}", .{self.int});
}
fn deinit(value: *JSONValue) void { fn deinit(value: *JSONValue) void {
var self: *JSONIntValue = @fieldParentPtr("value", value); var self: *JSONIntValue = @fieldParentPtr("value", value);
self.value.allocator.destroy(self); self.value.allocator.destroy(self);
} }
}; };
const JSONFloatValue = struct { pub const JSONFloatValue = struct {
value: JSONValue, value: JSONValue,
float: f64, float: f64,
fn init(allocator: std.mem.Allocator, value: f64) !*JSONValue { pub fn create(allocator: std.mem.Allocator, value: f64) !*JSONValue {
var self = try allocator.create(JSONFloatValue); var self = try allocator.create(JSONFloatValue);
self.float = value; self.float = value;
self.value = .{ self.value = .{
.type = .Float, .type = .Float,
.vtable = .{ .getFloat = getValue, .deinit = deinit }, .vtable = .{ .getFloat = getValue, .print = print, .toString = toString, .deinit = deinit },
.allocator = allocator, .allocator = allocator,
}; };
@ -222,22 +383,32 @@ const JSONFloatValue = struct {
return self.float; return self.float;
} }
fn print(value: *const JSONValue, writer: std.fs.File.Writer, _: JSONFormat, _: usize) std.fs.File.WriteError!void {
const self: *const JSONFloatValue = @fieldParentPtr("value", value);
try writer.print("{d}", .{self.float});
}
fn toString(value: *const JSONValue, allocator: std.mem.Allocator, _: JSONFormat, _: usize) std.mem.Allocator.Error![]const u8 {
const self: *const JSONFloatValue = @fieldParentPtr("value", value);
return std.fmt.allocPrint(allocator, "{d}", .{self.float});
}
fn deinit(value: *JSONValue) void { fn deinit(value: *JSONValue) void {
var self: *JSONIntValue = @fieldParentPtr("value", value); var self: *const JSONFloatValue = @fieldParentPtr("value", value);
self.value.allocator.destroy(self); self.value.allocator.destroy(self);
} }
}; };
const JSONBoolValue = struct { pub const JSONBoolValue = struct {
value: JSONValue, value: JSONValue,
boolean: bool, boolean: bool,
fn init(allocator: std.mem.Allocator, value: bool) !*JSONValue { pub fn create(allocator: std.mem.Allocator, value: bool) !*JSONValue {
var self = try allocator.create(JSONBoolValue); var self = try allocator.create(JSONBoolValue);
self.boolean = value; self.boolean = value;
self.value = .{ self.value = .{
.type = .Bool, .type = .Bool,
.vtable = .{ .getBool = getValue, .deinit = deinit }, .vtable = .{ .getBool = getValue, .print = print, .toString = toString, .deinit = deinit },
.allocator = allocator, .allocator = allocator,
}; };
@ -249,6 +420,24 @@ const JSONBoolValue = struct {
return self.boolean; return self.boolean;
} }
fn print(value: *const JSONValue, writer: std.fs.File.Writer, _: JSONFormat, _: usize) std.fs.File.WriteError!void {
const self: *const JSONBoolValue = @fieldParentPtr("value", value);
if (self.boolean) {
try writer.writeAll("true");
} else {
try writer.writeAll("false");
}
}
fn toString(value: *const JSONValue, allocator: std.mem.Allocator, _: JSONFormat, _: usize) std.mem.Allocator.Error![]const u8 {
const self: *const JSONBoolValue = @fieldParentPtr("value", value);
if (self.boolean) {
return try allocator.dupe(u8, "true");
} else {
return try allocator.dupe(u8, "false");
}
}
fn deinit(value: *JSONValue) void { fn deinit(value: *JSONValue) void {
var self: *JSONBoolValue = @fieldParentPtr("value", value); var self: *JSONBoolValue = @fieldParentPtr("value", value);
self.value.allocator.destroy(self); self.value.allocator.destroy(self);
@ -259,12 +448,12 @@ pub const JSONStringValue = struct {
value: JSONValue, value: JSONValue,
string: []const u8, string: []const u8,
pub fn init(allocator: std.mem.Allocator, string: []const u8) !*JSONValue { pub fn create(allocator: std.mem.Allocator, string: []const u8) !*JSONValue {
var self = try allocator.create(JSONStringValue); var self = try allocator.create(JSONStringValue);
self.string = string; self.string = string;
self.value = .{ self.value = .{
.type = .String, .type = .String,
.vtable = .{ .getString = getValue, .deinit = deinit }, .vtable = .{ .getString = getValue, .print = print, .toString = toString, .deinit = deinit },
.allocator = allocator, .allocator = allocator,
}; };
@ -276,6 +465,16 @@ pub const JSONStringValue = struct {
return self.string; return self.string;
} }
fn print(value: *const JSONValue, writer: std.fs.File.Writer, _: JSONFormat, _: usize) std.fs.File.WriteError!void {
const self: *const JSONStringValue = @fieldParentPtr("value", value);
try writer.print("\"{s}\"", .{self.string});
}
fn toString(value: *const JSONValue, allocator: std.mem.Allocator, _: JSONFormat, _: usize) std.mem.Allocator.Error![]const u8 {
const self: *const JSONStringValue = @fieldParentPtr("value", value);
return std.fmt.allocPrint(allocator, "\"{s}\"", .{self.string});
}
fn deinit(value: *JSONValue) void { fn deinit(value: *JSONValue) void {
var self: *JSONStringValue = @fieldParentPtr("value", value); var self: *JSONStringValue = @fieldParentPtr("value", value);
self.value.allocator.free(self.string); self.value.allocator.free(self.string);
@ -300,6 +499,14 @@ pub fn parseString(allocator: std.mem.Allocator, string: []const u8) !*JSONObjec
return parser.parse(); return parser.parse();
} }
pub fn writeToFile(fileName: []const u8, json: *JSONObject, format: JSONFormat) !void {
var file = try std.fs.cwd().createFile(fileName, .{});
defer file.close();
const writer = file.writer();
return json.print(writer, format, 0);
}
const JSONParser = struct { const JSONParser = struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
iter: std.unicode.Utf8Iterator, iter: std.unicode.Utf8Iterator,
@ -354,7 +561,7 @@ const JSONParser = struct {
} }
fn parseObject(self: *JSONParser) JSONError!*JSONObject { fn parseObject(self: *JSONParser) JSONError!*JSONObject {
const obj = try JSONObject.init(self.allocator); const obj = try JSONObject.create(self.allocator);
errdefer obj.deinit(); errdefer obj.deinit();
try self.expectNext('{'); try self.expectNext('{');
@ -427,7 +634,7 @@ const JSONParser = struct {
'"' => { '"' => {
//std.debug.print("Parsing string\n", .{}); //std.debug.print("Parsing string\n", .{});
const str = try self.parseString(); const str = try self.parseString();
value = try JSONStringValue.init(self.allocator, str); value = try JSONStringValue.create(self.allocator, str);
}, },
'-', '0'...'9' => { '-', '0'...'9' => {
//std.debug.print("Parsing number\n", .{}); //std.debug.print("Parsing number\n", .{});
@ -436,17 +643,17 @@ const JSONParser = struct {
'{' => { '{' => {
//std.debug.print("Parsing object\n", .{}); //std.debug.print("Parsing object\n", .{});
const obj = try self.parseObject(); const obj = try self.parseObject();
value = try JSONObjectValue.init(self.allocator, obj); value = try JSONObjectValue.create(self.allocator, obj);
}, },
'[' => { '[' => {
//std.debug.print("Parsing array\n", .{}); //std.debug.print("Parsing array\n", .{});
const arr = try self.parseArray(); const arr = try self.parseArray();
value = try JSONArrayValue.init(self.allocator, arr); value = try JSONArrayValue.create(self.allocator, arr);
}, },
't', 'f' => { 't', 'f' => {
//std.debug.print("Parsing bool\n", .{}); //std.debug.print("Parsing bool\n", .{});
const b = try self.parseBool(); const b = try self.parseBool();
value = try JSONBoolValue.init(self.allocator, b); value = try JSONBoolValue.create(self.allocator, b);
}, },
'n' => { 'n' => {
//std.debug.print("Parsing null\n", .{}); //std.debug.print("Parsing null\n", .{});
@ -640,10 +847,10 @@ const JSONParser = struct {
value *= std.math.pow(f64, 10.0, @floatFromInt(exp)); value *= std.math.pow(f64, 10.0, @floatFromInt(exp));
} }
return try JSONFloatValue.init(self.allocator, value); return try JSONFloatValue.create(self.allocator, value);
} else { } else {
const value: isize = std.fmt.parseInt(isize, buf.items, 10) catch unreachable; const value: isize = std.fmt.parseInt(isize, buf.items, 10) catch unreachable;
return try JSONIntValue.init(self.allocator, value); return try JSONIntValue.create(self.allocator, value);
} }
} }
}; };
@ -892,3 +1099,8 @@ test "file parsing" {
const root = try parseFile(std.testing.allocator, "test/test.json"); const root = try parseFile(std.testing.allocator, "test/test.json");
root.deinit(); root.deinit();
} }
test "123" {
var root = try JSONObject.create(std.testing.allocator);
defer root.deinit();
}

View File

@ -8,12 +8,14 @@ pub fn main() void {
} }
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer { defer {
if (gpa.deinit() == .leak) { if (gpa.deinit() == .leak) {
std.debug.print("Memory was leaked D:\n", .{}); std.debug.print("Memory was leaked D:\n", .{});
} }
} }
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit();
const allocator = arena.allocator();
const fileName = std.mem.span(std.os.argv[1]); const fileName = std.mem.span(std.os.argv[1]);
@ -21,10 +23,18 @@ pub fn main() void {
std.debug.print("Failed to parse json: {any}\n", .{err}); std.debug.print("Failed to parse json: {any}\n", .{err});
std.process.exit(2); std.process.exit(2);
}; };
defer root.deinit(); //defer root.deinit();
printObject(root, 0); printObject(root, 0);
std.debug.print("\n", .{}); std.debug.print("\n", .{});
const stdout = std.io.getStdOut();
const writer = stdout.writer();
root.print(writer, .{}, 0) catch {};
std.debug.print("\n", .{});
const json_str = root.toString(allocator, .{}, 0) catch return;
std.debug.print("{s}\n", .{json_str});
} }
fn printIndent(indent: usize) void { fn printIndent(indent: usize) void {