mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-02 18:57:10 -04:00

For reference: https://github.com/ziglang/zig/pull/24329 some commits have been extracted from #1583, but the x86_64 backend has been disabled due to its horrible performance. Remaining work: - [x] Wait for official builds on ziglang.org and upload them to our repository - [x] Add workaround for https://github.com/ziglang/zig/pull/24466 - [x] Fix TODO comment about ANSI support in stdout - [x] Check for compile-time performance changes → it went from 13.1 to 11.9 seconds 🎉
774 lines
25 KiB
Zig
774 lines
25 KiB
Zig
const std = @import("std");
|
||
|
||
const main = @import("main");
|
||
const NeverFailingAllocator = main.heap.NeverFailingAllocator;
|
||
const List = main.List;
|
||
|
||
const JsonType = enum(u8) {
|
||
JsonInt,
|
||
JsonFloat,
|
||
JsonString,
|
||
JsonStringOwned,
|
||
JsonBool,
|
||
JsonNull,
|
||
JsonArray,
|
||
JsonObject,
|
||
};
|
||
|
||
pub const JsonElement = union(JsonType) { // MARK: JsonElement
|
||
JsonInt: i64,
|
||
JsonFloat: f64,
|
||
JsonString: []const u8,
|
||
JsonStringOwned: []const u8,
|
||
JsonBool: bool,
|
||
JsonNull: void,
|
||
JsonArray: *List(JsonElement),
|
||
JsonObject: *std.StringHashMap(JsonElement),
|
||
|
||
pub fn initObject(allocator: NeverFailingAllocator) JsonElement {
|
||
const map = allocator.create(std.StringHashMap(JsonElement));
|
||
map.* = .init(allocator.allocator);
|
||
return JsonElement{.JsonObject = map};
|
||
}
|
||
|
||
pub fn initArray(allocator: NeverFailingAllocator) JsonElement {
|
||
const list = allocator.create(List(JsonElement));
|
||
list.* = .init(allocator);
|
||
return JsonElement{.JsonArray = list};
|
||
}
|
||
|
||
pub fn getAtIndex(self: *const JsonElement, comptime _type: type, index: usize, replacement: _type) _type {
|
||
if(self.* != .JsonArray) {
|
||
return replacement;
|
||
} else {
|
||
if(index < self.JsonArray.items.len) {
|
||
return self.JsonArray.items[index].as(_type, replacement);
|
||
} else {
|
||
return replacement;
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn getChildAtIndex(self: *const JsonElement, index: usize) JsonElement {
|
||
if(self.* != .JsonArray) {
|
||
return JsonElement{.JsonNull = {}};
|
||
} else {
|
||
if(index < self.JsonArray.items.len) {
|
||
return self.JsonArray.items[index];
|
||
} else {
|
||
return JsonElement{.JsonNull = {}};
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn get(self: *const JsonElement, comptime _type: type, key: []const u8, replacement: _type) _type {
|
||
if(self.* != .JsonObject) {
|
||
return replacement;
|
||
} else {
|
||
if(self.JsonObject.get(key)) |elem| {
|
||
return elem.as(_type, replacement);
|
||
} else {
|
||
return replacement;
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn getChild(self: *const JsonElement, key: []const u8) JsonElement {
|
||
if(self.* != .JsonObject) {
|
||
return JsonElement{.JsonNull = {}};
|
||
} else {
|
||
if(self.JsonObject.get(key)) |elem| {
|
||
return elem;
|
||
} else {
|
||
return JsonElement{.JsonNull = {}};
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn as(self: *const JsonElement, comptime T: type, replacement: T) T {
|
||
comptime var typeInfo: std.builtin.Type = @typeInfo(T);
|
||
comptime var innerType = T;
|
||
inline while(typeInfo == .optional) {
|
||
innerType = typeInfo.optional.child;
|
||
typeInfo = @typeInfo(innerType);
|
||
}
|
||
switch(typeInfo) {
|
||
.int => {
|
||
switch(self.*) {
|
||
.JsonInt => return std.math.cast(innerType, self.JsonInt) orelse replacement,
|
||
.JsonFloat => return std.math.lossyCast(innerType, std.math.round(self.JsonFloat)),
|
||
else => return replacement,
|
||
}
|
||
},
|
||
.float => {
|
||
switch(self.*) {
|
||
.JsonInt => return @floatFromInt(self.JsonInt),
|
||
.JsonFloat => return @floatCast(self.JsonFloat),
|
||
else => return replacement,
|
||
}
|
||
},
|
||
.vector => {
|
||
const len = typeInfo.vector.len;
|
||
const elems = self.toSlice();
|
||
if(elems.len != len) return replacement;
|
||
var result: innerType = undefined;
|
||
if(innerType == T) result = replacement;
|
||
inline for(0..len) |i| {
|
||
if(innerType == T) {
|
||
result[i] = elems[i].as(typeInfo.vector.child, result[i]);
|
||
} else {
|
||
result[i] = elems[i].as(?typeInfo.vector.child, null) orelse return replacement;
|
||
}
|
||
}
|
||
return result;
|
||
},
|
||
else => {
|
||
switch(innerType) {
|
||
[]const u8 => {
|
||
switch(self.*) {
|
||
.JsonString => return self.JsonString,
|
||
.JsonStringOwned => return self.JsonStringOwned,
|
||
else => return replacement,
|
||
}
|
||
},
|
||
bool => {
|
||
switch(self.*) {
|
||
.JsonBool => return self.JsonBool,
|
||
else => return replacement,
|
||
}
|
||
},
|
||
else => {
|
||
@compileError("Unsupported type '" ++ @typeName(T) ++ "'.");
|
||
},
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
fn createElementFromRandomType(value: anytype, allocator: std.mem.Allocator) JsonElement {
|
||
switch(@typeInfo(@TypeOf(value))) {
|
||
.void => return JsonElement{.JsonNull = {}},
|
||
.null => return JsonElement{.JsonNull = {}},
|
||
.bool => return JsonElement{.JsonBool = value},
|
||
.int, .comptime_int => return JsonElement{.JsonInt = @intCast(value)},
|
||
.float, .comptime_float => return JsonElement{.JsonFloat = @floatCast(value)},
|
||
.@"union" => {
|
||
if(@TypeOf(value) == JsonElement) {
|
||
return value;
|
||
} else {
|
||
@compileError("Unknown value type.");
|
||
}
|
||
},
|
||
.pointer => |ptr| {
|
||
if(ptr.child == u8 and ptr.size == .Slice) {
|
||
return JsonElement{.JsonString = value};
|
||
} else {
|
||
const childInfo = @typeInfo(ptr.child);
|
||
if(ptr.size == .One and childInfo == .array and childInfo.array.child == u8) {
|
||
return JsonElement{.JsonString = value};
|
||
} else {
|
||
@compileError("Unknown value type.");
|
||
}
|
||
}
|
||
},
|
||
.optional => {
|
||
if(value) |val| {
|
||
return createElementFromRandomType(val, allocator);
|
||
} else {
|
||
return JsonElement{.JsonNull = {}};
|
||
}
|
||
},
|
||
.vector => {
|
||
const len = @typeInfo(@TypeOf(value)).vector.len;
|
||
const result = initArray(main.heap.NeverFailingAllocator{.allocator = allocator, .IAssertThatTheProvidedAllocatorCantFail = {}});
|
||
result.JsonArray.ensureCapacity(len);
|
||
inline for(0..len) |i| {
|
||
result.JsonArray.appendAssumeCapacity(createElementFromRandomType(value[i], allocator));
|
||
}
|
||
return result;
|
||
},
|
||
else => {
|
||
if(@TypeOf(value) == JsonElement) {
|
||
return value;
|
||
} else {
|
||
@compileError("Unknown value type.");
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
pub fn put(self: *const JsonElement, key: []const u8, value: anytype) void {
|
||
const result = createElementFromRandomType(value, self.JsonObject.allocator);
|
||
self.JsonObject.put(self.JsonObject.allocator.dupe(u8, key) catch unreachable, result) catch unreachable;
|
||
}
|
||
|
||
pub fn putOwnedString(self: *const JsonElement, key: []const u8, value: []const u8) void {
|
||
const result = JsonElement{.JsonStringOwned = self.JsonObject.allocator.dupe(u8, value) catch unreachable};
|
||
self.JsonObject.put(self.JsonObject.allocator.dupe(u8, key) catch unreachable, result) catch unreachable;
|
||
}
|
||
|
||
pub fn toSlice(self: *const JsonElement) []JsonElement {
|
||
switch(self.*) {
|
||
.JsonArray => |arr| {
|
||
return arr.items;
|
||
},
|
||
else => return &[0]JsonElement{},
|
||
}
|
||
}
|
||
|
||
pub fn free(self: *const JsonElement, allocator: NeverFailingAllocator) void {
|
||
switch(self.*) {
|
||
.JsonInt, .JsonFloat, .JsonBool, .JsonNull, .JsonString => return,
|
||
.JsonStringOwned => {
|
||
allocator.free(self.JsonStringOwned);
|
||
},
|
||
.JsonArray => {
|
||
for(self.JsonArray.items) |*elem| {
|
||
elem.free(allocator);
|
||
}
|
||
self.JsonArray.clearAndFree();
|
||
allocator.destroy(self.JsonArray);
|
||
},
|
||
.JsonObject => {
|
||
var iterator = self.JsonObject.iterator();
|
||
while(true) {
|
||
const elem = iterator.next() orelse break;
|
||
allocator.free(elem.key_ptr.*);
|
||
elem.value_ptr.free(allocator);
|
||
}
|
||
self.JsonObject.clearAndFree();
|
||
allocator.destroy(self.JsonObject);
|
||
},
|
||
}
|
||
}
|
||
|
||
pub fn isNull(self: *const JsonElement) bool {
|
||
return self.* == .JsonNull;
|
||
}
|
||
|
||
fn escape(list: *List(u8), string: []const u8) void {
|
||
for(string) |char| {
|
||
switch(char) {
|
||
'\\' => list.appendSlice("\\\\"),
|
||
'\n' => list.appendSlice("\\n"),
|
||
'\"' => list.appendSlice("\\\""),
|
||
'\t' => list.appendSlice("\\t"),
|
||
else => list.append(char),
|
||
}
|
||
}
|
||
}
|
||
fn writeTabs(list: *List(u8), tabs: u32) void {
|
||
for(0..tabs) |_| {
|
||
list.append('\t');
|
||
}
|
||
}
|
||
fn recurseToString(json: JsonElement, list: *List(u8), tabs: u32, comptime visualCharacters: bool) void {
|
||
switch(json) {
|
||
.JsonInt => |value| {
|
||
list.writer().print("{d}", .{value}) catch unreachable;
|
||
},
|
||
.JsonFloat => |value| {
|
||
list.writer().print("{e}", .{value}) catch unreachable;
|
||
},
|
||
.JsonBool => |value| {
|
||
if(value) {
|
||
list.appendSlice("true");
|
||
} else {
|
||
list.appendSlice("false");
|
||
}
|
||
},
|
||
.JsonNull => {
|
||
list.appendSlice("null");
|
||
},
|
||
.JsonString, .JsonStringOwned => |value| {
|
||
list.append('\"');
|
||
escape(list, value);
|
||
list.append('\"');
|
||
},
|
||
.JsonArray => |array| {
|
||
list.append('[');
|
||
for(array.items, 0..) |elem, i| {
|
||
if(i != 0) {
|
||
list.append(',');
|
||
}
|
||
if(visualCharacters) list.append('\n');
|
||
if(visualCharacters) writeTabs(list, tabs + 1);
|
||
recurseToString(elem, list, tabs + 1, visualCharacters);
|
||
}
|
||
if(visualCharacters) list.append('\n');
|
||
if(visualCharacters) writeTabs(list, tabs);
|
||
list.append(']');
|
||
},
|
||
.JsonObject => |obj| {
|
||
list.append('{');
|
||
var iterator = obj.iterator();
|
||
var first: bool = true;
|
||
while(true) {
|
||
const elem = iterator.next() orelse break;
|
||
if(!first) {
|
||
list.append(',');
|
||
}
|
||
if(visualCharacters) list.append('\n');
|
||
if(visualCharacters) writeTabs(list, tabs + 1);
|
||
list.append('\"');
|
||
list.appendSlice(elem.key_ptr.*);
|
||
list.append('\"');
|
||
if(visualCharacters) list.append(' ');
|
||
list.append(':');
|
||
if(visualCharacters) list.append(' ');
|
||
|
||
recurseToString(elem.value_ptr.*, list, tabs + 1, visualCharacters);
|
||
first = false;
|
||
}
|
||
if(visualCharacters) list.append('\n');
|
||
if(visualCharacters) writeTabs(list, tabs);
|
||
list.append('}');
|
||
},
|
||
}
|
||
}
|
||
pub fn toString(json: JsonElement, allocator: NeverFailingAllocator) []const u8 {
|
||
var string = List(u8).init(allocator);
|
||
recurseToString(json, &string, 0, true);
|
||
return string.toOwnedSlice();
|
||
}
|
||
|
||
/// Ignores all the visual characters(spaces, tabs and newlines) and allows adding a custom prefix(which is for example required by networking).
|
||
pub fn toStringEfficient(json: JsonElement, allocator: NeverFailingAllocator, prefix: []const u8) []const u8 {
|
||
var string = List(u8).init(allocator);
|
||
string.appendSlice(prefix);
|
||
recurseToString(json, &string, 0, false);
|
||
return string.toOwnedSlice();
|
||
}
|
||
|
||
pub fn parseFromString(allocator: NeverFailingAllocator, string: []const u8) JsonElement {
|
||
var index: u32 = 0;
|
||
Parser.skipWhitespaces(string, &index);
|
||
return Parser.parseElement(allocator, string, &index);
|
||
}
|
||
};
|
||
|
||
const Parser = struct { // MARK: Parser
|
||
/// All whitespaces from unicode 14.
|
||
const whitespaces = [_][]const u8{"\u{0009}", "\u{000A}", "\u{000B}", "\u{000C}", "\u{000D}", "\u{0020}", "\u{0085}", "\u{00A0}", "\u{1680}", "\u{2000}", "\u{2001}", "\u{2002}", "\u{2003}", "\u{2004}", "\u{2005}", "\u{2006}", "\u{2007}", "\u{2008}", "\u{2009}", "\u{200A}", "\u{2028}", "\u{2029}", "\u{202F}", "\u{205F}", "\u{3000}"};
|
||
|
||
fn skipWhitespaces(chars: []const u8, index: *u32) void {
|
||
outerLoop: while(index.* < chars.len) {
|
||
whitespaceLoop: for(whitespaces) |whitespace| {
|
||
for(whitespace, 0..) |char, i| {
|
||
if(char != chars[index.* + i]) {
|
||
continue :whitespaceLoop;
|
||
}
|
||
}
|
||
index.* += @intCast(whitespace.len);
|
||
continue :outerLoop;
|
||
}
|
||
// Next character is no whitespace.
|
||
return;
|
||
}
|
||
}
|
||
|
||
/// Assumes that the region starts with a number character ('+', '-', '.' or a digit).
|
||
fn parseNumber(chars: []const u8, index: *u32) JsonElement {
|
||
var sign: i2 = 1;
|
||
if(chars[index.*] == '-') {
|
||
sign = -1;
|
||
index.* += 1;
|
||
} else if(chars[index.*] == '+') {
|
||
index.* += 1;
|
||
}
|
||
var intPart: i64 = 0;
|
||
if(index.* + 1 < chars.len and chars[index.*] == '0' and chars[index.* + 1] == 'x') {
|
||
// Parse hex int
|
||
index.* += 2;
|
||
while(index.* < chars.len) : (index.* += 1) {
|
||
switch(chars[index.*]) {
|
||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
||
intPart = (chars[index.*] - '0') +% intPart*%16;
|
||
},
|
||
'a', 'b', 'c', 'd', 'e', 'f' => {
|
||
intPart = (chars[index.*] - 'a' + 10) +% intPart*%16;
|
||
},
|
||
'A', 'B', 'C', 'D', 'E', 'F' => {
|
||
intPart = (chars[index.*] - 'A' + 10) +% intPart*%16;
|
||
},
|
||
else => {
|
||
break;
|
||
},
|
||
}
|
||
}
|
||
return JsonElement{.JsonInt = sign*intPart};
|
||
}
|
||
while(index.* < chars.len) : (index.* += 1) {
|
||
switch(chars[index.*]) {
|
||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
||
intPart = (chars[index.*] - '0') +% intPart*%10;
|
||
},
|
||
else => {
|
||
break;
|
||
},
|
||
}
|
||
}
|
||
if(index.* >= chars.len or (chars[index.*] != '.' and chars[index.*] != 'e' and chars[index.*] != 'E')) { // This is an int
|
||
return JsonElement{.JsonInt = sign*intPart};
|
||
}
|
||
// So this is a float apparently.
|
||
|
||
var floatPart: f64 = 0;
|
||
var currentFactor: f64 = 0.1;
|
||
if(chars[index.*] == '.') {
|
||
index.* += 1;
|
||
while(index.* < chars.len) : (index.* += 1) {
|
||
switch(chars[index.*]) {
|
||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
||
floatPart += @as(f64, @floatFromInt(chars[index.*] - '0'))*currentFactor;
|
||
currentFactor *= 0.1;
|
||
},
|
||
else => {
|
||
break;
|
||
},
|
||
}
|
||
}
|
||
}
|
||
var exponent: i64 = 0;
|
||
var exponentSign: i2 = 1;
|
||
if(index.* < chars.len and (chars[index.*] == 'e' or chars[index.*] == 'E')) {
|
||
index.* += 1;
|
||
if(index.* < chars.len and chars[index.*] == '-') {
|
||
exponentSign = -1;
|
||
index.* += 1;
|
||
} else if(index.* < chars.len and chars[index.*] == '+') {
|
||
index.* += 1;
|
||
}
|
||
while(index.* < chars.len) : (index.* += 1) {
|
||
switch(chars[index.*]) {
|
||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
||
exponent = (chars[index.*] - '0') +% exponent*%10;
|
||
},
|
||
else => {
|
||
break;
|
||
},
|
||
}
|
||
}
|
||
}
|
||
return JsonElement{.JsonFloat = @as(f64, @floatFromInt(sign))*(@as(f64, @floatFromInt(intPart)) + floatPart)*std.math.pow(f64, 10, @as(f64, @floatFromInt(exponentSign*exponent)))};
|
||
}
|
||
|
||
fn parseString(allocator: NeverFailingAllocator, chars: []const u8, index: *u32) []const u8 {
|
||
var builder = List(u8).init(allocator);
|
||
while(index.* < chars.len) : (index.* += 1) {
|
||
if(chars[index.*] == '\"') {
|
||
index.* += 1;
|
||
break;
|
||
} else if(chars[index.*] == '\\') {
|
||
index.* += 1;
|
||
if(index.* >= chars.len)
|
||
break;
|
||
switch(chars[index.*]) {
|
||
't' => {
|
||
builder.append('\t');
|
||
},
|
||
'n' => {
|
||
builder.append('\n');
|
||
},
|
||
'r' => {
|
||
builder.append('\r');
|
||
},
|
||
else => {
|
||
builder.append(chars[index.*]);
|
||
},
|
||
}
|
||
} else {
|
||
builder.append(chars[index.*]);
|
||
}
|
||
}
|
||
return builder.toOwnedSlice();
|
||
}
|
||
|
||
fn parseArray(allocator: NeverFailingAllocator, chars: []const u8, index: *u32) JsonElement {
|
||
const list = allocator.create(List(JsonElement));
|
||
list.* = .init(allocator);
|
||
while(index.* < chars.len) {
|
||
skipWhitespaces(chars, index);
|
||
if(index.* >= chars.len) break;
|
||
if(chars[index.*] == ']') {
|
||
index.* += 1;
|
||
return JsonElement{.JsonArray = list};
|
||
}
|
||
list.append(parseElement(allocator, chars, index));
|
||
skipWhitespaces(chars, index);
|
||
if(index.* < chars.len and chars[index.*] == ',') {
|
||
index.* += 1;
|
||
}
|
||
}
|
||
printError(chars, index.*, "Unexpected end of file in array parsing.");
|
||
return JsonElement{.JsonArray = list};
|
||
}
|
||
|
||
fn parseObject(allocator: NeverFailingAllocator, chars: []const u8, index: *u32) JsonElement {
|
||
const map = allocator.create(std.StringHashMap(JsonElement));
|
||
map.* = .init(allocator.allocator);
|
||
while(index.* < chars.len) {
|
||
skipWhitespaces(chars, index);
|
||
if(index.* >= chars.len) break;
|
||
if(chars[index.*] == '}') {
|
||
index.* += 1;
|
||
return JsonElement{.JsonObject = map};
|
||
} else if(chars[index.*] != '\"') {
|
||
printError(chars, index.*, "Unexpected character in object parsing.");
|
||
index.* += 1;
|
||
continue;
|
||
}
|
||
index.* += 1;
|
||
const key: []const u8 = parseString(allocator, chars, index);
|
||
skipWhitespaces(chars, index);
|
||
while(index.* < chars.len and chars[index.*] != ':') {
|
||
printError(chars, index.*, "Unexpected character in object parsing, expected ':'.");
|
||
index.* += 1;
|
||
}
|
||
index.* += 1;
|
||
skipWhitespaces(chars, index);
|
||
const value: JsonElement = parseElement(allocator, chars, index);
|
||
if(map.fetchPut(key, value) catch unreachable) |old| {
|
||
printError(chars, index.*, "Duplicate key.");
|
||
allocator.free(old.key);
|
||
old.value.free(allocator);
|
||
}
|
||
skipWhitespaces(chars, index);
|
||
if(index.* < chars.len and chars[index.*] == ',') {
|
||
index.* += 1;
|
||
}
|
||
}
|
||
printError(chars, index.*, "Unexpected end of file in object parsing.");
|
||
return JsonElement{.JsonObject = map};
|
||
}
|
||
|
||
fn printError(chars: []const u8, index: u32, msg: []const u8) void {
|
||
var lineNumber: u32 = 1;
|
||
var lineStart: u32 = 0;
|
||
var i: u32 = 0;
|
||
while(i < index and i < chars.len) : (i += 1) {
|
||
if(chars[i] == '\n') {
|
||
lineNumber += 1;
|
||
lineStart = i;
|
||
}
|
||
}
|
||
while(i < chars.len) : (i += 1) {
|
||
if(chars[i] == '\n') {
|
||
break;
|
||
}
|
||
}
|
||
const lineEnd: u32 = i;
|
||
std.log.warn("Error in line {}: {s}", .{lineNumber, msg});
|
||
std.log.warn("{s}", .{chars[lineStart..lineEnd]});
|
||
// Mark the position:
|
||
var message: [512]u8 = undefined;
|
||
i = lineStart;
|
||
var outputI: u32 = 0;
|
||
while(i < index and i < chars.len) : (i += 1) {
|
||
if((chars[i] & 128) != 0 and (chars[i] & 64) == 0) {
|
||
// Not the start of a utf8 character
|
||
continue;
|
||
}
|
||
if(chars[i] == '\t') {
|
||
message[outputI] = '\t';
|
||
} else {
|
||
message[outputI] = ' ';
|
||
}
|
||
outputI += 1;
|
||
if(outputI >= message.len) {
|
||
return; // 512 characters is too long for this output to be helpful.
|
||
}
|
||
}
|
||
message[outputI] = '^';
|
||
outputI += 1;
|
||
std.log.warn("{s}", .{message[0..outputI]});
|
||
}
|
||
|
||
/// Assumes that the region starts with a non-space character.
|
||
fn parseElement(allocator: NeverFailingAllocator, chars: []const u8, index: *u32) JsonElement {
|
||
if(index.* >= chars.len) {
|
||
printError(chars, index.*, "Unexpected end of file.");
|
||
return JsonElement{.JsonNull = {}};
|
||
}
|
||
switch(chars[index.*]) {
|
||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '-', '.' => {
|
||
return parseNumber(chars, index);
|
||
},
|
||
't' => { // Value can only be true.
|
||
if(index.* + 3 >= chars.len) {
|
||
printError(chars, index.*, "Unexpected end of file.");
|
||
} else if(chars[index.* + 1] != 'r' or chars[index.* + 2] != 'u' or chars[index.* + 3] != 'e') {
|
||
printError(chars, index.*, "Unknown expression, interpreting as true.");
|
||
}
|
||
index.* += 4;
|
||
return JsonElement{.JsonBool = true};
|
||
},
|
||
'f' => { // Value can only be false.
|
||
if(index.* + 4 >= chars.len) {
|
||
printError(chars, index.*, "Unexpected end of file.");
|
||
} else if(chars[index.* + 1] != 'a' or chars[index.* + 2] != 'l' or chars[index.* + 3] != 's' or chars[index.* + 4] != 'e') {
|
||
printError(chars, index.*, "Unknown expression, interpreting as false.");
|
||
}
|
||
index.* += 5;
|
||
return JsonElement{.JsonBool = false};
|
||
},
|
||
'n' => { // Value can only be null.
|
||
if(index.* + 3 >= chars.len) {
|
||
printError(chars, index.*, "Unexpected end of file.");
|
||
} else if(chars[index.* + 1] != 'u' or chars[index.* + 2] != 'l' or chars[index.* + 3] != 'l') {
|
||
printError(chars, index.*, "Unknown expression, interpreting as null.");
|
||
}
|
||
index.* += 4;
|
||
return JsonElement{.JsonNull = {}};
|
||
},
|
||
'\"' => {
|
||
index.* += 1;
|
||
return JsonElement{.JsonStringOwned = parseString(allocator, chars, index)};
|
||
},
|
||
'[' => {
|
||
index.* += 1;
|
||
return parseArray(allocator, chars, index);
|
||
},
|
||
'{' => {
|
||
index.* += 1;
|
||
return parseObject(allocator, chars, index);
|
||
},
|
||
else => {
|
||
printError(chars, index.*, "Unexpected character.");
|
||
return JsonElement{.JsonNull = {}};
|
||
},
|
||
}
|
||
}
|
||
};
|
||
|
||
// ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
|
||
// MARK: Testing
|
||
// ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
|
||
|
||
test "skipWhitespaces" {
|
||
var index: u32 = 0;
|
||
var testString: []const u8 = " fbdn ";
|
||
Parser.skipWhitespaces(testString, &index);
|
||
try std.testing.expectEqual(index, 2);
|
||
testString = "\nĦŊ@Λħŋ";
|
||
index = 0;
|
||
Parser.skipWhitespaces(testString, &index);
|
||
try std.testing.expectEqual(index, 1);
|
||
testString = "\tβρδ→øβν";
|
||
index = 0;
|
||
Parser.skipWhitespaces(testString, &index);
|
||
try std.testing.expectEqual(index, 1);
|
||
testString = "\t \n \t a lot of whitespaces";
|
||
index = 0;
|
||
Parser.skipWhitespaces(testString, &index);
|
||
try std.testing.expectEqual(index, 8);
|
||
testString = " unicode whitespace";
|
||
index = 0;
|
||
Parser.skipWhitespaces(testString, &index);
|
||
try std.testing.expectEqual(index, 3);
|
||
testString = "starting in the middle";
|
||
index = 8;
|
||
Parser.skipWhitespaces(testString, &index);
|
||
try std.testing.expectEqual(index, 13);
|
||
}
|
||
|
||
test "number parsing" {
|
||
// Integers:
|
||
var index: u32 = 0;
|
||
try std.testing.expectEqual(Parser.parseNumber("0", &index), JsonElement{.JsonInt = 0});
|
||
index = 0;
|
||
try std.testing.expectEqual(Parser.parseNumber("+0", &index), JsonElement{.JsonInt = 0});
|
||
index = 0;
|
||
try std.testing.expectEqual(Parser.parseNumber("abcd", &index), JsonElement{.JsonInt = 0});
|
||
index = 0;
|
||
try std.testing.expectEqual(Parser.parseNumber("-0+1", &index), JsonElement{.JsonInt = 0});
|
||
index = 5;
|
||
try std.testing.expectEqual(Parser.parseNumber(" abcd185473896", &index), JsonElement{.JsonInt = 185473896});
|
||
index = 0;
|
||
try std.testing.expectEqual(Parser.parseNumber("0xff34786056.0", &index), JsonElement{.JsonInt = 0xff34786056});
|
||
// Floats:
|
||
index = 0;
|
||
try std.testing.expectEqual(Parser.parseNumber("0.0", &index), JsonElement{.JsonFloat = 0.0});
|
||
index = 0;
|
||
try std.testing.expectEqual(Parser.parseNumber("0e10e10", &index), JsonElement{.JsonFloat = 0.0});
|
||
index = 0;
|
||
try std.testing.expectEqual(Parser.parseNumber("-0.0.0", &index), JsonElement{.JsonFloat = 0.0});
|
||
index = 0;
|
||
try std.testing.expectEqual(Parser.parseNumber("0xabcd.0e10+-+-", &index), JsonElement{.JsonInt = 0xabcd});
|
||
index = 0;
|
||
try std.testing.expectApproxEqAbs(Parser.parseNumber("1.234589e10", &index).JsonFloat, 1.234589e10, 1.0);
|
||
index = 5;
|
||
try std.testing.expectApproxEqAbs(Parser.parseNumber("_____0.0000000000234589e10abcdfe", &index).JsonFloat, 0.234589, 1e-10);
|
||
}
|
||
|
||
test "element parsing" {
|
||
var wrap = main.heap.ErrorHandlingAllocator.init(std.testing.allocator);
|
||
const allocator = wrap.allocator();
|
||
// Integers:
|
||
var index: u32 = 0;
|
||
try std.testing.expectEqual(Parser.parseElement(allocator, "0", &index), JsonElement{.JsonInt = 0});
|
||
index = 0;
|
||
try std.testing.expectEqual(Parser.parseElement(allocator, "0xff34786056.0, true", &index), JsonElement{.JsonInt = 0xff34786056});
|
||
// Floats:
|
||
index = 9;
|
||
try std.testing.expectEqual(Parser.parseElement(allocator, "{\"abcd\": 0.0,}", &index), JsonElement{.JsonFloat = 0.0});
|
||
index = 0;
|
||
try std.testing.expectApproxEqAbs((Parser.parseElement(allocator, "1543.234589e10", &index)).JsonFloat, 1543.234589e10, 1.0);
|
||
index = 5;
|
||
try std.testing.expectApproxEqAbs((Parser.parseElement(allocator, "_____0.0000000000675849301354e10abcdfe", &index)).JsonFloat, 0.675849301354, 1e-10);
|
||
// Null:
|
||
index = 0;
|
||
try std.testing.expectEqual(Parser.parseElement(allocator, "null", &index), JsonElement{.JsonNull = {}});
|
||
// true:
|
||
index = 0;
|
||
try std.testing.expectEqual(Parser.parseElement(allocator, "true", &index), JsonElement{.JsonBool = true});
|
||
// false:
|
||
index = 0;
|
||
try std.testing.expectEqual(Parser.parseElement(allocator, "false", &index), JsonElement{.JsonBool = false});
|
||
|
||
// String:
|
||
index = 0;
|
||
var result: JsonElement = Parser.parseElement(allocator, "\"abcd\\\"\\\\ħσ→ ↑Φ∫€ ⌬ ε→Π\"", &index);
|
||
try std.testing.expectEqualStrings("abcd\"\\ħσ→ ↑Φ∫€ ⌬ ε→Π", result.as([]const u8, ""));
|
||
result.free(allocator);
|
||
index = 0;
|
||
result = Parser.parseElement(allocator, "\"12345", &index);
|
||
try std.testing.expectEqualStrings("12345", result.as([]const u8, ""));
|
||
result.free(allocator);
|
||
|
||
// Object:
|
||
index = 0;
|
||
result = Parser.parseElement(allocator, "{\"name\": 1}", &index);
|
||
try std.testing.expectEqual(JsonType.JsonObject, std.meta.activeTag(result));
|
||
try std.testing.expectEqual(result.JsonObject.get("name"), JsonElement{.JsonInt = 1});
|
||
result.free(allocator);
|
||
index = 0;
|
||
result = Parser.parseElement(allocator, "{\"object\":{},}", &index);
|
||
try std.testing.expectEqual(JsonType.JsonObject, std.meta.activeTag(result));
|
||
try std.testing.expectEqual(JsonType.JsonObject, std.meta.activeTag(result.JsonObject.get("object") orelse JsonType.JsonNull));
|
||
result.free(allocator);
|
||
index = 0;
|
||
result = Parser.parseElement(allocator, "{ \"object1\" : \"\" \n, \"object2\" :\t{\n},\"object3\" :1.0e4\t,\"\nobject1\":{},\"\tobject1θ\":[],}", &index);
|
||
try std.testing.expectEqual(JsonType.JsonObject, std.meta.activeTag(result));
|
||
try std.testing.expectEqual(JsonType.JsonFloat, std.meta.activeTag(result.JsonObject.get("object3") orelse JsonType.JsonNull));
|
||
try std.testing.expectEqual(JsonType.JsonStringOwned, std.meta.activeTag(result.JsonObject.get("object1") orelse JsonType.JsonNull));
|
||
try std.testing.expectEqual(JsonType.JsonObject, std.meta.activeTag(result.JsonObject.get("\nobject1") orelse JsonType.JsonNull));
|
||
try std.testing.expectEqual(JsonType.JsonArray, std.meta.activeTag(result.JsonObject.get("\tobject1θ") orelse JsonType.JsonNull));
|
||
result.free(allocator);
|
||
|
||
//Array:
|
||
index = 0;
|
||
result = Parser.parseElement(allocator, "[\"name\",1]", &index);
|
||
try std.testing.expectEqual(JsonType.JsonArray, std.meta.activeTag(result));
|
||
try std.testing.expectEqual(JsonType.JsonStringOwned, std.meta.activeTag(result.JsonArray.items[0]));
|
||
try std.testing.expectEqual(JsonElement{.JsonInt = 1}, result.JsonArray.items[1]);
|
||
result.free(allocator);
|
||
index = 0;
|
||
result = Parser.parseElement(allocator, "[ \"name\"\t1\n, 17.1]", &index);
|
||
try std.testing.expectEqual(JsonType.JsonArray, std.meta.activeTag(result));
|
||
try std.testing.expectEqual(JsonType.JsonStringOwned, std.meta.activeTag(result.JsonArray.items[0]));
|
||
try std.testing.expectEqual(JsonElement{.JsonInt = 1}, result.JsonArray.items[1]);
|
||
try std.testing.expectEqual(JsonElement{.JsonFloat = 17.1}, result.JsonArray.items[2]);
|
||
result.free(allocator);
|
||
}
|