Tasmota/lib/libesp32/berry/src/be_jsonlib.c

535 lines
15 KiB
C

/********************************************************************
** Copyright (c) 2018-2020 Guan Wenliang
** This file is part of the Berry default interpreter.
** skiars@qq.com, https://github.com/Skiars/berry
** See Copyright Notice in the LICENSE file or at
** https://github.com/Skiars/berry/blob/master/LICENSE
********************************************************************/
#include "be_object.h"
#include "be_mem.h"
#include "be_lexer.h"
#include <string.h>
#include <math.h>
#if BE_USE_JSON_MODULE
#define MAX_INDENT 24
#define INDENT_WIDTH 2
#define INDENT_CHAR ' '
static const char* parser_value(bvm *vm, const char *json);
static void value_dump(bvm *vm, int *indent, int idx, int fmt);
static const char* skip_space(const char *s)
{
int c;
while (((c = *s) != '\0') && ((c == ' ')
|| (c == '\t') || (c == '\r') || (c == '\n'))) {
++s;
}
return s;
}
static int is_digit(int c)
{
return c >= '0' && c <= '9';
}
static const char* match_char(const char *json, int ch)
{
json = skip_space(json);
if (*json == ch) {
return skip_space(json + 1);
}
return NULL;
}
static int is_object(bvm *vm, const char *class, int idx)
{
if (be_isinstance(vm, idx)) {
be_pushvalue(vm, idx);
while (1) {
be_getsuper(vm, -1);
if (be_isnil(vm, -1)) {
be_pop(vm, 1);
break;
}
be_remove(vm, -2);
}
const char *name = be_classname(vm, -1);
bbool ret = !strcmp(name, class);
be_pop(vm, 1);
return ret;
}
return 0;
}
static int json_strlen(const char *json)
{
int ch;
const char *s = json + 1; /* skip '"' */
/* get string length "(\\.|[^"])*" */
while ((ch = *s) != '\0' && ch != '"') {
++s;
if (ch == '\\') {
ch = *s++;
if (ch == '\0') {
return -1;
}
}
}
return ch ? cast_int(s - json - 1) : -1;
}
static void json2berry(bvm *vm, const char *class)
{
be_getbuiltin(vm, class);
be_pushvalue(vm, -2);
be_call(vm, 1);
be_moveto(vm, -2, -3);
be_pop(vm, 2);
}
static const char* parser_true(bvm *vm, const char *json)
{
if (!strncmp(json, "true", 4)) {
be_pushbool(vm, btrue);
return json + 4;
}
return NULL;
}
static const char* parser_false(bvm *vm, const char *json)
{
if (!strncmp(json, "false", 5)) {
be_pushbool(vm, bfalse);
return json + 5;
}
return NULL;
}
static const char* parser_null(bvm *vm, const char *json)
{
if (!strncmp(json, "null", 4)) {
be_pushnil(vm);
return json + 4;
}
return NULL;
}
static const char* parser_string(bvm *vm, const char *json)
{
if (*json == '"') {
int len = json_strlen(json++);
if (len > -1) {
int ch;
char *buf, *dst = buf = be_malloc(vm, len);
while ((ch = *json) != '\0' && ch != '"') {
++json;
if (ch == '\\') {
ch = *json++; /* skip '\' */
switch (ch) {
case '"': *dst++ = '"'; break;
case '\\': *dst++ = '\\'; break;
case '/': *dst++ = '/'; break;
case 'b': *dst++ = '\b'; break;
case 'f': *dst++ = '\f'; break;
case 'n': *dst++ = '\n'; break;
case 'r': *dst++ = '\r'; break;
case 't': *dst++ = '\t'; break;
case 'u': { /* load unicode */
dst = be_load_unicode(dst, json);
if (dst == NULL) {
be_free(vm, buf, len);
return NULL;
}
json += 4;
break;
}
default: be_free(vm, buf, len); return NULL; /* error */
}
} else if(ch >= 0 && ch <= 0x1f) {
/* control characters must be escaped
as per https://www.rfc-editor.org/rfc/rfc7159#section-7 */
be_free(vm, buf, len);
return NULL;
} else {
*dst++ = (char)ch;
}
}
be_assert(ch == '"');
/* require the stack to have some free space for the string,
since parsing deeply nested objects might
crash the VM due to insufficient stack space. */
be_stack_require(vm, 1 + BE_STACK_FREE_MIN);
be_pushnstring(vm, buf, cast_int(dst - buf));
be_free(vm, buf, len);
return json + 1; /* skip '"' */
}
}
return NULL;
}
static const char* parser_field(bvm *vm, const char *json)
{
be_stack_require(vm, 2 + BE_STACK_FREE_MIN);
if (json && *json == '"') {
json = parser_string(vm, json);
if (json) {
json = match_char(json, ':');
if (json) {
json = parser_value(vm, json);
if (json) {
be_data_insert(vm, -3);
be_pop(vm, 2); /* pop key and value */
return json;
}
}
be_pop(vm, 1); /* pop key */
}
}
return NULL;
}
static const char* parser_object(bvm *vm, const char *json)
{
json = match_char(json, '{');
be_newmap(vm);
if (*json != '}') {
const char *s;
json = parser_field(vm, json);
if (json == NULL) {
be_pop(vm, 1); /* pop map */
return NULL;
}
while ((s = match_char(json, ',')) != NULL) {
json = parser_field(vm, s);
if (json == NULL) {
be_pop(vm, 1); /* pop map */
return NULL;
}
}
}
if ((json = match_char(json, '}')) == NULL) {
be_pop(vm, 1); /* pop map */
return NULL;
}
json2berry(vm, "map");
return json;
}
static const char* parser_array(bvm *vm, const char *json)
{
json = match_char(json, '[');
be_newlist(vm);
if (*json != ']') {
const char *s;
json = parser_value(vm, json);
if (json == NULL) {
be_pop(vm, 1); /* pop map */
return NULL;
}
be_data_push(vm, -2);
be_pop(vm, 1); /* pop value */
while ((s = match_char(json, ',')) != NULL) {
json = parser_value(vm, s);
if (json == NULL) {
be_pop(vm, 1); /* pop map */
return NULL;
}
be_data_push(vm, -2);
be_pop(vm, 1); /* pop value */
}
}
if ((json = match_char(json, ']')) == NULL) {
be_pop(vm, 1); /* pop map */
return NULL;
}
json2berry(vm, "list");
return json;
}
static const char* parser_number(bvm *vm, const char *json)
{
char c = *json++;
bbool is_neg = c == '-';
if(is_neg) {
c = *json++;
if(!is_digit(c)) {
/* minus must be followed by digit */
return NULL;
}
}
bint intv = 0;
if(c != '0') {
/* parse integer part */
while(is_digit(c)) {
intv = intv * 10 + c - '0';
c = *json++;
}
} else {
/*
Number starts with zero, this is only allowed
if the number is just '0' or
it has a fractional part or exponent.
*/
c = *json++;
}
if(c != '.' && c != 'e' && c != 'E') {
/*
No fractional part or exponent follows, this is an integer.
If digits follow after it (for example due to a leading zero)
this will cause an error in the calling function.
*/
be_pushint(vm, intv * (is_neg ? -1 : 1));
json--;
return json;
}
breal realval = (breal) intv;
if(c == '.') {
breal deci = 0.0, point = 0.1;
/* fractional part */
c = *json++;
if(!is_digit(c)) {
/* decimal point must be followed by digit */
return NULL;
}
while (is_digit(c)) {
deci = deci + ((breal)c - '0') * point;
point *= (breal)0.1;
c = *json++;
}
realval += deci;
}
if(c == 'e' || c == 'E') {
c = *json++;
/* exponent part */
breal ratio = c == '-' ? (breal)0.1 : 10;
if (c == '+' || c == '-') {
c = *json++;
if(!is_digit(c)) {
return NULL;
}
}
if(!is_digit(c)) {
/* e and sign must be followed by digit */
return NULL;
}
unsigned int e = 0;
while (is_digit(c)) {
e = e * 10 + c - '0';
c = *json++;
}
/* e > 0 must be here to prevent infinite loops when e overflows */
while (e--) {
realval *= ratio;
}
}
be_pushreal(vm, realval * (is_neg ? -1.0 : 1.0));
json--;
return json;
}
/* parser json value */
static const char* parser_value(bvm *vm, const char *json)
{
json = skip_space(json);
/*
Each value will push at least one thig to the stack, so we must ensure it's big enough.
We need to take special care to extend the stack in values which have variable length (arrays and objects)
*/
be_stack_require(vm, 1 + BE_STACK_FREE_MIN);
switch (*json) {
case '{': /* object */
return parser_object(vm, json);
case '[': /* array */
return parser_array(vm, json);
case '"': /* string */
return parser_string(vm, json);
case 't': /* true */
return parser_true(vm, json);
case 'f': /* false */
return parser_false(vm, json);
case 'n': /* null */
return parser_null(vm, json);
default: /* number */
if (*json == '-' || is_digit(*json)) {
return parser_number(vm, json);
}
}
return NULL;
}
static int m_json_load(bvm *vm)
{
if (be_isstring(vm, 1)) {
const char *json = be_tostring(vm, 1);
json = parser_value(vm, json);
if (json != NULL && *json == '\0') {
be_return(vm);
}
}
be_return_nil(vm);
}
static void make_indent(bvm *vm, int stridx, int indent)
{
if (indent) {
be_stack_require(vm, 1 + BE_STACK_FREE_MIN);
char buf[MAX_INDENT * INDENT_WIDTH + 1];
indent = (indent < MAX_INDENT ? indent : MAX_INDENT) * INDENT_WIDTH;
memset(buf, INDENT_CHAR, indent);
buf[indent] = '\0';
stridx = be_absindex(vm, stridx);
be_pushstring(vm, buf);
be_strconcat(vm, stridx);
be_pop(vm, 1);
}
}
void string_dump(bvm *vm, int index)
{
be_stack_require(vm, 1 + BE_STACK_FREE_MIN);
be_tostring(vm, index); /* convert value to string */
be_toescape(vm, index, 'u');
be_pushvalue(vm, index);
}
static void object_dump(bvm *vm, int *indent, int idx, int fmt)
{
be_stack_require(vm, 3 + BE_STACK_FREE_MIN); /* 3 pushes outside the loop */
be_getmember(vm, idx, ".p");
be_pushstring(vm, fmt ? "{\n" : "{");
be_pushiter(vm, -2); /* map iterator use 1 register */
*indent += fmt;
while (be_iter_hasnext(vm, -3)) {
be_stack_require(vm, 3 + BE_STACK_FREE_MIN); /* 3 pushes inside the loop */
make_indent(vm, -2, fmt ? *indent : 0);
be_iter_next(vm, -3);
/* key.tostring() */
string_dump(vm, -2);
be_strconcat(vm, -5);
be_pop(vm, 1);
be_pushstring(vm, fmt ? ": " : ":"); /* add ': ' */
be_strconcat(vm, -5);
be_pop(vm, 1);
/* value.tostring() */
value_dump(vm, indent, -1, fmt);
be_strconcat(vm, -5);
be_pop(vm, 3);
if (be_iter_hasnext(vm, -3)) {
be_pushstring(vm, fmt ? ",\n" : ",");
be_strconcat(vm, -3);
be_pop(vm, 1);
} else if (fmt) {
be_pushstring(vm, "\n");
be_strconcat(vm, -3);
be_pop(vm, 1);
}
}
*indent -= fmt;
be_pop(vm, 1); /* pop iterator */
make_indent(vm, -1, fmt ? *indent : 0);
be_pushstring(vm, "}");
be_strconcat(vm, -2);
be_moveto(vm, -2, -3);
be_pop(vm, 2);
}
static void array_dump(bvm *vm, int *indent, int idx, int fmt)
{
be_stack_require(vm, 3 + BE_STACK_FREE_MIN);
be_getmember(vm, idx, ".p");
be_pushstring(vm, fmt ? "[\n" : "[");
be_pushiter(vm, -2);
*indent += fmt;
while (be_iter_hasnext(vm, -3)) {
make_indent(vm, -2, fmt ? *indent : 0);
be_iter_next(vm, -3);
value_dump(vm, indent, -1, fmt);
be_strconcat(vm, -4);
be_pop(vm, 2);
be_stack_require(vm, 1 + BE_STACK_FREE_MIN);
if (be_iter_hasnext(vm, -3)) {
be_pushstring(vm, fmt ? ",\n" : ",");
be_strconcat(vm, -3);
be_pop(vm, 1);
} else if (fmt) {
be_pushstring(vm, "\n");
be_strconcat(vm, -3);
be_pop(vm, 1);
}
}
*indent -= fmt;
be_pop(vm, 1); /* pop iterator */
make_indent(vm, -1, fmt ? *indent : 0);
be_pushstring(vm, "]");
be_strconcat(vm, -2);
be_moveto(vm, -2, -3);
be_pop(vm, 2);
}
static void value_dump(bvm *vm, int *indent, int idx, int fmt)
{
// be_stack_require(vm, 1 + BE_STACK_FREE_MIN);
if (is_object(vm, "map", idx)) { /* convert to json object */
object_dump(vm, indent, idx, fmt);
} else if (is_object(vm, "list", idx)) { /* convert to json array */
array_dump(vm, indent, idx, fmt);
} else if (be_isnil(vm, idx)) { /* convert to json null */
be_stack_require(vm, 1 + BE_STACK_FREE_MIN);
be_pushstring(vm, "null");
} else if (be_isreal(vm, idx)) {
be_stack_require(vm, 1 + BE_STACK_FREE_MIN);
breal v = be_toreal(vm, idx);
if (isnan(v) || isinf(v)) {
be_pushstring(vm, "null");
} else {
be_tostring(vm, idx);
be_pushvalue(vm, idx); /* push to top */
};
} else if (be_isnumber(vm, idx) || be_isbool(vm, idx)) { /* convert to json number and boolean */
be_stack_require(vm, 1 + BE_STACK_FREE_MIN);
be_tostring(vm, idx);
be_pushvalue(vm, idx); /* push to top */
} else { /* convert to string */
string_dump(vm, idx);
}
}
static int m_json_dump(bvm *vm)
{
int indent = 0, argc = be_top(vm);
int fmt = 0;
if (argc > 1) {
fmt = !strcmp(be_tostring(vm, 2), "format");
}
value_dump(vm, &indent, 1, fmt);
be_return(vm);
}
#if !BE_USE_PRECOMPILED_OBJECT
be_native_module_attr_table(json) {
be_native_module_function("load", m_json_load),
be_native_module_function("dump", m_json_dump)
};
be_define_native_module(json, NULL);
#else
/* @const_object_info_begin
module json (scope: global, depend: BE_USE_JSON_MODULE) {
load, func(m_json_load)
dump, func(m_json_dump)
}
@const_object_info_end */
#include "../generate/be_fixed_json.h"
#endif
#endif /* BE_USE_JSON_MODULE */