mcap/modules/jaison/examples/example.jai
Musa Mahmood 811c096dd0 [0.0.8] Import: any folder of screenshots and photos into the library, a folder for each source
- Import window (and -import SOURCE DEST): every image under a folder (PNG, JPEG, HEIC, AVIF, WebP,
  GIF, BMP, TIFF, camera RAW) becomes an .mcap holding the file as it was, a preview turned by its
  EXIF orientation, and metadata.json and import.json Capture Text (date from EXIF, the file name or
  its time; file name as title; camera as app). .snagx files become the same .mcap SnagitConverter
  makes. Duplicates across the whole library are skipped by a hash index (.import-index); videos
  and sidecars are skipped; source files are only read.
- modules/Image_Decode: stb_image, then WIC for everything else; EXIF; orientation; previews.
  tools/image_probe.jai shows what it reads.
- modules/Snagx (and modules/jaison), copied from SnagitConverter.
- The library is a folder of folders: recursive scan, a folder filter, and a folder: search prefix.
- OCR, the viewer and ocr-bench decode any image format, turned the right way up.
- Rename D:\SnagitIndex to D:\McapIndex.
2026-10-09 08:15:12 +01:00

472 lines
13 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, "floats": [0.5, null]}
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;
MessageEntry :: struct {
union {
player: int;
player_v2: int;
}
union {
tag: int;
tag_v2: int;
}
name: string;
strict: bool;
union {
messagemode: string;
message_mode: string;
}
roundtrip: int;
identifier: string;
message: string;
};
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);
assert(level.floats.count == 2);
assert(is_nan(level.floats[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);
}
{
success, result := json_parse_string("{}", MessageEntry, ignore_unknown=false);
assert(success);
log("Typed parsing result 4:\n%\n\n", result);
}
}
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);
// Inf/Nan handlig:
floats := float.[
0.5,
0h7FFF_FFFF, // NaN
0hFF80_0001, // also NaN
0hFF80_0000, // -inf
0h7F80_0000, // +inf
];
json_string = json_write_string(floats, indent_char = "");
log("floats:\n%\n\n", json_string);
assert(json_string == "[0.5,null,null,null,null]");
json_string = json_write_string(floats, indent_char = "", float_handling = .SUPPORT_STRINGS);
log("floats:\n%\n\n", json_string);
assert(json_string == "[0.5,\"NaN\",\"NaN\",\"-Infinity\",\"Infinity\"]");
float64s := float64.[
0.5,
0h7FFF_FFFF_FFFF_FFFF, // NaN
0hFFF0_0000_0000_0001, // also NaN
0hFFF0_0000_0000_0000, // -inf
0h7FF0_0000_0000_0000, // +inf
];
json_string = json_write_string(float64s, indent_char = "");
log("float64s:\n%\n\n", json_string);
assert(json_string == "[0.5,null,null,null,null]");
json_string = json_write_string(float64s, indent_char = "", float_handling = .SUPPORT_STRINGS);
log("float64s:\n%\n\n", json_string);
assert(json_string == "[0.5,\"NaN\",\"NaN\",\"-Infinity\",\"Infinity\"]");
}
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(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(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);
// Inf/Nan handling
floats: [..] JSON_Value;
array_add(*floats, .{type=.NUMBER, number=0.5});
array_add(*floats, .{type=.NUMBER, number=0h7FFF_FFFF_FFFF_FFFF}); // NaN
array_add(*floats, .{type=.NUMBER, number=0hFFF0_0000_0000_0001}); // also NaN
array_add(*floats, .{type=.NUMBER, number=0hFFF0_0000_0000_0000}); // -inf
array_add(*floats, .{type=.NUMBER, number=0h7FF0_0000_0000_0000}); // +inf
array.array = floats;
json_string = json_write_string(array, indent_char = "");
log("numbers:\n%\n\n", json_string);
assert(json_string == "[0.5,null,null,null,null]");
}
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);
}
}
is_nan :: (value: float) -> bool {
v := value;
bits := (cast(*s32) *v).*;
is_special := (bits & 0x7F80_0000) == 0x7F80_0000;
mant := (bits & 0x007F_FFFF);
return (is_special && mant);
}
#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";