mirror of
https://github.com/flipperdevices/flipperzero-firmware.git
synced 2025-12-12 04:41:26 +04:00
275 lines
10 KiB
C
275 lines
10 KiB
C
#include "js_value.h"
|
|
#include <stdarg.h>
|
|
|
|
size_t js_value_buffer_size(const JsValueDeclaration* declaration) {
|
|
JsValueType type = declaration->type;
|
|
|
|
if(type == JsValueTypeString) return 1;
|
|
|
|
if(type == JsValueTypeObject) {
|
|
size_t total = 0;
|
|
for(size_t i = 0; i < declaration->n_children; i++) {
|
|
total += js_value_buffer_size(&declaration->children[i]);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t js_value_resulting_c_values_count(const JsValueDeclaration* declaration) {
|
|
JsValueType type = declaration->type;
|
|
|
|
if(type == JsValueTypeObject) {
|
|
size_t total = 0;
|
|
for(size_t i = 0; i < declaration->n_children; i++) {
|
|
total += js_value_resulting_c_values_count(&declaration->children[i]);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool js_value_declaration_valid(const JsValueDeclaration* declaration) {
|
|
JsValueType type = declaration->type;
|
|
|
|
// Args can have an arbitrary number of children of arbitrary types
|
|
if(type == JsValueTypeArgs) {
|
|
for(size_t i = 0; i < declaration->n_children; i++)
|
|
if(!js_value_declaration_valid(&declaration->children[i])) return false;
|
|
if(declaration->permit_null) return false;
|
|
return true;
|
|
}
|
|
|
|
// Enums can only have EnumValue children
|
|
if(type == JsValueTypeEnum) {
|
|
if(declaration->enum_size != 1 && declaration->enum_size != 2 &&
|
|
declaration->enum_size != 4)
|
|
return false;
|
|
for(size_t i = 0; i < declaration->n_children; i++) {
|
|
const JsValueDeclaration* child = &declaration->children[i];
|
|
if(!js_value_declaration_valid(child) || child->type != JsValueTypeEnumValue)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Objects must have valid children
|
|
if(type == JsValueTypeObject) {
|
|
for(size_t i = 0; i < declaration->n_children; i++) {
|
|
const JsValueDeclaration* child = &declaration->children[i];
|
|
if(!js_value_declaration_valid(child) || !child->object_field_name) return false;
|
|
}
|
|
if(declaration->permit_null) return false;
|
|
return true;
|
|
}
|
|
|
|
// EnumValues must have their string field set
|
|
if(type == JsValueTypeEnumValue) {
|
|
return declaration->n_children == 0 && declaration->enum_string_value != NULL;
|
|
}
|
|
|
|
// Literal types can't have default values
|
|
if(type == JsValueTypeAny || type == JsValueTypeAnyArray || type == JsValueTypeAnyObject ||
|
|
type == JsValueTypeFunction) {
|
|
if(declaration->permit_null) return false;
|
|
}
|
|
|
|
// All other types can't have children
|
|
return declaration->n_children == 0;
|
|
}
|
|
|
|
#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)
|
|
|
|
static void js_value_assign_enum_val(va_list* out_pointers, size_t enum_size, uint32_t value) {
|
|
if(enum_size == 1)
|
|
*va_arg(*out_pointers, uint8_t*) = value;
|
|
else if(enum_size == 2)
|
|
*va_arg(*out_pointers, uint16_t*) = value;
|
|
else if(enum_size == 4)
|
|
*va_arg(*out_pointers, uint32_t*) = 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->permit_null && js_value_is_null_or_undefined(val_ptr)) {
|
|
memcpy(destination, &declaration->default_value, size);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static JsValueParseStatus js_value_parse_va(
|
|
struct mjs* mjs,
|
|
const JsValueDeclaration* declaration,
|
|
JsValueParseFlag flags,
|
|
mjs_val_t* source,
|
|
mjs_val_t* buffer,
|
|
size_t* buffer_index,
|
|
va_list* out_pointers) {
|
|
switch(declaration->type) {
|
|
// Literal terms
|
|
case JsValueTypeAny: {
|
|
*va_arg(*out_pointers, mjs_val_t*) = *source;
|
|
break;
|
|
}
|
|
case JsValueTypeAnyArray: {
|
|
if(!mjs_is_array(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected array");
|
|
*va_arg(*out_pointers, mjs_val_t*) = *source;
|
|
break;
|
|
}
|
|
case JsValueTypeAnyObject: {
|
|
if(!mjs_is_object(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected object");
|
|
*va_arg(*out_pointers, mjs_val_t*) = *source;
|
|
break;
|
|
}
|
|
case JsValueTypeFunction: {
|
|
if(!mjs_is_function(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected function");
|
|
*va_arg(*out_pointers, mjs_val_t*) = *source;
|
|
break;
|
|
}
|
|
|
|
// Primitive types
|
|
case JsValueTypeRawPointer: {
|
|
void** destination = *va_arg(*out_pointers, void**);
|
|
if(js_value_maybe_assign_default(declaration, source, destination, sizeof(void*))) break;
|
|
if(!mjs_is_foreign(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected pointer");
|
|
*destination = mjs_get_ptr(mjs, *source);
|
|
break;
|
|
}
|
|
case JsValueTypeInt32: {
|
|
int32_t* destination = va_arg(*out_pointers, int32_t*);
|
|
if(js_value_maybe_assign_default(declaration, source, destination, sizeof(int32_t))) break;
|
|
if(!mjs_is_number(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected number");
|
|
*destination = mjs_get_int32(mjs, *source);
|
|
break;
|
|
}
|
|
case JsValueTypeDouble: {
|
|
double* destination = va_arg(*out_pointers, double*);
|
|
if(js_value_maybe_assign_default(declaration, source, destination, sizeof(double))) break;
|
|
if(!mjs_is_number(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected number");
|
|
*destination = mjs_get_double(mjs, *source);
|
|
break;
|
|
}
|
|
case JsValueTypeBool: {
|
|
bool* destination = va_arg(*out_pointers, bool*);
|
|
if(js_value_maybe_assign_default(declaration, source, destination, sizeof(bool))) break;
|
|
if(!mjs_is_boolean(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected bool");
|
|
*destination = mjs_get_bool(mjs, *source);
|
|
break;
|
|
}
|
|
case JsValueTypeString: {
|
|
const char** destination = va_arg(*out_pointers, const char**);
|
|
if(js_value_maybe_assign_default(declaration, source, destination, sizeof(const char*)))
|
|
break;
|
|
if(!mjs_is_string(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected string");
|
|
buffer[*buffer_index] = *source;
|
|
*destination = mjs_get_string(mjs, &buffer[*buffer_index], NULL);
|
|
(*buffer_index)++;
|
|
break;
|
|
}
|
|
|
|
// Types with children
|
|
case JsValueTypeArgs: {
|
|
furi_check(source == JS_VAL_PARSE_SOURCE_ARGS);
|
|
size_t args_provided = mjs_nargs(mjs);
|
|
for(size_t i = 0; i < declaration->n_children; i++) {
|
|
mjs_val_t arg = (i < args_provided) ? mjs_arg(mjs, i) : MJS_UNDEFINED;
|
|
JsValueParseStatus status = js_value_parse_va(
|
|
mjs, declaration, flags, &arg, buffer, buffer_index, out_pointers);
|
|
if(status != JsValueParseStatusOk)
|
|
PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "argument %zu: ", i);
|
|
}
|
|
break;
|
|
}
|
|
case JsValueTypeEnum: {
|
|
if(declaration->permit_null && js_value_is_null_or_undefined(source)) {
|
|
js_value_assign_enum_val(
|
|
out_pointers, declaration->enum_size, declaration->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 < declaration->n_children; i++) {
|
|
const JsValueDeclaration* child = &declaration->children[i];
|
|
furi_check(child->type == JsValueTypeEnumValue);
|
|
if(strcmp(str, child->enum_string_value) == 0) {
|
|
js_value_assign_enum_val(
|
|
out_pointers, declaration->enum_size, child->enum_value);
|
|
match_found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!match_found)
|
|
PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected one of permitted strings");
|
|
} else {
|
|
PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected string");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JsValueTypeObject: {
|
|
if(!mjs_is_object(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected object");
|
|
for(size_t i = 0; i < declaration->n_children; i++) {
|
|
const JsValueDeclaration* child = &declaration->children[i];
|
|
mjs_val_t field = mjs_get(mjs, *source, child->object_field_name, ~0);
|
|
JsValueParseStatus status =
|
|
js_value_parse_va(mjs, child, flags, &field, buffer, buffer_index, out_pointers);
|
|
if(status != JsValueParseStatusOk)
|
|
PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "field %s: ", child->object_field_name);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JsValueTypeEnumValue:
|
|
furi_crash();
|
|
}
|
|
|
|
return JsValueParseStatusOk;
|
|
}
|
|
|
|
JsValueParseStatus js_value_parse(
|
|
struct mjs* mjs,
|
|
const JsValueDeclaration* 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(declaration);
|
|
furi_check(buffer);
|
|
|
|
// These are asserts and not checks because argument parsing has to be fast.
|
|
// People bitbang I2C from JS.
|
|
furi_assert(js_value_declaration_valid(declaration));
|
|
furi_assert(buf_size == js_value_buffer_size(declaration));
|
|
furi_assert(n_c_vals == js_value_resulting_c_values_count(declaration));
|
|
|
|
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;
|
|
}
|