2025-08-31 17:57:56 +03:00

392 lines
10 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#import, file "../module.jai";
main :: () {
{
// Use the temporary allocator so we don't have to worry about individually freeing
// all the allocations.
push_allocator(temp);
typed_parsing();
typed_printing();
generic_parsing();
generic_printing();
rename_by_note();
custom_rename_procedure();
decode_into_struct_with_using();
enum_errors();
}
// Since the program ends here, this doesn't matter, but just setting an example.
reset_temporary_storage();
}
// Data structures for typed parsing/printing
LevelData :: struct {
kind: LevelKind;
flags: LevelFlags;
secret: bool;
player: Entity;
player2: *Entity;
union {
score: float;
score_v2: float;
}
entities: [..] Entity;
floats: [] float;
LevelKind :: enum {
EASY :: 0;
HARD :: 1;
LEGENDARY :: 2;
}
LevelFlags :: enum_flags {
FLAG_A :: 0x1;
FLAG_B :: 0x2;
FLAG_C :: 0x4;
}
}
Entity :: struct {
name: string;
x, y: int;
dirty: bool; @JsonIgnore
}
LEVEL_DATA_1_JSON := #string DONE
{"kind": ".HARD", "flags": "FLAG_A | LevelFlags.FLAG_C", "secret": false,"score":5.5,"player": {"name": "Pat","x": 10,"y": 10},"player2": {"name": "Chris"},"entities": [{"name": "fdsa","x": 0,"y": 0},{"name": "fdsa","x": 0,"y": 0}], "floats": [0.00, 1.11111111111111111, 2.0202, 3e-5, 4.444444, -5.0]}
DONE;
LEVEL_DATA_2_JSON := #string DONE
{"kind": 2, "score_v2": 25.1}
DONE;
Fixed_Size_Data :: struct {
data: [3] int;
}
DATA_ARRAY_1 := #string DONE
{"data": [1, 2, 3]}
DONE;
DATA_ARRAY_2 := #string DONE
{"data": [1, 2]}
DONE;
typed_parsing :: () {
{
success, level := json_parse_string(LEVEL_DATA_1_JSON, LevelData, ignore_unknown=false);
// success, level := json_parse_file("level.json", LevelData, ignore_unknown=false);
assert(success);
log("Typed parsing result 1:\n%\nscore: %\n\n", level, level.score);
assert(level.floats.count == 6);
assert(level.floats[1] > 1); // Regression test, we had a bug here…
}
{
// Test parsing integers into enum slots & alternative union fields
success, level := json_parse_string(LEVEL_DATA_2_JSON, LevelData, ignore_unknown=false);
assert(success);
log("Typed parsing result 2:\n%\nscore: %\n\n", level, level.score);
assert(level.kind == .LEGENDARY);
assert(level.score == 25.1);
}
{
// Test parsing into fixed-sized arrays
success, result := json_parse_string(DATA_ARRAY_1, Fixed_Size_Data, ignore_unknown=false);
assert(success);
log("Typed parsing result 3:\n%\n\n", result);
assert(result.data[0] == 1);
assert(result.data[1] == 2);
assert(result.data[2] == 3);
}
{
// Test parsing into fixed-sized arrays (incorrect size)
success, result := json_parse_string(DATA_ARRAY_2, Fixed_Size_Data, ignore_unknown=false);
assert(!success);
}
}
typed_printing :: () {
level := LevelData.{kind=.LEGENDARY, flags=LevelData.LevelFlags.FLAG_B|.FLAG_C, secret=true, score=500};
level.player = .{name="Pat", x=4, y=4, dirty=true};
array_add(*level.entities, .{name="Chris", x=6, y=6});
json_string := json_write_string(level);
log("Typed printing result:\n%\n\n", json_string);
// success := json_write_file("level.json", level, indent_char="");
// assert(success);
}
generic_parsing :: () {
// In this scenario, some parts of the structure are known, but other parts are not.
json := #string DONE
{
"version": 3,
"entities": [
{
"name": "Player",
"x": 2,
"y": 2,
"player_index": 0
},
{
"name": "Snake",
"x": 4,
"y": 4,
"snake_color": 1
}
],
"stuff": [null, true, false]
}
DONE
success, root := json_parse_string(json);
// success, root := json_parse_file("level.json");
assert(success);
log("Generic parsing result:");
// Print things out, for demonstration purposes
traverse_node :: (node: JSON_Value, depth: int) {
INDENTATION :: 4;
print("% ", node.type);
if node.type == {
case .NULL;
print("\n");
case .BOOLEAN;
print("%\n", node.boolean);
case .NUMBER;
print("%\n", node.number);
case .STRING;
print("%\n", node.str);
case .OBJECT;
print("{\n");
for node.object {
for 1..(depth+1)*INDENTATION print(" ");
print("%: ", it_index);
traverse_node(it, depth+1);
}
for 1..depth*INDENTATION print(" ");
print("}\n");
case .ARRAY;
print("[\n");
for node.array {
for 1..(depth+1)*INDENTATION print(" ");
traverse_node(it, depth + 1);
}
for 1..depth*INDENTATION print(" ");
print("]\n");
}
}
traverse_node(root, 0);
print("\n");
// Convenience function for grabbing object members
get :: (json_val: JSON_Value, key: string, expected_type: JSON_Type) -> JSON_Value {
assert(json_val.type == .OBJECT);
table := json_val.object;
success, val := Hash_Table.table_find_new(table, key);
assert(success);
assert(val.type == expected_type);
return val;
}
// Check for version number that may or may not exist
version: float64 = -1;
assert(root.type == .OBJECT);
success2, val := Hash_Table.table_find_new(root.object, "version");
if success2 {
if val.type == .NUMBER {
version = val.number;
}
}
log("version: %\n", version);
// Traverse a structure we are confident about
for get(root, "entities", .ARRAY).array {
entity_name := get(it, "name", .STRING).str;
x := get(it, "x", .NUMBER).number / 32;
y := get(it, "y", .NUMBER).number / 32;
if entity_name == {
case "Player";
player_index := cast(int) get(it, "player_index", .NUMBER).number;
log("Player with player_index=%\n", player_index);
case "Snake";
snake_color := cast(int) get(it, "snake_color", .NUMBER).number;
log("Snake with snake_color=%\n", snake_color);
case;
//...
}
}
log("\n");
}
generic_printing :: () {
// We want to write JSON with arbitrary structure.
// Create and initialize object
root_obj: JSON_Object;
root := json_value(*root_obj);
// Add music index to object, in certain cases
should_add_music := true;
if should_add_music {
json_set(*root_obj, "music_index", .{type=.NUMBER, number=3});
}
// Create an array of values
temp: [..] JSON_Value;
junk := JSON_Value.{type=.STRING, str="junk"};
array_add(*temp, junk);
array_add(*temp, junk);
// Create json array value
array := JSON_Value.{type=.ARRAY};
array.array = temp;
// Add array to object
json_set(*root_obj, "junk_array", array);
// Print result
json_string := json_write_string(root);
log("Generic_printing result:\n%\n\n", json_string);
//json_write_file("level.json", root);
}
rename_by_note :: () {
// Sometimes the JSON we are parsing contains members with names we cannot use or don´t wanna.
Message :: struct {
value: string;
_context: struct { // We cannot use "context" because it´s a reserved keyword in Jai.
channel: int;
parent: int;
} @JsonName(context) // This member in JSON is "context" but we need to have it as "_context". So we can use the JsonName note to encode and decode it as "context".
}
json := #string DONE
{
"value": "Hello!",
"context": {
"channel": 1,
"parent": 897820
}
}
DONE
success, message_decoded := json_parse_string(json, Message, ignore_unknown = false);
assert(success, "Could not decode message!");
log("Rename_by_note decode result:\n%\n\n", message_decoded);
assert(message_decoded._context.channel == 1);
message_encoded := json_write_string(message_decoded);
log("Rename_by_note encode result:\n%\n\n", message_encoded);
assert(message_encoded.count != 0);
assert(find_index_from_left(message_encoded, "_context") == -1);
}
custom_rename_procedure :: () {
// We can also pass a custom rename procedure for renaming certain members.
rename_to_upper :: (member: *Type_Info_Struct_Member) -> string {
return to_upper_copy(member.name,,temp);
}
player := Entity.{
name="Player",
x = 10,
y = 50
};
player_encoded := json_write_string(player, rename=rename_to_upper);
assert(player_encoded.count != 0);
log("encoded with rename_to_upper:\n%\n\n", player_encoded);
assert(find_index_from_left(player_encoded, "name") == -1);
assert(find_index_from_left(player_encoded, "NAME") != -1);
success, player_decoded := json_parse_string(player_encoded, Entity, ignore_unknown = false, rename = rename_to_upper);
assert(success);
log("decoded with rename_to_upper:\n%\n\n", player_decoded);
assert(player_decoded.name == "Player");
}
decode_into_struct_with_using :: () {
Coordinates :: struct {
x: int;
y: int;
}
Tile :: struct {
color: string;
using coordinates: Coordinates;
}
JSON :: #string END
{
"color": "green",
"x": 1,
"y": 3
}
END
success, tile := json_parse_string(JSON, Tile, ignore_unknown = false);
assert(success);
log("Decoded tile: %", tile);
assert(tile.color == "green");
assert(tile.x == 1);
assert(tile.y == 3);
}
enum_errors :: () {
// Testing error messages when parsing invalid enum values
{
LEVEL_DATA_BROKEN_JSON := #string DONE
{"kind": ".VERY_HARD"}
DONE;
success, level := json_parse_string(LEVEL_DATA_BROKEN_JSON, LevelData, ignore_unknown=false);
assert(!success);
}
{
LEVEL_DATA_BROKEN_JSON := #string DONE
{"flags": "FLAG_A | FLAG_D"}
DONE;
success, level := json_parse_string(LEVEL_DATA_BROKEN_JSON, LevelData, ignore_unknown=false);
assert(!success);
}
}
#import "Basic";
#import "Hash_Table"; // To be able to iterate over node.object
#import "String"; // For to_upper_copy
#import "Compiler"; // For Type_Info_Struct_Member
Hash_Table :: #import "Hash_Table";