1
mirror of https://github.com/DarkFlippers/unleashed-firmware.git synced 2025-12-12 04:34:43 +04:00

[FL-3963] Move JS modules to new arg parser (#4139)

* 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

* js: move to new arg parser

* style: format

---------

Co-authored-by: hedger <hedger@users.noreply.github.com>
This commit is contained in:
Anna Antonenko
2025-04-05 03:17:30 +04:00
committed by GitHub
parent 7192c9e68b
commit dac1457f0a
13 changed files with 525 additions and 620 deletions

View File

@@ -7,6 +7,10 @@
#include <flipper_application/plugins/plugin_manager.h>
#include <flipper_application/plugins/composite_resolver.h>
#ifdef __cplusplus
extern "C" {
#endif
#define PLUGIN_APP_ID "js"
#define PLUGIN_API_VERSION 1
@@ -64,226 +68,6 @@ typedef enum {
JsForeignMagic_JsEventLoopContract,
} JsForeignMagic;
// Are you tired of your silly little JS+C glue code functions being 75%
// argument validation code and 25% actual logic? Introducing: ASS (Argument
// Schema for Scripts)! ASS is a set of macros that reduce the typical
// boilerplate code of "check argument count, get arguments, validate arguments,
// extract C values from arguments" down to just one line!
/**
* When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies
* that the function requires exactly as many arguments as were specified.
*/
#define JS_EXACTLY ==
/**
* When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies
* that the function requires at least as many arguments as were specified.
*/
#define JS_AT_LEAST >=
typedef struct {
const char* name;
size_t value;
} JsEnumMapping;
#define JS_ENUM_MAP(var_name, ...) \
static const JsEnumMapping var_name##_mapping[] = { \
{NULL, sizeof(var_name)}, \
__VA_ARGS__, \
{NULL, 0}, \
};
typedef struct {
const char* name;
size_t offset;
} JsObjectMapping;
#define JS_OBJ_MAP(var_name, ...) \
static const JsObjectMapping var_name##_mapping[] = { \
__VA_ARGS__, \
{NULL, 0}, \
};
typedef struct {
void* out;
int (*validator)(mjs_val_t);
void (*converter)(struct mjs*, mjs_val_t*, void* out, const void* extra);
const char* expected_type;
bool (*extended_validator)(struct mjs*, mjs_val_t, const void* extra);
const void* extra_data;
} _js_arg_decl;
static inline void _js_to_int32(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) {
UNUSED(extra);
*(int32_t*)out = mjs_get_int32(mjs, *in);
}
#define JS_ARG_INT32(out) ((_js_arg_decl){out, mjs_is_number, _js_to_int32, "number", NULL, NULL})
static inline void _js_to_ptr(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) {
UNUSED(extra);
*(void**)out = mjs_get_ptr(mjs, *in);
}
#define JS_ARG_PTR(out) \
((_js_arg_decl){out, mjs_is_foreign, _js_to_ptr, "opaque pointer", NULL, NULL})
static inline void _js_to_string(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) {
UNUSED(extra);
*(const char**)out = mjs_get_string(mjs, in, NULL);
}
#define JS_ARG_STR(out) ((_js_arg_decl){out, mjs_is_string, _js_to_string, "string", NULL, NULL})
static inline void _js_to_bool(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) {
UNUSED(extra);
*(bool*)out = !!mjs_get_bool(mjs, *in);
}
#define JS_ARG_BOOL(out) ((_js_arg_decl){out, mjs_is_boolean, _js_to_bool, "boolean", NULL, NULL})
static inline void _js_passthrough(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) {
UNUSED(extra);
UNUSED(mjs);
*(mjs_val_t*)out = *in;
}
#define JS_ARG_ANY(out) ((_js_arg_decl){out, NULL, _js_passthrough, "any", NULL, NULL})
#define JS_ARG_OBJ(out) ((_js_arg_decl){out, mjs_is_object, _js_passthrough, "any", NULL, NULL})
#define JS_ARG_FN(out) \
((_js_arg_decl){out, mjs_is_function, _js_passthrough, "function", NULL, NULL})
#define JS_ARG_ARR(out) ((_js_arg_decl){out, mjs_is_array, _js_passthrough, "array", NULL, NULL})
static inline bool _js_validate_struct(struct mjs* mjs, mjs_val_t val, const void* extra) {
JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra;
JsForeignMagic struct_magic = *(JsForeignMagic*)mjs_get_ptr(mjs, val);
return struct_magic == expected_magic;
}
#define JS_ARG_STRUCT(type, out) \
((_js_arg_decl){ \
out, \
mjs_is_foreign, \
_js_to_ptr, \
#type, \
_js_validate_struct, \
(void*)JsForeignMagic##_##type})
static inline bool _js_validate_obj_w_struct(struct mjs* mjs, mjs_val_t val, const void* extra) {
JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra;
JsForeignMagic struct_magic = *(JsForeignMagic*)JS_GET_INST(mjs, val);
return struct_magic == expected_magic;
}
#define JS_ARG_OBJ_WITH_STRUCT(type, out) \
((_js_arg_decl){ \
out, \
mjs_is_object, \
_js_passthrough, \
#type, \
_js_validate_obj_w_struct, \
(void*)JsForeignMagic##_##type})
static inline bool _js_validate_enum(struct mjs* mjs, mjs_val_t val, const void* extra) {
for(const JsEnumMapping* mapping = (JsEnumMapping*)extra + 1; mapping->name; mapping++)
if(strcmp(mapping->name, mjs_get_string(mjs, &val, NULL)) == 0) return true;
return false;
}
static inline void
_js_convert_enum(struct mjs* mjs, mjs_val_t* val, void* out, const void* extra) {
const JsEnumMapping* mapping = (JsEnumMapping*)extra;
size_t size = mapping->value; // get enum size from first entry
for(mapping++; mapping->name; mapping++) {
if(strcmp(mapping->name, mjs_get_string(mjs, val, NULL)) == 0) {
if(size == 1)
*(uint8_t*)out = mapping->value;
else if(size == 2)
*(uint16_t*)out = mapping->value;
else if(size == 4)
*(uint32_t*)out = mapping->value;
else if(size == 8)
*(uint64_t*)out = mapping->value;
return;
}
}
// unreachable, thanks to _js_validate_enum
}
#define JS_ARG_ENUM(var_name, name) \
((_js_arg_decl){ \
&var_name, \
mjs_is_string, \
_js_convert_enum, \
name " enum", \
_js_validate_enum, \
var_name##_mapping})
static inline bool _js_validate_object(struct mjs* mjs, mjs_val_t val, const void* extra) {
for(const JsObjectMapping* mapping = (JsObjectMapping*)extra; mapping->name; mapping++)
if(mjs_get(mjs, val, mapping->name, ~0) == MJS_UNDEFINED) return false;
return true;
}
static inline void
_js_convert_object(struct mjs* mjs, mjs_val_t* val, void* out, const void* extra) {
const JsObjectMapping* mapping = (JsObjectMapping*)extra;
for(; mapping->name; mapping++) {
mjs_val_t field_val = mjs_get(mjs, *val, mapping->name, ~0);
*(mjs_val_t*)((uint8_t*)out + mapping->offset) = field_val;
}
}
#define JS_ARG_OBJECT(var_name, name) \
((_js_arg_decl){ \
&var_name, \
mjs_is_object, \
_js_convert_object, \
name " object", \
_js_validate_object, \
var_name##_mapping})
/**
* @brief Validates and converts a JS value with a declarative interface
*
* Example: `int32_t my_value; JS_CONVERT_OR_RETURN(mjs, &mjs_val, JS_ARG_INT32(&my_value), "value source");`
*
* @warning This macro executes `return;` by design in case of a validation failure
*/
#define JS_CONVERT_OR_RETURN(mjs, value, decl, source, ...) \
if(decl.validator) \
if(!decl.validator(*value)) \
JS_ERROR_AND_RETURN( \
mjs, \
MJS_BAD_ARGS_ERROR, \
source ": expected %s", \
##__VA_ARGS__, \
decl.expected_type); \
if(decl.extended_validator) \
if(!decl.extended_validator(mjs, *value, decl.extra_data)) \
JS_ERROR_AND_RETURN( \
mjs, \
MJS_BAD_ARGS_ERROR, \
source ": expected %s", \
##__VA_ARGS__, \
decl.expected_type); \
decl.converter(mjs, value, decl.out, decl.extra_data);
//-V:JS_FETCH_ARGS_OR_RETURN:1008
/**
* @brief Fetches and validates the arguments passed to a JS function
*
* Example: `int32_t my_arg; JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&my_arg));`
*
* @warning This macro executes `return;` by design in case of an argument count
* mismatch or a validation failure
*/
#define JS_FETCH_ARGS_OR_RETURN(mjs, arg_operator, ...) \
_js_arg_decl _js_args[] = {__VA_ARGS__}; \
int _js_arg_cnt = COUNT_OF(_js_args); \
mjs_val_t _js_arg_vals[_js_arg_cnt]; \
if(!(mjs_nargs(mjs) arg_operator _js_arg_cnt)) \
JS_ERROR_AND_RETURN( \
mjs, \
MJS_BAD_ARGS_ERROR, \
"expected %s%d arguments, got %d", \
#arg_operator, \
_js_arg_cnt, \
mjs_nargs(mjs)); \
for(int _i = 0; _i < _js_arg_cnt; _i++) { \
_js_arg_vals[_i] = mjs_arg(mjs, _i); \
JS_CONVERT_OR_RETURN(mjs, &_js_arg_vals[_i], _js_args[_i], "argument %d", _i); \
}
/**
* @brief Prepends an error, sets the JS return value to `undefined` and returns
* from the C function
@@ -358,3 +142,7 @@ void js_does_sdk_support(struct mjs* mjs);
* @brief `checkSdkFeatures` function
*/
void js_check_sdk_features(struct mjs* mjs);
#ifdef __cplusplus
}
#endif