Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,649 @@
|
||||
// Copyright (c) 2019-2024 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 json
|
||||
|
||||
import math
|
||||
import strconv
|
||||
import time
|
||||
|
||||
#flag -I @VEXEROOT/thirdparty/cJSON
|
||||
#flag @VEXEROOT/thirdparty/cJSON/cJSON.o
|
||||
#include "cJSON.h"
|
||||
#define js_get(object, key) cJSON_GetObjectItemCaseSensitive((object), (key))
|
||||
|
||||
// As cJSON use `libm`, we need to link it.
|
||||
$if windows {
|
||||
$if tinyc {
|
||||
#flag @VEXEROOT/thirdparty/tcc/lib/openlibm.o
|
||||
}
|
||||
} $else {
|
||||
#flag -lm
|
||||
}
|
||||
|
||||
pub struct C.cJSON {
|
||||
next &C.cJSON
|
||||
prev &C.cJSON
|
||||
child &C.cJSON
|
||||
type int
|
||||
valuestring &char
|
||||
valueint int
|
||||
valuedouble f64
|
||||
}
|
||||
|
||||
fn C.cJSON_IsTrue(&C.cJSON) bool
|
||||
fn C.cJSON_IsFalse(&C.cJSON) bool
|
||||
fn C.cJSON_IsNull(&C.cJSON) bool
|
||||
fn C.cJSON_IsNumber(&C.cJSON) bool
|
||||
fn C.cJSON_IsString(&C.cJSON) bool
|
||||
fn C.cJSON_IsObject(&C.cJSON) bool
|
||||
fn C.cJSON_IsArray(&C.cJSON) bool
|
||||
|
||||
fn C.cJSON_CreateNumber(f64) &C.cJSON
|
||||
fn C.cJSON_CreateRaw(&char) &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateBool(bool) &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateString(&char) &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateRaw(&char) &C.cJSON
|
||||
|
||||
fn C.cJSON_Parse(&char) &C.cJSON
|
||||
|
||||
fn C.cJSON_PrintUnformatted(&C.cJSON) &char
|
||||
|
||||
fn C.cJSON_Print(&C.cJSON) &char
|
||||
|
||||
fn C.cJSON_free(voidptr)
|
||||
fn C.malloc(usize) voidptr
|
||||
fn C.memcpy(voidptr, voidptr, usize) voidptr
|
||||
|
||||
// decode tries to decode the provided JSON string, into a V structure.
|
||||
// If it can not do that, it returns an error describing the reason for
|
||||
// the parsing failure.
|
||||
pub fn decode(typ voidptr, s string) !voidptr {
|
||||
// compiler implementation
|
||||
return 0
|
||||
}
|
||||
|
||||
// encode serialises the provided V value as a JSON string, optimised for shortness.
|
||||
pub fn encode(x voidptr) string {
|
||||
// compiler implementation
|
||||
return ''
|
||||
}
|
||||
|
||||
// encode_pretty serialises the provided V value as a JSON string, in a formatted way, optimised for viewing by humans.
|
||||
pub fn encode_pretty(x voidptr) string {
|
||||
// compiler implementation
|
||||
return ''
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_int(root &C.cJSON) int {
|
||||
if isnil(root) {
|
||||
return 0
|
||||
}
|
||||
return root.valueint
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_i8(root &C.cJSON) i8 {
|
||||
if isnil(root) {
|
||||
return i8(0)
|
||||
}
|
||||
return i8(root.valueint)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_i16(root &C.cJSON) i16 {
|
||||
if isnil(root) {
|
||||
return i16(0)
|
||||
}
|
||||
return i16(root.valueint)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_i64(root &C.cJSON) i64 {
|
||||
if isnil(root) {
|
||||
return i64(0)
|
||||
}
|
||||
if value := decode_exact_i64(root) {
|
||||
return value
|
||||
}
|
||||
return i64(root.valuedouble) // i64 is double in C
|
||||
}
|
||||
|
||||
// TODO: remove when `byte` is removed
|
||||
@[markused]
|
||||
fn decode_byte(root &C.cJSON) u8 {
|
||||
return decode_u8(root)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_u8(root &C.cJSON) u8 {
|
||||
if isnil(root) {
|
||||
return u8(0)
|
||||
}
|
||||
return u8(root.valueint)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_u16(root &C.cJSON) u16 {
|
||||
if isnil(root) {
|
||||
return u16(0)
|
||||
}
|
||||
return u16(root.valueint)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_u32(root &C.cJSON) u32 {
|
||||
if isnil(root) {
|
||||
return u32(0)
|
||||
}
|
||||
return u32(root.valueint)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_u64(root &C.cJSON) u64 {
|
||||
if isnil(root) {
|
||||
return u64(0)
|
||||
}
|
||||
if value := decode_exact_u64(root) {
|
||||
return value
|
||||
}
|
||||
return u64(root.valuedouble)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_f32(root &C.cJSON) f32 {
|
||||
if isnil(root) {
|
||||
return f32(0)
|
||||
}
|
||||
return f32(root.valuedouble)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_f64(root &C.cJSON) f64 {
|
||||
if isnil(root) {
|
||||
return f64(0)
|
||||
}
|
||||
return root.valuedouble
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_rune(root &C.cJSON) rune {
|
||||
if isnil(root) {
|
||||
return rune(0)
|
||||
}
|
||||
if isnil(root.valuestring) || !C.cJSON_IsString(root) {
|
||||
return rune(0)
|
||||
}
|
||||
|
||||
// TODO: Parse as runes, bypassing string casting...?
|
||||
return unsafe { tos_clone(&u8(root.valuestring)).runes().first() }
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_string(root &C.cJSON) string {
|
||||
if isnil(root) {
|
||||
return ''
|
||||
}
|
||||
if !isnil(root.valuestring) && C.cJSON_IsString(root) {
|
||||
return unsafe { tos_clone(&u8(root.valuestring)) } // , _strlen(root.valuestring))
|
||||
}
|
||||
// Object/array values can be stringified JSON payloads (e.g. `{}` from JSON.stringify()).
|
||||
if C.cJSON_IsObject(root) || C.cJSON_IsArray(root) {
|
||||
return json_print(root)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_bool(root &C.cJSON) bool {
|
||||
if isnil(root) {
|
||||
return false
|
||||
}
|
||||
return C.cJSON_IsTrue(root)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_time(root &C.cJSON) !time.Time {
|
||||
if isnil(root) || C.cJSON_IsNull(root) {
|
||||
return time.Time{}
|
||||
}
|
||||
mut decoded_time := time.Time{}
|
||||
if C.cJSON_IsString(root) {
|
||||
decoded_time.from_json_string(decode_string(root))!
|
||||
return decoded_time
|
||||
}
|
||||
if C.cJSON_IsNumber(root) {
|
||||
decoded_time.from_json_number(json_print(root))!
|
||||
return decoded_time
|
||||
}
|
||||
return error('expected time.Time to decode from a JSON string or number, got: ${json_print(root)}')
|
||||
}
|
||||
|
||||
// ///////////////////
|
||||
|
||||
@[markused]
|
||||
fn encode_int(val int) &C.cJSON {
|
||||
return C.cJSON_CreateNumber(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_i8(val i8) &C.cJSON {
|
||||
return C.cJSON_CreateNumber(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_i16(val i16) &C.cJSON {
|
||||
return C.cJSON_CreateNumber(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_i64(val i64) &C.cJSON {
|
||||
lit := val.str()
|
||||
return C.cJSON_CreateRaw(&char(lit.str))
|
||||
}
|
||||
|
||||
// TODO: remove when `byte` is removed
|
||||
@[markused]
|
||||
fn encode_byte(root u8) &C.cJSON {
|
||||
return encode_u8(root)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_u8(val u8) &C.cJSON {
|
||||
return C.cJSON_CreateNumber(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_u16(val u16) &C.cJSON {
|
||||
return C.cJSON_CreateNumber(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_u32(val u32) &C.cJSON {
|
||||
return C.cJSON_CreateNumber(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_u64(val u64) &C.cJSON {
|
||||
lit := val.str()
|
||||
return C.cJSON_CreateRaw(&char(lit.str))
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_f32(val f32) &C.cJSON {
|
||||
return C.cJSON_CreateRaw(&char(json_float_to_raw_string(val).str))
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_f64(val f64) &C.cJSON {
|
||||
return C.cJSON_CreateRaw(&char(json_float_to_raw_string(val).str))
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_bool(val bool) &C.cJSON {
|
||||
return C.cJSON_CreateBool(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_rune(val rune) &C.cJSON {
|
||||
return C.cJSON_CreateRaw(&char(json_ascii_string(val.str()).str))
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_string(val string) &C.cJSON {
|
||||
return C.cJSON_CreateRaw(&char(json_ascii_string(val).str))
|
||||
}
|
||||
|
||||
// json_ascii_string returns a quoted JSON string with non-ASCII runes escaped as `\uXXXX`.
|
||||
fn json_ascii_string(val string) string {
|
||||
mut output := []u8{cap: val.len + 2}
|
||||
output << `"`
|
||||
for character in val.runes() {
|
||||
match character {
|
||||
`"` {
|
||||
output << `\\`
|
||||
output << `"`
|
||||
}
|
||||
`\\` {
|
||||
output << `\\`
|
||||
output << `\\`
|
||||
}
|
||||
`\b` {
|
||||
output << `\\`
|
||||
output << `b`
|
||||
}
|
||||
`\f` {
|
||||
output << `\\`
|
||||
output << `f`
|
||||
}
|
||||
`\n` {
|
||||
output << `\\`
|
||||
output << `n`
|
||||
}
|
||||
`\r` {
|
||||
output << `\\`
|
||||
output << `r`
|
||||
}
|
||||
`\t` {
|
||||
output << `\\`
|
||||
output << `t`
|
||||
}
|
||||
else {
|
||||
if character < 0x20 || character > 0x7f {
|
||||
if character <= 0xffff {
|
||||
output << `\\`
|
||||
output << `u`
|
||||
hex_string := '${u32(character):04x}'
|
||||
unsafe { output.push_many(hex_string.str, 4) }
|
||||
} else {
|
||||
unicode_point_low := u32(character) - 0x10000
|
||||
surrogate_pair := '\\u${0xD800 + ((unicode_point_low >> 10) & 0x3FF):04X}\\u${
|
||||
0xDC00 + (unicode_point_low & 0x3FF):04x}'
|
||||
unsafe { output.push_many(surrogate_pair.str, surrogate_pair.len) }
|
||||
}
|
||||
} else {
|
||||
output << u8(character)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
output << `"`
|
||||
return output.bytestr()
|
||||
}
|
||||
|
||||
// json_float_to_raw_string uses V's float formatter so json.encode keeps exact float round-trips.
|
||||
fn json_float_to_raw_string[T](val T) string {
|
||||
if val == 0 {
|
||||
return '0'
|
||||
}
|
||||
if math.is_nan(f64(val)) || math.is_inf(f64(val), 0) {
|
||||
return 'null'
|
||||
}
|
||||
mut raw := val.str()
|
||||
if raw.len > 2 && raw[raw.len - 2] == `.` && raw[raw.len - 1] == `0` {
|
||||
raw = raw[..raw.len - 2]
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
fn decode_exact_i64(root &C.cJSON) ?i64 {
|
||||
if isnil(root) || isnil(root.valuestring) {
|
||||
return none
|
||||
}
|
||||
lit := unsafe { tos_clone(&u8(root.valuestring)) }
|
||||
value := strconv.parse_int(lit, 10, 64) or { return none }
|
||||
return value
|
||||
}
|
||||
|
||||
fn decode_exact_u64(root &C.cJSON) ?u64 {
|
||||
if isnil(root) || isnil(root.valuestring) {
|
||||
return none
|
||||
}
|
||||
lit := unsafe { tos_clone(&u8(root.valuestring)) }
|
||||
value := strconv.parse_uint(lit, 10, 64) or { return none }
|
||||
return value
|
||||
}
|
||||
|
||||
fn clone_cstring(s string) &char {
|
||||
buf := C.malloc(usize(s.len + 1))
|
||||
if buf == unsafe { nil } {
|
||||
return unsafe { nil }
|
||||
}
|
||||
unsafe {
|
||||
if s.len > 0 {
|
||||
C.memcpy(buf, s.str, usize(s.len))
|
||||
}
|
||||
(&u8(buf))[s.len] = 0
|
||||
}
|
||||
return &char(buf)
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn is_json_whitespace(ch u8) bool {
|
||||
return ch == ` ` || ch == `\n` || ch == `\r` || ch == `\t`
|
||||
}
|
||||
|
||||
fn skip_json_whitespace(src string, idx int) int {
|
||||
mut pos := idx
|
||||
for pos < src.len && is_json_whitespace(src[pos]) {
|
||||
pos++
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
fn skip_json_string(src string, idx int) int {
|
||||
mut pos := idx
|
||||
if pos >= src.len || src[pos] != `"` {
|
||||
return -1
|
||||
}
|
||||
pos++
|
||||
for pos < src.len {
|
||||
match src[pos] {
|
||||
`"` {
|
||||
return pos + 1
|
||||
}
|
||||
`\\` {
|
||||
pos++
|
||||
if pos >= src.len {
|
||||
return -1
|
||||
}
|
||||
if src[pos] == `u` {
|
||||
pos += 5
|
||||
} else {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
else {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fn skip_json_literal(src string, lit string, idx int) int {
|
||||
if idx + lit.len > src.len || src[idx..idx + lit.len] != lit {
|
||||
return -1
|
||||
}
|
||||
return idx + lit.len
|
||||
}
|
||||
|
||||
fn skip_json_number(src string, idx int) (string, int) {
|
||||
mut pos := idx
|
||||
start := pos
|
||||
if pos < src.len && src[pos] == `-` {
|
||||
pos++
|
||||
}
|
||||
if pos >= src.len {
|
||||
return '', -1
|
||||
}
|
||||
if src[pos] == `0` {
|
||||
pos++
|
||||
} else {
|
||||
if !src[pos].is_digit() {
|
||||
return '', -1
|
||||
}
|
||||
for pos < src.len && src[pos].is_digit() {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
if pos < src.len && src[pos] == `.` {
|
||||
pos++
|
||||
if pos >= src.len || !src[pos].is_digit() {
|
||||
return '', -1
|
||||
}
|
||||
for pos < src.len && src[pos].is_digit() {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
if pos < src.len && (src[pos] == `e` || src[pos] == `E`) {
|
||||
pos++
|
||||
if pos < src.len && (src[pos] == `+` || src[pos] == `-`) {
|
||||
pos++
|
||||
}
|
||||
if pos >= src.len || !src[pos].is_digit() {
|
||||
return '', -1
|
||||
}
|
||||
for pos < src.len && src[pos].is_digit() {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
return src[start..pos], pos
|
||||
}
|
||||
|
||||
fn annotate_json_number_literal(root &C.cJSON, lit string) {
|
||||
if lit.len == 0 || !isnil(root.valuestring) {
|
||||
return
|
||||
}
|
||||
unsafe {
|
||||
root.valuestring = clone_cstring(lit)
|
||||
}
|
||||
}
|
||||
|
||||
fn annotate_json_value(root &C.cJSON, src string, idx int) int {
|
||||
if isnil(root) {
|
||||
return -1
|
||||
}
|
||||
mut pos := skip_json_whitespace(src, idx)
|
||||
if C.cJSON_IsObject(root) {
|
||||
if pos >= src.len || src[pos] != `{` {
|
||||
return -1
|
||||
}
|
||||
pos++
|
||||
pos = skip_json_whitespace(src, pos)
|
||||
mut child := root.child
|
||||
if isnil(child) {
|
||||
if pos >= src.len || src[pos] != `}` {
|
||||
return -1
|
||||
}
|
||||
return pos + 1
|
||||
}
|
||||
for !isnil(child) {
|
||||
pos = skip_json_string(src, pos)
|
||||
if pos == -1 {
|
||||
return -1
|
||||
}
|
||||
pos = skip_json_whitespace(src, pos)
|
||||
if pos >= src.len || src[pos] != `:` {
|
||||
return -1
|
||||
}
|
||||
pos++
|
||||
pos = annotate_json_value(child, src, pos)
|
||||
if pos == -1 {
|
||||
return -1
|
||||
}
|
||||
pos = skip_json_whitespace(src, pos)
|
||||
if !isnil(child.next) {
|
||||
if pos >= src.len || src[pos] != `,` {
|
||||
return -1
|
||||
}
|
||||
pos++
|
||||
}
|
||||
child = child.next
|
||||
}
|
||||
if pos >= src.len || src[pos] != `}` {
|
||||
return -1
|
||||
}
|
||||
return pos + 1
|
||||
}
|
||||
if C.cJSON_IsArray(root) {
|
||||
if pos >= src.len || src[pos] != `[` {
|
||||
return -1
|
||||
}
|
||||
pos++
|
||||
pos = skip_json_whitespace(src, pos)
|
||||
mut child := root.child
|
||||
if isnil(child) {
|
||||
if pos >= src.len || src[pos] != `]` {
|
||||
return -1
|
||||
}
|
||||
return pos + 1
|
||||
}
|
||||
for !isnil(child) {
|
||||
pos = annotate_json_value(child, src, pos)
|
||||
if pos == -1 {
|
||||
return -1
|
||||
}
|
||||
pos = skip_json_whitespace(src, pos)
|
||||
if !isnil(child.next) {
|
||||
if pos >= src.len || src[pos] != `,` {
|
||||
return -1
|
||||
}
|
||||
pos++
|
||||
}
|
||||
child = child.next
|
||||
}
|
||||
if pos >= src.len || src[pos] != `]` {
|
||||
return -1
|
||||
}
|
||||
return pos + 1
|
||||
}
|
||||
if C.cJSON_IsString(root) {
|
||||
return skip_json_string(src, pos)
|
||||
}
|
||||
if C.cJSON_IsNumber(root) {
|
||||
lit, next_pos := skip_json_number(src, pos)
|
||||
if next_pos == -1 {
|
||||
return -1
|
||||
}
|
||||
annotate_json_number_literal(root, lit)
|
||||
return next_pos
|
||||
}
|
||||
if C.cJSON_IsTrue(root) {
|
||||
return skip_json_literal(src, 'true', pos)
|
||||
}
|
||||
if C.cJSON_IsFalse(root) {
|
||||
return skip_json_literal(src, 'false', pos)
|
||||
}
|
||||
if C.cJSON_IsNull(root) {
|
||||
return skip_json_literal(src, 'null', pos)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fn annotate_json_number_literals(root &C.cJSON, src string) {
|
||||
_ := annotate_json_value(root, src, 0)
|
||||
}
|
||||
|
||||
// ///////////////////////
|
||||
// user := decode_User(json_parse(js_string_var))
|
||||
@[markused]
|
||||
fn json_parse(s string) &C.cJSON {
|
||||
root := C.cJSON_Parse(&char(s.str))
|
||||
if !isnil(root) {
|
||||
annotate_json_number_literals(root, s)
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
// json_string := json_print(encode_User(user))
|
||||
@[markused]
|
||||
fn json_print(data &C.cJSON) string {
|
||||
s := C.cJSON_PrintUnformatted(data)
|
||||
if s == unsafe { nil } {
|
||||
return ''
|
||||
}
|
||||
r := unsafe { tos_clone(&u8(s)) }
|
||||
C.cJSON_free(s)
|
||||
return r
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn json_print_pretty(data &C.cJSON) string {
|
||||
s := C.cJSON_Print(data)
|
||||
if s == unsafe { nil } {
|
||||
return ''
|
||||
}
|
||||
r := unsafe { tos_clone(&u8(s)) }
|
||||
C.cJSON_free(s)
|
||||
return r
|
||||
}
|
||||
|
||||
// / cjson wrappers
|
||||
// fn json_array_for_each(val, root &C.cJSON) {
|
||||
// #cJSON_ArrayForEach (val ,root)
|
||||
// }
|
||||
Reference in new issue
Block a user