mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2025-10-09 22:52:32 +02:00
* js: value destructuring and tests * js: temporary fix to see size impact * js_val: reduce code size 1 * i may be stupid. * test: js_value args * Revert "js: temporary fix to see size impact" This reverts commit f51d726dbafc4300d3552020de1c3b8f9ecd3ac1. * pvs: silence warnings * style: formatting * pvs: silence warnings? * pvs: silence warnings?? * js_value: redesign declaration types for less code * js: temporary fix to see size impact * style: formatting * pvs: fix helpful warnings * js_value: reduce .rodata size * pvs: fix helpful warning * js_value: reduce code size 1 * fix build error * style: format * Revert "js: temporary fix to see size impact" This reverts commit d6a46f01794132e882e03fd273dec24386a4f8ba. * style: format --------- Co-authored-by: hedger <hedger@users.noreply.github.com>
292 lines
10 KiB
C
292 lines
10 KiB
C
#include "js_value.h"
|
|
#include <stdarg.h>
|
|
|
|
#ifdef APP_UNIT_TESTS
|
|
#define JS_VAL_DEBUG
|
|
#endif
|
|
|
|
size_t js_value_buffer_size(const JsValueParseDeclaration declaration) {
|
|
if(declaration.source == JsValueParseSourceValue) {
|
|
const JsValueDeclaration* value_decl = declaration.value_decl;
|
|
JsValueType type = value_decl->type & JsValueTypeMask;
|
|
|
|
if(type == JsValueTypeString) return 1;
|
|
|
|
if(type == JsValueTypeObject) {
|
|
size_t total = 0;
|
|
for(size_t i = 0; i < value_decl->n_children; i++)
|
|
total += js_value_buffer_size(
|
|
JS_VALUE_PARSE_SOURCE_VALUE(value_decl->object_fields[i].value));
|
|
return total;
|
|
}
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
const JsValueArguments* arg_decl = declaration.argument_decl;
|
|
size_t total = 0;
|
|
for(size_t i = 0; i < arg_decl->n_children; i++)
|
|
total += js_value_buffer_size(JS_VALUE_PARSE_SOURCE_VALUE(&arg_decl->arguments[i]));
|
|
return total;
|
|
}
|
|
}
|
|
|
|
static size_t js_value_resulting_c_values_count(const JsValueParseDeclaration declaration) {
|
|
if(declaration.source == JsValueParseSourceValue) {
|
|
const JsValueDeclaration* value_decl = declaration.value_decl;
|
|
JsValueType type = value_decl->type & JsValueTypeMask;
|
|
|
|
if(type == JsValueTypeObject) {
|
|
size_t total = 0;
|
|
for(size_t i = 0; i < value_decl->n_children; i++)
|
|
total += js_value_resulting_c_values_count(
|
|
JS_VALUE_PARSE_SOURCE_VALUE(value_decl->object_fields[i].value));
|
|
return total;
|
|
}
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
const JsValueArguments* arg_decl = declaration.argument_decl;
|
|
size_t total = 0;
|
|
for(size_t i = 0; i < arg_decl->n_children; i++)
|
|
total += js_value_resulting_c_values_count(
|
|
JS_VALUE_PARSE_SOURCE_VALUE(&arg_decl->arguments[i]));
|
|
return total;
|
|
}
|
|
}
|
|
|
|
#define PREPEND_JS_ERROR_AND_RETURN(mjs, flags, ...) \
|
|
do { \
|
|
if((flags) & JsValueParseFlagReturnOnError) \
|
|
mjs_prepend_errorf((mjs), MJS_BAD_ARGS_ERROR, __VA_ARGS__); \
|
|
return JsValueParseStatusJsError; \
|
|
} while(0)
|
|
|
|
#define PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, type) \
|
|
PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected %s", type)
|
|
|
|
static void js_value_assign_enum_val(void* destination, JsValueType type_w_flags, uint32_t value) {
|
|
if(type_w_flags & JsValueTypeEnumSize1) {
|
|
*(uint8_t*)destination = value;
|
|
} else if(type_w_flags & JsValueTypeEnumSize2) {
|
|
*(uint16_t*)destination = value;
|
|
} else if(type_w_flags & JsValueTypeEnumSize4) {
|
|
*(uint32_t*)destination = value;
|
|
}
|
|
}
|
|
|
|
static bool js_value_is_null_or_undefined(mjs_val_t* val_ptr) {
|
|
return mjs_is_null(*val_ptr) || mjs_is_undefined(*val_ptr);
|
|
}
|
|
|
|
static bool js_value_maybe_assign_default(
|
|
const JsValueDeclaration* declaration,
|
|
mjs_val_t* val_ptr,
|
|
void* destination,
|
|
size_t size) {
|
|
if((declaration->type & JsValueTypePermitNull) && js_value_is_null_or_undefined(val_ptr)) {
|
|
memcpy(destination, &declaration->default_value, size);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
typedef int (*MjsTypecheckFn)(mjs_val_t value);
|
|
|
|
static JsValueParseStatus js_value_parse_literal(
|
|
struct mjs* mjs,
|
|
JsValueParseFlag flags,
|
|
mjs_val_t* destination,
|
|
mjs_val_t* source,
|
|
MjsTypecheckFn typecheck,
|
|
const char* type_name) {
|
|
if(!typecheck(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, type_name);
|
|
*destination = *source;
|
|
return JsValueParseStatusOk;
|
|
}
|
|
|
|
static JsValueParseStatus js_value_parse_va(
|
|
struct mjs* mjs,
|
|
const JsValueParseDeclaration declaration,
|
|
JsValueParseFlag flags,
|
|
mjs_val_t* source,
|
|
mjs_val_t* buffer,
|
|
size_t* buffer_index,
|
|
va_list* out_pointers) {
|
|
if(declaration.source == JsValueParseSourceArguments) {
|
|
const JsValueArguments* arg_decl = declaration.argument_decl;
|
|
|
|
for(size_t i = 0; i < arg_decl->n_children; i++) {
|
|
mjs_val_t arg_val = mjs_arg(mjs, i);
|
|
JsValueParseStatus status = js_value_parse_va(
|
|
mjs,
|
|
JS_VALUE_PARSE_SOURCE_VALUE(&arg_decl->arguments[i]),
|
|
flags,
|
|
&arg_val,
|
|
buffer,
|
|
buffer_index,
|
|
out_pointers);
|
|
if(status != JsValueParseStatusOk) return status;
|
|
}
|
|
|
|
return JsValueParseStatusOk;
|
|
}
|
|
|
|
const JsValueDeclaration* value_decl = declaration.value_decl;
|
|
JsValueType type_w_flags = value_decl->type;
|
|
JsValueType type_noflags = type_w_flags & JsValueTypeMask;
|
|
bool is_null_but_allowed = (type_w_flags & JsValueTypePermitNull) &&
|
|
js_value_is_null_or_undefined(source);
|
|
|
|
void* destination = NULL;
|
|
if(type_noflags != JsValueTypeObject) destination = va_arg(*out_pointers, void*);
|
|
|
|
switch(type_noflags) {
|
|
// Literal terms
|
|
case JsValueTypeAny:
|
|
*(mjs_val_t*)destination = *source;
|
|
break;
|
|
case JsValueTypeAnyArray:
|
|
return js_value_parse_literal(mjs, flags, destination, source, mjs_is_array, "array");
|
|
case JsValueTypeAnyObject:
|
|
return js_value_parse_literal(mjs, flags, destination, source, mjs_is_object, "array");
|
|
case JsValueTypeFunction:
|
|
return js_value_parse_literal(
|
|
mjs, flags, destination, source, mjs_is_function, "function");
|
|
|
|
// Primitive types
|
|
case JsValueTypeRawPointer: {
|
|
if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(void*))) break;
|
|
if(!mjs_is_foreign(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "pointer");
|
|
*(void**)destination = mjs_get_ptr(mjs, *source);
|
|
break;
|
|
}
|
|
case JsValueTypeInt32: {
|
|
if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(int32_t))) break;
|
|
if(!mjs_is_number(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "number");
|
|
*(int32_t*)destination = mjs_get_int32(mjs, *source);
|
|
break;
|
|
}
|
|
case JsValueTypeDouble: {
|
|
if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(double))) break;
|
|
if(!mjs_is_number(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "number");
|
|
*(double*)destination = mjs_get_double(mjs, *source);
|
|
break;
|
|
}
|
|
case JsValueTypeBool: {
|
|
if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(bool))) break;
|
|
if(!mjs_is_boolean(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "bool");
|
|
*(bool*)destination = mjs_get_bool(mjs, *source);
|
|
break;
|
|
}
|
|
case JsValueTypeString: {
|
|
if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(const char*)))
|
|
break;
|
|
if(!mjs_is_string(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "string");
|
|
buffer[*buffer_index] = *source;
|
|
*(const char**)destination = mjs_get_string(mjs, &buffer[*buffer_index], NULL);
|
|
(*buffer_index)++;
|
|
break;
|
|
}
|
|
|
|
// Types with children
|
|
case JsValueTypeEnum: {
|
|
if(is_null_but_allowed) {
|
|
js_value_assign_enum_val(
|
|
destination, type_w_flags, value_decl->default_value.enum_val);
|
|
|
|
} else if(mjs_is_string(*source)) {
|
|
const char* str = mjs_get_string(mjs, source, NULL);
|
|
furi_check(str);
|
|
|
|
bool match_found = false;
|
|
for(size_t i = 0; i < value_decl->n_children; i++) {
|
|
const JsValueEnumVariant* variant = &value_decl->enum_variants[i];
|
|
if(strcmp(str, variant->string_value) == 0) {
|
|
js_value_assign_enum_val(destination, type_w_flags, variant->num_value);
|
|
match_found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!match_found)
|
|
PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "one of permitted strings");
|
|
|
|
} else {
|
|
PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "string");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JsValueTypeObject: {
|
|
if(!(is_null_but_allowed || mjs_is_object(*source)))
|
|
PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "object");
|
|
for(size_t i = 0; i < value_decl->n_children; i++) {
|
|
const JsValueObjectField* field = &value_decl->object_fields[i];
|
|
mjs_val_t field_val = mjs_get(mjs, *source, field->field_name, ~0);
|
|
JsValueParseStatus status = js_value_parse_va(
|
|
mjs,
|
|
JS_VALUE_PARSE_SOURCE_VALUE(field->value),
|
|
flags,
|
|
&field_val,
|
|
buffer,
|
|
buffer_index,
|
|
out_pointers);
|
|
if(status != JsValueParseStatusOk)
|
|
PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "field %s: ", field->field_name);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JsValueTypeMask:
|
|
case JsValueTypeEnumSize1:
|
|
case JsValueTypeEnumSize2:
|
|
case JsValueTypeEnumSize4:
|
|
case JsValueTypePermitNull:
|
|
furi_crash();
|
|
}
|
|
|
|
return JsValueParseStatusOk;
|
|
}
|
|
|
|
JsValueParseStatus js_value_parse(
|
|
struct mjs* mjs,
|
|
const JsValueParseDeclaration declaration,
|
|
JsValueParseFlag flags,
|
|
mjs_val_t* buffer,
|
|
size_t buf_size,
|
|
mjs_val_t* source,
|
|
size_t n_c_vals,
|
|
...) {
|
|
furi_check(mjs);
|
|
furi_check(buffer);
|
|
|
|
if(declaration.source == JsValueParseSourceValue) {
|
|
furi_check(source);
|
|
furi_check(declaration.value_decl);
|
|
} else {
|
|
furi_check(source == NULL);
|
|
furi_check(declaration.argument_decl);
|
|
}
|
|
|
|
#ifdef JS_VAL_DEBUG
|
|
furi_check(buf_size == js_value_buffer_size(declaration));
|
|
furi_check(n_c_vals == js_value_resulting_c_values_count(declaration));
|
|
#else
|
|
UNUSED(js_value_resulting_c_values_count);
|
|
#endif
|
|
|
|
va_list out_pointers;
|
|
va_start(out_pointers, n_c_vals);
|
|
|
|
size_t buffer_index = 0;
|
|
JsValueParseStatus status =
|
|
js_value_parse_va(mjs, declaration, flags, source, buffer, &buffer_index, &out_pointers);
|
|
furi_check(buffer_index <= buf_size);
|
|
|
|
va_end(out_pointers);
|
|
|
|
return status;
|
|
}
|