Skip to content

Commit c75271f

Browse files
authored
cgen: json sumtype inlining (#11961)
1 parent 430677a commit c75271f

File tree

3 files changed

+178
-29
lines changed

3 files changed

+178
-29
lines changed

vlib/json/json_primitives.v

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module json
1010

1111
struct C.cJSON {
1212
valueint int
13-
valuedouble f32
13+
valuedouble f64
1414
valuestring &char
1515
}
1616

@@ -78,6 +78,13 @@ fn decode_byte(root &C.cJSON) byte {
7878
return byte(root.valueint)
7979
}
8080

81+
fn decode_u8(root &C.cJSON) u8 {
82+
if isnil(root) {
83+
return byte(0)
84+
}
85+
return byte(root.valueint)
86+
}
87+
8188
fn decode_u16(root &C.cJSON) u16 {
8289
if isnil(root) {
8390
return u16(0)
@@ -103,14 +110,26 @@ fn decode_f32(root &C.cJSON) f32 {
103110
if isnil(root) {
104111
return f32(0)
105112
}
106-
return root.valuedouble
113+
return f32(root.valuedouble)
107114
}
108115

109116
fn decode_f64(root &C.cJSON) f64 {
110117
if isnil(root) {
111118
return f64(0)
112119
}
113-
return f64(root.valuedouble)
120+
return root.valuedouble
121+
}
122+
123+
fn decode_rune(root &C.cJSON) rune {
124+
if isnil(root) {
125+
return rune(0)
126+
}
127+
if isnil(root.valuestring) {
128+
return rune(0)
129+
}
130+
131+
// TODO: Parse as runes, bypassing string casting...?
132+
return unsafe { tos_clone(&byte(root.valuestring)).runes().first() }
114133
}
115134

116135
fn decode_string(root &C.cJSON) string {
@@ -153,6 +172,10 @@ fn encode_byte(val byte) &C.cJSON {
153172
return C.cJSON_CreateNumber(val)
154173
}
155174

175+
fn encode_u8(val u8) &C.cJSON {
176+
return C.cJSON_CreateNumber(val)
177+
}
178+
156179
fn encode_u16(val u16) &C.cJSON {
157180
return C.cJSON_CreateNumber(val)
158181
}
@@ -177,6 +200,10 @@ fn encode_bool(val bool) &C.cJSON {
177200
return C.cJSON_CreateBool(val)
178201
}
179202

203+
fn encode_rune(val rune) &C.cJSON {
204+
return C.cJSON_CreateString(&char(val.str().str))
205+
}
206+
180207
fn encode_string(val string) &C.cJSON {
181208
return C.cJSON_CreateString(&char(val.str))
182209
}

vlib/json/json_test.v

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ fn test_encode_decode_sumtype() ? {
6565
other: [
6666
Entity(Item{'Pen'}),
6767
Item{'Cookie'},
68-
Animal.dog,
68+
Animal.cat,
6969
'Stool',
7070
time.now(),
7171
]
@@ -81,6 +81,7 @@ fn test_encode_decode_sumtype() ? {
8181

8282
assert game.title == dec.title
8383
assert game.player == dec.player
84+
assert (game.other[2] as Animal) == (dec.other[2] as Animal)
8485
assert (game.other[4] as time.Time).unix_time() == (dec.other[4] as time.Time).unix_time()
8586
}
8687

vlib/v/gen/c/json.v

Lines changed: 146 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,13 @@ $enc_fn_dec {
8686
return
8787
}
8888
enc.writeln('\to = cJSON_CreateObject();')
89-
if psym.info !is ast.Struct {
89+
if psym.info is ast.Struct {
90+
g.gen_struct_enc_dec(psym.info, styp, mut enc, mut dec)
91+
} else if psym.kind == .sum_type {
92+
verror('json: $sym.name aliased sumtypes does not work at the moment')
93+
} else {
9094
verror('json: $sym.name is not struct')
9195
}
92-
g.gen_struct_enc_dec(psym.info, styp, mut enc, mut dec)
9396
} else if sym.kind == .sum_type {
9497
enc.writeln('\to = cJSON_CreateObject();')
9598
// Sumtypes. Range through variants of sumtype
@@ -118,46 +121,164 @@ $enc_fn_dec {
118121
[inline]
119122
fn (mut g Gen) gen_sumtype_enc_dec(sym ast.TypeSymbol, mut enc strings.Builder, mut dec strings.Builder) {
120123
info := sym.info as ast.SumType
124+
type_var := g.new_tmp_var()
121125
typ := sym.idx
122126

127+
// DECODING (inline)
128+
$if !json_no_inline_sumtypes ? {
129+
type_tmp := g.new_tmp_var()
130+
dec.writeln('\tif (cJSON_IsObject(root)) {')
131+
dec.writeln('\t\tcJSON* $type_tmp = js_get(root, "_type");')
132+
dec.writeln('\t\tif ($type_tmp != 0) {')
133+
dec.writeln('\t\t\tchar* $type_var = cJSON_GetStringValue($type_tmp);')
134+
// dec.writeln('\t\t\tcJSON_DeleteItemFromObjectCaseSensitive(root, "_type");')
135+
}
136+
137+
mut variant_types := []string{}
138+
mut variant_symbols := []ast.TypeSymbol{}
139+
mut at_least_one_prim := false
123140
for variant in info.variants {
124141
variant_typ := g.typ(variant)
142+
variant_types << variant_typ
125143
variant_sym := g.table.get_type_symbol(variant)
144+
variant_symbols << variant_sym
145+
at_least_one_prim = at_least_one_prim || is_js_prim(variant_typ)
146+
|| variant_sym.kind == .enum_ || variant_sym.name == 'time.Time'
126147
unmangled_variant_name := variant_sym.name.split('.').last()
127148

149+
// TODO: Do not generate dec/enc for 'time.Time', because we handle it by saving it as u64
128150
g.gen_json_for_type(variant)
151+
152+
// Helpers for decoding
129153
g.write_sumtype_casting_fn(variant, typ)
130154
g.definitions.writeln('static inline $sym.cname ${variant_typ}_to_sumtype_${sym.cname}($variant_typ* x);')
131155

132156
// ENCODING
133157
enc.writeln('\tif (val._typ == $variant) {')
134-
if variant_sym.kind == .enum_ {
135-
enc.writeln('\t\tcJSON_AddItemToObject(o, "$unmangled_variant_name", json__encode_u64(*val._$variant_typ));')
136-
} else if variant_sym.name == 'time.Time' {
137-
enc.writeln('\t\tcJSON_AddItemToObject(o, "$unmangled_variant_name", json__encode_i64(val._$variant_typ->_v_unix));')
138-
} else {
139-
enc.writeln('\t\tcJSON_AddItemToObject(o, "$unmangled_variant_name", json__encode_${variant_typ}(*val._$variant_typ));')
158+
$if json_no_inline_sumtypes ? {
159+
if variant_sym.kind == .enum_ {
160+
enc.writeln('\t\tcJSON_AddItemToObject(o, "$unmangled_variant_name", ${js_enc_name('u64')}(*val._$variant_typ));')
161+
} else if variant_sym.name == 'time.Time' {
162+
enc.writeln('\t\tcJSON_AddItemToObject(o, "$unmangled_variant_name", ${js_enc_name('i64')}(val._$variant_typ->_v_unix));')
163+
} else {
164+
enc.writeln('\t\tcJSON_AddItemToObject(o, "$unmangled_variant_name", ${js_enc_name(variant_typ)}(*val._$variant_typ));')
165+
}
166+
} $else {
167+
if is_js_prim(variant_typ) {
168+
enc.writeln('\t\to = ${js_enc_name(variant_typ)}(*val._$variant_typ);')
169+
} else if variant_sym.kind == .enum_ {
170+
enc.writeln('\t\to = ${js_enc_name('u64')}(*val._$variant_typ);')
171+
} else if variant_sym.name == 'time.Time' {
172+
enc.writeln('\t\tcJSON_AddItemToObject(o, "_type", cJSON_CreateString("$unmangled_variant_name"));')
173+
enc.writeln('\t\tcJSON_AddItemToObject(o, "value", ${js_enc_name('i64')}(val._$variant_typ->_v_unix));')
174+
} else {
175+
enc.writeln('\t\to = ${js_enc_name(variant_typ)}(*val._$variant_typ);')
176+
enc.writeln('\t\tcJSON_AddItemToObject(o, "_type", cJSON_CreateString("$unmangled_variant_name"));')
177+
}
140178
}
141179
enc.writeln('\t}')
142180

143181
// DECODING
144-
dec.writeln('\tif (strcmp("$unmangled_variant_name", root->child->string) == 0) {')
145182
tmp := g.new_tmp_var()
146-
if is_js_prim(variant_typ) {
147-
gen_js_get(variant_typ, tmp, unmangled_variant_name, mut dec, false)
148-
dec.writeln('\t\t$variant_typ value = (${js_dec_name(variant_typ)})(jsonroot_$tmp);')
149-
} else if variant_sym.kind == .enum_ {
150-
gen_js_get(variant_typ, tmp, unmangled_variant_name, mut dec, false)
151-
dec.writeln('\t\t$variant_typ value = json__decode_u64(jsonroot_$tmp);')
152-
} else if variant_sym.name == 'time.Time' {
153-
gen_js_get(variant_typ, tmp, unmangled_variant_name, mut dec, false)
154-
dec.writeln('\t\t$variant_typ value = time__unix(json__decode_i64(jsonroot_$tmp));')
155-
} else {
156-
gen_js_get_opt(js_dec_name(variant_typ), variant_typ, sym.cname, tmp, unmangled_variant_name, mut
157-
dec, false)
158-
dec.writeln('\t\t$variant_typ value = *($variant_typ*)(${tmp}.data);')
183+
$if json_no_inline_sumtypes ? {
184+
dec.writeln('\tif (strcmp("$unmangled_variant_name", root->child->string) == 0) {')
185+
if is_js_prim(variant_typ) {
186+
gen_js_get(variant_typ, tmp, unmangled_variant_name, mut dec, true)
187+
dec.writeln('\t\t$variant_typ value = ${js_dec_name(variant_typ)}(jsonroot_$tmp);')
188+
} else if variant_sym.kind == .enum_ {
189+
gen_js_get(variant_typ, tmp, unmangled_variant_name, mut dec, true)
190+
dec.writeln('\t\t$variant_typ value = ${js_dec_name('u64')}(jsonroot_$tmp);')
191+
} else if variant_sym.name == 'time.Time' {
192+
gen_js_get(variant_typ, tmp, unmangled_variant_name, mut dec, true)
193+
dec.writeln('\t\t$variant_typ value = time__unix(${js_dec_name('i64')}(jsonroot_$tmp));')
194+
} else {
195+
gen_js_get_opt(js_dec_name(variant_typ), variant_typ, sym.cname, tmp,
196+
unmangled_variant_name, mut dec, true)
197+
dec.writeln('\t\t$variant_typ value = *($variant_typ*)(${tmp}.data);')
198+
}
199+
dec.writeln('\t\tres = ${variant_typ}_to_sumtype_${sym.cname}(&value);')
200+
dec.writeln('\t}')
201+
} $else {
202+
if variant_sym.name == 'time.Time' {
203+
dec.writeln('\t\t\tif (strcmp("Time", $type_var) == 0) {')
204+
gen_js_get(sym.cname, tmp, 'value', mut dec, true)
205+
dec.writeln('\t\t\t\t$variant_typ $tmp = time__unix(${js_dec_name('i64')}(jsonroot_$tmp));')
206+
dec.writeln('\t\t\t\tres = ${variant_typ}_to_sumtype_${sym.cname}(&$tmp);')
207+
dec.writeln('\t\t\t}')
208+
} else if !is_js_prim(variant_typ) && variant_sym.kind != .enum_ {
209+
dec.writeln('\t\t\tif (strcmp("$unmangled_variant_name", $type_var) == 0) {')
210+
dec.writeln('\t\t\t\tOption_$variant_typ $tmp = ${js_dec_name(variant_typ)}(root);')
211+
dec.writeln('\t\t\t\tif (${tmp}.state != 0) {')
212+
dec.writeln('\t\t\t\t\treturn (Option_$sym.cname){ .state = ${tmp}.state, .err = ${tmp}.err, .data = {0} };')
213+
dec.writeln('\t\t\t\t}')
214+
dec.writeln('\t\t\t\tres = ${variant_typ}_to_sumtype_${sym.cname}(($variant_typ*)${tmp}.data);')
215+
dec.writeln('\t\t\t}')
216+
}
217+
}
218+
}
219+
220+
// DECODING (inline)
221+
$if !json_no_inline_sumtypes ? {
222+
dec.writeln('\t\t}')
223+
224+
mut number_is_met := false
225+
mut string_is_met := false
226+
mut last_number_type := ''
227+
228+
if at_least_one_prim {
229+
dec.writeln('\t} else {')
230+
231+
if 'bool' in variant_types {
232+
var_t := 'bool'
233+
dec.writeln('\t\tif (cJSON_IsBool(root)) {')
234+
dec.writeln('\t\t\t$var_t value = ${js_dec_name(var_t)}(root);')
235+
dec.writeln('\t\t\tres = ${var_t}_to_sumtype_${sym.cname}(&value);')
236+
dec.writeln('\t\t}')
237+
}
238+
239+
for i, var_t in variant_types {
240+
if variant_symbols[i].kind == .enum_ {
241+
if number_is_met {
242+
var_num := var_t.replace('__', '.')
243+
last_num := last_number_type.replace('__', '.')
244+
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')
245+
}
246+
number_is_met = true
247+
last_number_type = var_t
248+
dec.writeln('\t\tif (cJSON_IsNumber(root)) {')
249+
dec.writeln('\t\t\t$var_t value = ${js_dec_name('u64')}(root);')
250+
dec.writeln('\t\t\tres = ${var_t}_to_sumtype_${sym.cname}(&value);')
251+
dec.writeln('\t\t}')
252+
}
253+
254+
if var_t in ['string', 'rune'] {
255+
if string_is_met {
256+
var_num := var_t.replace('__', '.')
257+
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')
258+
}
259+
string_is_met = true
260+
dec.writeln('\t\tif (cJSON_IsString(root)) {')
261+
dec.writeln('\t\t\t$var_t value = ${js_dec_name(var_t)}(root);')
262+
dec.writeln('\t\t\tres = ${var_t}_to_sumtype_${sym.cname}(&value);')
263+
dec.writeln('\t\t}')
264+
}
265+
266+
if var_t in ['i64', 'int', 'i8', 'u64', 'u32', 'u16', 'byte', 'u8', 'rune', 'f64',
267+
'f32'] {
268+
if number_is_met {
269+
var_num := var_t.replace('__', '.')
270+
last_num := last_number_type.replace('__', '.')
271+
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')
272+
}
273+
number_is_met = true
274+
last_number_type = var_t
275+
dec.writeln('\t\tif (cJSON_IsNumber(root)) {')
276+
dec.writeln('\t\t\t$var_t value = ${js_dec_name(var_t)}(root);')
277+
dec.writeln('\t\t\tres = ${var_t}_to_sumtype_${sym.cname}(&value);')
278+
dec.writeln('\t\t}')
279+
}
280+
}
159281
}
160-
dec.writeln('\t\tres = ${variant_typ}_to_sumtype_${sym.cname}(&value);')
161282
dec.writeln('\t}')
162283
}
163284
}
@@ -294,8 +415,8 @@ fn js_dec_name(typ string) string {
294415
}
295416

296417
fn is_js_prim(typ string) bool {
297-
return typ in ['int', 'string', 'bool', 'f32', 'f64', 'i8', 'i16', 'i64', 'u16', 'u32', 'u64',
298-
'byte']
418+
return typ in ['int', 'rune', 'string', 'bool', 'f32', 'f64', 'i8', 'i16', 'i64', 'u8', 'u16',
419+
'u32', 'u64', 'byte']
299420
}
300421

301422
fn (mut g Gen) decode_array(value_type ast.Type) string {

0 commit comments

Comments
 (0)