mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-24 03:29:57 -07:00
Merge remote-tracking branch 'OFW/dev' into dev
This commit is contained in:
@@ -11,6 +11,7 @@ App(
|
||||
"js_app.c",
|
||||
"js_modules.c",
|
||||
"js_thread.c",
|
||||
"js_value.c",
|
||||
"plugin_api/app_api_table.cpp",
|
||||
"views/console_view.c",
|
||||
"modules/js_flipper.c",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include "js_thread_i.h"
|
||||
#include "js_value.h"
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <flipper_application/plugins/plugin_manager.h>
|
||||
#include <flipper_application/plugins/composite_resolver.h>
|
||||
|
||||
291
applications/system/js_app/js_value.c
Normal file
291
applications/system/js_app/js_value.c
Normal file
@@ -0,0 +1,291 @@
|
||||
#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;
|
||||
}
|
||||
212
applications/system/js_app/js_value.h
Normal file
212
applications/system/js_app/js_value.h
Normal file
@@ -0,0 +1,212 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include "js_modules.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
// literal types
|
||||
JsValueTypeAny, //<! Literal term
|
||||
JsValueTypeAnyArray, //<! Literal term, after ensuring that it's an array
|
||||
JsValueTypeAnyObject, //<! Literal term, after ensuring that it's an object
|
||||
JsValueTypeFunction, //<! Literal term, after ensuring that it's a function
|
||||
|
||||
// primitive types
|
||||
JsValueTypeRawPointer, //<! Unchecked `void*`
|
||||
JsValueTypeInt32, //<! Number cast to `int32_t`
|
||||
JsValueTypeDouble, //<! Number cast to `double`
|
||||
JsValueTypeString, //<! Any string cast to `const char*`
|
||||
JsValueTypeBool, //<! Bool cast to `bool`
|
||||
|
||||
// types with children
|
||||
JsValueTypeEnum, //<! String with predefined possible values cast to a C enum via a mapping
|
||||
JsValueTypeObject, //<! Object with predefined recursive fields cast to several C values
|
||||
|
||||
JsValueTypeMask = 0xff,
|
||||
|
||||
// enum sizes
|
||||
JsValueTypeEnumSize1 = (1 << 8),
|
||||
JsValueTypeEnumSize2 = (2 << 8),
|
||||
JsValueTypeEnumSize4 = (4 << 8),
|
||||
|
||||
// flags
|
||||
JsValueTypePermitNull = (1 << 16), //<! If the value is absent, assign default value
|
||||
} JsValueType;
|
||||
|
||||
#define JS_VALUE_TYPE_ENUM_SIZE(x) ((x) << 8)
|
||||
|
||||
typedef struct {
|
||||
const char* string_value;
|
||||
size_t num_value;
|
||||
} JsValueEnumVariant;
|
||||
|
||||
typedef union {
|
||||
void* ptr_val;
|
||||
int32_t int32_val;
|
||||
double double_val;
|
||||
const char* str_val;
|
||||
size_t enum_val;
|
||||
bool bool_val;
|
||||
} JsValueDefaultValue;
|
||||
|
||||
typedef struct JsValueObjectField JsValueObjectField;
|
||||
|
||||
typedef struct {
|
||||
JsValueType type;
|
||||
JsValueDefaultValue default_value;
|
||||
|
||||
size_t n_children;
|
||||
union {
|
||||
const JsValueEnumVariant* enum_variants;
|
||||
const JsValueObjectField* object_fields;
|
||||
};
|
||||
} JsValueDeclaration;
|
||||
|
||||
struct JsValueObjectField {
|
||||
const char* field_name;
|
||||
const JsValueDeclaration* value;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
size_t n_children;
|
||||
const JsValueDeclaration* arguments;
|
||||
} JsValueArguments;
|
||||
|
||||
#define JS_VALUE_ENUM(c_type, variants) \
|
||||
{ \
|
||||
.type = JsValueTypeEnum | JS_VALUE_TYPE_ENUM_SIZE(sizeof(c_type)), \
|
||||
.n_children = COUNT_OF(variants), \
|
||||
.enum_variants = variants, \
|
||||
}
|
||||
|
||||
#define JS_VALUE_ENUM_W_DEFAULT(c_type, variants, default) \
|
||||
{ \
|
||||
.type = JsValueTypeEnum | JsValueTypePermitNull | \
|
||||
JS_VALUE_TYPE_ENUM_SIZE(sizeof(c_type)), \
|
||||
.default_value.enum_val = default, \
|
||||
.n_children = COUNT_OF(variants), \
|
||||
.enum_variants = variants, \
|
||||
}
|
||||
|
||||
#define JS_VALUE_OBJECT(fields) \
|
||||
{ \
|
||||
.type = JsValueTypeObject, \
|
||||
.n_children = COUNT_OF(fields), \
|
||||
.object_fields = fields, \
|
||||
}
|
||||
|
||||
#define JS_VALUE_OBJECT_W_DEFAULTS(fields) \
|
||||
{ \
|
||||
.type = JsValueTypeObject | JsValueTypePermitNull, \
|
||||
.n_children = COUNT_OF(fields), \
|
||||
.object_fields = fields, \
|
||||
}
|
||||
|
||||
#define JS_VALUE_SIMPLE(t) {.type = t}
|
||||
|
||||
#define JS_VALUE_SIMPLE_W_DEFAULT(t, name, val) \
|
||||
{.type = (t) | JsValueTypePermitNull, .default_value.name = (val)}
|
||||
|
||||
#define JS_VALUE_ARGS(args) \
|
||||
{ \
|
||||
.n_children = COUNT_OF(args), \
|
||||
.arguments = args, \
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
JsValueParseFlagNone = 0,
|
||||
JsValueParseFlagReturnOnError =
|
||||
(1
|
||||
<< 0), //<! Sets mjs error string to a description of the parsing error and returns from the JS function
|
||||
} JsValueParseFlag;
|
||||
|
||||
typedef enum {
|
||||
JsValueParseStatusOk, //<! Parsing completed successfully
|
||||
JsValueParseStatusJsError, //<! Parsing failed due to incorrect JS input
|
||||
} JsValueParseStatus;
|
||||
|
||||
typedef enum {
|
||||
JsValueParseSourceValue,
|
||||
JsValueParseSourceArguments,
|
||||
} JsValueParseSource;
|
||||
|
||||
typedef struct {
|
||||
JsValueParseSource source;
|
||||
union {
|
||||
const JsValueDeclaration* value_decl;
|
||||
const JsValueArguments* argument_decl;
|
||||
};
|
||||
} JsValueParseDeclaration;
|
||||
|
||||
#define JS_VALUE_PARSE_SOURCE_VALUE(declaration) \
|
||||
((JsValueParseDeclaration){.source = JsValueParseSourceValue, .value_decl = declaration})
|
||||
#define JS_VALUE_PARSE_SOURCE_ARGS(declaration) \
|
||||
((JsValueParseDeclaration){ \
|
||||
.source = JsValueParseSourceArguments, .argument_decl = declaration})
|
||||
|
||||
/**
|
||||
* @brief Determines the size of the buffer array of `mjs_val_t`s that needs to
|
||||
* be passed to `js_value_parse`.
|
||||
*/
|
||||
size_t js_value_buffer_size(const JsValueParseDeclaration declaration);
|
||||
|
||||
/**
|
||||
* @brief Converts a JS value into a series of C values.
|
||||
*
|
||||
* @param[in] mjs mJS instance pointer
|
||||
* @param[in] declaration Declaration for the input value. Chooses where the
|
||||
* values are to be fetched from (an `mjs_val_t` or
|
||||
* function arguments)
|
||||
* @param[in] flags See the corresponding enum.
|
||||
* @param[out] buffer Temporary buffer for values that need to live
|
||||
* longer than the function call. To determine the
|
||||
* size of the buffer, use `js_value_buffer_size`.
|
||||
* Values parsed by this function will become invalid
|
||||
* when this buffer goes out of scope.
|
||||
* @param[in] buf_size Number of entries in the temporary buffer (i.e.
|
||||
* `COUNT_OF`, not `sizeof`).
|
||||
* @param[in] source Source JS value that needs to be converted. May be
|
||||
* NULL if `declaration.source` is
|
||||
* `JsValueParseSourceArguments`.
|
||||
* @param[in] n_c_vals Number of output C values
|
||||
* @param[out] ... Pointers to output C values. The order in which
|
||||
* these values are populated corresponds to the order
|
||||
* in which the values are defined in the declaration.
|
||||
*
|
||||
* @returns Parsing status
|
||||
*/
|
||||
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,
|
||||
...);
|
||||
|
||||
#define JS_VALUE_PARSE(mjs, declaration, flags, status_ptr, value_ptr, ...) \
|
||||
void* _args[] = {__VA_ARGS__}; \
|
||||
size_t _n_args = COUNT_OF(_args); \
|
||||
size_t _temp_buf_len = js_value_buffer_size(declaration); \
|
||||
mjs_val_t _temp_buffer[_temp_buf_len]; \
|
||||
*(status_ptr) = js_value_parse( \
|
||||
mjs, declaration, flags, _temp_buffer, _temp_buf_len, value_ptr, _n_args, __VA_ARGS__);
|
||||
|
||||
#define JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, declaration, ...) \
|
||||
JsValueParseStatus _status; \
|
||||
JS_VALUE_PARSE( \
|
||||
mjs, \
|
||||
JS_VALUE_PARSE_SOURCE_ARGS(declaration), \
|
||||
JsValueParseFlagReturnOnError, \
|
||||
&_status, \
|
||||
NULL, \
|
||||
__VA_ARGS__); \
|
||||
if(_status != JsValueParseStatusOk) return;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user