summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/data/json_config.zc191
-rw-r--r--src/codegen/codegen_decl.c13
-rw-r--r--src/codegen/codegen_stmt.c2
-rw-r--r--src/parser/parser_core.c215
-rw-r--r--src/parser/parser_stmt.c24
-rw-r--r--std/json.zc203
6 files changed, 574 insertions, 74 deletions
diff --git a/examples/data/json_config.zc b/examples/data/json_config.zc
index ccfb1a2..f6283b2 100644
--- a/examples/data/json_config.zc
+++ b/examples/data/json_config.zc
@@ -5,15 +5,21 @@ import "std/map.zc"
import "std/option.zc"
import "std/string.zc"
import "std/core.zc"
+import "std/vec.zc"
-raw {
- typedef struct JsonValue* JsonValuePtr;
+@derive(FromJson, ToJson)
+struct Features {
+ logging: bool;
+ metrics: bool;
}
+@derive(FromJson, ToJson)
struct Config {
server_name: String;
port: int;
- logging: bool;
+ max_connections: int;
+ features: Features;
+ allowed_hosts: Vec<String>;
}
fn main() {
@@ -24,76 +30,135 @@ fn main() {
!"Failed to read config file: {content_res.err}";
return 1;
}
-
- var json_str = content_res.unwrap();
- var json_res = JsonValue::parse(json_str.c_str());
- if json_res.is_err() {
- !"JSON Parse Error: {json_res.err}";
+ var json_str = content_res.unwrap();
+ var parse_res = JsonValue::parse(json_str.c_str());
+ if parse_res.is_err() {
+ !"JSON Parse Error";
json_str.free();
return 1;
}
-
- var root = json_res.unwrap();
-
- defer {
- json_str.free();
- JsonValue::free(root);
- free(root);
- }
-
- if (*root).kind.tag != JsonType::JSON_OBJECT().tag {
- !"Expected JSON Object at root";
- return 1;
- }
-
- var config = Config {
- server_name: String::new("Unknown"),
- port: 0,
- logging: false
- };
-
- var obj_map = (*root).object_val;
-
- if Map<JsonValue*>::contains(obj_map, "server_name") {
- var opt = Map<JsonValue*>::get(obj_map, "server_name");
- var val = opt.unwrap();
- if (*val).kind.tag == JsonType::JSON_STRING().tag {
- config.server_name.free();
- config.server_name = String::new((*val).string_val);
- }
- }
-
- if Map<JsonValue*>::contains(obj_map, "port") {
- var opt = Map<JsonValue*>::get(obj_map, "port");
- var val = opt.unwrap();
- if (*val).kind.tag == JsonType::JSON_NUMBER().tag {
- config.port = (int)(*val).number_val;
- }
+
+ var root = parse_res.unwrap();
+
+ // ============================================
+ // Demo 1: Using accessor methods
+ // ============================================
+ "=== Accessor methods ===";
+
+ var server_opt = (*root).get_string("server_name");
+ var server = server_opt.unwrap_or("default");
+
+ var port_opt = (*root).get_int("port");
+ var port = port_opt.unwrap_or(8080);
+
+ var max_conn_opt = (*root).get_int("max_connections");
+ var max_conn = max_conn_opt.unwrap_or(10);
+
+ var logging = false;
+ var metrics = false;
+ var features_opt = (*root).get_object("features");
+ if features_opt.is_some() {
+ var features = features_opt.unwrap();
+ var logging_opt = features.get_bool("logging");
+ logging = logging_opt.unwrap_or(false);
+ var metrics_opt = features.get_bool("metrics");
+ metrics = metrics_opt.unwrap_or(false);
}
-
- if Map<JsonValue*>::contains(obj_map, "features") {
- var opt = Map<JsonValue*>::get(obj_map, "features");
- var features = opt.unwrap();
- if (*features).kind.tag == JsonType::JSON_OBJECT().tag {
- var f_obj = (*features).object_val;
- if Map<JsonValue*>::contains(f_obj, "logging") {
- var l_opt = Map<JsonValue*>::get(f_obj, "logging");
- var l = l_opt.unwrap();
- if (*l).kind.tag == JsonType::JSON_BOOL().tag {
- config.logging = (*l).bool_val;
+
+ "Server: {server}";
+ "Port: {port}";
+ "Max Connections: {max_conn}";
+ "Logging: {logging}";
+ "Metrics: {metrics}";
+
+ // Reading array: allowed_hosts
+ var hosts_opt = (*root).get_array("allowed_hosts");
+ if hosts_opt.is_some() {
+ var hosts = hosts_opt.unwrap();
+ var count = hosts.len();
+ "Allowed Hosts: ({count} entries)";
+ for var i: usize = 0; i < count; i = i + 1 {
+ var host_opt = hosts.at(i);
+ if host_opt.is_some() {
+ var host_val = host_opt.unwrap();
+ var host_str = (*host_val).as_string();
+ if host_str.is_some() {
+ var h = host_str.unwrap();
+ " - {h}";
}
}
}
}
-
- "Configuration Loaded:";
+
+ // ============================================
+ // Demo 2: from_json / to_json
+ // ============================================
+ "";
+ "=== @derive(FromJson, ToJson) ===";
+
+ var config_res = Config::from_json(root);
+ var config = config_res.unwrap();
+
var s_name = config.server_name.c_str();
- "Server: {s_name}";
- "Port: {config.port}";
- "Logging: {config.logging}";
-
+ "Config from JSON:";
+ " server_name: {s_name}";
+ " port: {config.port}";
+ " max_connections: {config.max_connections}";
+ " features.logging: {config.features.logging}";
+ " features.metrics: {config.features.metrics}";
+ var hosts_count = config.allowed_hosts.length();
+ " allowed_hosts: ({hosts_count} entries)";
+ for var i: usize = 0; i < hosts_count; i = i + 1 {
+ var host = config.allowed_hosts.get(i).c_str();
+ " - {host}";
+ }
+
+ // Serialize back to JSON
+ var json_out = config.to_json();
+
+ var out_name_opt = json_out.get_string("server_name");
+ var out_name = out_name_opt.unwrap_or("?");
+
+ var out_port_opt = json_out.get_int("port");
+ var out_port = out_port_opt.unwrap_or(0);
+
+ var out_features_opt = json_out.get_object("features");
+ var out_logging = false;
+ var out_metrics = false;
+ if out_features_opt.is_some() {
+ var out_features = out_features_opt.unwrap();
+ var logging_opt = out_features.get_bool("logging");
+ out_logging = logging_opt.unwrap_or(false);
+ var metrics_opt = out_features.get_bool("metrics");
+ out_metrics = metrics_opt.unwrap_or(false);
+ }
+
+ // Check allowed_hosts in serialized output
+ var out_hosts_opt = json_out.get_array("allowed_hosts");
+ var out_hosts_count: usize = 0;
+ if out_hosts_opt.is_some() {
+ out_hosts_count = out_hosts_opt.unwrap().len();
+ }
+
+ "";
+ "Round-trip to_json:";
+ " server_name: {out_name}";
+ " port: {out_port}";
+ " features.logging: {out_logging}";
+ " features.metrics: {out_metrics}";
+ " allowed_hosts: {out_hosts_count} entries";
+
+ // Cleanup
config.server_name.free();
-
+ for var i: usize = 0; i < config.allowed_hosts.length(); i = i + 1 {
+ config.allowed_hosts.get(i).free();
+ }
+ config.allowed_hosts.free();
+ json_out.free();
+ json_str.free();
+ JsonValue::free(root);
+ free(root);
+
return 0;
}
diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c
index d525963..e5d73a0 100644
--- a/src/codegen/codegen_decl.c
+++ b/src/codegen/codegen_decl.c
@@ -22,13 +22,15 @@ static void emit_freestanding_preamble(FILE *out)
"uint64_t\n",
out);
fputs("#define F32 float\n#define F64 double\n", out);
- fputs("#define _z_str(x) _Generic((x), _Bool: \"%d\", char: \"%c\", "
+ fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : \"false\"; }\n", out);
+ fputs("#define _z_str(x) _Generic((x), _Bool: \"%s\", char: \"%c\", "
"signed char: \"%c\", unsigned char: \"%u\", short: \"%d\", "
"unsigned short: \"%u\", int: \"%d\", unsigned int: \"%u\", "
"long: \"%ld\", unsigned long: \"%lu\", long long: \"%lld\", "
"unsigned long long: \"%llu\", float: \"%f\", double: \"%f\", "
"char*: \"%s\", void*: \"%p\")\n",
out);
+ fputs("#define _z_arg(x) _Generic((x), _Bool: _z_bool_str(x), default: (x))\n", out);
fputs("typedef struct { void *func; void *ctx; } z_closure_T;\n", out);
fputs("__attribute__((weak)) void* z_malloc(usize sz) { return NULL; }\n", out);
@@ -62,7 +64,10 @@ void emit_preamble(ParserContext *ctx, FILE *out)
fputs("#define ZC_AUTO auto\n", out);
fputs("#define ZC_CAST(T, x) static_cast<T>(x)\n", out);
// C++ _z_str via overloads
- fputs("inline const char* _z_str(bool) { return \"%d\"; }\n", out);
+ fputs("inline const char* _z_bool_str(bool b) { return b ? \"true\" : \"false\"; }\n", out);
+ fputs("inline const char* _z_str(bool) { return \"%s\"; }\n", out);
+ fputs("inline const char* _z_arg(bool b) { return _z_bool_str(b); }\n", out);
+ fputs("template<typename T> inline T _z_arg(T x) { return x; }\n", out);
fputs("inline const char* _z_str(char) { return \"%c\"; }\n", out);
fputs("inline const char* _z_str(int) { return \"%d\"; }\n", out);
fputs("inline const char* _z_str(unsigned int) { return \"%u\"; }\n", out);
@@ -82,13 +87,15 @@ void emit_preamble(ParserContext *ctx, FILE *out)
fputs("#define ZC_AUTO __auto_type\n", out);
fputs("#define ZC_CAST(T, x) ((T)(x))\n", out);
fputs("#ifdef __TINYC__\n#define __auto_type __typeof__\n#endif\n", out);
- fputs("#define _z_str(x) _Generic((x), _Bool: \"%d\", char: \"%c\", "
+ fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : \"false\"; }\n", out);
+ fputs("#define _z_str(x) _Generic((x), _Bool: \"%s\", char: \"%c\", "
"signed char: \"%c\", unsigned char: \"%u\", short: \"%d\", "
"unsigned short: \"%u\", int: \"%d\", unsigned int: \"%u\", "
"long: \"%ld\", unsigned long: \"%lu\", long long: \"%lld\", "
"unsigned long long: \"%llu\", float: \"%f\", double: \"%f\", "
"char*: \"%s\", void*: \"%p\")\n",
out);
+ fputs("#define _z_arg(x) _Generic((x), _Bool: _z_bool_str(x), default: (x))\n", out);
}
fputs("typedef size_t usize;\ntypedef char* string;\n", out);
diff --git a/src/codegen/codegen_stmt.c b/src/codegen/codegen_stmt.c
index ff8ea46..cba55b4 100644
--- a/src/codegen/codegen_stmt.c
+++ b/src/codegen/codegen_stmt.c
@@ -1519,7 +1519,7 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out)
emit_auto_type(ctx, node->repl_print.expr, node->token, out);
fprintf(out, " _zval = (");
codegen_expression(ctx, node->repl_print.expr, out);
- fprintf(out, "); fprintf(stdout, _z_str(_zval), _zval); fprintf(stdout, "
+ fprintf(out, "); fprintf(stdout, _z_str(_zval), _z_arg(_zval)); fprintf(stdout, "
"\"\\n\"); }\n");
break;
}
diff --git a/src/parser/parser_core.c b/src/parser/parser_core.c
index 7468bfb..3bc6d66 100644
--- a/src/parser/parser_core.c
+++ b/src/parser/parser_core.c
@@ -637,6 +637,221 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char *
code = xmalloc(1024);
sprintf(code, "impl Copy for %s {}", name);
}
+ else if (0 == strcmp(trait, "FromJson"))
+ {
+ // Generate from_json(j: JsonValue*) -> Result<StructName>
+ // Only works for structs (not enums)
+ if (strct->type != NODE_STRUCT)
+ {
+ zwarn_at(strct->token, "@derive(FromJson) only works on structs");
+ continue;
+ }
+
+ char body[8192];
+ body[0] = 0;
+
+ // Track Vec<String> fields for forget calls
+ char *vec_fields[32];
+ int vec_field_count = 0;
+
+ // Build field assignments
+ ASTNode *f = strct->strct.fields;
+ while (f)
+ {
+ if (f->type == NODE_FIELD)
+ {
+ char *fn = f->field.name;
+ char *ft = f->field.type;
+ char assign[2048];
+
+ if (!fn || !ft)
+ {
+ f = f->next;
+ continue;
+ }
+
+ // Map types to appropriate get_* calls
+ if (strcmp(ft, "int") == 0)
+ {
+ sprintf(assign, "var _f_%s = (*j).get_int(\"%s\").unwrap_or(0);\n", fn, fn);
+ }
+ else if (strcmp(ft, "double") == 0)
+ {
+ sprintf(assign, "var _f_%s = (*j).get_float(\"%s\").unwrap_or(0.0);\n", fn, fn);
+ }
+ else if (strcmp(ft, "bool") == 0)
+ {
+ sprintf(assign, "var _f_%s = (*j).get_bool(\"%s\").unwrap_or(false);\n", fn, fn);
+ }
+ else if (strcmp(ft, "char*") == 0)
+ {
+ sprintf(assign, "var _f_%s = (*j).get_string(\"%s\").unwrap_or(\"\");\n", fn, fn);
+ }
+ else if (strcmp(ft, "String") == 0)
+ {
+ sprintf(assign, "var _f_%s = String::new((*j).get_string(\"%s\").unwrap_or(\"\"));\n", fn, fn);
+ }
+ else if (ft && strstr(ft, "Vec") && strstr(ft, "String"))
+ {
+ // Track this field for forget() call later
+ if (vec_field_count < 32)
+ {
+ vec_fields[vec_field_count++] = fn;
+ }
+ sprintf(assign,
+ "var _f_%s = Vec<String>::new();\n"
+ "var _arr_%s = (*j).get_array(\"%s\");\n"
+ "if _arr_%s.is_some() {\n"
+ " var _a_%s = _arr_%s.unwrap();\n"
+ " for var _i_%s: usize = 0; _i_%s < _a_%s.len(); _i_%s = _i_%s + 1 {\n"
+ " var _item_%s = _a_%s.at(_i_%s);\n"
+ " if _item_%s.is_some() {\n"
+ " var _str_%s = (*_item_%s.unwrap()).as_string();\n"
+ " if _str_%s.is_some() {\n"
+ " var _s_%s = String::new(_str_%s.unwrap());\n"
+ " _f_%s.push(_s_%s); _s_%s.forget();\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}\n",
+ fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn);
+ }
+ else
+ {
+ // Nested struct: call NestedType::from_json recursively
+ sprintf(assign,
+ "var _opt_%s = (*j).get(\"%s\");\n"
+ "var _f_%s: %s;\n"
+ "if _opt_%s.is_some() { _f_%s = %s::from_json(_opt_%s.unwrap()).unwrap(); }\n",
+ fn, fn, fn, ft, fn, fn, ft, fn);
+ }
+ strcat(body, assign);
+ }
+ f = f->next;
+ }
+
+ // Build struct initialization
+ strcat(body, "return Result<");
+ strcat(body, name);
+ strcat(body, ">::Ok(");
+ strcat(body, name);
+ strcat(body, " { ");
+
+ f = strct->strct.fields;
+ int first = 1;
+ while (f)
+ {
+ if (f->type == NODE_FIELD)
+ {
+ if (!first) strcat(body, ", ");
+ char init[128];
+ // Check if this is a Vec<String> field - clone it to avoid double-free
+ int is_vec_field = 0;
+ for (int vi = 0; vi < vec_field_count; vi++)
+ {
+ if (strcmp(vec_fields[vi], f->field.name) == 0)
+ {
+ is_vec_field = 1;
+ break;
+ }
+ }
+ if (is_vec_field)
+ {
+ sprintf(init, "%s: _f_%s.clone()", f->field.name, f->field.name);
+ }
+ else
+ {
+ sprintf(init, "%s: _f_%s", f->field.name, f->field.name);
+ }
+ strcat(body, init);
+ first = 0;
+ }
+ f = f->next;
+ }
+ strcat(body, " }); ");
+
+ code = xmalloc(8192 + 1024);
+ sprintf(code,
+ "impl %s { fn from_json(j: JsonValue*) -> Result<%s> { %s } }",
+ name, name, body);
+ }
+ else if (0 == strcmp(trait, "ToJson"))
+ {
+ // Generate to_json(self) -> JsonValue
+ // Only works for structs (not enums)
+ if (strct->type != NODE_STRUCT)
+ {
+ zwarn_at(strct->token, "@derive(ToJson) only works on structs");
+ continue;
+ }
+
+ char body[8192];
+ strcpy(body, "var _obj = JsonValue::object();\n");
+
+ ASTNode *f = strct->strct.fields;
+ while (f)
+ {
+ if (f->type == NODE_FIELD)
+ {
+ char *fn = f->field.name;
+ char *ft = f->field.type;
+ char set_call[2048];
+
+ if (!fn || !ft)
+ {
+ f = f->next;
+ continue;
+ }
+
+ if (strcmp(ft, "int") == 0)
+ {
+ sprintf(set_call, "_obj.set(\"%s\", JsonValue::number((double)self.%s));\n", fn, fn);
+ }
+ else if (strcmp(ft, "double") == 0)
+ {
+ sprintf(set_call, "_obj.set(\"%s\", JsonValue::number(self.%s));\n", fn, fn);
+ }
+ else if (strcmp(ft, "bool") == 0)
+ {
+ sprintf(set_call, "_obj.set(\"%s\", JsonValue::bool(self.%s));\n", fn, fn);
+ }
+ else if (strcmp(ft, "char*") == 0)
+ {
+ sprintf(set_call, "_obj.set(\"%s\", JsonValue::string(self.%s));\n", fn, fn);
+ }
+ else if (strcmp(ft, "String") == 0)
+ {
+ sprintf(set_call, "_obj.set(\"%s\", JsonValue::string(self.%s.c_str()));\n", fn, fn);
+ }
+ else if (ft && strstr(ft, "Vec") && strstr(ft, "String"))
+ {
+ sprintf(set_call,
+ "var _arr_%s = JsonValue::array();\n"
+ "for var _i_%s: usize = 0; _i_%s < self.%s.length(); _i_%s = _i_%s + 1 {\n"
+ " _arr_%s.push(JsonValue::string(self.%s.get(_i_%s).c_str()));\n"
+ "}\n"
+ "_obj.set(\"%s\", _arr_%s);\n",
+ fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn);
+ }
+ else
+ {
+ // Nested struct: call to_json recursively
+ sprintf(set_call,
+ "_obj.set(\"%s\", self.%s.to_json());\n",
+ fn, fn);
+ }
+ strcat(body, set_call);
+ }
+ f = f->next;
+ }
+
+ strcat(body, "return _obj;");
+
+ code = xmalloc(8192 + 1024);
+ sprintf(code,
+ "impl %s { fn to_json(self) -> JsonValue { %s } }",
+ name, body);
+ }
if (code)
{
diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c
index 4b09c83..54e9b88 100644
--- a/src/parser/parser_stmt.c
+++ b/src/parser/parser_stmt.c
@@ -1591,10 +1591,15 @@ char *process_printf_sugar(ParserContext *ctx, const char *content, int newline,
Type *t = expr_node ? expr_node->type_info : NULL;
char *inferred_type = t ? type_to_string(t) : find_symbol_type(ctx, clean_expr);
+ int is_bool = 0;
if (inferred_type)
{
- if (strcmp(inferred_type, "int") == 0 || strcmp(inferred_type, "i32") == 0 ||
- strcmp(inferred_type, "bool") == 0)
+ if (strcmp(inferred_type, "bool") == 0)
+ {
+ format_spec = "%s";
+ is_bool = 1;
+ }
+ else if (strcmp(inferred_type, "int") == 0 || strcmp(inferred_type, "i32") == 0)
{
format_spec = "%d";
}
@@ -1657,7 +1662,16 @@ char *process_printf_sugar(ParserContext *ctx, const char *content, int newline,
strcat(gen, buf);
strcat(gen, format_spec);
strcat(gen, "\", ");
- strcat(gen, rw_expr);
+ if (is_bool)
+ {
+ strcat(gen, "_z_bool_str(");
+ strcat(gen, rw_expr);
+ strcat(gen, ")");
+ }
+ else
+ {
+ strcat(gen, rw_expr);
+ }
strcat(gen, "); ");
}
else
@@ -1667,9 +1681,9 @@ char *process_printf_sugar(ParserContext *ctx, const char *content, int newline,
sprintf(buf, "fprintf(%s, _z_str(", target);
strcat(gen, buf);
strcat(gen, rw_expr);
- strcat(gen, "), ");
+ strcat(gen, "), _z_arg(");
strcat(gen, rw_expr);
- strcat(gen, "); ");
+ strcat(gen, ")); ");
}
}
diff --git a/std/json.zc b/std/json.zc
index cfef2c3..6c4a0da 100644
--- a/std/json.zc
+++ b/std/json.zc
@@ -4,6 +4,7 @@ import "./vec.zc"
import "./map.zc"
import "./string.zc"
import "./result.zc"
+import "./option.zc"
@derive(Eq)
enum JsonType {
@@ -201,7 +202,47 @@ impl JsonValue {
*m = Map_JsonValuePtr::new();
return JsonValue { kind: JsonType::JSON_OBJECT(), string_val: 0, number_val: 0, bool_val: false, array_val: 0, object_val: m };
}
-
+
+ // ============================================
+ // Heap-allocated factory methods (returns pointers)
+ // ============================================
+
+ fn null_ptr() -> JsonValue* {
+ var p: JsonValue* = malloc(sizeof(JsonValue));
+ *p = JsonValue::null();
+ return p;
+ }
+
+ fn bool_ptr(b: bool) -> JsonValue* {
+ var p: JsonValue* = malloc(sizeof(JsonValue));
+ *p = JsonValue::bool(b);
+ return p;
+ }
+
+ fn number_ptr(n: double) -> JsonValue* {
+ var p: JsonValue* = malloc(sizeof(JsonValue));
+ *p = JsonValue::number(n);
+ return p;
+ }
+
+ fn string_ptr(s: char*) -> JsonValue* {
+ var p: JsonValue* = malloc(sizeof(JsonValue));
+ *p = JsonValue::string(s);
+ return p;
+ }
+
+ fn array_ptr() -> JsonValue* {
+ var p: JsonValue* = malloc(sizeof(JsonValue));
+ *p = JsonValue::array();
+ return p;
+ }
+
+ fn object_ptr() -> JsonValue* {
+ var p: JsonValue* = malloc(sizeof(JsonValue));
+ *p = JsonValue::object();
+ return p;
+ }
+
fn push(self, val: JsonValue) {
if (self.kind.tag != JsonType::JSON_ARRAY().tag) return;
var p: JsonValue* = malloc(sizeof(JsonValue));
@@ -223,7 +264,165 @@ impl JsonValue {
}
return Result<JsonValue*>::Err("JSON parse error");
}
-
+
+ // ============================================
+ // Type checking helpers
+ // ============================================
+
+ fn is_null(self) -> bool {
+ return self.kind.tag == JsonType::JSON_NULL().tag;
+ }
+
+ fn is_bool(self) -> bool {
+ return self.kind.tag == JsonType::JSON_BOOL().tag;
+ }
+
+ fn is_number(self) -> bool {
+ return self.kind.tag == JsonType::JSON_NUMBER().tag;
+ }
+
+ fn is_string(self) -> bool {
+ return self.kind.tag == JsonType::JSON_STRING().tag;
+ }
+
+ fn is_array(self) -> bool {
+ return self.kind.tag == JsonType::JSON_ARRAY().tag;
+ }
+
+ fn is_object(self) -> bool {
+ return self.kind.tag == JsonType::JSON_OBJECT().tag;
+ }
+
+ // ============================================
+ // Direct value extractors
+ // ============================================
+
+ fn as_string(self) -> Option<char*> {
+ if self.kind.tag == JsonType::JSON_STRING().tag {
+ return Option<char*>::Some(self.string_val);
+ }
+ return Option<char*>::None();
+ }
+
+ fn as_int(self) -> Option<int> {
+ if self.kind.tag == JsonType::JSON_NUMBER().tag {
+ return Option<int>::Some((int)self.number_val);
+ }
+ return Option<int>::None();
+ }
+
+ fn as_float(self) -> Option<double> {
+ if self.kind.tag == JsonType::JSON_NUMBER().tag {
+ return Option<double>::Some(self.number_val);
+ }
+ return Option<double>::None();
+ }
+
+ fn as_bool(self) -> Option<bool> {
+ if self.kind.tag == JsonType::JSON_BOOL().tag {
+ return Option<bool>::Some(self.bool_val);
+ }
+ return Option<bool>::None();
+ }
+
+ // ============================================
+ // Object key accessors
+ // ============================================
+
+ fn get(self, key: char*) -> Option<JsonValue*> {
+ if self.kind.tag != JsonType::JSON_OBJECT().tag {
+ return Option<JsonValue*>::None();
+ }
+ if Map<JsonValue*>::contains(self.object_val, key) {
+ return Map<JsonValue*>::get(self.object_val, key);
+ }
+ return Option<JsonValue*>::None();
+ }
+
+ fn get_string(self, key: char*) -> Option<char*> {
+ var opt = self.get(key);
+ if opt.is_none() {
+ return Option<char*>::None();
+ }
+ var val = opt.unwrap();
+ return (*val).as_string();
+ }
+
+ fn get_int(self, key: char*) -> Option<int> {
+ var opt = self.get(key);
+ if opt.is_none() {
+ return Option<int>::None();
+ }
+ var val = opt.unwrap();
+ return (*val).as_int();
+ }
+
+ fn get_float(self, key: char*) -> Option<double> {
+ var opt = self.get(key);
+ if opt.is_none() {
+ return Option<double>::None();
+ }
+ var val = opt.unwrap();
+ return (*val).as_float();
+ }
+
+ fn get_bool(self, key: char*) -> Option<bool> {
+ var opt = self.get(key);
+ if opt.is_none() {
+ return Option<bool>::None();
+ }
+ var val = opt.unwrap();
+ return (*val).as_bool();
+ }
+
+ fn get_object(self, key: char*) -> Option<JsonValue*> {
+ var opt = self.get(key);
+ if opt.is_none() {
+ return Option<JsonValue*>::None();
+ }
+ var val = opt.unwrap();
+ if (*val).kind.tag == JsonType::JSON_OBJECT().tag {
+ return Option<JsonValue*>::Some(val);
+ }
+ return Option<JsonValue*>::None();
+ }
+
+ fn get_array(self, key: char*) -> Option<JsonValue*> {
+ var opt = self.get(key);
+ if opt.is_none() {
+ return Option<JsonValue*>::None();
+ }
+ var val = opt.unwrap();
+ if (*val).kind.tag == JsonType::JSON_ARRAY().tag {
+ return Option<JsonValue*>::Some(val);
+ }
+ return Option<JsonValue*>::None();
+ }
+
+ // ============================================
+ // Array accessors
+ // ============================================
+
+ fn at(self, index: usize) -> Option<JsonValue*> {
+ if self.kind.tag != JsonType::JSON_ARRAY().tag {
+ return Option<JsonValue*>::None();
+ }
+ if index >= self.array_val.length() {
+ return Option<JsonValue*>::None();
+ }
+ return Option<JsonValue*>::Some(self.array_val.get(index));
+ }
+
+ fn len(self) -> usize {
+ if self.kind.tag == JsonType::JSON_ARRAY().tag {
+ return self.array_val.length();
+ }
+ if self.kind.tag == JsonType::JSON_OBJECT().tag {
+ return self.object_val.length();
+ }
+ return 0;
+ }
+
fn free(self) {
if (self.kind.tag == JsonType::JSON_STRING().tag) free(self.string_val);
if (self.kind.tag == JsonType::JSON_ARRAY().tag) {