Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
20294a8
js: value destructuring and tests
portasynthinca3 Mar 3, 2025
f51d726
js: temporary fix to see size impact
portasynthinca3 Mar 3, 2025
ca0fc5f
js_val: reduce code size 1
portasynthinca3 Mar 4, 2025
74700ce
i may be stupid.
portasynthinca3 Mar 4, 2025
cca650e
test: js_value args
portasynthinca3 Mar 4, 2025
11b82c0
Revert "js: temporary fix to see size impact"
portasynthinca3 Mar 4, 2025
4a69443
pvs: silence warnings
portasynthinca3 Mar 4, 2025
35d500d
style: formatting
portasynthinca3 Mar 4, 2025
4d9d1c6
pvs: silence warnings?
portasynthinca3 Mar 4, 2025
1c3f2e3
pvs: silence warnings??
portasynthinca3 Mar 4, 2025
e59eed4
js_value: redesign declaration types for less code
portasynthinca3 Mar 5, 2025
d6a46f0
js: temporary fix to see size impact
portasynthinca3 Mar 5, 2025
541ea2a
style: formatting
portasynthinca3 Mar 5, 2025
245800e
pvs: fix helpful warnings
portasynthinca3 Mar 5, 2025
e1f7a78
js_value: reduce .rodata size
portasynthinca3 Mar 5, 2025
7a9b0a9
pvs: fix helpful warning
portasynthinca3 Mar 5, 2025
bccc4f4
js_value: reduce code size 1
portasynthinca3 Mar 5, 2025
6b6ea5d
fix build error
portasynthinca3 Mar 5, 2025
b5a1f95
style: format
portasynthinca3 Mar 5, 2025
eb075f7
Revert "js: temporary fix to see size impact"
portasynthinca3 Mar 5, 2025
544193e
style: format
portasynthinca3 Mar 5, 2025
49e5649
js: move to new arg parser
portasynthinca3 Mar 5, 2025
db899d4
style: format
portasynthinca3 Mar 5, 2025
99c64be
Merge branch 'dev' into portasynthinca3/3963-js-use-js_value
hedger Apr 1, 2025
71dff65
Merge branch 'dev' into portasynthinca3/3963-js-use-js_value
hedger Apr 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions applications/system/js_app/js_modules.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,15 @@ static JsSdkCompatStatus
return JsSdkCompatStatusCompatible;
}

#define JS_SDK_COMPAT_ARGS \
int32_t major, minor; \
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&major), JS_ARG_INT32(&minor));
static const JsValueDeclaration js_sdk_version_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeInt32),
JS_VALUE_SIMPLE(JsValueTypeInt32),
};
static const JsValueArguments js_sdk_version_args = JS_VALUE_ARGS(js_sdk_version_arg_list);

void js_sdk_compatibility_status(struct mjs* mjs) {
JS_SDK_COMPAT_ARGS;
int32_t major, minor;
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_version_args, &major, &minor);
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
switch(status) {
case JsSdkCompatStatusCompatible:
Expand All @@ -223,7 +226,8 @@ void js_sdk_compatibility_status(struct mjs* mjs) {
}

void js_is_sdk_compatible(struct mjs* mjs) {
JS_SDK_COMPAT_ARGS;
int32_t major, minor;
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_version_args, &major, &minor);
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
mjs_return(mjs, mjs_mk_boolean(mjs, status == JsSdkCompatStatusCompatible));
}
Expand All @@ -246,7 +250,8 @@ static bool js_internal_compat_ask_user(const char* message) {
}

void js_check_sdk_compatibility(struct mjs* mjs) {
JS_SDK_COMPAT_ARGS;
int32_t major, minor;
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_version_args, &major, &minor);
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
if(status != JsSdkCompatStatusCompatible) {
FURI_LOG_E(
Expand Down Expand Up @@ -300,15 +305,20 @@ static bool js_internal_supports_all_of(struct mjs* mjs, mjs_val_t feature_arr)
return true;
}

static const JsValueDeclaration js_sdk_features_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeAnyArray),
};
static const JsValueArguments js_sdk_features_args = JS_VALUE_ARGS(js_sdk_features_arg_list);

void js_does_sdk_support(struct mjs* mjs) {
mjs_val_t features;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features));
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_features_args, &features);
mjs_return(mjs, mjs_mk_boolean(mjs, js_internal_supports_all_of(mjs, features)));
}

void js_check_sdk_features(struct mjs* mjs) {
mjs_val_t features;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features));
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_features_args, &features);
if(!js_internal_supports_all_of(mjs, features)) {
FURI_LOG_E(TAG, "Script requests unsupported features");

Expand Down
228 changes: 8 additions & 220 deletions applications/system/js_app/js_modules.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
19 changes: 8 additions & 11 deletions applications/system/js_app/js_thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -198,18 +198,15 @@ static void js_require(struct mjs* mjs) {
}

static void js_parse_int(struct mjs* mjs) {
static const JsValueDeclaration js_parse_int_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeString),
JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 10),
};
static const JsValueArguments js_parse_int_args = JS_VALUE_ARGS(js_parse_int_arg_list);

const char* str;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_AT_LEAST, JS_ARG_STR(&str));

int32_t base = 10;
if(mjs_nargs(mjs) >= 2) {
mjs_val_t base_arg = mjs_arg(mjs, 1);
if(!mjs_is_number(base_arg)) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Base must be a number");
mjs_return(mjs, MJS_UNDEFINED);
}
base = mjs_get_int(mjs, base_arg);
}
int32_t base;
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_parse_int_args, &str, &base);

int32_t num;
if(strint_to_int32(str, NULL, &num, base) != StrintParseNoError) {
Expand Down
8 changes: 8 additions & 0 deletions applications/system/js_app/js_thread_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#include <mjs_primitive_public.h>
#include <mjs_array_buf_public.h>

#ifdef __cplusplus
extern "C" {
#endif

#define INST_PROP_NAME "_"

typedef enum {
Expand All @@ -23,3 +27,7 @@ bool js_delay_with_flags(struct mjs* mjs, uint32_t time);
void js_flags_set(struct mjs* mjs, uint32_t flags);

uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout);

#ifdef __cplusplus
}
#endif
Loading
Loading