392 lines
10 KiB
Plaintext
392 lines
10 KiB
Plaintext
#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";
|