v/vlib/v/gen/c/json.v

569 lines
19 KiB
V

// Copyright (c) 2019-2022 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module c
import v.ast
import v.util
import strings
// TODO replace with comptime code generation.
// TODO remove cJSON dependency.
// OLD: User decode_User(string js) {
// now it's
// User decode_User(cJSON* root) {
// User res;
// res.name = decode_string(js_get(root, "name"));
// res.profile = decode_Profile(js_get(root, "profile"));
// return res;
// }
// Codegen json_decode/encode funcs
fn (mut g Gen) gen_json_for_type(typ ast.Type) {
utyp := g.unwrap_generic(typ).set_nr_muls(0)
sym := g.table.sym(utyp)
if is_js_prim(sym.name) || sym.kind == .enum_ {
return
}
g.json_types << utyp
}
fn (mut g Gen) gen_jsons() {
mut done := []ast.Type{}
for i := 0; i < g.json_types.len; i++ {
utyp := g.json_types[i]
if utyp in done {
continue
}
done << utyp
mut dec := strings.new_builder(100)
mut enc := strings.new_builder(100)
sym := g.table.sym(utyp)
styp := g.typ(utyp)
g.register_optional(utyp)
// println('gen_json_for_type($sym.name)')
// decode_TYPE funcs receive an actual cJSON* object to decode
// cJSON_Parse(str) call is added by the compiler
// Code gen decoder
dec_fn_name := js_dec_name(styp)
dec_fn_dec := 'Option_$styp ${dec_fn_name}(cJSON* root)'
mut init_styp := '$styp res'
if sym.kind == .struct_ {
mut skips := 0
info := sym.info as ast.Struct
for field in info.fields {
for attr in field.attrs {
if attr.name == 'skip' {
skips++
}
}
}
if skips > 0 {
init_styp += ' = '
init_styp += g.expr_string(ast.Expr(ast.StructInit{
typ: utyp
typ_str: styp
}))
}
}
dec.writeln('
$dec_fn_dec {
$init_styp;
if (!root) {
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL) {
const size_t error_pos = cJSON_GetErrorPos();
int maxcontext_chars = 30;
byte *buf = vcalloc_noscan(maxcontext_chars + 10);
if(error_pos > 0) {
int backlines = 1;
int backchars = error_pos < maxcontext_chars-7 ? (int)error_pos : maxcontext_chars-7 ;
char *prevline_ptr = (char*)error_ptr;
while(backchars--){
char prevc = *(prevline_ptr - 1);
if(0==prevc){
break;
}
if(10==prevc && !backlines--){
break;
}
prevline_ptr--;
if(123==prevc) {
break; // stop at `{` too
}
}
int maxchars = vstrlen_char(prevline_ptr);
vmemcpy(buf, prevline_ptr, (maxchars < maxcontext_chars ? maxchars : maxcontext_chars));
}
return (Option_$styp){.state = 2,.err = _v_error(tos2(buf)),.data = {0}};
}
}
')
g.json_forward_decls.writeln('$dec_fn_dec;')
// Code gen encoder
// encode_TYPE funcs receive an object to encode
enc_fn_name := js_enc_name(styp)
enc_fn_dec := 'cJSON* ${enc_fn_name}($styp val)'
g.json_forward_decls.writeln('$enc_fn_dec;\n')
enc.writeln('
$enc_fn_dec {
\tcJSON *o;')
if sym.kind == .array {
// Handle arrays
value_type := g.table.value_type(utyp)
// If we have `[]Profile`, have to register a Profile en(de)coder first
g.gen_json_for_type(value_type)
dec.writeln(g.decode_array(value_type))
enc.writeln(g.encode_array(value_type))
// enc += g.encode_array(t)
} else if sym.kind == .map {
// Handle maps
m := sym.info as ast.Map
g.gen_json_for_type(m.key_type)
g.gen_json_for_type(m.value_type)
dec.writeln(g.decode_map(m.key_type, m.value_type))
enc.writeln(g.encode_map(m.key_type, m.value_type))
} else if sym.kind == .alias {
a := sym.info as ast.Alias
parent_typ := a.parent_type
psym := g.table.sym(parent_typ)
if is_js_prim(g.typ(parent_typ)) {
g.gen_json_for_type(parent_typ)
continue
}
enc.writeln('\to = cJSON_CreateObject();')
if psym.info is ast.Struct {
g.gen_struct_enc_dec(psym.info, styp, mut enc, mut dec)
} else if psym.kind == .sum_type {
verror('json: $sym.name aliased sumtypes does not work at the moment')
} else {
verror('json: $sym.name is not struct')
}
} else if sym.kind == .sum_type {
enc.writeln('\to = cJSON_CreateObject();')
// Sumtypes. Range through variants of sumtype
if sym.info !is ast.SumType {
verror('json: $sym.name is not a sumtype')
}
g.gen_sumtype_enc_dec(sym, mut enc, mut dec)
} else {
enc.writeln('\to = cJSON_CreateObject();')
// Structs. Range through fields
if sym.info !is ast.Struct {
verror('json: $sym.name is not struct')
}
g.gen_struct_enc_dec(sym.info, styp, mut enc, mut dec)
}
// cJSON_delete
// p.cgen.fns << '$dec return opt_ok(res); \n}'
dec.writeln('\tOption_$styp ret;')
dec.writeln('\topt_ok(&res, (Option*)&ret, sizeof(res));')
dec.writeln('\treturn ret;\n}')
enc.writeln('\treturn o;\n}')
g.definitions.writeln(dec.str())
g.gowrappers.writeln(enc.str())
}
}
[inline]
fn (mut g Gen) gen_sumtype_enc_dec(sym ast.TypeSymbol, mut enc strings.Builder, mut dec strings.Builder) {
info := sym.info as ast.SumType
type_var := g.new_tmp_var()
typ := g.table.type_idxs[sym.name]
// DECODING (inline)
$if !json_no_inline_sumtypes ? {
type_tmp := g.new_tmp_var()
dec.writeln('\tif (cJSON_IsObject(root)) {')
dec.writeln('\t\tcJSON* $type_tmp = js_get(root, "_type");')
dec.writeln('\t\tif ($type_tmp != 0) {')
dec.writeln('\t\t\tchar* $type_var = cJSON_GetStringValue($type_tmp);')
// dec.writeln('\t\t\tcJSON_DeleteItemFromObjectCaseSensitive(root, "_type");')
}
mut variant_types := []string{}
mut variant_symbols := []ast.TypeSymbol{}
mut at_least_one_prim := false
for variant in info.variants {
variant_typ := g.typ(variant)
variant_types << variant_typ
variant_sym := g.table.sym(variant)
variant_symbols << variant_sym
at_least_one_prim = at_least_one_prim || is_js_prim(variant_typ)
|| variant_sym.kind == .enum_ || variant_sym.name == 'time.Time'
unmangled_variant_name := variant_sym.name.split('.').last()
// TODO: Do not generate dec/enc for 'time.Time', because we handle it by saving it as u64
g.gen_json_for_type(variant)
// Helpers for decoding
g.get_sumtype_casting_fn(variant, typ)
g.definitions.writeln('static inline $sym.cname ${variant_typ}_to_sumtype_${sym.cname}($variant_typ* x);')
// ENCODING
enc.writeln('\tif (val._typ == $variant.idx()) {')
$if json_no_inline_sumtypes ? {
if variant_sym.kind == .enum_ {
enc.writeln('\t\tcJSON_AddItemToObject(o, "$unmangled_variant_name", ${js_enc_name('u64')}(*val._$variant_typ));')
} else if variant_sym.name == 'time.Time' {
enc.writeln('\t\tcJSON_AddItemToObject(o, "$unmangled_variant_name", ${js_enc_name('i64')}(val._$variant_typ->_v_unix));')
} else {
enc.writeln('\t\tcJSON_AddItemToObject(o, "$unmangled_variant_name", ${js_enc_name(variant_typ)}(*val._$variant_typ));')
}
} $else {
if is_js_prim(variant_typ) {
enc.writeln('\t\to = ${js_enc_name(variant_typ)}(*val._$variant_typ);')
} else if variant_sym.kind == .enum_ {
enc.writeln('\t\to = ${js_enc_name('u64')}(*val._$variant_typ);')
} else if variant_sym.name == 'time.Time' {
enc.writeln('\t\tcJSON_AddItemToObject(o, "_type", cJSON_CreateString("$unmangled_variant_name"));')
enc.writeln('\t\tcJSON_AddItemToObject(o, "value", ${js_enc_name('i64')}(val._$variant_typ->_v_unix));')
} else {
enc.writeln('\t\to = ${js_enc_name(variant_typ)}(*val._$variant_typ);')
enc.writeln('\t\tcJSON_AddItemToObject(o, "_type", cJSON_CreateString("$unmangled_variant_name"));')
}
}
enc.writeln('\t}')
// DECODING
tmp := g.new_tmp_var()
$if json_no_inline_sumtypes ? {
dec.writeln('\tif (strcmp("$unmangled_variant_name", root->child->string) == 0) {')
if is_js_prim(variant_typ) {
gen_js_get(variant_typ, tmp, unmangled_variant_name, mut dec, true)
dec.writeln('\t\t$variant_typ value = ${js_dec_name(variant_typ)}(jsonroot_$tmp);')
} else if variant_sym.kind == .enum_ {
gen_js_get(variant_typ, tmp, unmangled_variant_name, mut dec, true)
dec.writeln('\t\t$variant_typ value = ${js_dec_name('u64')}(jsonroot_$tmp);')
} else if variant_sym.name == 'time.Time' {
gen_js_get(variant_typ, tmp, unmangled_variant_name, mut dec, true)
dec.writeln('\t\t$variant_typ value = time__unix(${js_dec_name('i64')}(jsonroot_$tmp));')
} else {
gen_js_get_opt(js_dec_name(variant_typ), variant_typ, sym.cname, tmp,
unmangled_variant_name, mut dec, true)
dec.writeln('\t\t$variant_typ value = *($variant_typ*)(${tmp}.data);')
}
dec.writeln('\t\tres = ${variant_typ}_to_sumtype_${sym.cname}(&value);')
dec.writeln('\t}')
} $else {
if variant_sym.name == 'time.Time' {
dec.writeln('\t\t\tif (strcmp("Time", $type_var) == 0) {')
gen_js_get(sym.cname, tmp, 'value', mut dec, true)
dec.writeln('\t\t\t\t$variant_typ $tmp = time__unix(${js_dec_name('i64')}(jsonroot_$tmp));')
dec.writeln('\t\t\t\tres = ${variant_typ}_to_sumtype_${sym.cname}(&$tmp);')
dec.writeln('\t\t\t}')
} else if !is_js_prim(variant_typ) && variant_sym.kind != .enum_ {
dec.writeln('\t\t\tif (strcmp("$unmangled_variant_name", $type_var) == 0) {')
dec.writeln('\t\t\t\tOption_$variant_typ $tmp = ${js_dec_name(variant_typ)}(root);')
dec.writeln('\t\t\t\tif (${tmp}.state != 0) {')
dec.writeln('\t\t\t\t\treturn (Option_$sym.cname){ .state = ${tmp}.state, .err = ${tmp}.err, .data = {0} };')
dec.writeln('\t\t\t\t}')
dec.writeln('\t\t\t\tres = ${variant_typ}_to_sumtype_${sym.cname}(($variant_typ*)${tmp}.data);')
dec.writeln('\t\t\t}')
}
}
}
// DECODING (inline)
$if !json_no_inline_sumtypes ? {
dec.writeln('\t\t}')
mut number_is_met := false
mut string_is_met := false
mut last_number_type := ''
if at_least_one_prim {
dec.writeln('\t} else {')
if 'bool' in variant_types {
var_t := 'bool'
dec.writeln('\t\tif (cJSON_IsBool(root)) {')
dec.writeln('\t\t\t$var_t value = ${js_dec_name(var_t)}(root);')
dec.writeln('\t\t\tres = ${var_t}_to_sumtype_${sym.cname}(&value);')
dec.writeln('\t\t}')
}
for i, var_t in variant_types {
if variant_symbols[i].kind == .enum_ {
if number_is_met {
var_num := var_t.replace('__', '.')
last_num := last_number_type.replace('__', '.')
verror('json: can not decode `$sym.name` sumtype, too many numeric types (conflict of `$last_num` and `$var_num`), you can try to use alias for `$var_num` or compile v with `json_no_inline_sumtypes` flag')
}
number_is_met = true
last_number_type = var_t
dec.writeln('\t\tif (cJSON_IsNumber(root)) {')
dec.writeln('\t\t\t$var_t value = ${js_dec_name('u64')}(root);')
dec.writeln('\t\t\tres = ${var_t}_to_sumtype_${sym.cname}(&value);')
dec.writeln('\t\t}')
}
if var_t in ['string', 'rune'] {
if string_is_met {
var_num := var_t.replace('__', '.')
verror('json: can not decode `$sym.name` sumtype, too many string types (conflict of `string` and `rune`), you can try to use alias for `$var_num` or compile v with `json_no_inline_sumtypes` flag')
}
string_is_met = true
dec.writeln('\t\tif (cJSON_IsString(root)) {')
dec.writeln('\t\t\t$var_t value = ${js_dec_name(var_t)}(root);')
dec.writeln('\t\t\tres = ${var_t}_to_sumtype_${sym.cname}(&value);')
dec.writeln('\t\t}')
}
if var_t in ['i64', 'int', 'i8', 'u64', 'u32', 'u16', 'byte', 'u8', 'rune', 'f64',
'f32'] {
if number_is_met {
var_num := var_t.replace('__', '.')
last_num := last_number_type.replace('__', '.')
verror('json: can not decode `$sym.name` sumtype, too many numeric types (conflict of `$last_num` and `$var_num`), you can try to use alias for `$var_num` or compile v with `json_no_inline_sumtypes` flag')
}
number_is_met = true
last_number_type = var_t
dec.writeln('\t\tif (cJSON_IsNumber(root)) {')
dec.writeln('\t\t\t$var_t value = ${js_dec_name(var_t)}(root);')
dec.writeln('\t\t\tres = ${var_t}_to_sumtype_${sym.cname}(&value);')
dec.writeln('\t\t}')
}
}
}
dec.writeln('\t}')
}
}
[inline]
fn (mut g Gen) gen_struct_enc_dec(type_info ast.TypeInfo, styp string, mut enc strings.Builder, mut dec strings.Builder) {
info := type_info as ast.Struct
for field in info.fields {
mut name := field.name
mut is_raw := false
mut is_skip := false
mut is_required := false
mut is_omit_empty := false
for attr in field.attrs {
match attr.name {
'json' {
name = attr.arg
}
'skip' {
is_skip = true
}
'raw' {
is_raw = true
}
'required' {
is_required = true
}
'omitempty' {
is_omit_empty = true
}
else {}
}
}
if is_skip {
continue
}
field_type := g.typ(field.typ)
field_sym := g.table.sym(field.typ)
// First generate decoding
if is_raw {
dec.writeln('\tres.${c_name(field.name)} = tos5(cJSON_PrintUnformatted(' +
'js_get(root, "$name")));')
} else {
// Now generate decoders for all field types in this struct
// need to do it here so that these functions are generated first
g.gen_json_for_type(field.typ)
dec_name := js_dec_name(field_type)
if is_js_prim(field_type) {
tmp := g.new_tmp_var()
gen_js_get(styp, tmp, name, mut dec, is_required)
dec.writeln('\tres.${c_name(field.name)} = $dec_name (jsonroot_$tmp);')
} else if field_sym.kind == .enum_ {
tmp := g.new_tmp_var()
gen_js_get(styp, tmp, name, mut dec, is_required)
dec.writeln('\tres.${c_name(field.name)} = json__decode_u64(jsonroot_$tmp);')
} else if field_sym.name == 'time.Time' {
// time struct requires special treatment
// it has to be decoded from a unix timestamp number
tmp := g.new_tmp_var()
gen_js_get(styp, tmp, name, mut dec, is_required)
dec.writeln('\tres.${c_name(field.name)} = time__unix(json__decode_u64(jsonroot_$tmp));')
} else if field_sym.kind == .alias {
alias := field_sym.info as ast.Alias
parent_type := g.typ(alias.parent_type)
parent_dec_name := js_dec_name(parent_type)
if is_js_prim(parent_type) {
tmp := g.new_tmp_var()
gen_js_get(styp, tmp, name, mut dec, is_required)
dec.writeln('\tres.${c_name(field.name)} = $parent_dec_name (jsonroot_$tmp);')
} else {
g.gen_json_for_type(field.typ)
tmp := g.new_tmp_var()
gen_js_get_opt(dec_name, field_type, styp, tmp, name, mut dec, is_required)
dec.writeln('\tres.${c_name(field.name)} = *($field_type*) ${tmp}.data;')
}
} else {
tmp := g.new_tmp_var()
gen_js_get_opt(dec_name, field_type, styp, tmp, name, mut dec, is_required)
dec.writeln('\tres.${c_name(field.name)} = *($field_type*) ${tmp}.data;')
}
}
// Encoding
mut enc_name := js_enc_name(field_type)
if is_omit_empty {
enc.writeln('\t if (val.${c_name(field.name)} != ${g.type_default(field.typ)}) \n')
}
if !is_js_prim(field_type) {
if field_sym.kind == .alias {
ainfo := field_sym.info as ast.Alias
enc_name = js_enc_name(g.typ(ainfo.parent_type))
}
}
if field_sym.kind == .enum_ {
enc.writeln('\tcJSON_AddItemToObject(o, "$name", json__encode_u64(val.${c_name(field.name)}));\n')
} else {
if field_sym.name == 'time.Time' {
// time struct requires special treatment
// it has to be encoded as a unix timestamp number
enc.writeln('\tcJSON_AddItemToObject(o, "$name", json__encode_u64(val.${c_name(field.name)}._v_unix));')
} else {
enc.writeln('\tcJSON_AddItemToObject(o, "$name", ${enc_name}(val.${c_name(field.name)}));\n')
}
}
}
}
fn gen_js_get(styp string, tmp string, name string, mut dec strings.Builder, is_required bool) {
dec.writeln('\tcJSON *jsonroot_$tmp = js_get(root,"$name");')
if is_required {
dec.writeln('\tif(jsonroot_$tmp == 0) {')
dec.writeln('\t\treturn (Option_$styp){ .state = 2, .err = _v_error(_SLIT("expected field \'$name\' is missing")), .data = {0} };')
dec.writeln('\t}')
}
}
fn gen_js_get_opt(dec_name string, field_type string, styp string, tmp string, name string, mut dec strings.Builder, is_required bool) {
gen_js_get(styp, tmp, name, mut dec, is_required)
dec.writeln('\tOption_$field_type $tmp = $dec_name (jsonroot_$tmp);')
dec.writeln('\tif(${tmp}.state != 0) {')
dec.writeln('\t\treturn (Option_$styp){ .state = ${tmp}.state, .err = ${tmp}.err, .data = {0} };')
dec.writeln('\t}')
}
fn js_enc_name(typ string) string {
suffix := if typ.ends_with('*') { typ.replace('*', '') } else { typ }
name := 'json__encode_$suffix'
return util.no_dots(name)
}
fn js_dec_name(typ string) string {
name := 'json__decode_$typ'
return util.no_dots(name)
}
fn is_js_prim(typ string) bool {
return typ in ['int', 'rune', 'string', 'bool', 'f32', 'f64', 'i8', 'i16', 'i64', 'u8', 'u16',
'u32', 'u64', 'byte']
}
fn (mut g Gen) decode_array(value_type ast.Type) string {
styp := g.typ(value_type)
fn_name := js_dec_name(styp)
mut s := ''
if is_js_prim(styp) {
s = '$styp val = ${fn_name}((cJSON *)jsval); '
} else {
s = '
Option_$styp val2 = $fn_name ((cJSON *)jsval);
if(val2.state != 0) {
array_free(&res);
return *(Option_Array_$styp*)&val2;
}
$styp val = *($styp*)val2.data;
'
}
noscan := g.check_noscan(value_type)
return '
if(root && !cJSON_IsArray(root) && !cJSON_IsNull(root)) {
return (Option_Array_$styp){.state = 2, .err = _v_error(string__plus(_SLIT("Json element is not an array: "), tos2((byteptr)cJSON_PrintUnformatted(root)))), .data = {0}};
}
res = __new_array${noscan}(0, 0, sizeof($styp));
const cJSON *jsval = NULL;
cJSON_ArrayForEach(jsval, root)
{
$s
array_push${noscan}((array*)&res, &val);
}
'
}
fn (mut g Gen) encode_array(value_type ast.Type) string {
styp := g.typ(value_type)
fn_name := js_enc_name(styp)
return '
o = cJSON_CreateArray();
for (int i = 0; i < val.len; i++){
cJSON_AddItemToArray(o, $fn_name ( (($styp*)val.data)[i] ));
}
'
}
fn (mut g Gen) decode_map(key_type ast.Type, value_type ast.Type) string {
styp := g.typ(key_type)
styp_v := g.typ(value_type)
key_type_symbol := g.table.sym(key_type)
hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_type_symbol)
fn_name_v := js_dec_name(styp_v)
mut s := ''
if is_js_prim(styp_v) {
s = '$styp_v val = $fn_name_v (js_get(root, jsval->string));'
} else {
s = '
Option_$styp_v val2 = $fn_name_v (js_get(root, jsval->string));
if(val2.state != 0) {
map_free(&res);
return *(Option_Map_${styp}_$styp_v*)&val2;
}
$styp_v val = *($styp_v*)val2.data;
'
}
return '
if(!cJSON_IsObject(root) && !cJSON_IsNull(root)) {
return (Option_Map_${styp}_$styp_v){ .state = 2, .err = _v_error(string__plus(_SLIT("Json element is not an object: "), tos2((byteptr)cJSON_PrintUnformatted(root)))), .data = {0}};
}
res = new_map(sizeof($styp), sizeof($styp_v), $hash_fn, $key_eq_fn, $clone_fn, $free_fn);
cJSON *jsval = NULL;
cJSON_ArrayForEach(jsval, root)
{
$s
string key = tos2((byteptr)jsval->string);
map_set(&res, &key, &val);
}
'
}
fn (mut g Gen) encode_map(key_type ast.Type, value_type ast.Type) string {
styp := g.typ(key_type)
styp_v := g.typ(value_type)
fn_name_v := js_enc_name(styp_v)
zero := g.type_default(value_type)
keys_tmp := g.new_tmp_var()
mut key := 'string key = '
if key_type.is_string() {
key += '(($styp*)${keys_tmp}.data)[i];'
} else {
// key += '${styp}_str((($styp*)${keys_tmp}.data)[i]);'
verror('json: encode only maps with string keys')
}
return '
o = cJSON_CreateObject();
Array_$styp $keys_tmp = map_keys(&val);
for (int i = 0; i < ${keys_tmp}.len; ++i) {
$key
cJSON_AddItemToObject(o, (char*) key.str, $fn_name_v ( *($styp_v*) map_get(&val, &key, &($styp_v[]) { $zero } ) ) );
}
array_free(&$keys_tmp);
'
}