From 20294a82b99ea1b9feae4b61e8a91eafda86ae27 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Mon, 3 Mar 2025 06:44:08 +0400 Subject: [PATCH 01/26] js: value destructuring and tests --- .../debug/unit_tests/tests/js/js_test.c | 264 +++++++++++++++++ .../debug/unit_tests/unit_test_api_table_i.h | 13 + applications/system/js_app/application.fam | 1 + applications/system/js_app/js_modules.h | 1 + applications/system/js_app/js_value.c | 274 ++++++++++++++++++ applications/system/js_app/js_value.h | 136 +++++++++ 6 files changed, 689 insertions(+) create mode 100644 applications/system/js_app/js_value.c create mode 100644 applications/system/js_app/js_value.h diff --git a/applications/debug/unit_tests/tests/js/js_test.c b/applications/debug/unit_tests/tests/js/js_test.c index af590e89956..eb4ddf865f3 100644 --- a/applications/debug/unit_tests/tests/js/js_test.c +++ b/applications/debug/unit_tests/tests/js/js_test.c @@ -6,9 +6,12 @@ #include #include +#include #include +#define TAG "JsUnitTests" + #define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js") typedef enum { @@ -73,7 +76,268 @@ MU_TEST(js_test_storage) { js_test_run(JS_SCRIPT_PATH("storage")); } +static void js_value_test_compatibility_matrix(struct mjs* mjs) { + static const JsValueType types[] = { + JsValueTypeAny, + JsValueTypeAnyArray, + JsValueTypeAnyObject, + JsValueTypeFunction, + JsValueTypeRawPointer, + JsValueTypeInt32, + JsValueTypeDouble, + JsValueTypeString, + JsValueTypeBool, + }; + + mjs_val_t values[] = { + mjs_mk_undefined(), + mjs_mk_foreign(mjs, (void*)0xDEADBEEF), + mjs_mk_array(mjs), + mjs_mk_object(mjs), + mjs_mk_number(mjs, 123.456), + mjs_mk_string(mjs, "test", ~0, false), + mjs_mk_boolean(mjs, true), + }; + +// for proper matrix formatting and better readability +#define YES true +#define NO_ false + static const int success_matrix[COUNT_OF(types)][COUNT_OF(values)] = { + // types: + {YES, YES, YES, YES, YES, YES, YES}, // any + {NO_, NO_, YES, NO_, NO_, NO_, NO_}, // array + {NO_, NO_, YES, YES, NO_, NO_, NO_}, // obj + {NO_, NO_, NO_, NO_, NO_, NO_, NO_}, // fn + {NO_, YES, NO_, NO_, NO_, NO_, NO_}, // ptr + {NO_, NO_, NO_, NO_, YES, NO_, NO_}, // int32 + {NO_, NO_, NO_, NO_, YES, NO_, NO_}, // double + {NO_, NO_, NO_, NO_, NO_, YES, NO_}, // str + {NO_, NO_, NO_, NO_, NO_, NO_, YES}, // bool + //und ptr arr obj num str bool <- values + }; +#undef NO_ +#undef YES + + for(size_t i = 0; i < COUNT_OF(types); i++) { + for(size_t j = 0; j < COUNT_OF(values); j++) { + const JsValueDeclaration declaration = { + .type = types[i], + .permit_null = false, + .n_children = 0, + }; + // we only care about the status, not the result. double has the largest size out of + // all the results + uint8_t result[sizeof(double)]; + JsValueParseStatus status; + JS_VALUE_PARSE(mjs, &declaration, JsValueParseFlagNone, &status, &values[j], result); + if((status == JsValueParseStatusOk) != success_matrix[i][j]) { + FURI_LOG_E(TAG, "type %zu, value %zu", i, j); + mu_fail("see serial logs"); + } + } + } +} + +static void js_value_test_literal(struct mjs* mjs) { + static const JsValueType types[] = { + JsValueTypeAny, + JsValueTypeAnyArray, + JsValueTypeAnyObject, + }; + + mjs_val_t values[] = { + mjs_mk_undefined(), + mjs_mk_array(mjs), + mjs_mk_object(mjs), + }; + + mu_assert_int_eq(COUNT_OF(types), COUNT_OF(values)); + for(size_t i = 0; i < COUNT_OF(types); i++) { + const JsValueDeclaration declaration = { + .type = types[i], + .permit_null = false, + .n_children = 0, + }; + mjs_val_t result; + JsValueParseStatus status; + JS_VALUE_PARSE(mjs, &declaration, JsValueParseFlagNone, &status, &values[i], &result); + mu_assert_int_eq(JsValueParseStatusOk, status); + mu_assert(result == values[i], "wrong result"); + } +} + +static void js_value_test_primitive( + struct mjs* mjs, + JsValueType type, + const void* c_value, + size_t c_value_size, + mjs_val_t js_val) { + const JsValueDeclaration declaration = { + .type = type, + .permit_null = false, + .n_children = 0, + }; + uint8_t result[c_value_size]; + JsValueParseStatus status; + JS_VALUE_PARSE(mjs, &declaration, JsValueParseFlagNone, &status, &js_val, result); + mu_assert_int_eq(JsValueParseStatusOk, status); + if(type == JsValueTypeString) { + const char* result_str = *(const char**)&result; + mu_assert_string_eq(c_value, result_str); + } else { + mu_assert_mem_eq(c_value, result, c_value_size); + } +} + +static void js_value_test_primitives(struct mjs* mjs) { + int32_t i32 = 123; + js_value_test_primitive(mjs, JsValueTypeInt32, &i32, sizeof(i32), mjs_mk_number(mjs, i32)); + + double dbl = 123.456; + js_value_test_primitive(mjs, JsValueTypeDouble, &dbl, sizeof(dbl), mjs_mk_number(mjs, dbl)); + + const char* str = "test"; + js_value_test_primitive( + mjs, JsValueTypeString, str, strlen(str) + 1, mjs_mk_string(mjs, str, ~0, false)); + + bool boolean = true; + js_value_test_primitive( + mjs, JsValueTypeBool, &boolean, sizeof(boolean), mjs_mk_boolean(mjs, boolean)); +} + +static uint32_t + js_value_test_enum(struct mjs* mjs, const JsValueDeclaration* decl, const char* value) { + mjs_val_t str = mjs_mk_string(mjs, value, ~0, false); + uint32_t result; + furi_check(decl->enum_size == sizeof(result)); + JsValueParseStatus status; + JS_VALUE_PARSE(mjs, decl, JsValueParseFlagNone, &status, &str, &result); + if(status != JsValueParseStatusOk) return 0; + return result; +} + +static void js_value_test_enums(struct mjs* mjs) { + static const JsValueDeclaration enum_1_variants[] = { + JS_VALUE_ENUM_VARIANT("variant 1", 1), + JS_VALUE_ENUM_VARIANT("variant 2", 2), + JS_VALUE_ENUM_VARIANT("variant 3", 3), + }; + static const JsValueDeclaration enum_1 = { + .type = JsValueTypeEnum, + .enum_size = sizeof(uint32_t), + JS_VALUE_CHILDREN(enum_1_variants), + }; + + static const JsValueDeclaration enum_2_variants[] = { + JS_VALUE_ENUM_VARIANT("read", 4), + JS_VALUE_ENUM_VARIANT("write", 8), + }; + static const JsValueDeclaration enum_2 = { + .type = JsValueTypeEnum, + .enum_size = sizeof(uint32_t), + JS_VALUE_CHILDREN(enum_2_variants), + }; + + mu_assert_int_eq(1, js_value_test_enum(mjs, &enum_1, "variant 1")); + mu_assert_int_eq(2, js_value_test_enum(mjs, &enum_1, "variant 2")); + mu_assert_int_eq(3, js_value_test_enum(mjs, &enum_1, "variant 3")); + mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_1, "not a thing")); + + mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 1")); + mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 2")); + mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 3")); + mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "not a thing")); + mu_assert_int_eq(4, js_value_test_enum(mjs, &enum_2, "read")); + mu_assert_int_eq(8, js_value_test_enum(mjs, &enum_2, "write")); +} + +static void js_value_test_object(struct mjs* mjs) { + static const JsValueDeclaration enum_variants[] = { + JS_VALUE_ENUM_VARIANT("variant 1", 1), + JS_VALUE_ENUM_VARIANT("variant 2", 2), + JS_VALUE_ENUM_VARIANT("variant 3", 3), + }; + static const JsValueDeclaration fields[] = { + {.type = JsValueTypeInt32, .object_field_name = "int"}, + {.type = JsValueTypeString, .object_field_name = "str"}, + {.type = JsValueTypeEnum, + .object_field_name = "enum", + .enum_size = sizeof(uint32_t), + JS_VALUE_CHILDREN(enum_variants)}}; + static const JsValueDeclaration object_decl = { + .type = JsValueTypeObject, + JS_VALUE_CHILDREN(fields), + }; + + mjs_val_t object = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, object) { + JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false)); + JS_FIELD("int", mjs_mk_number(mjs, 123)); + JS_FIELD("enum", mjs_mk_string(mjs, "variant 2", ~0, false)); + } + + const char* result_str; + int32_t result_int; + uint32_t result_enum; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, + &object_decl, + JsValueParseFlagNone, + &status, + &object, + &result_int, + &result_str, + &result_enum); + mu_assert_int_eq(JsValueParseStatusOk, status); + mu_assert_string_eq("Helloooo!", result_str); + mu_assert_int_eq(123, result_int); + mu_assert_int_eq(2, result_enum); +} + +static void js_value_test_default(struct mjs* mjs) { + static const JsValueDeclaration fields[] = { + {.type = JsValueTypeInt32, + .permit_null = true, + .default_value = {.int32_val = 123}, + .object_field_name = "int"}, + {.type = JsValueTypeString, .object_field_name = "str"}, + }; + static const JsValueDeclaration object_decl = { + .type = JsValueTypeObject, + JS_VALUE_CHILDREN(fields), + }; + + mjs_val_t object = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, object) { + JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false)); + JS_FIELD("int", mjs_mk_undefined()); + } + + const char* result_str; + int32_t result_int; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, &object_decl, JsValueParseFlagNone, &status, &object, &result_int, &result_str); + mu_assert_string_eq("Helloooo!", result_str); + mu_assert_int_eq(123, result_int); +} + +MU_TEST(js_value_test) { + struct mjs* mjs = mjs_create(NULL); + + js_value_test_compatibility_matrix(mjs); + js_value_test_literal(mjs); + js_value_test_primitives(mjs); + js_value_test_enums(mjs); + js_value_test_object(mjs); + js_value_test_default(mjs); + + mjs_destroy(mjs); +} + MU_TEST_SUITE(test_js) { + MU_RUN_TEST(js_value_test); MU_RUN_TEST(js_test_basic); MU_RUN_TEST(js_test_math); MU_RUN_TEST(js_test_event_loop); diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index 10b08902256..c682c56279a 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -8,6 +8,7 @@ #include #include #include +#include static constexpr auto unit_tests_api_table = sort(create_array_t( API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)), @@ -38,4 +39,16 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( JsThread*, (const char* script_path, JsThreadCallback callback, void* context)), API_METHOD(js_thread_stop, void, (JsThread * worker)), + API_METHOD(js_value_buffer_size, size_t, (const JsValueDeclaration* declaration)), + API_METHOD( + js_value_parse, + JsValueParseStatus, + (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, + ...)), API_VARIABLE(PB_Main_msg, PB_Main_msg_t))); diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index db1521b9d82..66ec221ec75 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -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", diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h index 2babe231ef5..130ee975c14 100644 --- a/applications/system/js_app/js_modules.h +++ b/applications/system/js_app/js_modules.h @@ -2,6 +2,7 @@ #include #include "js_thread_i.h" +#include "js_value.h" #include #include #include diff --git a/applications/system/js_app/js_value.c b/applications/system/js_app/js_value.c new file mode 100644 index 00000000000..75304952f39 --- /dev/null +++ b/applications/system/js_app/js_value.c @@ -0,0 +1,274 @@ +#include "js_value.h" +#include + +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; +} diff --git a/applications/system/js_app/js_value.h b/applications/system/js_app/js_value.h new file mode 100644 index 00000000000..d5509d04e75 --- /dev/null +++ b/applications/system/js_app/js_value.h @@ -0,0 +1,136 @@ +#pragma once + +#include +#include "js_modules.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + JsValueTypeArgs, // Date: Mon, 3 Mar 2025 15:34:36 +0400 Subject: [PATCH 02/26] js: temporary fix to see size impact --- .../debug/unit_tests/unit_test_api_table_i.h | 12 ------------ applications/system/js_app/application.fam | 3 +++ targets/f18/api_symbols.csv | 19 +++++++++++++++++-- targets/f7/api_symbols.csv | 19 +++++++++++++++++-- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index c682c56279a..28d918c5654 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -39,16 +39,4 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( JsThread*, (const char* script_path, JsThreadCallback callback, void* context)), API_METHOD(js_thread_stop, void, (JsThread * worker)), - API_METHOD(js_value_buffer_size, size_t, (const JsValueDeclaration* declaration)), - API_METHOD( - js_value_parse, - JsValueParseStatus, - (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, - ...)), API_VARIABLE(PB_Main_msg, PB_Main_msg_t))); diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 66ec221ec75..9c6f41cd381 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -17,6 +17,9 @@ App( "modules/js_flipper.c", "modules/js_tests.c", ], + sdk_headers=[ + "js_value.h", + ], ) App( diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 2074fa13120..a9c6668a333 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,82.1,, +Version,+,82.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -40,6 +40,7 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, +Header,+,applications/system/js_app/js_value.h,, Header,+,lib/bit_lib/bit_lib.h,, Header,+,lib/ble_profile/extra_profiles/hid_profile.h,, Header,+,lib/ble_profile/extra_services/hid_service.h,, @@ -1815,6 +1816,20 @@ Function,-,j1f,float,float Function,-,jn,double,"int, double" Function,-,jnf,float,"int, float" Function,-,jrand48,long,unsigned short[3] +Function,-,js_check_sdk_compatibility,void,mjs* +Function,-,js_check_sdk_features,void,mjs* +Function,-,js_delay_with_flags,_Bool,"mjs*, uint32_t" +Function,-,js_does_sdk_support,void,mjs* +Function,-,js_flags_set,void,"mjs*, uint32_t" +Function,-,js_flags_wait,uint32_t,"mjs*, uint32_t, uint32_t" +Function,-,js_is_sdk_compatible,void,mjs* +Function,-,js_module_get,void*,"JsModules*, const char*" +Function,-,js_module_require,mjs_val_t,"JsModules*, const char*, size_t" +Function,-,js_modules_create,JsModules*,"mjs*, CompositeApiResolver*" +Function,-,js_modules_destroy,void,JsModules* +Function,-,js_sdk_compatibility_status,void,mjs* +Function,+,js_value_buffer_size,size_t,const JsValueDeclaration* +Function,+,js_value_parse,JsValueParseStatus,"mjs*, const JsValueDeclaration*, JsValueParseFlag, mjs_val_t*, size_t, mjs_val_t*, size_t, ..." Function,+,keys_dict_add_key,_Bool,"KeysDict*, const uint8_t*, size_t" Function,+,keys_dict_alloc,KeysDict*,"const char*, KeysDictMode, size_t" Function,+,keys_dict_check_presence,_Bool,const char* @@ -2361,13 +2376,13 @@ Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" -Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1168a6eea53..af060ab80d3 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,82.1,, +Version,+,82.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -41,6 +41,7 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, +Header,+,applications/system/js_app/js_value.h,, Header,+,lib/bit_lib/bit_lib.h,, Header,+,lib/ble_profile/extra_profiles/hid_profile.h,, Header,+,lib/ble_profile/extra_services/hid_service.h,, @@ -2218,6 +2219,20 @@ Function,-,j1f,float,float Function,-,jn,double,"int, double" Function,-,jnf,float,"int, float" Function,-,jrand48,long,unsigned short[3] +Function,-,js_check_sdk_compatibility,void,mjs* +Function,-,js_check_sdk_features,void,mjs* +Function,-,js_delay_with_flags,_Bool,"mjs*, uint32_t" +Function,-,js_does_sdk_support,void,mjs* +Function,-,js_flags_set,void,"mjs*, uint32_t" +Function,-,js_flags_wait,uint32_t,"mjs*, uint32_t, uint32_t" +Function,-,js_is_sdk_compatible,void,mjs* +Function,-,js_module_get,void*,"JsModules*, const char*" +Function,-,js_module_require,mjs_val_t,"JsModules*, const char*, size_t" +Function,-,js_modules_create,JsModules*,"mjs*, CompositeApiResolver*" +Function,-,js_modules_destroy,void,JsModules* +Function,-,js_sdk_compatibility_status,void,mjs* +Function,+,js_value_buffer_size,size_t,const JsValueDeclaration* +Function,+,js_value_parse,JsValueParseStatus,"mjs*, const JsValueDeclaration*, JsValueParseFlag, mjs_val_t*, size_t, mjs_val_t*, size_t, ..." Function,+,keys_dict_add_key,_Bool,"KeysDict*, const uint8_t*, size_t" Function,+,keys_dict_alloc,KeysDict*,"const char*, KeysDictMode, size_t" Function,+,keys_dict_check_presence,_Bool,const char* @@ -2999,13 +3014,13 @@ Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" -Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" From ca0fc5fa8a2da718d9df19d11df3b0f79b78d867 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 4 Mar 2025 08:05:21 +0400 Subject: [PATCH 03/26] js_val: reduce code size 1 --- applications/system/js_app/js_value.c | 41 ++++++++++++++------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/applications/system/js_app/js_value.c b/applications/system/js_app/js_value.c index 75304952f39..d82c12b32c3 100644 --- a/applications/system/js_app/js_value.c +++ b/applications/system/js_app/js_value.c @@ -120,64 +120,64 @@ static JsValueParseStatus js_value_parse_va( mjs_val_t* buffer, size_t* buffer_index, va_list* out_pointers) { + // fetch out pointer + void* destination = NULL; + if(declaration->type != JsValueTypeEnum && declaration->type != JsValueTypeObject) + declaration = va_arg(*out_pointers, void*); + switch(declaration->type) { // Literal terms case JsValueTypeAny: { - *va_arg(*out_pointers, mjs_val_t*) = *source; + *(mjs_val_t*)destination = *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; + *(mjs_val_t*)destination = *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; + *(mjs_val_t*)destination = *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; + *(mjs_val_t*)destination = *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); + *(void**)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); + *(int32_t*)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); + *(double*)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); + *(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); + *(const char**)destination = mjs_get_string(mjs, &buffer[*buffer_index], NULL); (*buffer_index)++; break; } @@ -254,11 +254,14 @@ JsValueParseStatus js_value_parse( 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)); +#ifdef JS_VAL_DEBUG + furi_check(js_value_declaration_valid(declaration)); + 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_declaration_valid); + UNUSED(js_value_resulting_c_values_count); +#endif va_list out_pointers; va_start(out_pointers, n_c_vals); From 74700ce793bcd5295f9e8dcc704c7608756ce9f1 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 4 Mar 2025 08:08:14 +0400 Subject: [PATCH 04/26] i may be stupid. --- applications/system/js_app/js_value.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/system/js_app/js_value.c b/applications/system/js_app/js_value.c index d82c12b32c3..067ea01ac55 100644 --- a/applications/system/js_app/js_value.c +++ b/applications/system/js_app/js_value.c @@ -123,7 +123,7 @@ static JsValueParseStatus js_value_parse_va( // fetch out pointer void* destination = NULL; if(declaration->type != JsValueTypeEnum && declaration->type != JsValueTypeObject) - declaration = va_arg(*out_pointers, void*); + destination = va_arg(*out_pointers, void*); switch(declaration->type) { // Literal terms From cca650ef14714a6cdef0152346a1a4c323e6a581 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 4 Mar 2025 08:37:45 +0400 Subject: [PATCH 05/26] test: js_value args --- .../debug/unit_tests/tests/js/js_test.c | 33 +++++++++++++++++++ applications/system/js_app/js_value.c | 3 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/applications/debug/unit_tests/tests/js/js_test.c b/applications/debug/unit_tests/tests/js/js_test.c index eb4ddf865f3..0476da9613f 100644 --- a/applications/debug/unit_tests/tests/js/js_test.c +++ b/applications/debug/unit_tests/tests/js/js_test.c @@ -323,6 +323,38 @@ static void js_value_test_default(struct mjs* mjs) { mu_assert_int_eq(123, result_int); } +static void js_value_test_args_fn(struct mjs* mjs) { + static const JsValueDeclaration args[] = { + {.type = JsValueTypeInt32}, + {.type = JsValueTypeInt32}, + {.type = JsValueTypeInt32}, + }; + static const JsValueDeclaration declaration = { + .type = JsValueTypeArgs, + JS_VALUE_CHILDREN(args), + }; + + int32_t a, b, c; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &declaration, &a, &b, &c); + + // mjs_apply reverses argument order + mu_assert_int_eq(-420, a); + mu_assert_int_eq(123, b); + mu_assert_int_eq(456, c); +} + +static void js_value_test_args(struct mjs* mjs) { + mjs_val_t function = MJS_MK_FN(js_value_test_args_fn); + + mjs_val_t result; + mjs_val_t args[] = { + mjs_mk_number(mjs, 123), + mjs_mk_number(mjs, 456), + mjs_mk_number(mjs, -420), + }; + mu_assert_int_eq(MJS_OK, mjs_apply(mjs, &result, function, MJS_UNDEFINED, COUNT_OF(args), args)); +} + MU_TEST(js_value_test) { struct mjs* mjs = mjs_create(NULL); @@ -332,6 +364,7 @@ MU_TEST(js_value_test) { js_value_test_enums(mjs); js_value_test_object(mjs); js_value_test_default(mjs); + js_value_test_args(mjs); mjs_destroy(mjs); } diff --git a/applications/system/js_app/js_value.c b/applications/system/js_app/js_value.c index 067ea01ac55..e88f08c4f6e 100644 --- a/applications/system/js_app/js_value.c +++ b/applications/system/js_app/js_value.c @@ -188,8 +188,9 @@ static JsValueParseStatus js_value_parse_va( 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; + const JsValueDeclaration* child = &declaration->children[i]; JsValueParseStatus status = js_value_parse_va( - mjs, declaration, flags, &arg, buffer, buffer_index, out_pointers); + mjs, child, flags, &arg, buffer, buffer_index, out_pointers); if(status != JsValueParseStatusOk) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "argument %zu: ", i); } From 11b82c0692991d2aea2ac3795584eec693db181f Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 4 Mar 2025 08:38:03 +0400 Subject: [PATCH 06/26] Revert "js: temporary fix to see size impact" This reverts commit f51d726dbafc4300d3552020de1c3b8f9ecd3ac1. --- .../debug/unit_tests/unit_test_api_table_i.h | 12 ++++++++++++ applications/system/js_app/application.fam | 3 --- targets/f18/api_symbols.csv | 19 ++----------------- targets/f7/api_symbols.csv | 19 ++----------------- 4 files changed, 16 insertions(+), 37 deletions(-) diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index 28d918c5654..c682c56279a 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -39,4 +39,16 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( JsThread*, (const char* script_path, JsThreadCallback callback, void* context)), API_METHOD(js_thread_stop, void, (JsThread * worker)), + API_METHOD(js_value_buffer_size, size_t, (const JsValueDeclaration* declaration)), + API_METHOD( + js_value_parse, + JsValueParseStatus, + (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, + ...)), API_VARIABLE(PB_Main_msg, PB_Main_msg_t))); diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 9c6f41cd381..66ec221ec75 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -17,9 +17,6 @@ App( "modules/js_flipper.c", "modules/js_tests.c", ], - sdk_headers=[ - "js_value.h", - ], ) App( diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index a9c6668a333..2074fa13120 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,82.2,, +Version,+,82.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -40,7 +40,6 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, -Header,+,applications/system/js_app/js_value.h,, Header,+,lib/bit_lib/bit_lib.h,, Header,+,lib/ble_profile/extra_profiles/hid_profile.h,, Header,+,lib/ble_profile/extra_services/hid_service.h,, @@ -1816,20 +1815,6 @@ Function,-,j1f,float,float Function,-,jn,double,"int, double" Function,-,jnf,float,"int, float" Function,-,jrand48,long,unsigned short[3] -Function,-,js_check_sdk_compatibility,void,mjs* -Function,-,js_check_sdk_features,void,mjs* -Function,-,js_delay_with_flags,_Bool,"mjs*, uint32_t" -Function,-,js_does_sdk_support,void,mjs* -Function,-,js_flags_set,void,"mjs*, uint32_t" -Function,-,js_flags_wait,uint32_t,"mjs*, uint32_t, uint32_t" -Function,-,js_is_sdk_compatible,void,mjs* -Function,-,js_module_get,void*,"JsModules*, const char*" -Function,-,js_module_require,mjs_val_t,"JsModules*, const char*, size_t" -Function,-,js_modules_create,JsModules*,"mjs*, CompositeApiResolver*" -Function,-,js_modules_destroy,void,JsModules* -Function,-,js_sdk_compatibility_status,void,mjs* -Function,+,js_value_buffer_size,size_t,const JsValueDeclaration* -Function,+,js_value_parse,JsValueParseStatus,"mjs*, const JsValueDeclaration*, JsValueParseFlag, mjs_val_t*, size_t, mjs_val_t*, size_t, ..." Function,+,keys_dict_add_key,_Bool,"KeysDict*, const uint8_t*, size_t" Function,+,keys_dict_alloc,KeysDict*,"const char*, KeysDictMode, size_t" Function,+,keys_dict_check_presence,_Bool,const char* @@ -2376,13 +2361,13 @@ Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" -Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index af060ab80d3..1168a6eea53 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,82.2,, +Version,+,82.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -41,7 +41,6 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, -Header,+,applications/system/js_app/js_value.h,, Header,+,lib/bit_lib/bit_lib.h,, Header,+,lib/ble_profile/extra_profiles/hid_profile.h,, Header,+,lib/ble_profile/extra_services/hid_service.h,, @@ -2219,20 +2218,6 @@ Function,-,j1f,float,float Function,-,jn,double,"int, double" Function,-,jnf,float,"int, float" Function,-,jrand48,long,unsigned short[3] -Function,-,js_check_sdk_compatibility,void,mjs* -Function,-,js_check_sdk_features,void,mjs* -Function,-,js_delay_with_flags,_Bool,"mjs*, uint32_t" -Function,-,js_does_sdk_support,void,mjs* -Function,-,js_flags_set,void,"mjs*, uint32_t" -Function,-,js_flags_wait,uint32_t,"mjs*, uint32_t, uint32_t" -Function,-,js_is_sdk_compatible,void,mjs* -Function,-,js_module_get,void*,"JsModules*, const char*" -Function,-,js_module_require,mjs_val_t,"JsModules*, const char*, size_t" -Function,-,js_modules_create,JsModules*,"mjs*, CompositeApiResolver*" -Function,-,js_modules_destroy,void,JsModules* -Function,-,js_sdk_compatibility_status,void,mjs* -Function,+,js_value_buffer_size,size_t,const JsValueDeclaration* -Function,+,js_value_parse,JsValueParseStatus,"mjs*, const JsValueDeclaration*, JsValueParseFlag, mjs_val_t*, size_t, mjs_val_t*, size_t, ..." Function,+,keys_dict_add_key,_Bool,"KeysDict*, const uint8_t*, size_t" Function,+,keys_dict_alloc,KeysDict*,"const char*, KeysDictMode, size_t" Function,+,keys_dict_check_presence,_Bool,const char* @@ -3014,13 +2999,13 @@ Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" -Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" From 4a69443fd9660cceca329054aa7628b98732bc52 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 4 Mar 2025 08:40:01 +0400 Subject: [PATCH 07/26] pvs: silence warnings --- applications/debug/unit_tests/tests/minunit.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/applications/debug/unit_tests/tests/minunit.h b/applications/debug/unit_tests/tests/minunit.h index 9ca3bb403db..de8c8109842 100644 --- a/applications/debug/unit_tests/tests/minunit.h +++ b/applications/debug/unit_tests/tests/minunit.h @@ -396,6 +396,8 @@ void minunit_printf_warning(const char* format, ...); return; \ } else { minunit_print_progress(); }) +//-V:mu_assert_string_eq:526 + #define mu_assert_string_eq(expected, result) \ MU__SAFE_BLOCK( \ const char* minunit_tmp_e = expected; const char* minunit_tmp_r = result; \ @@ -416,6 +418,9 @@ void minunit_printf_warning(const char* format, ...); return; \ } else { minunit_print_progress(); }) +//-V:mu_assert_mem_eq:526 +//-V:mu_assert_mem_eq:547 + #define mu_assert_mem_eq(expected, result, size) \ MU__SAFE_BLOCK( \ const void* minunit_tmp_e = expected; const void* minunit_tmp_r = result; \ From 35d500d5cb230ce6200b98995b60bab9a2f1dcd7 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 4 Mar 2025 08:41:51 +0400 Subject: [PATCH 08/26] style: formatting --- applications/debug/unit_tests/tests/js/js_test.c | 3 ++- applications/system/js_app/js_value.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/applications/debug/unit_tests/tests/js/js_test.c b/applications/debug/unit_tests/tests/js/js_test.c index 0476da9613f..666062819a4 100644 --- a/applications/debug/unit_tests/tests/js/js_test.c +++ b/applications/debug/unit_tests/tests/js/js_test.c @@ -352,7 +352,8 @@ static void js_value_test_args(struct mjs* mjs) { mjs_mk_number(mjs, 456), mjs_mk_number(mjs, -420), }; - mu_assert_int_eq(MJS_OK, mjs_apply(mjs, &result, function, MJS_UNDEFINED, COUNT_OF(args), args)); + mu_assert_int_eq( + MJS_OK, mjs_apply(mjs, &result, function, MJS_UNDEFINED, COUNT_OF(args), args)); } MU_TEST(js_value_test) { diff --git a/applications/system/js_app/js_value.c b/applications/system/js_app/js_value.c index e88f08c4f6e..3833722f472 100644 --- a/applications/system/js_app/js_value.c +++ b/applications/system/js_app/js_value.c @@ -189,8 +189,8 @@ static JsValueParseStatus js_value_parse_va( for(size_t i = 0; i < declaration->n_children; i++) { mjs_val_t arg = (i < args_provided) ? mjs_arg(mjs, i) : MJS_UNDEFINED; const JsValueDeclaration* child = &declaration->children[i]; - JsValueParseStatus status = js_value_parse_va( - mjs, child, flags, &arg, buffer, buffer_index, out_pointers); + JsValueParseStatus status = + js_value_parse_va(mjs, child, flags, &arg, buffer, buffer_index, out_pointers); if(status != JsValueParseStatusOk) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "argument %zu: ", i); } From 4d9d1c6b878b0ebb93cc9e203b31ac62af763599 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 4 Mar 2025 08:45:15 +0400 Subject: [PATCH 09/26] pvs: silence warnings? --- applications/debug/unit_tests/tests/minunit.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/applications/debug/unit_tests/tests/minunit.h b/applications/debug/unit_tests/tests/minunit.h index de8c8109842..14418d4da54 100644 --- a/applications/debug/unit_tests/tests/minunit.h +++ b/applications/debug/unit_tests/tests/minunit.h @@ -418,8 +418,7 @@ void minunit_printf_warning(const char* format, ...); return; \ } else { minunit_print_progress(); }) -//-V:mu_assert_mem_eq:526 -//-V:mu_assert_mem_eq:547 +//-V:mu_assert_mem_eq:526,547 #define mu_assert_mem_eq(expected, result, size) \ MU__SAFE_BLOCK( \ From 1c3f2e355569b803d1ccf91edc617afd8af9de12 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 4 Mar 2025 08:51:32 +0400 Subject: [PATCH 10/26] pvs: silence warnings?? --- applications/debug/unit_tests/tests/minunit.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/debug/unit_tests/tests/minunit.h b/applications/debug/unit_tests/tests/minunit.h index 14418d4da54..c854c4673bc 100644 --- a/applications/debug/unit_tests/tests/minunit.h +++ b/applications/debug/unit_tests/tests/minunit.h @@ -396,7 +396,7 @@ void minunit_printf_warning(const char* format, ...); return; \ } else { minunit_print_progress(); }) -//-V:mu_assert_string_eq:526 +//-V:mu_assert_string_eq:526, 547 #define mu_assert_string_eq(expected, result) \ MU__SAFE_BLOCK( \ @@ -418,7 +418,7 @@ void minunit_printf_warning(const char* format, ...); return; \ } else { minunit_print_progress(); }) -//-V:mu_assert_mem_eq:526,547 +//-V:mu_assert_mem_eq:526 #define mu_assert_mem_eq(expected, result, size) \ MU__SAFE_BLOCK( \ From e59eed4d30bb469ffb1ff9095be7fdd4895519fc Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 5 Mar 2025 08:14:29 +0400 Subject: [PATCH 11/26] js_value: redesign declaration types for less code --- .../debug/unit_tests/tests/js/js_test.c | 115 ++++------ .../debug/unit_tests/unit_test_api_table_i.h | 4 +- applications/system/js_app/js_value.c | 211 ++++++++---------- applications/system/js_app/js_value.h | 130 ++++++++--- 4 files changed, 248 insertions(+), 212 deletions(-) diff --git a/applications/debug/unit_tests/tests/js/js_test.c b/applications/debug/unit_tests/tests/js/js_test.c index 666062819a4..b1f8819fa87 100644 --- a/applications/debug/unit_tests/tests/js/js_test.c +++ b/applications/debug/unit_tests/tests/js/js_test.c @@ -102,8 +102,8 @@ static void js_value_test_compatibility_matrix(struct mjs* mjs) { // for proper matrix formatting and better readability #define YES true #define NO_ false - static const int success_matrix[COUNT_OF(types)][COUNT_OF(values)] = { - // types: + static const bool success_matrix[COUNT_OF(types)][COUNT_OF(values)] = { + // types: {YES, YES, YES, YES, YES, YES, YES}, // any {NO_, NO_, YES, NO_, NO_, NO_, NO_}, // array {NO_, NO_, YES, YES, NO_, NO_, NO_}, // obj @@ -113,6 +113,7 @@ static void js_value_test_compatibility_matrix(struct mjs* mjs) { {NO_, NO_, NO_, NO_, YES, NO_, NO_}, // double {NO_, NO_, NO_, NO_, NO_, YES, NO_}, // str {NO_, NO_, NO_, NO_, NO_, NO_, YES}, // bool + // //und ptr arr obj num str bool <- values }; #undef NO_ @@ -122,14 +123,13 @@ static void js_value_test_compatibility_matrix(struct mjs* mjs) { for(size_t j = 0; j < COUNT_OF(values); j++) { const JsValueDeclaration declaration = { .type = types[i], - .permit_null = false, .n_children = 0, }; // we only care about the status, not the result. double has the largest size out of // all the results uint8_t result[sizeof(double)]; JsValueParseStatus status; - JS_VALUE_PARSE(mjs, &declaration, JsValueParseFlagNone, &status, &values[j], result); + JS_VALUE_PARSE(mjs, JS_VALUE_PARSE_SOURCE_VALUE(&declaration), JsValueParseFlagNone, &status, &values[j], result); if((status == JsValueParseStatusOk) != success_matrix[i][j]) { FURI_LOG_E(TAG, "type %zu, value %zu", i, j); mu_fail("see serial logs"); @@ -155,12 +155,11 @@ static void js_value_test_literal(struct mjs* mjs) { for(size_t i = 0; i < COUNT_OF(types); i++) { const JsValueDeclaration declaration = { .type = types[i], - .permit_null = false, .n_children = 0, }; mjs_val_t result; JsValueParseStatus status; - JS_VALUE_PARSE(mjs, &declaration, JsValueParseFlagNone, &status, &values[i], &result); + JS_VALUE_PARSE(mjs, JS_VALUE_PARSE_SOURCE_VALUE(&declaration), JsValueParseFlagNone, &status, &values[i], &result); mu_assert_int_eq(JsValueParseStatusOk, status); mu_assert(result == values[i], "wrong result"); } @@ -174,12 +173,11 @@ static void js_value_test_primitive( mjs_val_t js_val) { const JsValueDeclaration declaration = { .type = type, - .permit_null = false, .n_children = 0, }; uint8_t result[c_value_size]; JsValueParseStatus status; - JS_VALUE_PARSE(mjs, &declaration, JsValueParseFlagNone, &status, &js_val, result); + JS_VALUE_PARSE(mjs, JS_VALUE_PARSE_SOURCE_VALUE(&declaration), JsValueParseFlagNone, &status, &js_val, result); mu_assert_int_eq(JsValueParseStatusOk, status); if(type == JsValueTypeString) { const char* result_str = *(const char**)&result; @@ -209,34 +207,25 @@ static uint32_t js_value_test_enum(struct mjs* mjs, const JsValueDeclaration* decl, const char* value) { mjs_val_t str = mjs_mk_string(mjs, value, ~0, false); uint32_t result; - furi_check(decl->enum_size == sizeof(result)); JsValueParseStatus status; - JS_VALUE_PARSE(mjs, decl, JsValueParseFlagNone, &status, &str, &result); + JS_VALUE_PARSE(mjs, JS_VALUE_PARSE_SOURCE_VALUE(decl), JsValueParseFlagNone, &status, &str, &result); if(status != JsValueParseStatusOk) return 0; return result; } static void js_value_test_enums(struct mjs* mjs) { - static const JsValueDeclaration enum_1_variants[] = { - JS_VALUE_ENUM_VARIANT("variant 1", 1), - JS_VALUE_ENUM_VARIANT("variant 2", 2), - JS_VALUE_ENUM_VARIANT("variant 3", 3), - }; - static const JsValueDeclaration enum_1 = { - .type = JsValueTypeEnum, - .enum_size = sizeof(uint32_t), - JS_VALUE_CHILDREN(enum_1_variants), + static const JsValueEnumVariant enum_1_variants[] = { + {"variant 1", 1}, + {"variant 2", 2}, + {"variant 3", 3}, }; + static const JsValueDeclaration enum_1 = JS_VALUE_ENUM(uint32_t, enum_1_variants); - static const JsValueDeclaration enum_2_variants[] = { - JS_VALUE_ENUM_VARIANT("read", 4), - JS_VALUE_ENUM_VARIANT("write", 8), - }; - static const JsValueDeclaration enum_2 = { - .type = JsValueTypeEnum, - .enum_size = sizeof(uint32_t), - JS_VALUE_CHILDREN(enum_2_variants), + static const JsValueEnumVariant enum_2_variants[] = { + {"read", 4}, + {"write", 8}, }; + static const JsValueDeclaration enum_2 = JS_VALUE_ENUM(uint32_t, enum_2_variants); mu_assert_int_eq(1, js_value_test_enum(mjs, &enum_1, "variant 1")); mu_assert_int_eq(2, js_value_test_enum(mjs, &enum_1, "variant 2")); @@ -252,22 +241,23 @@ static void js_value_test_enums(struct mjs* mjs) { } static void js_value_test_object(struct mjs* mjs) { - static const JsValueDeclaration enum_variants[] = { - JS_VALUE_ENUM_VARIANT("variant 1", 1), - JS_VALUE_ENUM_VARIANT("variant 2", 2), - JS_VALUE_ENUM_VARIANT("variant 3", 3), + static const JsValueDeclaration int_decl = JS_VALUE_SIMPLE(JsValueTypeInt32); + + static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString); + + static const JsValueEnumVariant enum_variants[] = { + {"variant 1", 1}, + {"variant 2", 2}, + {"variant 3", 3}, }; - static const JsValueDeclaration fields[] = { - {.type = JsValueTypeInt32, .object_field_name = "int"}, - {.type = JsValueTypeString, .object_field_name = "str"}, - {.type = JsValueTypeEnum, - .object_field_name = "enum", - .enum_size = sizeof(uint32_t), - JS_VALUE_CHILDREN(enum_variants)}}; - static const JsValueDeclaration object_decl = { - .type = JsValueTypeObject, - JS_VALUE_CHILDREN(fields), + static const JsValueDeclaration enum_decl = JS_VALUE_ENUM(uint32_t, enum_variants); + + static const JsValueObjectField fields[] = { + {"int", &int_decl}, + {"str", &str_decl}, + {"enum", &enum_decl}, }; + static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields); mjs_val_t object = mjs_mk_object(mjs); JS_ASSIGN_MULTI(mjs, object) { @@ -282,7 +272,7 @@ static void js_value_test_object(struct mjs* mjs) { JsValueParseStatus status; JS_VALUE_PARSE( mjs, - &object_decl, + JS_VALUE_PARSE_SOURCE_VALUE(&object_decl), JsValueParseFlagNone, &status, &object, @@ -296,17 +286,14 @@ static void js_value_test_object(struct mjs* mjs) { } static void js_value_test_default(struct mjs* mjs) { - static const JsValueDeclaration fields[] = { - {.type = JsValueTypeInt32, - .permit_null = true, - .default_value = {.int32_val = 123}, - .object_field_name = "int"}, - {.type = JsValueTypeString, .object_field_name = "str"}, - }; - static const JsValueDeclaration object_decl = { - .type = JsValueTypeObject, - JS_VALUE_CHILDREN(fields), + static const JsValueDeclaration int_decl = JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 123); + static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString); + + static const JsValueObjectField fields[] = { + {"int", &int_decl}, + {"str", &str_decl}, }; + static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields); mjs_val_t object = mjs_mk_object(mjs); JS_ASSIGN_MULTI(mjs, object) { @@ -318,29 +305,25 @@ static void js_value_test_default(struct mjs* mjs) { int32_t result_int; JsValueParseStatus status; JS_VALUE_PARSE( - mjs, &object_decl, JsValueParseFlagNone, &status, &object, &result_int, &result_str); + mjs, JS_VALUE_PARSE_SOURCE_VALUE(&object_decl), JsValueParseFlagNone, &status, &object, &result_int, &result_str); mu_assert_string_eq("Helloooo!", result_str); mu_assert_int_eq(123, result_int); } static void js_value_test_args_fn(struct mjs* mjs) { - static const JsValueDeclaration args[] = { - {.type = JsValueTypeInt32}, - {.type = JsValueTypeInt32}, - {.type = JsValueTypeInt32}, - }; - static const JsValueDeclaration declaration = { - .type = JsValueTypeArgs, - JS_VALUE_CHILDREN(args), + static const JsValueDeclaration arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_SIMPLE(JsValueTypeInt32), }; + static const JsValueArguments args = JS_VALUE_ARGS(arg_list); int32_t a, b, c; - JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &declaration, &a, &b, &c); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &args, &a, &b, &c); - // mjs_apply reverses argument order - mu_assert_int_eq(-420, a); - mu_assert_int_eq(123, b); - mu_assert_int_eq(456, c); + mu_assert_int_eq(123, a); + mu_assert_int_eq(456, b); + mu_assert_int_eq(-420, c); } static void js_value_test_args(struct mjs* mjs) { diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index c682c56279a..03b89dfd32d 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -39,12 +39,12 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( JsThread*, (const char* script_path, JsThreadCallback callback, void* context)), API_METHOD(js_thread_stop, void, (JsThread * worker)), - API_METHOD(js_value_buffer_size, size_t, (const JsValueDeclaration* declaration)), + API_METHOD(js_value_buffer_size, size_t, (const JsValueParseDeclaration declaration)), API_METHOD( js_value_parse, JsValueParseStatus, (struct mjs * mjs, - const JsValueDeclaration* declaration, + const JsValueParseDeclaration declaration, JsValueParseFlag flags, mjs_val_t* buffer, size_t buf_size, diff --git a/applications/system/js_app/js_value.c b/applications/system/js_app/js_value.c index 3833722f472..dc45f3f60e8 100644 --- a/applications/system/js_app/js_value.c +++ b/applications/system/js_app/js_value.c @@ -1,83 +1,56 @@ #include "js_value.h" #include -size_t js_value_buffer_size(const JsValueDeclaration* declaration) { - JsValueType type = declaration->type; +#ifdef APP_UNIT_TESTS +#define JS_VAL_DEBUG +#endif - if(type == JsValueTypeString) return 1; +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 == 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; - } + if(type == JsValueTypeString) return 1; - return 0; -} + 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; + } -static size_t js_value_resulting_c_values_count(const JsValueDeclaration* declaration) { - JsValueType type = declaration->type; + return 0; - if(type == JsValueTypeObject) { + } else { + const JsValueArguments* arg_decl = declaration.argument_decl; size_t total = 0; - for(size_t i = 0; i < declaration->n_children; i++) { - total += js_value_resulting_c_values_count(&declaration->children[i]); - } + 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; } - - 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; - } +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; - // 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(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; } - 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; - } + return 1; - // Literal types can't have default values - if(type == JsValueTypeAny || type == JsValueTypeAnyArray || type == JsValueTypeAnyObject || - type == JsValueTypeFunction) { - if(declaration->permit_null) return false; + } 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; } - - // All other types can't have children - return declaration->n_children == 0; } #define PREPEND_JS_ERROR_AND_RETURN(mjs, flags, ...) \ @@ -87,13 +60,14 @@ static bool js_value_declaration_valid(const JsValueDeclaration* declaration) { 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 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) { @@ -105,7 +79,7 @@ static bool js_value_maybe_assign_default( mjs_val_t* val_ptr, void* destination, size_t size) { - if(declaration->permit_null && js_value_is_null_or_undefined(val_ptr)) { + if((declaration->type & JsValueTypePermitNull) && js_value_is_null_or_undefined(val_ptr)) { memcpy(destination, &declaration->default_value, size); return true; } @@ -114,18 +88,34 @@ static bool js_value_maybe_assign_default( static JsValueParseStatus js_value_parse_va( struct mjs* mjs, - const JsValueDeclaration* declaration, + const JsValueParseDeclaration declaration, JsValueParseFlag flags, mjs_val_t* source, mjs_val_t* buffer, size_t* buffer_index, va_list* out_pointers) { - // fetch out pointer + 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(declaration->type != JsValueTypeEnum && declaration->type != JsValueTypeObject) + if(type_noflags != JsValueTypeObject) destination = va_arg(*out_pointers, void*); - switch(declaration->type) { + switch(type_noflags) { // Literal terms case JsValueTypeAny: { *(mjs_val_t*)destination = *source; @@ -149,31 +139,31 @@ static JsValueParseStatus js_value_parse_va( // Primitive types case JsValueTypeRawPointer: { - if(js_value_maybe_assign_default(declaration, source, destination, sizeof(void*))) break; + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(void*))) break; if(!mjs_is_foreign(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected pointer"); *(void**)destination = mjs_get_ptr(mjs, *source); break; } case JsValueTypeInt32: { - if(js_value_maybe_assign_default(declaration, source, destination, sizeof(int32_t))) break; + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(int32_t))) break; if(!mjs_is_number(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected number"); *(int32_t*)destination = mjs_get_int32(mjs, *source); break; } case JsValueTypeDouble: { - if(js_value_maybe_assign_default(declaration, source, destination, sizeof(double))) break; + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(double))) break; if(!mjs_is_number(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected number"); *(double*)destination = mjs_get_double(mjs, *source); break; } case JsValueTypeBool: { - if(js_value_maybe_assign_default(declaration, source, destination, sizeof(bool))) break; + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(bool))) break; if(!mjs_is_boolean(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected bool"); *(bool*)destination = mjs_get_bool(mjs, *source); break; } case JsValueTypeString: { - if(js_value_maybe_assign_default(declaration, source, destination, sizeof(const char*))) + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(const char*))) break; if(!mjs_is_string(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected string"); buffer[*buffer_index] = *source; @@ -183,39 +173,27 @@ static JsValueParseStatus js_value_parse_va( } // 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; - const JsValueDeclaration* child = &declaration->children[i]; - JsValueParseStatus status = - js_value_parse_va(mjs, child, 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); + 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 < 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); + 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_ERROR_AND_RETURN(mjs, flags, "expected one of permitted strings"); + } else { PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected string"); } @@ -223,19 +201,23 @@ static JsValueParseStatus js_value_parse_va( } 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); + if(!(is_null_but_allowed || mjs_is_object(*source))) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected 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, child, flags, &field, buffer, buffer_index, out_pointers); + 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: ", child->object_field_name); + PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "field %s: ", field->field_name); } break; } - case JsValueTypeEnumValue: + case JsValueTypeMask: + case JsValueTypeEnumSize1: + case JsValueTypeEnumSize2: + case JsValueTypeEnumSize4: + case JsValueTypePermitNull: furi_crash(); } @@ -244,7 +226,7 @@ static JsValueParseStatus js_value_parse_va( JsValueParseStatus js_value_parse( struct mjs* mjs, - const JsValueDeclaration* declaration, + const JsValueParseDeclaration declaration, JsValueParseFlag flags, mjs_val_t* buffer, size_t buf_size, @@ -252,15 +234,20 @@ JsValueParseStatus js_value_parse( size_t n_c_vals, ...) { furi_check(mjs); - furi_check(declaration); 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(js_value_declaration_valid(declaration)); 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_declaration_valid); UNUSED(js_value_resulting_c_values_count); #endif diff --git a/applications/system/js_app/js_value.h b/applications/system/js_app/js_value.h index d5509d04e75..3239a50f2b9 100644 --- a/applications/system/js_app/js_value.h +++ b/applications/system/js_app/js_value.h @@ -8,52 +8,106 @@ extern "C" { #endif typedef enum { - JsValueTypeArgs, // Date: Wed, 5 Mar 2025 08:17:32 +0400 Subject: [PATCH 12/26] js: temporary fix to see size impact --- .../debug/unit_tests/unit_test_api_table_i.h | 12 ------------ applications/system/js_app/application.fam | 3 +++ targets/f18/api_symbols.csv | 19 +++++++++++++++++-- targets/f7/api_symbols.csv | 19 +++++++++++++++++-- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index 03b89dfd32d..28d918c5654 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -39,16 +39,4 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( JsThread*, (const char* script_path, JsThreadCallback callback, void* context)), API_METHOD(js_thread_stop, void, (JsThread * worker)), - API_METHOD(js_value_buffer_size, size_t, (const JsValueParseDeclaration declaration)), - API_METHOD( - js_value_parse, - JsValueParseStatus, - (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, - ...)), API_VARIABLE(PB_Main_msg, PB_Main_msg_t))); diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 66ec221ec75..9c6f41cd381 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -17,6 +17,9 @@ App( "modules/js_flipper.c", "modules/js_tests.c", ], + sdk_headers=[ + "js_value.h", + ], ) App( diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 2074fa13120..5e8278f61b7 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,82.1,, +Version,+,82.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -40,6 +40,7 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, +Header,+,applications/system/js_app/js_value.h,, Header,+,lib/bit_lib/bit_lib.h,, Header,+,lib/ble_profile/extra_profiles/hid_profile.h,, Header,+,lib/ble_profile/extra_services/hid_service.h,, @@ -1815,6 +1816,20 @@ Function,-,j1f,float,float Function,-,jn,double,"int, double" Function,-,jnf,float,"int, float" Function,-,jrand48,long,unsigned short[3] +Function,-,js_check_sdk_compatibility,void,mjs* +Function,-,js_check_sdk_features,void,mjs* +Function,-,js_delay_with_flags,_Bool,"mjs*, uint32_t" +Function,-,js_does_sdk_support,void,mjs* +Function,-,js_flags_set,void,"mjs*, uint32_t" +Function,-,js_flags_wait,uint32_t,"mjs*, uint32_t, uint32_t" +Function,-,js_is_sdk_compatible,void,mjs* +Function,-,js_module_get,void*,"JsModules*, const char*" +Function,-,js_module_require,mjs_val_t,"JsModules*, const char*, size_t" +Function,-,js_modules_create,JsModules*,"mjs*, CompositeApiResolver*" +Function,-,js_modules_destroy,void,JsModules* +Function,-,js_sdk_compatibility_status,void,mjs* +Function,+,js_value_buffer_size,size_t,const JsValueParseDeclaration +Function,+,js_value_parse,JsValueParseStatus,"mjs*, const JsValueParseDeclaration, JsValueParseFlag, mjs_val_t*, size_t, mjs_val_t*, size_t, ..." Function,+,keys_dict_add_key,_Bool,"KeysDict*, const uint8_t*, size_t" Function,+,keys_dict_alloc,KeysDict*,"const char*, KeysDictMode, size_t" Function,+,keys_dict_check_presence,_Bool,const char* @@ -2361,13 +2376,13 @@ Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" -Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1168a6eea53..c833c8bec19 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,82.1,, +Version,+,82.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -41,6 +41,7 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, +Header,+,applications/system/js_app/js_value.h,, Header,+,lib/bit_lib/bit_lib.h,, Header,+,lib/ble_profile/extra_profiles/hid_profile.h,, Header,+,lib/ble_profile/extra_services/hid_service.h,, @@ -2218,6 +2219,20 @@ Function,-,j1f,float,float Function,-,jn,double,"int, double" Function,-,jnf,float,"int, float" Function,-,jrand48,long,unsigned short[3] +Function,-,js_check_sdk_compatibility,void,mjs* +Function,-,js_check_sdk_features,void,mjs* +Function,-,js_delay_with_flags,_Bool,"mjs*, uint32_t" +Function,-,js_does_sdk_support,void,mjs* +Function,-,js_flags_set,void,"mjs*, uint32_t" +Function,-,js_flags_wait,uint32_t,"mjs*, uint32_t, uint32_t" +Function,-,js_is_sdk_compatible,void,mjs* +Function,-,js_module_get,void*,"JsModules*, const char*" +Function,-,js_module_require,mjs_val_t,"JsModules*, const char*, size_t" +Function,-,js_modules_create,JsModules*,"mjs*, CompositeApiResolver*" +Function,-,js_modules_destroy,void,JsModules* +Function,-,js_sdk_compatibility_status,void,mjs* +Function,+,js_value_buffer_size,size_t,const JsValueParseDeclaration +Function,+,js_value_parse,JsValueParseStatus,"mjs*, const JsValueParseDeclaration, JsValueParseFlag, mjs_val_t*, size_t, mjs_val_t*, size_t, ..." Function,+,keys_dict_add_key,_Bool,"KeysDict*, const uint8_t*, size_t" Function,+,keys_dict_alloc,KeysDict*,"const char*, KeysDictMode, size_t" Function,+,keys_dict_check_presence,_Bool,const char* @@ -2999,13 +3014,13 @@ Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" -Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" From 541ea2a20c63d79724db0e39e6bf0eb1c6c393e0 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 5 Mar 2025 08:18:07 +0400 Subject: [PATCH 13/26] style: formatting --- .../debug/unit_tests/tests/js/js_test.c | 38 ++++++++++++--- applications/system/js_app/js_value.c | 40 +++++++++++----- applications/system/js_app/js_value.h | 48 +++++++++++-------- 3 files changed, 90 insertions(+), 36 deletions(-) diff --git a/applications/debug/unit_tests/tests/js/js_test.c b/applications/debug/unit_tests/tests/js/js_test.c index b1f8819fa87..dd695a3a126 100644 --- a/applications/debug/unit_tests/tests/js/js_test.c +++ b/applications/debug/unit_tests/tests/js/js_test.c @@ -129,7 +129,13 @@ static void js_value_test_compatibility_matrix(struct mjs* mjs) { // all the results uint8_t result[sizeof(double)]; JsValueParseStatus status; - JS_VALUE_PARSE(mjs, JS_VALUE_PARSE_SOURCE_VALUE(&declaration), JsValueParseFlagNone, &status, &values[j], result); + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&declaration), + JsValueParseFlagNone, + &status, + &values[j], + result); if((status == JsValueParseStatusOk) != success_matrix[i][j]) { FURI_LOG_E(TAG, "type %zu, value %zu", i, j); mu_fail("see serial logs"); @@ -159,7 +165,13 @@ static void js_value_test_literal(struct mjs* mjs) { }; mjs_val_t result; JsValueParseStatus status; - JS_VALUE_PARSE(mjs, JS_VALUE_PARSE_SOURCE_VALUE(&declaration), JsValueParseFlagNone, &status, &values[i], &result); + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&declaration), + JsValueParseFlagNone, + &status, + &values[i], + &result); mu_assert_int_eq(JsValueParseStatusOk, status); mu_assert(result == values[i], "wrong result"); } @@ -177,7 +189,13 @@ static void js_value_test_primitive( }; uint8_t result[c_value_size]; JsValueParseStatus status; - JS_VALUE_PARSE(mjs, JS_VALUE_PARSE_SOURCE_VALUE(&declaration), JsValueParseFlagNone, &status, &js_val, result); + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&declaration), + JsValueParseFlagNone, + &status, + &js_val, + result); mu_assert_int_eq(JsValueParseStatusOk, status); if(type == JsValueTypeString) { const char* result_str = *(const char**)&result; @@ -208,7 +226,8 @@ static uint32_t mjs_val_t str = mjs_mk_string(mjs, value, ~0, false); uint32_t result; JsValueParseStatus status; - JS_VALUE_PARSE(mjs, JS_VALUE_PARSE_SOURCE_VALUE(decl), JsValueParseFlagNone, &status, &str, &result); + JS_VALUE_PARSE( + mjs, JS_VALUE_PARSE_SOURCE_VALUE(decl), JsValueParseFlagNone, &status, &str, &result); if(status != JsValueParseStatusOk) return 0; return result; } @@ -286,7 +305,8 @@ static void js_value_test_object(struct mjs* mjs) { } static void js_value_test_default(struct mjs* mjs) { - static const JsValueDeclaration int_decl = JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 123); + static const JsValueDeclaration int_decl = + JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 123); static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString); static const JsValueObjectField fields[] = { @@ -305,7 +325,13 @@ static void js_value_test_default(struct mjs* mjs) { int32_t result_int; JsValueParseStatus status; JS_VALUE_PARSE( - mjs, JS_VALUE_PARSE_SOURCE_VALUE(&object_decl), JsValueParseFlagNone, &status, &object, &result_int, &result_str); + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&object_decl), + JsValueParseFlagNone, + &status, + &object, + &result_int, + &result_str); mu_assert_string_eq("Helloooo!", result_str); mu_assert_int_eq(123, result_int); } diff --git a/applications/system/js_app/js_value.c b/applications/system/js_app/js_value.c index dc45f3f60e8..810ba50efc9 100644 --- a/applications/system/js_app/js_value.c +++ b/applications/system/js_app/js_value.c @@ -15,7 +15,8 @@ size_t js_value_buffer_size(const JsValueParseDeclaration declaration) { 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)); + total += js_value_buffer_size( + JS_VALUE_PARSE_SOURCE_VALUE(value_decl->object_fields[i].value)); return total; } @@ -38,7 +39,8 @@ static size_t js_value_resulting_c_values_count(const JsValueParseDeclaration de 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)); + total += js_value_resulting_c_values_count( + JS_VALUE_PARSE_SOURCE_VALUE(value_decl->object_fields[i].value)); return total; } @@ -48,7 +50,8 @@ static size_t js_value_resulting_c_values_count(const JsValueParseDeclaration de 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])); + total += js_value_resulting_c_values_count( + JS_VALUE_PARSE_SOURCE_VALUE(&arg_decl->arguments[i])); return total; } } @@ -99,7 +102,14 @@ static JsValueParseStatus js_value_parse_va( 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); + 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; } @@ -109,11 +119,11 @@ static JsValueParseStatus js_value_parse_va( 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); + 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*); + if(type_noflags != JsValueTypeObject) destination = va_arg(*out_pointers, void*); switch(type_noflags) { // Literal terms @@ -175,7 +185,8 @@ static JsValueParseStatus js_value_parse_va( // 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); + 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); @@ -201,12 +212,19 @@ static JsValueParseStatus js_value_parse_va( } case JsValueTypeObject: { - if(!(is_null_but_allowed || mjs_is_object(*source))) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected object"); + if(!(is_null_but_allowed || mjs_is_object(*source))) + PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected 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); + 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); } diff --git a/applications/system/js_app/js_value.h b/applications/system/js_app/js_value.h index 3239a50f2b9..358e5e3a8f5 100644 --- a/applications/system/js_app/js_value.h +++ b/applications/system/js_app/js_value.h @@ -75,38 +75,45 @@ typedef struct { const JsValueDeclaration* arguments; } JsValueArguments; -#define JS_VALUE_ENUM(c_type, variants) { \ +#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, \ + .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_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, \ +#define JS_VALUE_OBJECT(fields) \ + { \ + .type = JsValueTypeObject, \ .n_children = COUNT_OF(fields), \ - .object_fields = fields, \ + .object_fields = fields, \ } -#define JS_VALUE_OBJECT_W_DEFAULTS(fields) { \ +#define JS_VALUE_OBJECT_W_DEFAULTS(fields) \ + { \ .type = JsValueTypeObject | JsValueTypePermitNull, \ - .n_children = COUNT_OF(fields), \ - .object_fields = fields, \ + .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_SIMPLE_W_DEFAULT(t, name, val) \ + {.type = t | JsValueTypePermitNull, .default_value.name = val} -#define JS_VALUE_ARGS(args) { \ +#define JS_VALUE_ARGS(args) \ + { \ .n_children = COUNT_OF(args), \ - .arguments = args, \ + .arguments = args, \ } typedef enum { @@ -134,8 +141,11 @@ typedef struct { }; } 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}) +#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 From 245800e7a24a09e7a8cfba373334ab9b1311ab85 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 5 Mar 2025 08:26:03 +0400 Subject: [PATCH 14/26] pvs: fix helpful warnings --- applications/system/js_app/js_value.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/system/js_app/js_value.h b/applications/system/js_app/js_value.h index 358e5e3a8f5..28afbd73bed 100644 --- a/applications/system/js_app/js_value.h +++ b/applications/system/js_app/js_value.h @@ -36,7 +36,7 @@ typedef enum { JsValueTypePermitNull = (1 << 16), // Date: Wed, 5 Mar 2025 08:30:43 +0400 Subject: [PATCH 15/26] js_value: reduce .rodata size --- applications/system/js_app/js_value.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/applications/system/js_app/js_value.c b/applications/system/js_app/js_value.c index 810ba50efc9..7db1a73b393 100644 --- a/applications/system/js_app/js_value.c +++ b/applications/system/js_app/js_value.c @@ -63,6 +63,9 @@ static size_t js_value_resulting_c_values_count(const JsValueParseDeclaration de 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; @@ -132,17 +135,17 @@ static JsValueParseStatus js_value_parse_va( break; } case JsValueTypeAnyArray: { - if(!mjs_is_array(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected array"); + if(!mjs_is_array(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "array"); *(mjs_val_t*)destination = *source; break; } case JsValueTypeAnyObject: { - if(!mjs_is_object(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected object"); + if(!mjs_is_object(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "object"); *(mjs_val_t*)destination = *source; break; } case JsValueTypeFunction: { - if(!mjs_is_function(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected function"); + if(!mjs_is_function(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "function"); *(mjs_val_t*)destination = *source; break; } @@ -150,32 +153,32 @@ static JsValueParseStatus js_value_parse_va( // Primitive types case JsValueTypeRawPointer: { if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(void*))) break; - if(!mjs_is_foreign(*source)) PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected pointer"); + 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_ERROR_AND_RETURN(mjs, flags, "expected number"); + 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_ERROR_AND_RETURN(mjs, flags, "expected number"); + 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_ERROR_AND_RETURN(mjs, flags, "expected bool"); + 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_ERROR_AND_RETURN(mjs, flags, "expected string"); + 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)++; @@ -203,17 +206,17 @@ static JsValueParseStatus js_value_parse_va( } if(!match_found) - PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected one of permitted strings"); + PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "one of permitted strings"); } else { - PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected string"); + PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "string"); } break; } case JsValueTypeObject: { if(!(is_null_but_allowed || mjs_is_object(*source))) - PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected object"); + 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); From 7a9b0a90f0d126cfefd05bc65423d511c64c31f1 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 5 Mar 2025 09:06:38 +0400 Subject: [PATCH 16/26] pvs: fix helpful warning --- applications/system/js_app/js_value.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/system/js_app/js_value.h b/applications/system/js_app/js_value.h index 28afbd73bed..765bcb3bba2 100644 --- a/applications/system/js_app/js_value.h +++ b/applications/system/js_app/js_value.h @@ -108,7 +108,7 @@ typedef struct { #define JS_VALUE_SIMPLE(t) {.type = t} #define JS_VALUE_SIMPLE_W_DEFAULT(t, name, val) \ - {.type = t | JsValueTypePermitNull, .default_value.name = (val)} + {.type = (t) | JsValueTypePermitNull, .default_value.name = (val)} #define JS_VALUE_ARGS(args) \ { \ From bccc4f494deceb8b039ac54bb8f70209b2dfa290 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 5 Mar 2025 09:13:13 +0400 Subject: [PATCH 17/26] js_value: reduce code size 1 --- applications/system/js_app/js_value.c | 32 +++++++++++++-------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/applications/system/js_app/js_value.c b/applications/system/js_app/js_value.c index 7db1a73b393..650019f57e5 100644 --- a/applications/system/js_app/js_value.c +++ b/applications/system/js_app/js_value.c @@ -92,6 +92,14 @@ static bool js_value_maybe_assign_default( return false; } +typedef bool (*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, @@ -130,25 +138,15 @@ static JsValueParseStatus js_value_parse_va( switch(type_noflags) { // Literal terms - case JsValueTypeAny: { - *(mjs_val_t*)destination = *source; - break; - } - case JsValueTypeAnyArray: { - if(!mjs_is_array(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "array"); + case JsValueTypeAny: *(mjs_val_t*)destination = *source; break; - } - case JsValueTypeAnyObject: { - if(!mjs_is_object(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "object"); - *(mjs_val_t*)destination = *source; - break; - } - case JsValueTypeFunction: { - if(!mjs_is_function(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "function"); - *(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: { From 6b6ea5d5c8f1aad54c9577c1c05002bf42e66f3b Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 5 Mar 2025 09:16:32 +0400 Subject: [PATCH 18/26] fix build error --- applications/system/js_app/js_value.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/system/js_app/js_value.c b/applications/system/js_app/js_value.c index 650019f57e5..b21345c4a26 100644 --- a/applications/system/js_app/js_value.c +++ b/applications/system/js_app/js_value.c @@ -92,7 +92,7 @@ static bool js_value_maybe_assign_default( return false; } -typedef bool (*MjsTypecheckFn)(mjs_val_t value); +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); From b5a1f95a237d8be1937e7cc09b890de7a2d85e3f Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 5 Mar 2025 09:31:45 +0400 Subject: [PATCH 19/26] style: format --- applications/system/js_app/js_value.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/applications/system/js_app/js_value.c b/applications/system/js_app/js_value.c index b21345c4a26..6ce1cf37a42 100644 --- a/applications/system/js_app/js_value.c +++ b/applications/system/js_app/js_value.c @@ -94,7 +94,13 @@ static bool js_value_maybe_assign_default( 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) { +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; @@ -146,7 +152,8 @@ static JsValueParseStatus js_value_parse_va( 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"); + return js_value_parse_literal( + mjs, flags, destination, source, mjs_is_function, "function"); // Primitive types case JsValueTypeRawPointer: { From eb075f7a9d527f5593870c0e99ddb6c98156dce4 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 5 Mar 2025 09:32:05 +0400 Subject: [PATCH 20/26] Revert "js: temporary fix to see size impact" This reverts commit d6a46f01794132e882e03fd273dec24386a4f8ba. --- .../debug/unit_tests/unit_test_api_table_i.h | 12 ++++++++++++ applications/system/js_app/application.fam | 3 --- targets/f18/api_symbols.csv | 19 ++----------------- targets/f7/api_symbols.csv | 19 ++----------------- 4 files changed, 16 insertions(+), 37 deletions(-) diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index 28d918c5654..03b89dfd32d 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -39,4 +39,16 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( JsThread*, (const char* script_path, JsThreadCallback callback, void* context)), API_METHOD(js_thread_stop, void, (JsThread * worker)), + API_METHOD(js_value_buffer_size, size_t, (const JsValueParseDeclaration declaration)), + API_METHOD( + js_value_parse, + JsValueParseStatus, + (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, + ...)), API_VARIABLE(PB_Main_msg, PB_Main_msg_t))); diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 9c6f41cd381..66ec221ec75 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -17,9 +17,6 @@ App( "modules/js_flipper.c", "modules/js_tests.c", ], - sdk_headers=[ - "js_value.h", - ], ) App( diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 5e8278f61b7..2074fa13120 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,82.2,, +Version,+,82.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -40,7 +40,6 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, -Header,+,applications/system/js_app/js_value.h,, Header,+,lib/bit_lib/bit_lib.h,, Header,+,lib/ble_profile/extra_profiles/hid_profile.h,, Header,+,lib/ble_profile/extra_services/hid_service.h,, @@ -1816,20 +1815,6 @@ Function,-,j1f,float,float Function,-,jn,double,"int, double" Function,-,jnf,float,"int, float" Function,-,jrand48,long,unsigned short[3] -Function,-,js_check_sdk_compatibility,void,mjs* -Function,-,js_check_sdk_features,void,mjs* -Function,-,js_delay_with_flags,_Bool,"mjs*, uint32_t" -Function,-,js_does_sdk_support,void,mjs* -Function,-,js_flags_set,void,"mjs*, uint32_t" -Function,-,js_flags_wait,uint32_t,"mjs*, uint32_t, uint32_t" -Function,-,js_is_sdk_compatible,void,mjs* -Function,-,js_module_get,void*,"JsModules*, const char*" -Function,-,js_module_require,mjs_val_t,"JsModules*, const char*, size_t" -Function,-,js_modules_create,JsModules*,"mjs*, CompositeApiResolver*" -Function,-,js_modules_destroy,void,JsModules* -Function,-,js_sdk_compatibility_status,void,mjs* -Function,+,js_value_buffer_size,size_t,const JsValueParseDeclaration -Function,+,js_value_parse,JsValueParseStatus,"mjs*, const JsValueParseDeclaration, JsValueParseFlag, mjs_val_t*, size_t, mjs_val_t*, size_t, ..." Function,+,keys_dict_add_key,_Bool,"KeysDict*, const uint8_t*, size_t" Function,+,keys_dict_alloc,KeysDict*,"const char*, KeysDictMode, size_t" Function,+,keys_dict_check_presence,_Bool,const char* @@ -2376,13 +2361,13 @@ Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" -Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c833c8bec19..1168a6eea53 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,82.2,, +Version,+,82.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -41,7 +41,6 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, -Header,+,applications/system/js_app/js_value.h,, Header,+,lib/bit_lib/bit_lib.h,, Header,+,lib/ble_profile/extra_profiles/hid_profile.h,, Header,+,lib/ble_profile/extra_services/hid_service.h,, @@ -2219,20 +2218,6 @@ Function,-,j1f,float,float Function,-,jn,double,"int, double" Function,-,jnf,float,"int, float" Function,-,jrand48,long,unsigned short[3] -Function,-,js_check_sdk_compatibility,void,mjs* -Function,-,js_check_sdk_features,void,mjs* -Function,-,js_delay_with_flags,_Bool,"mjs*, uint32_t" -Function,-,js_does_sdk_support,void,mjs* -Function,-,js_flags_set,void,"mjs*, uint32_t" -Function,-,js_flags_wait,uint32_t,"mjs*, uint32_t, uint32_t" -Function,-,js_is_sdk_compatible,void,mjs* -Function,-,js_module_get,void*,"JsModules*, const char*" -Function,-,js_module_require,mjs_val_t,"JsModules*, const char*, size_t" -Function,-,js_modules_create,JsModules*,"mjs*, CompositeApiResolver*" -Function,-,js_modules_destroy,void,JsModules* -Function,-,js_sdk_compatibility_status,void,mjs* -Function,+,js_value_buffer_size,size_t,const JsValueParseDeclaration -Function,+,js_value_parse,JsValueParseStatus,"mjs*, const JsValueParseDeclaration, JsValueParseFlag, mjs_val_t*, size_t, mjs_val_t*, size_t, ..." Function,+,keys_dict_add_key,_Bool,"KeysDict*, const uint8_t*, size_t" Function,+,keys_dict_alloc,KeysDict*,"const char*, KeysDictMode, size_t" Function,+,keys_dict_check_presence,_Bool,const char* @@ -3014,13 +2999,13 @@ Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" -Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" From 544193e72e847c7e7de25ba57de79e477a09d151 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 5 Mar 2025 09:35:31 +0400 Subject: [PATCH 21/26] style: format --- applications/debug/unit_tests/unit_test_api_table_i.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index 03b89dfd32d..4f0e4dec915 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -44,7 +44,7 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( js_value_parse, JsValueParseStatus, (struct mjs * mjs, - const JsValueParseDeclaration declaration, + const JsValueParseDeclaration declaration, JsValueParseFlag flags, mjs_val_t* buffer, size_t buf_size, From 49e5649c237337b7ee6d69c0a394ad83e858f48b Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 5 Mar 2025 11:55:06 +0400 Subject: [PATCH 22/26] js: move to new arg parser --- applications/system/js_app/js_modules.c | 26 +- applications/system/js_app/js_modules.h | 230 +-------------- applications/system/js_app/js_thread.c | 19 +- applications/system/js_app/js_thread_i.h | 8 + .../modules/js_event_loop/js_event_loop.c | 51 ++-- applications/system/js_app/modules/js_gpio.c | 193 ++++++++----- .../js_app/modules/js_gui/file_picker.c | 8 +- .../system/js_app/modules/js_gui/icon.c | 9 +- .../system/js_app/modules/js_gui/js_gui.c | 84 ++++-- .../system/js_app/modules/js_serial.c | 271 ++++++------------ .../system/js_app/modules/js_storage.c | 182 ++++++++---- .../js_app/plugin_api/app_api_table_i.h | 17 +- .../system/js_app/plugin_api/js_plugin_api.h | 22 -- 13 files changed, 498 insertions(+), 622 deletions(-) delete mode 100644 applications/system/js_app/plugin_api/js_plugin_api.h diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index a8480e6a259..f9c08058ffe 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -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: @@ -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)); } @@ -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( @@ -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"); diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h index 130ee975c14..9b20ad135e3 100644 --- a/applications/system/js_app/js_modules.h +++ b/applications/system/js_app/js_modules.h @@ -7,6 +7,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define PLUGIN_APP_ID "js" #define PLUGIN_API_VERSION 1 @@ -65,226 +69,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 @@ -321,6 +105,8 @@ typedef struct { const ElfApiInterface* api_interface; } JsModuleDescriptor; + + JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver); void js_modules_destroy(JsModules* modules); @@ -359,3 +145,7 @@ void js_does_sdk_support(struct mjs* mjs); * @brief `checkSdkFeatures` function */ void js_check_sdk_features(struct mjs* mjs); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 4a6d2301120..a41a28d11b5 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -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) { diff --git a/applications/system/js_app/js_thread_i.h b/applications/system/js_app/js_thread_i.h index a73cbb4bc51..5fbdb06d033 100644 --- a/applications/system/js_app/js_thread_i.h +++ b/applications/system/js_app/js_thread_i.h @@ -11,6 +11,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define INST_PROP_NAME "_" typedef enum { @@ -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 diff --git a/applications/system/js_app/modules/js_event_loop/js_event_loop.c b/applications/system/js_app/modules/js_event_loop/js_event_loop.c index 625301ad16b..3c7ee37bea0 100644 --- a/applications/system/js_app/modules/js_event_loop/js_event_loop.c +++ b/applications/system/js_app/modules/js_event_loop/js_event_loop.c @@ -144,10 +144,15 @@ static void js_event_loop_subscribe(struct mjs* mjs) { JsEventLoop* module = JS_GET_CONTEXT(mjs); // get arguments + static const JsValueDeclaration js_loop_subscribe_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeRawPointer), + JS_VALUE_SIMPLE(JsValueTypeFunction), + }; + static const JsValueArguments js_loop_subscribe_args = JS_VALUE_ARGS(js_loop_subscribe_arg_list); + JsEventLoopContract* contract; mjs_val_t callback; - JS_FETCH_ARGS_OR_RETURN( - mjs, JS_AT_LEAST, JS_ARG_STRUCT(JsEventLoopContract, &contract), JS_ARG_FN(&callback)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_subscribe_args, &contract, &callback); // create subscription object JsEventLoopSubscription* subscription = malloc(sizeof(JsEventLoopSubscription)); @@ -242,20 +247,22 @@ static void js_event_loop_stop(struct mjs* mjs) { * event */ static void js_event_loop_timer(struct mjs* mjs) { - // get arguments - const char* mode_str; - int32_t interval; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&mode_str), JS_ARG_INT32(&interval)); - JsEventLoop* module = JS_GET_CONTEXT(mjs); + static const JsValueEnumVariant js_loop_timer_mode_variants[] = { + {"periodic", FuriEventLoopTimerTypePeriodic}, + {"oneshot", FuriEventLoopTimerTypeOnce}, + }; + + static const JsValueDeclaration js_loop_timer_arg_list[] = { + JS_VALUE_ENUM(FuriEventLoopTimerType, js_loop_timer_mode_variants), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_loop_timer_args = JS_VALUE_ARGS(js_loop_timer_arg_list); FuriEventLoopTimerType mode; - if(strcasecmp(mode_str, "periodic") == 0) { - mode = FuriEventLoopTimerTypePeriodic; - } else if(strcasecmp(mode_str, "oneshot") == 0) { - mode = FuriEventLoopTimerTypeOnce; - } else { - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 0: unknown mode"); - } + int32_t interval; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_timer_args, &mode, &interval); + + JsEventLoop* module = JS_GET_CONTEXT(mjs); // make timer contract JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract)); @@ -293,8 +300,14 @@ static mjs_val_t */ static void js_event_loop_queue_send(struct mjs* mjs) { // get arguments + static const JsValueDeclaration js_loop_q_send_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_loop_q_send_args = JS_VALUE_ARGS(js_loop_q_send_arg_list); + mjs_val_t message; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&message)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_q_send_args, &message); + JsEventLoopContract* contract = JS_GET_CONTEXT(mjs); // send message @@ -311,8 +324,14 @@ static void js_event_loop_queue_send(struct mjs* mjs) { */ static void js_event_loop_queue(struct mjs* mjs) { // get arguments + static const JsValueDeclaration js_loop_q_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_loop_q_args = JS_VALUE_ARGS(js_loop_q_arg_list); + int32_t length; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&length)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_q_args, &length); + JsEventLoop* module = JS_GET_CONTEXT(mjs); // make queue contract diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c index 2a559570f54..0b739046dbb 100644 --- a/applications/system/js_app/modules/js_gpio.c +++ b/applications/system/js_app/modules/js_gpio.c @@ -54,83 +54,108 @@ static void js_gpio_int_cb(void* arg) { * ``` */ static void js_gpio_init(struct mjs* mjs) { - // deconstruct mode object - mjs_val_t mode_arg; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&mode_arg)); - mjs_val_t direction_arg = mjs_get(mjs, mode_arg, "direction", ~0); - mjs_val_t out_mode_arg = mjs_get(mjs, mode_arg, "outMode", ~0); - mjs_val_t in_mode_arg = mjs_get(mjs, mode_arg, "inMode", ~0); - mjs_val_t edge_arg = mjs_get(mjs, mode_arg, "edge", ~0); - mjs_val_t pull_arg = mjs_get(mjs, mode_arg, "pull", ~0); - - // get strings - const char* direction = mjs_get_string(mjs, &direction_arg, NULL); - const char* out_mode = mjs_get_string(mjs, &out_mode_arg, NULL); - const char* in_mode = mjs_get_string(mjs, &in_mode_arg, NULL); - const char* edge = mjs_get_string(mjs, &edge_arg, NULL); - const char* pull = mjs_get_string(mjs, &pull_arg, NULL); - if(!direction) - JS_ERROR_AND_RETURN( - mjs, MJS_BAD_ARGS_ERROR, "Expected string in \"direction\" field of mode object"); - if(!out_mode) out_mode = "open_drain"; - if(!in_mode) in_mode = "plain_digital"; - if(!edge) edge = "rising"; - - // convert strings to mode - GpioMode mode; - if(strcmp(direction, "out") == 0) { - if(strcmp(out_mode, "push_pull") == 0) - mode = GpioModeOutputPushPull; - else if(strcmp(out_mode, "open_drain") == 0) - mode = GpioModeOutputOpenDrain; - else - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid outMode"); - } else if(strcmp(direction, "in") == 0) { - if(strcmp(in_mode, "analog") == 0) { - mode = GpioModeAnalog; - } else if(strcmp(in_mode, "plain_digital") == 0) { - mode = GpioModeInput; - } else if(strcmp(in_mode, "interrupt") == 0) { - if(strcmp(edge, "rising") == 0) - mode = GpioModeInterruptRise; - else if(strcmp(edge, "falling") == 0) - mode = GpioModeInterruptFall; - else if(strcmp(edge, "both") == 0) - mode = GpioModeInterruptRiseFall; - else - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge"); - } else if(strcmp(in_mode, "event") == 0) { - if(strcmp(edge, "rising") == 0) - mode = GpioModeEventRise; - else if(strcmp(edge, "falling") == 0) - mode = GpioModeEventFall; - else if(strcmp(edge, "both") == 0) - mode = GpioModeEventRiseFall; - else - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge"); - } else { - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid inMode"); - } - } else { - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid direction"); - } + // direction variants + typedef enum { + JsGpioDirectionIn, + JsGpioDirectionOut, + } JsGpioDirection; + static const JsValueEnumVariant js_gpio_direction_variants[] = { + {"in", JsGpioDirectionIn}, + {"out", JsGpioDirectionOut}, + }; + static const JsValueDeclaration js_gpio_direction = JS_VALUE_ENUM(JsGpioDirection, js_gpio_direction_variants); + + // inMode variants + typedef enum { + JsGpioInModeAnalog = (0 << 0), + JsGpioInModePlainDigital = (1 << 0), + JsGpioInModeInterrupt = (2 << 0), + JsGpioInModeEvent = (3 << 0), + } JsGpioInMode; + static const JsValueEnumVariant js_gpio_in_mode_variants[] = { + {"analog", JsGpioInModeAnalog}, + {"plain_digital", JsGpioInModePlainDigital}, + {"interrupt", JsGpioInModeInterrupt}, + {"event", JsGpioInModeEvent}, + }; + static const JsValueDeclaration js_gpio_in_mode = JS_VALUE_ENUM_W_DEFAULT(JsGpioInMode, js_gpio_in_mode_variants, JsGpioInModePlainDigital); + + // outMode variants + typedef enum { + JsGpioOutModePushPull, + JsGpioOutModeOpenDrain, + } JsGpioOutMode; + static const JsValueEnumVariant js_gpio_out_mode_variants[] = { + {"push_pull", JsGpioOutModePushPull}, + {"open_drain", JsGpioOutModeOpenDrain}, + }; + static const JsValueDeclaration js_gpio_out_mode = JS_VALUE_ENUM_W_DEFAULT(JsGpioOutMode, js_gpio_out_mode_variants, JsGpioOutModeOpenDrain); + + // edge variants + typedef enum { + JsGpioEdgeRising = (0 << 2), + JsGpioEdgeFalling = (1 << 2), + JsGpioEdgeBoth = (2 << 2), + } JsGpioEdge; + static const JsValueEnumVariant js_gpio_edge_variants[] = { + {"rising", JsGpioEdgeRising}, + {"falling", JsGpioEdgeFalling}, + {"both", JsGpioEdgeBoth}, + }; + static const JsValueDeclaration js_gpio_edge = JS_VALUE_ENUM_W_DEFAULT(JsGpioEdge, js_gpio_edge_variants, JsGpioEdgeRising); + + // pull variants + static const JsValueEnumVariant js_gpio_pull_variants[] = { + {"up", GpioPullUp}, + {"down", GpioPullDown}, + }; + static const JsValueDeclaration js_gpio_pull = JS_VALUE_ENUM_W_DEFAULT(GpioPull, js_gpio_pull_variants, GpioPullNo); + + // complete mode object + static const JsValueObjectField js_gpio_mode_object_fields[] = { + {"direction", &js_gpio_direction}, + {"inMode", &js_gpio_in_mode}, + {"outMode", &js_gpio_out_mode}, + {"edge", &js_gpio_edge}, + {"pull", &js_gpio_pull}, + }; + + // function args + static const JsValueDeclaration js_gpio_init_arg_list[] = { + JS_VALUE_OBJECT_W_DEFAULTS(js_gpio_mode_object_fields), + }; + static const JsValueArguments js_gpio_init_args = JS_VALUE_ARGS(js_gpio_init_arg_list); + + JsGpioDirection direction; + JsGpioInMode in_mode; + JsGpioOutMode out_mode; + JsGpioEdge edge; + GpioPull pull; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_init_args, &direction, &in_mode, &out_mode, &edge, &pull); - // convert pull - GpioPull pull_mode; - if(!pull) { - pull_mode = GpioPullNo; - } else if(strcmp(pull, "up") == 0) { - pull_mode = GpioPullUp; - } else if(strcmp(pull, "down") == 0) { - pull_mode = GpioPullDown; + GpioMode mode; + if(direction == JsGpioDirectionOut) { + static const GpioMode js_gpio_out_mode_lut[] = { + [JsGpioOutModePushPull] = GpioModeOutputPushPull, + [JsGpioOutModeOpenDrain] = GpioModeOutputOpenDrain, + }; + mode = js_gpio_out_mode_lut[out_mode]; } else { - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid pull"); + static const GpioMode js_gpio_in_mode_lut[] = { + [JsGpioInModeAnalog] = GpioModeAnalog, + [JsGpioInModePlainDigital] = GpioModeInput, + [JsGpioInModeInterrupt | JsGpioEdgeRising] = GpioModeInterruptRise, + [JsGpioInModeInterrupt | JsGpioEdgeFalling] = GpioModeInterruptFall, + [JsGpioInModeInterrupt | JsGpioEdgeBoth] = GpioModeInterruptRiseFall, + [JsGpioInModeEvent | JsGpioEdgeRising] = GpioModeEventRise, + [JsGpioInModeEvent | JsGpioEdgeFalling] = GpioModeEventFall, + [JsGpioInModeEvent | JsGpioEdgeBoth] = GpioModeEventRiseFall, + }; + mode = js_gpio_in_mode_lut[in_mode | edge]; } - // init GPIO JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); - furi_hal_gpio_init(manager_data->pin, mode, pull_mode, GpioSpeedVeryHigh); - mjs_return(mjs, MJS_UNDEFINED); + furi_hal_gpio_init(manager_data->pin, mode, pull, GpioSpeedVeryHigh); } /** @@ -146,8 +171,13 @@ static void js_gpio_init(struct mjs* mjs) { * ``` */ static void js_gpio_write(struct mjs* mjs) { + static const JsValueDeclaration js_gpio_write_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeBool), + }; + static const JsValueArguments js_gpio_write_args = JS_VALUE_ARGS(js_gpio_write_arg_list); bool level; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_BOOL(&level)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_write_args, &level); + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); furi_hal_gpio_write(manager_data->pin, level); mjs_return(mjs, MJS_UNDEFINED); @@ -261,9 +291,15 @@ static void js_gpio_is_pwm_supported(struct mjs* mjs) { * ``` */ static void js_gpio_pwm_write(struct mjs* mjs) { - JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + static const JsValueDeclaration js_gpio_pwm_write_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_gpio_pwm_write_args = JS_VALUE_ARGS(js_gpio_pwm_write_arg_list); int32_t frequency, duty; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&frequency), JS_ARG_INT32(&duty)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_pwm_write_args, &frequency, &duty); + + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); if(manager_data->pwm_output == FuriHalPwmOutputIdNone) { JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin"); } @@ -326,8 +362,13 @@ static void js_gpio_pwm_stop(struct mjs* mjs) { * ``` */ static void js_gpio_get(struct mjs* mjs) { + static const JsValueDeclaration js_gpio_get_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gpio_get_args = JS_VALUE_ARGS(js_gpio_get_arg_list); mjs_val_t name_arg; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&name_arg)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_get_args, &name_arg); + const char* name_string = mjs_get_string(mjs, &name_arg, NULL); const GpioPinRecord* pin_record = NULL; diff --git a/applications/system/js_app/modules/js_gui/file_picker.c b/applications/system/js_app/modules/js_gui/file_picker.c index 49cf5e89dca..7b36596cde4 100644 --- a/applications/system/js_app/modules/js_gui/file_picker.c +++ b/applications/system/js_app/modules/js_gui/file_picker.c @@ -3,8 +3,14 @@ #include static void js_gui_file_picker_pick_file(struct mjs* mjs) { + static const JsValueDeclaration js_picker_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeString), + }; + static const JsValueArguments js_picker_args = JS_VALUE_ARGS(js_picker_arg_list); + const char *base_path, *extension; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&base_path), JS_ARG_STR(&extension)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_picker_args, &base_path, &extension); DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); const DialogsFileBrowserOptions browser_options = { diff --git a/applications/system/js_app/modules/js_gui/icon.c b/applications/system/js_app/modules/js_gui/icon.c index 3d8a67a8bb4..4fc6da2e088 100644 --- a/applications/system/js_app/modules/js_gui/icon.c +++ b/applications/system/js_app/modules/js_gui/icon.c @@ -39,9 +39,14 @@ typedef struct { FxbmIconWrapperList_t fxbm_list; } JsGuiIconInst; +static const JsValueDeclaration js_icon_get_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), +}; +static const JsValueArguments js_icon_get_args = JS_VALUE_ARGS(js_icon_get_arg_list); + static void js_gui_icon_get_builtin(struct mjs* mjs) { const char* icon_name; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&icon_name)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_icon_get_args, &icon_name); for(size_t i = 0; i < COUNT_OF(builtin_icons); i++) { if(strcmp(icon_name, builtin_icons[i].name) == 0) { @@ -55,7 +60,7 @@ static void js_gui_icon_get_builtin(struct mjs* mjs) { static void js_gui_icon_load_fxbm(struct mjs* mjs) { const char* fxbm_path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&fxbm_path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_icon_get_args, &fxbm_path); Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); diff --git a/applications/system/js_app/modules/js_gui/js_gui.c b/applications/system/js_app/modules/js_gui/js_gui.c index e505681df52..a99685974dd 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.c +++ b/applications/system/js_app/modules/js_gui/js_gui.c @@ -68,8 +68,13 @@ static bool js_gui_vd_nav_callback(void* context) { * @brief `viewDispatcher.sendCustom` */ static void js_gui_vd_send_custom(struct mjs* mjs) { + static const JsValueDeclaration js_gui_vd_send_custom_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_gui_vd_send_custom_args = JS_VALUE_ARGS(js_gui_vd_send_custom_arg_list); + int32_t event; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&event)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_send_custom_args, &event); JsGui* module = JS_GET_CONTEXT(mjs); view_dispatcher_send_custom_event(module->dispatcher, (uint32_t)event); @@ -79,15 +84,24 @@ static void js_gui_vd_send_custom(struct mjs* mjs) { * @brief `viewDispatcher.sendTo` */ static void js_gui_vd_send_to(struct mjs* mjs) { - enum { - SendDirToFront, - SendDirToBack, - } send_direction; - JS_ENUM_MAP(send_direction, {"front", SendDirToFront}, {"back", SendDirToBack}); - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ENUM(send_direction, "SendDirection")); + typedef enum { + JsSendDirToFront, + JsSendDirToBack, + } JsSendDir; + static const JsValueEnumVariant js_send_dir_variants[] = { + {"front", JsSendDirToFront}, + {"back", JsSendDirToBack}, + }; + static const JsValueDeclaration js_gui_vd_send_to_arg_list[] = { + JS_VALUE_ENUM(JsSendDir, js_send_dir_variants), + }; + static const JsValueArguments js_gui_vd_send_to_args = JS_VALUE_ARGS(js_gui_vd_send_to_arg_list); + + JsSendDir send_direction; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_send_to_args, &send_direction); JsGui* module = JS_GET_CONTEXT(mjs); - if(send_direction == SendDirToBack) { + if(send_direction == JsSendDirToBack) { view_dispatcher_send_to_back(module->dispatcher); } else { view_dispatcher_send_to_front(module->dispatcher); @@ -98,8 +112,14 @@ static void js_gui_vd_send_to(struct mjs* mjs) { * @brief `viewDispatcher.switchTo` */ static void js_gui_vd_switch_to(struct mjs* mjs) { + static const JsValueDeclaration js_gui_vd_switch_to_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gui_vd_switch_to_args = JS_VALUE_ARGS(js_gui_vd_switch_to_arg_list); + mjs_val_t view; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&view)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_switch_to_args, &view); + JsGuiViewData* view_data = JS_GET_INST(mjs, view); mjs_val_t vd_obj = mjs_get_this(mjs); JsGui* module = JS_GET_INST(mjs, vd_obj); @@ -267,9 +287,16 @@ static bool * @brief `View.set` */ static void js_gui_view_set(struct mjs* mjs) { + static const JsValueDeclaration js_gui_view_set_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gui_view_set_args = JS_VALUE_ARGS(js_gui_view_set_arg_list); + const char* name; mjs_val_t value; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&name), JS_ARG_ANY(&value)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_set_args, &name, &value); + JsGuiViewData* data = JS_GET_CONTEXT(mjs); bool success = js_gui_view_assign(mjs, name, value, data); UNUSED(success); @@ -280,12 +307,18 @@ static void js_gui_view_set(struct mjs* mjs) { * @brief `View.addChild` */ static void js_gui_view_add_child(struct mjs* mjs) { + static const JsValueDeclaration js_gui_view_add_child_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gui_view_add_child_args = JS_VALUE_ARGS(js_gui_view_add_child_arg_list); + + mjs_val_t child; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_add_child_args, &child); + JsGuiViewData* data = JS_GET_CONTEXT(mjs); if(!data->descriptor->add_child || !data->descriptor->reset_children) JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); - mjs_val_t child; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&child)); bool success = data->descriptor->add_child(mjs, data->specific_view, data->custom_data, child); UNUSED(success); mjs_return(mjs, MJS_UNDEFINED); @@ -307,12 +340,18 @@ static void js_gui_view_reset_children(struct mjs* mjs) { * @brief `View.setChildren` */ static void js_gui_view_set_children(struct mjs* mjs) { + static const JsValueDeclaration js_gui_view_set_children_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAnyArray), + }; + static const JsValueArguments js_gui_view_set_children_args = JS_VALUE_ARGS(js_gui_view_set_children_arg_list); + + mjs_val_t children; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_set_children_args, &children); + JsGuiViewData* data = JS_GET_CONTEXT(mjs); if(!data->descriptor->add_child || !data->descriptor->reset_children) JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); - mjs_val_t children; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&children)); js_gui_view_internal_set_children(mjs, children, data); } @@ -365,7 +404,6 @@ static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descr * @brief `ViewFactory.make` */ static void js_gui_vf_make(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); mjs_return(mjs, js_gui_make_view(mjs, descriptor)); } @@ -374,8 +412,14 @@ static void js_gui_vf_make(struct mjs* mjs) { * @brief `ViewFactory.makeWith` */ static void js_gui_vf_make_with(struct mjs* mjs) { - mjs_val_t props; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_AT_LEAST, JS_ARG_OBJ(&props)); + static const JsValueDeclaration js_gui_vf_make_with_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAnyObject), + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gui_vf_make_with_args = JS_VALUE_ARGS(js_gui_vf_make_with_arg_list); + + mjs_val_t props, children; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vf_make_with_args, &props, &children); const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); // make the object like normal @@ -396,14 +440,10 @@ static void js_gui_vf_make_with(struct mjs* mjs) { } // assign children - if(mjs_nargs(mjs) >= 2) { + if(mjs_is_array(children)) { if(!data->descriptor->add_child || !data->descriptor->reset_children) JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); - mjs_val_t children = mjs_arg(mjs, 1); - if(!mjs_is_array(children)) - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 1: expected array"); - if(!js_gui_view_internal_set_children(mjs, children, data)) return; } diff --git a/applications/system/js_app/modules/js_serial.c b/applications/system/js_app/modules/js_serial.c index 20b18a4f146..abc366319b8 100644 --- a/applications/system/js_app/modules/js_serial.c +++ b/applications/system/js_app/modules/js_serial.c @@ -35,60 +35,57 @@ static void } static void js_serial_setup(struct mjs* mjs) { + static const JsValueEnumVariant js_serial_id_variants[] = { + {"lpuart", FuriHalSerialIdLpuart}, + {"usart", FuriHalSerialIdUsart}, + }; + + static const JsValueEnumVariant js_serial_data_bit_variants[] = { + {"6", FuriHalSerialDataBits6}, + {"7", FuriHalSerialDataBits7}, + {"8", FuriHalSerialDataBits8}, + {"9", FuriHalSerialDataBits9}, + }; + static const JsValueDeclaration js_serial_data_bits = JS_VALUE_ENUM_W_DEFAULT(FuriHalSerialDataBits, js_serial_data_bit_variants, FuriHalSerialDataBits8); + + static const JsValueEnumVariant js_serial_parity_variants[] = { + {"none", FuriHalSerialParityNone}, + {"even", FuriHalSerialParityEven}, + {"odd", FuriHalSerialParityOdd}, + }; + static const JsValueDeclaration js_serial_parity = JS_VALUE_ENUM_W_DEFAULT(FuriHalSerialParity, js_serial_parity_variants, FuriHalSerialParityNone); + + static const JsValueEnumVariant js_serial_stop_bit_variants[] = { + {"0.5", FuriHalSerialStopBits0_5}, + {"1", FuriHalSerialStopBits1}, + {"1.5", FuriHalSerialStopBits1_5}, + {"2", FuriHalSerialStopBits2}, + }; + static const JsValueDeclaration js_serial_stop_bits = JS_VALUE_ENUM_W_DEFAULT(FuriHalSerialStopBits, js_serial_stop_bit_variants, FuriHalSerialStopBits1); + + static const JsValueObjectField js_serial_framing_fields[] = { + {"dataBits", &js_serial_data_bits}, + {"parity", &js_serial_parity}, + {"stopBits", &js_serial_stop_bits}, + }; + + static const JsValueDeclaration js_serial_setup_arg_list[] = { + JS_VALUE_ENUM(FuriHalSerialId, js_serial_id_variants), + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_OBJECT_W_DEFAULTS(js_serial_framing_fields), + }; + static const JsValueArguments js_serial_setup_args = JS_VALUE_ARGS(js_serial_setup_arg_list); + FuriHalSerialId serial_id; int32_t baudrate; - JS_ENUM_MAP(serial_id, {"lpuart", FuriHalSerialIdLpuart}, {"usart", FuriHalSerialIdUsart}); - JS_FETCH_ARGS_OR_RETURN( - mjs, JS_AT_LEAST, JS_ARG_ENUM(serial_id, "SerialId"), JS_ARG_INT32(&baudrate)); - FuriHalSerialDataBits data_bits = FuriHalSerialDataBits8; FuriHalSerialParity parity = FuriHalSerialParityNone; FuriHalSerialStopBits stop_bits = FuriHalSerialStopBits1; - if(mjs_nargs(mjs) > 2) { - struct framing { - mjs_val_t data_bits; - mjs_val_t parity; - mjs_val_t stop_bits; - } framing; - JS_OBJ_MAP( - framing, - {"dataBits", offsetof(struct framing, data_bits)}, - {"parity", offsetof(struct framing, parity)}, - {"stopBits", offsetof(struct framing, stop_bits)}); - JS_ENUM_MAP( - data_bits, - {"6", FuriHalSerialDataBits6}, - {"7", FuriHalSerialDataBits7}, - {"8", FuriHalSerialDataBits8}, - {"9", FuriHalSerialDataBits9}); - JS_ENUM_MAP( - parity, - {"none", FuriHalSerialParityNone}, - {"even", FuriHalSerialParityEven}, - {"odd", FuriHalSerialParityOdd}); - JS_ENUM_MAP( - stop_bits, - {"0.5", FuriHalSerialStopBits0_5}, - {"1", FuriHalSerialStopBits1}, - {"1.5", FuriHalSerialStopBits1_5}, - {"2", FuriHalSerialStopBits2}); - mjs_val_t framing_obj = mjs_arg(mjs, 2); - JS_CONVERT_OR_RETURN(mjs, &framing_obj, JS_ARG_OBJECT(framing, "Framing"), "argument 2"); - JS_CONVERT_OR_RETURN( - mjs, &framing.data_bits, JS_ARG_ENUM(data_bits, "DataBits"), "argument 2: dataBits"); - JS_CONVERT_OR_RETURN( - mjs, &framing.parity, JS_ARG_ENUM(parity, "Parity"), "argument 2: parity"); - JS_CONVERT_OR_RETURN( - mjs, &framing.stop_bits, JS_ARG_ENUM(stop_bits, "StopBits"), "argument 2: stopBits"); - } + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_setup_args, &serial_id, &baudrate, &data_bits, &parity, &stop_bits); JsSerialInst* serial = JS_GET_CONTEXT(mjs); - if(serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is already configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is already configured"); expansion_disable(furi_record_open(RECORD_EXPANSION)); furi_record_close(RECORD_EXPANSION); @@ -123,28 +120,18 @@ static void js_serial_deinit(JsSerialInst* js_serial) { } static void js_serial_end(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); js_serial_deinit(serial); } static void js_serial_write(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); bool args_correct = true; @@ -228,43 +215,19 @@ static size_t js_serial_receive(JsSerialInst* serial, char* buf, size_t len, uin return bytes_read; } +static const JsValueDeclaration js_serial_read_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, INT32_MAX), +}; +static const JsValueArguments js_serial_read_args = JS_VALUE_ARGS(js_serial_read_arg_list); + static void js_serial_read(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - size_t read_len = 0; - uint32_t timeout = FuriWaitForever; - - do { - size_t num_args = mjs_nargs(mjs); - if(num_args == 1) { - mjs_val_t arg = mjs_arg(mjs, 0); - if(!mjs_is_number(arg)) { - break; - } - read_len = mjs_get_int32(mjs, arg); - } else if(num_args == 2) { - mjs_val_t len_arg = mjs_arg(mjs, 0); - mjs_val_t timeout_arg = mjs_arg(mjs, 1); - if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) { - break; - } - read_len = mjs_get_int32(mjs, len_arg); - timeout = mjs_get_int32(mjs, timeout_arg); - } - } while(0); + if(!serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - if(read_len == 0) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + int32_t read_len, timeout; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_args, &read_len, &timeout); char* read_buf = malloc(read_len); size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout); @@ -278,37 +241,18 @@ static void js_serial_read(struct mjs* mjs) { } static void js_serial_readln(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - bool args_correct = false; - uint32_t timeout = FuriWaitForever; + static const JsValueDeclaration js_serial_readln_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_serial_readln_args = JS_VALUE_ARGS(js_serial_readln_arg_list); - do { - size_t num_args = mjs_nargs(mjs); - if(num_args > 1) { - break; - } else if(num_args == 1) { - mjs_val_t arg = mjs_arg(mjs, 0); - if(!mjs_is_number(arg)) { - break; - } - timeout = mjs_get_int32(mjs, arg); - } - args_correct = true; - } while(0); + int32_t timeout; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_readln_args, &timeout); - if(!args_correct) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } FuriString* rx_buf = furi_string_alloc(); size_t bytes_read = 0; char read_char = 0; @@ -335,42 +279,12 @@ static void js_serial_readln(struct mjs* mjs) { } static void js_serial_read_bytes(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - size_t read_len = 0; - uint32_t timeout = FuriWaitForever; - - do { - size_t num_args = mjs_nargs(mjs); - if(num_args == 1) { - mjs_val_t arg = mjs_arg(mjs, 0); - if(!mjs_is_number(arg)) { - break; - } - read_len = mjs_get_int32(mjs, arg); - } else if(num_args == 2) { - mjs_val_t len_arg = mjs_arg(mjs, 0); - mjs_val_t timeout_arg = mjs_arg(mjs, 1); - if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) { - break; - } - read_len = mjs_get_int32(mjs, len_arg); - timeout = mjs_get_int32(mjs, timeout_arg); - } - } while(0); - - if(read_len == 0) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + int32_t read_len, timeout; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_args, &read_len, &timeout); char* read_buf = malloc(read_len); size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout); @@ -399,27 +313,17 @@ static char* js_serial_receive_any(JsSerialInst* serial, size_t* len, uint32_t t } static void js_serial_read_any(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - uint32_t timeout = FuriWaitForever; + static const JsValueDeclaration js_serial_read_any_arg_list[] = { + JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, INT32_MAX), + }; + static const JsValueArguments js_serial_read_any_args = JS_VALUE_ARGS(js_serial_read_any_arg_list); - do { - size_t num_args = mjs_nargs(mjs); - if(num_args == 1) { - mjs_val_t timeout_arg = mjs_arg(mjs, 0); - if(!mjs_is_number(timeout_arg)) { - break; - } - timeout = mjs_get_int32(mjs, timeout_arg); - } - } while(0); + int32_t timeout; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_any_args, &timeout); size_t bytes_read = 0; char* read_buf = js_serial_receive_any(serial, &bytes_read, timeout); @@ -663,16 +567,19 @@ static void* js_serial_create(struct mjs* mjs, mjs_val_t* object, JsModules* mod UNUSED(modules); JsSerialInst* js_serial = malloc(sizeof(JsSerialInst)); js_serial->mjs = mjs; + mjs_val_t serial_obj = mjs_mk_object(mjs); - mjs_set(mjs, serial_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_serial)); - mjs_set(mjs, serial_obj, "setup", ~0, MJS_MK_FN(js_serial_setup)); - mjs_set(mjs, serial_obj, "end", ~0, MJS_MK_FN(js_serial_end)); - mjs_set(mjs, serial_obj, "write", ~0, MJS_MK_FN(js_serial_write)); - mjs_set(mjs, serial_obj, "read", ~0, MJS_MK_FN(js_serial_read)); - mjs_set(mjs, serial_obj, "readln", ~0, MJS_MK_FN(js_serial_readln)); - mjs_set(mjs, serial_obj, "readBytes", ~0, MJS_MK_FN(js_serial_read_bytes)); - mjs_set(mjs, serial_obj, "readAny", ~0, MJS_MK_FN(js_serial_read_any)); - mjs_set(mjs, serial_obj, "expect", ~0, MJS_MK_FN(js_serial_expect)); + JS_ASSIGN_MULTI(mjs, serial_obj) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, js_serial)); + JS_FIELD("setup", MJS_MK_FN(js_serial_setup)); + JS_FIELD("end", MJS_MK_FN(js_serial_end)); + JS_FIELD("write", MJS_MK_FN(js_serial_write)); + JS_FIELD("read", MJS_MK_FN(js_serial_read)); + JS_FIELD("readln", MJS_MK_FN(js_serial_readln)); + JS_FIELD("readBytes", MJS_MK_FN(js_serial_read_bytes)); + JS_FIELD("readAny", MJS_MK_FN(js_serial_read_any)); + JS_FIELD("expect", MJS_MK_FN(js_serial_expect)); + } *object = serial_obj; return js_serial; diff --git a/applications/system/js_app/modules/js_storage.c b/applications/system/js_app/modules/js_storage.c index 1d4053a5f79..ee858b3928b 100644 --- a/applications/system/js_app/modules/js_storage.c +++ b/applications/system/js_app/modules/js_storage.c @@ -1,42 +1,78 @@ #include "../js_modules.h" // IWYU pragma: keep #include -// ---=== file ops ===--- +// ========================== +// Common argument signatures +// ========================== + +static const JsValueDeclaration js_storage_1_int_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), +}; +static const JsValueArguments js_storage_1_int_args = JS_VALUE_ARGS(js_storage_1_int_arg_list); + +static const JsValueDeclaration js_storage_1_str_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), +}; +static const JsValueArguments js_storage_1_str_args = JS_VALUE_ARGS(js_storage_1_str_arg_list); + +static const JsValueDeclaration js_storage_2_str_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeString), +}; +static const JsValueArguments js_storage_2_str_args = JS_VALUE_ARGS(js_storage_2_str_arg_list); + +// ====================== +// File object operations +// ====================== static void js_storage_file_close(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_close(file))); } static void js_storage_file_is_open(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_is_open(file))); } static void js_storage_file_read(struct mjs* mjs) { - enum { - ReadModeAscii, - ReadModeBinary, - } read_mode; - JS_ENUM_MAP(read_mode, {"ascii", ReadModeAscii}, {"binary", ReadModeBinary}); + typedef enum { + JsStorageReadModeAscii, + JsStorageReadModeBinary, + } JsStorageReadMode; + static const JsValueEnumVariant js_storage_read_mode_variants[] = { + {"ascii", JsStorageReadModeAscii}, + {"binary", JsStorageReadModeBinary}, + }; + static const JsValueDeclaration js_storage_read_arg_list[] = { + JS_VALUE_ENUM(JsStorageReadMode, js_storage_read_mode_variants), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_storage_read_args = JS_VALUE_ARGS(js_storage_read_arg_list); + + JsStorageReadMode read_mode; int32_t length; - JS_FETCH_ARGS_OR_RETURN( - mjs, JS_EXACTLY, JS_ARG_ENUM(read_mode, "ReadMode"), JS_ARG_INT32(&length)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_read_args, &read_mode, &length); + File* file = JS_GET_CONTEXT(mjs); char buffer[length]; size_t actually_read = storage_file_read(file, buffer, length); - if(read_mode == ReadModeAscii) { + if(read_mode == JsStorageReadModeAscii) { mjs_return(mjs, mjs_mk_string(mjs, buffer, actually_read, true)); - } else if(read_mode == ReadModeBinary) { + } else if(read_mode == JsStorageReadModeBinary) { mjs_return(mjs, mjs_mk_array_buf(mjs, buffer, actually_read)); } } static void js_storage_file_write(struct mjs* mjs) { + static const JsValueDeclaration js_storage_file_write_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_storage_file_write_args = JS_VALUE_ARGS(js_storage_file_write_arg_list); + mjs_val_t data; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&data)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_file_write_args, &data); + const void* buf; size_t len; if(mjs_is_string(data)) { @@ -52,52 +88,57 @@ static void js_storage_file_write(struct mjs* mjs) { static void js_storage_file_seek_relative(struct mjs* mjs) { int32_t offset; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&offset)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_int_args, &offset); File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_seek(file, offset, false))); } static void js_storage_file_seek_absolute(struct mjs* mjs) { int32_t offset; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&offset)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_int_args, &offset); File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_seek(file, offset, true))); } static void js_storage_file_tell(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_number(mjs, storage_file_tell(file))); } static void js_storage_file_truncate(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_truncate(file))); } static void js_storage_file_size(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_number(mjs, storage_file_size(file))); } static void js_storage_file_eof(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_eof(file))); } static void js_storage_file_copy_to(struct mjs* mjs) { - File* source = JS_GET_CONTEXT(mjs); + static const JsValueDeclaration js_storage_file_write_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_storage_file_write_args = JS_VALUE_ARGS(js_storage_file_write_arg_list); + mjs_val_t dest_obj; int32_t bytes; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&dest_obj), JS_ARG_INT32(&bytes)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_file_write_args, &dest_obj, &bytes); + + File* source = JS_GET_CONTEXT(mjs); File* destination = JS_GET_INST(mjs, dest_obj); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_copy_to_file(source, destination, bytes))); } -// ---=== top-level file ops ===--- +// ========================= +// Top-level file operations +// ========================= // common destructor for file and dir objects static void js_storage_file_destructor(struct mjs* mjs, mjs_val_t obj) { @@ -106,23 +147,31 @@ static void js_storage_file_destructor(struct mjs* mjs, mjs_val_t obj) { } static void js_storage_open_file(struct mjs* mjs) { - const char* path; - FS_AccessMode access_mode; - FS_OpenMode open_mode; - JS_ENUM_MAP(access_mode, {"r", FSAM_READ}, {"w", FSAM_WRITE}, {"rw", FSAM_READ_WRITE}); - JS_ENUM_MAP( - open_mode, + static const JsValueEnumVariant js_storage_fsam_variants[] = { + {"r", FSAM_READ}, + {"w", FSAM_WRITE}, + {"rw", FSAM_READ_WRITE}, + }; + + static const JsValueEnumVariant js_storage_fsom_variants[] = { {"open_existing", FSOM_OPEN_EXISTING}, {"open_always", FSOM_OPEN_ALWAYS}, {"open_append", FSOM_OPEN_APPEND}, {"create_new", FSOM_CREATE_NEW}, - {"create_always", FSOM_CREATE_ALWAYS}); - JS_FETCH_ARGS_OR_RETURN( - mjs, - JS_EXACTLY, - JS_ARG_STR(&path), - JS_ARG_ENUM(access_mode, "AccessMode"), - JS_ARG_ENUM(open_mode, "OpenMode")); + {"create_always", FSOM_CREATE_ALWAYS}, + }; + + static const JsValueDeclaration js_storage_open_file_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_ENUM(FS_AccessMode, js_storage_fsam_variants), + JS_VALUE_ENUM(FS_OpenMode, js_storage_fsom_variants), + }; + static const JsValueArguments js_storage_open_file_args = JS_VALUE_ARGS(js_storage_open_file_arg_list); + + const char* path; + FS_AccessMode access_mode; + FS_OpenMode open_mode; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_open_file_args, &path, &access_mode, &open_mode); Storage* storage = JS_GET_CONTEXT(mjs); File* file = storage_file_alloc(storage); @@ -152,16 +201,18 @@ static void js_storage_open_file(struct mjs* mjs) { static void js_storage_file_exists(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_exists(storage, path))); } -// ---=== dir ops ===--- +// ==================== +// Directory operations +// ==================== static void js_storage_read_directory(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); File* dir = storage_file_alloc(storage); @@ -200,30 +251,32 @@ static void js_storage_read_directory(struct mjs* mjs) { static void js_storage_directory_exists(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_dir_exists(storage, path))); } static void js_storage_make_directory(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_mkdir(storage, path))); } -// ---=== common ops ===--- +// ================= +// Common operations +// ================= static void js_storage_file_or_dir_exists(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_exists(storage, path))); } static void js_storage_stat(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); FileInfo file_info; uint32_t timestamp; @@ -244,21 +297,21 @@ static void js_storage_stat(struct mjs* mjs) { static void js_storage_remove(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove(storage, path))); } static void js_storage_rmrf(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove_recursive(storage, path))); } static void js_storage_rename(struct mjs* mjs) { const char *old, *new; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&old), JS_ARG_STR(&new)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_2_str_args, &old, &new); Storage* storage = JS_GET_CONTEXT(mjs); FS_Error status = storage_common_rename(storage, old, new); mjs_return(mjs, mjs_mk_boolean(mjs, status == FSE_OK)); @@ -266,7 +319,7 @@ static void js_storage_rename(struct mjs* mjs) { static void js_storage_copy(struct mjs* mjs) { const char *source, *dest; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&source), JS_ARG_STR(&dest)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_2_str_args, &source, &dest); Storage* storage = JS_GET_CONTEXT(mjs); FS_Error status = storage_common_copy(storage, source, dest); mjs_return(mjs, mjs_mk_boolean(mjs, status == FSE_OK || status == FSE_EXIST)); @@ -274,7 +327,7 @@ static void js_storage_copy(struct mjs* mjs) { static void js_storage_fs_info(struct mjs* mjs) { const char* fs; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&fs)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &fs); Storage* storage = JS_GET_CONTEXT(mjs); uint64_t total_space, free_space; if(storage_common_fs_info(storage, fs, &total_space, &free_space) != FSE_OK) { @@ -290,15 +343,18 @@ static void js_storage_fs_info(struct mjs* mjs) { } static void js_storage_next_available_filename(struct mjs* mjs) { + static const JsValueDeclaration js_storage_naf_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_storage_naf_args = JS_VALUE_ARGS(js_storage_naf_arg_list); + const char *dir_path, *file_name, *file_ext; int32_t max_len; - JS_FETCH_ARGS_OR_RETURN( - mjs, - JS_EXACTLY, - JS_ARG_STR(&dir_path), - JS_ARG_STR(&file_name), - JS_ARG_STR(&file_ext), - JS_ARG_INT32(&max_len)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_naf_args, &dir_path, &file_name, &file_ext, &max_len); + Storage* storage = JS_GET_CONTEXT(mjs); FuriString* next_name = furi_string_alloc(); storage_get_next_filename(storage, dir_path, file_name, file_ext, next_name, max_len); @@ -306,23 +362,27 @@ static void js_storage_next_available_filename(struct mjs* mjs) { furi_string_free(next_name); } -// ---=== path ops ===--- +// =============== +// Path operations +// =============== static void js_storage_are_paths_equal(struct mjs* mjs) { const char *path1, *path2; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path1), JS_ARG_STR(&path2)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_2_str_args, &path1, &path2); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_equivalent_path(storage, path1, path2))); } static void js_storage_is_subpath_of(struct mjs* mjs) { const char *parent, *child; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&parent), JS_ARG_STR(&child)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_2_str_args, &parent, &child); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_is_subdir(storage, parent, child))); } -// ---=== module ctor & dtor ===--- +// ================== +// Module ctor & dtor +// ================== static void* js_storage_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { UNUSED(modules); @@ -363,7 +423,9 @@ static void js_storage_destroy(void* data) { furi_record_close(RECORD_STORAGE); } -// ---=== boilerplate ===--- +// =========== +// Boilerplate +// =========== static const JsModuleDescriptor js_storage_desc = { "storage", diff --git a/applications/system/js_app/plugin_api/app_api_table_i.h b/applications/system/js_app/plugin_api/app_api_table_i.h index b2debbde877..76556bcdd34 100644 --- a/applications/system/js_app/plugin_api/app_api_table_i.h +++ b/applications/system/js_app/plugin_api/app_api_table_i.h @@ -1,4 +1,5 @@ -#include "js_plugin_api.h" +#include "../js_modules.h" + /* * A list of app's private functions and objects to expose for plugins. * It is used to generate a table of symbols for import resolver to use. @@ -8,4 +9,16 @@ static constexpr auto app_api_table = sort(create_array_t( API_METHOD(js_delay_with_flags, bool, (struct mjs*, uint32_t)), API_METHOD(js_flags_set, void, (struct mjs*, uint32_t)), API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t)), - API_METHOD(js_module_get, void*, (JsModules*, const char*)))); + API_METHOD(js_module_get, void*, (JsModules*, const char*)), + API_METHOD(js_value_buffer_size, size_t, (const JsValueParseDeclaration declaration)), + API_METHOD( + js_value_parse, + JsValueParseStatus, + (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, + ...)))); diff --git a/applications/system/js_app/plugin_api/js_plugin_api.h b/applications/system/js_app/plugin_api/js_plugin_api.h deleted file mode 100644 index 421b6857624..00000000000 --- a/applications/system/js_app/plugin_api/js_plugin_api.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef void JsModules; - -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); - -void* js_module_get(JsModules* modules, const char* name); - -#ifdef __cplusplus -} -#endif From db899d4a3dbc6034da9f4784f37486a7bc7f4305 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 5 Mar 2025 12:22:38 +0400 Subject: [PATCH 23/26] style: format --- applications/system/js_app/js_modules.h | 2 - .../modules/js_event_loop/js_event_loop.c | 5 ++- applications/system/js_app/modules/js_gpio.c | 27 ++++++++----- .../system/js_app/modules/js_gui/js_gui.c | 18 ++++++--- .../system/js_app/modules/js_serial.c | 40 ++++++++++++------- .../system/js_app/modules/js_storage.c | 15 ++++--- 6 files changed, 68 insertions(+), 39 deletions(-) diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h index 9b20ad135e3..83e4b4fe7d5 100644 --- a/applications/system/js_app/js_modules.h +++ b/applications/system/js_app/js_modules.h @@ -105,8 +105,6 @@ typedef struct { const ElfApiInterface* api_interface; } JsModuleDescriptor; - - JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver); void js_modules_destroy(JsModules* modules); diff --git a/applications/system/js_app/modules/js_event_loop/js_event_loop.c b/applications/system/js_app/modules/js_event_loop/js_event_loop.c index 3c7ee37bea0..1aded8de458 100644 --- a/applications/system/js_app/modules/js_event_loop/js_event_loop.c +++ b/applications/system/js_app/modules/js_event_loop/js_event_loop.c @@ -148,7 +148,8 @@ static void js_event_loop_subscribe(struct mjs* mjs) { JS_VALUE_SIMPLE(JsValueTypeRawPointer), JS_VALUE_SIMPLE(JsValueTypeFunction), }; - static const JsValueArguments js_loop_subscribe_args = JS_VALUE_ARGS(js_loop_subscribe_arg_list); + static const JsValueArguments js_loop_subscribe_args = + JS_VALUE_ARGS(js_loop_subscribe_arg_list); JsEventLoopContract* contract; mjs_val_t callback; @@ -261,7 +262,7 @@ static void js_event_loop_timer(struct mjs* mjs) { FuriEventLoopTimerType mode; int32_t interval; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_timer_args, &mode, &interval); - + JsEventLoop* module = JS_GET_CONTEXT(mjs); // make timer contract diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c index 0b739046dbb..63de6900a11 100644 --- a/applications/system/js_app/modules/js_gpio.c +++ b/applications/system/js_app/modules/js_gpio.c @@ -63,8 +63,9 @@ static void js_gpio_init(struct mjs* mjs) { {"in", JsGpioDirectionIn}, {"out", JsGpioDirectionOut}, }; - static const JsValueDeclaration js_gpio_direction = JS_VALUE_ENUM(JsGpioDirection, js_gpio_direction_variants); - + static const JsValueDeclaration js_gpio_direction = + JS_VALUE_ENUM(JsGpioDirection, js_gpio_direction_variants); + // inMode variants typedef enum { JsGpioInModeAnalog = (0 << 0), @@ -78,8 +79,9 @@ static void js_gpio_init(struct mjs* mjs) { {"interrupt", JsGpioInModeInterrupt}, {"event", JsGpioInModeEvent}, }; - static const JsValueDeclaration js_gpio_in_mode = JS_VALUE_ENUM_W_DEFAULT(JsGpioInMode, js_gpio_in_mode_variants, JsGpioInModePlainDigital); - + static const JsValueDeclaration js_gpio_in_mode = + JS_VALUE_ENUM_W_DEFAULT(JsGpioInMode, js_gpio_in_mode_variants, JsGpioInModePlainDigital); + // outMode variants typedef enum { JsGpioOutModePushPull, @@ -89,8 +91,9 @@ static void js_gpio_init(struct mjs* mjs) { {"push_pull", JsGpioOutModePushPull}, {"open_drain", JsGpioOutModeOpenDrain}, }; - static const JsValueDeclaration js_gpio_out_mode = JS_VALUE_ENUM_W_DEFAULT(JsGpioOutMode, js_gpio_out_mode_variants, JsGpioOutModeOpenDrain); - + static const JsValueDeclaration js_gpio_out_mode = + JS_VALUE_ENUM_W_DEFAULT(JsGpioOutMode, js_gpio_out_mode_variants, JsGpioOutModeOpenDrain); + // edge variants typedef enum { JsGpioEdgeRising = (0 << 2), @@ -102,14 +105,16 @@ static void js_gpio_init(struct mjs* mjs) { {"falling", JsGpioEdgeFalling}, {"both", JsGpioEdgeBoth}, }; - static const JsValueDeclaration js_gpio_edge = JS_VALUE_ENUM_W_DEFAULT(JsGpioEdge, js_gpio_edge_variants, JsGpioEdgeRising); + static const JsValueDeclaration js_gpio_edge = + JS_VALUE_ENUM_W_DEFAULT(JsGpioEdge, js_gpio_edge_variants, JsGpioEdgeRising); // pull variants static const JsValueEnumVariant js_gpio_pull_variants[] = { {"up", GpioPullUp}, {"down", GpioPullDown}, }; - static const JsValueDeclaration js_gpio_pull = JS_VALUE_ENUM_W_DEFAULT(GpioPull, js_gpio_pull_variants, GpioPullNo); + static const JsValueDeclaration js_gpio_pull = + JS_VALUE_ENUM_W_DEFAULT(GpioPull, js_gpio_pull_variants, GpioPullNo); // complete mode object static const JsValueObjectField js_gpio_mode_object_fields[] = { @@ -131,7 +136,8 @@ static void js_gpio_init(struct mjs* mjs) { JsGpioOutMode out_mode; JsGpioEdge edge; GpioPull pull; - JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_init_args, &direction, &in_mode, &out_mode, &edge, &pull); + JS_VALUE_PARSE_ARGS_OR_RETURN( + mjs, &js_gpio_init_args, &direction, &in_mode, &out_mode, &edge, &pull); GpioMode mode; if(direction == JsGpioDirectionOut) { @@ -295,7 +301,8 @@ static void js_gpio_pwm_write(struct mjs* mjs) { JS_VALUE_SIMPLE(JsValueTypeInt32), JS_VALUE_SIMPLE(JsValueTypeInt32), }; - static const JsValueArguments js_gpio_pwm_write_args = JS_VALUE_ARGS(js_gpio_pwm_write_arg_list); + static const JsValueArguments js_gpio_pwm_write_args = + JS_VALUE_ARGS(js_gpio_pwm_write_arg_list); int32_t frequency, duty; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_pwm_write_args, &frequency, &duty); diff --git a/applications/system/js_app/modules/js_gui/js_gui.c b/applications/system/js_app/modules/js_gui/js_gui.c index a99685974dd..c20d980aadb 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.c +++ b/applications/system/js_app/modules/js_gui/js_gui.c @@ -71,7 +71,8 @@ static void js_gui_vd_send_custom(struct mjs* mjs) { static const JsValueDeclaration js_gui_vd_send_custom_arg_list[] = { JS_VALUE_SIMPLE(JsValueTypeInt32), }; - static const JsValueArguments js_gui_vd_send_custom_args = JS_VALUE_ARGS(js_gui_vd_send_custom_arg_list); + static const JsValueArguments js_gui_vd_send_custom_args = + JS_VALUE_ARGS(js_gui_vd_send_custom_arg_list); int32_t event; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_send_custom_args, &event); @@ -95,7 +96,8 @@ static void js_gui_vd_send_to(struct mjs* mjs) { static const JsValueDeclaration js_gui_vd_send_to_arg_list[] = { JS_VALUE_ENUM(JsSendDir, js_send_dir_variants), }; - static const JsValueArguments js_gui_vd_send_to_args = JS_VALUE_ARGS(js_gui_vd_send_to_arg_list); + static const JsValueArguments js_gui_vd_send_to_args = + JS_VALUE_ARGS(js_gui_vd_send_to_arg_list); JsSendDir send_direction; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_send_to_args, &send_direction); @@ -115,7 +117,8 @@ static void js_gui_vd_switch_to(struct mjs* mjs) { static const JsValueDeclaration js_gui_vd_switch_to_arg_list[] = { JS_VALUE_SIMPLE(JsValueTypeAny), }; - static const JsValueArguments js_gui_vd_switch_to_args = JS_VALUE_ARGS(js_gui_vd_switch_to_arg_list); + static const JsValueArguments js_gui_vd_switch_to_args = + JS_VALUE_ARGS(js_gui_vd_switch_to_arg_list); mjs_val_t view; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_switch_to_args, &view); @@ -310,7 +313,8 @@ static void js_gui_view_add_child(struct mjs* mjs) { static const JsValueDeclaration js_gui_view_add_child_arg_list[] = { JS_VALUE_SIMPLE(JsValueTypeAny), }; - static const JsValueArguments js_gui_view_add_child_args = JS_VALUE_ARGS(js_gui_view_add_child_arg_list); + static const JsValueArguments js_gui_view_add_child_args = + JS_VALUE_ARGS(js_gui_view_add_child_arg_list); mjs_val_t child; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_add_child_args, &child); @@ -343,7 +347,8 @@ static void js_gui_view_set_children(struct mjs* mjs) { static const JsValueDeclaration js_gui_view_set_children_arg_list[] = { JS_VALUE_SIMPLE(JsValueTypeAnyArray), }; - static const JsValueArguments js_gui_view_set_children_args = JS_VALUE_ARGS(js_gui_view_set_children_arg_list); + static const JsValueArguments js_gui_view_set_children_args = + JS_VALUE_ARGS(js_gui_view_set_children_arg_list); mjs_val_t children; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_set_children_args, &children); @@ -416,7 +421,8 @@ static void js_gui_vf_make_with(struct mjs* mjs) { JS_VALUE_SIMPLE(JsValueTypeAnyObject), JS_VALUE_SIMPLE(JsValueTypeAny), }; - static const JsValueArguments js_gui_vf_make_with_args = JS_VALUE_ARGS(js_gui_vf_make_with_arg_list); + static const JsValueArguments js_gui_vf_make_with_args = + JS_VALUE_ARGS(js_gui_vf_make_with_arg_list); mjs_val_t props, children; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vf_make_with_args, &props, &children); diff --git a/applications/system/js_app/modules/js_serial.c b/applications/system/js_app/modules/js_serial.c index abc366319b8..d903939cef0 100644 --- a/applications/system/js_app/modules/js_serial.c +++ b/applications/system/js_app/modules/js_serial.c @@ -39,21 +39,23 @@ static void js_serial_setup(struct mjs* mjs) { {"lpuart", FuriHalSerialIdLpuart}, {"usart", FuriHalSerialIdUsart}, }; - + static const JsValueEnumVariant js_serial_data_bit_variants[] = { {"6", FuriHalSerialDataBits6}, {"7", FuriHalSerialDataBits7}, {"8", FuriHalSerialDataBits8}, {"9", FuriHalSerialDataBits9}, }; - static const JsValueDeclaration js_serial_data_bits = JS_VALUE_ENUM_W_DEFAULT(FuriHalSerialDataBits, js_serial_data_bit_variants, FuriHalSerialDataBits8); + static const JsValueDeclaration js_serial_data_bits = JS_VALUE_ENUM_W_DEFAULT( + FuriHalSerialDataBits, js_serial_data_bit_variants, FuriHalSerialDataBits8); static const JsValueEnumVariant js_serial_parity_variants[] = { {"none", FuriHalSerialParityNone}, {"even", FuriHalSerialParityEven}, {"odd", FuriHalSerialParityOdd}, }; - static const JsValueDeclaration js_serial_parity = JS_VALUE_ENUM_W_DEFAULT(FuriHalSerialParity, js_serial_parity_variants, FuriHalSerialParityNone); + static const JsValueDeclaration js_serial_parity = JS_VALUE_ENUM_W_DEFAULT( + FuriHalSerialParity, js_serial_parity_variants, FuriHalSerialParityNone); static const JsValueEnumVariant js_serial_stop_bit_variants[] = { {"0.5", FuriHalSerialStopBits0_5}, @@ -61,7 +63,8 @@ static void js_serial_setup(struct mjs* mjs) { {"1.5", FuriHalSerialStopBits1_5}, {"2", FuriHalSerialStopBits2}, }; - static const JsValueDeclaration js_serial_stop_bits = JS_VALUE_ENUM_W_DEFAULT(FuriHalSerialStopBits, js_serial_stop_bit_variants, FuriHalSerialStopBits1); + static const JsValueDeclaration js_serial_stop_bits = JS_VALUE_ENUM_W_DEFAULT( + FuriHalSerialStopBits, js_serial_stop_bit_variants, FuriHalSerialStopBits1); static const JsValueObjectField js_serial_framing_fields[] = { {"dataBits", &js_serial_data_bits}, @@ -81,11 +84,13 @@ static void js_serial_setup(struct mjs* mjs) { FuriHalSerialDataBits data_bits = FuriHalSerialDataBits8; FuriHalSerialParity parity = FuriHalSerialParityNone; FuriHalSerialStopBits stop_bits = FuriHalSerialStopBits1; - JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_setup_args, &serial_id, &baudrate, &data_bits, &parity, &stop_bits); + JS_VALUE_PARSE_ARGS_OR_RETURN( + mjs, &js_serial_setup_args, &serial_id, &baudrate, &data_bits, &parity, &stop_bits); JsSerialInst* serial = JS_GET_CONTEXT(mjs); - if(serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is already configured"); + if(serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is already configured"); expansion_disable(furi_record_open(RECORD_EXPANSION)); furi_record_close(RECORD_EXPANSION); @@ -123,7 +128,8 @@ static void js_serial_end(struct mjs* mjs) { JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); js_serial_deinit(serial); } @@ -131,7 +137,8 @@ static void js_serial_end(struct mjs* mjs) { static void js_serial_write(struct mjs* mjs) { JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); bool args_correct = true; @@ -224,7 +231,8 @@ static const JsValueArguments js_serial_read_args = JS_VALUE_ARGS(js_serial_read static void js_serial_read(struct mjs* mjs) { JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); int32_t read_len, timeout; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_args, &read_len, &timeout); @@ -243,7 +251,8 @@ static void js_serial_read(struct mjs* mjs) { static void js_serial_readln(struct mjs* mjs) { JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); static const JsValueDeclaration js_serial_readln_arg_list[] = { JS_VALUE_SIMPLE(JsValueTypeInt32), @@ -281,7 +290,8 @@ static void js_serial_readln(struct mjs* mjs) { static void js_serial_read_bytes(struct mjs* mjs) { JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); int32_t read_len, timeout; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_args, &read_len, &timeout); @@ -315,12 +325,14 @@ static char* js_serial_receive_any(JsSerialInst* serial, size_t* len, uint32_t t static void js_serial_read_any(struct mjs* mjs) { JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); static const JsValueDeclaration js_serial_read_any_arg_list[] = { JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, INT32_MAX), }; - static const JsValueArguments js_serial_read_any_args = JS_VALUE_ARGS(js_serial_read_any_arg_list); + static const JsValueArguments js_serial_read_any_args = + JS_VALUE_ARGS(js_serial_read_any_arg_list); int32_t timeout; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_any_args, &timeout); @@ -567,7 +579,7 @@ static void* js_serial_create(struct mjs* mjs, mjs_val_t* object, JsModules* mod UNUSED(modules); JsSerialInst* js_serial = malloc(sizeof(JsSerialInst)); js_serial->mjs = mjs; - + mjs_val_t serial_obj = mjs_mk_object(mjs); JS_ASSIGN_MULTI(mjs, serial_obj) { JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, js_serial)); diff --git a/applications/system/js_app/modules/js_storage.c b/applications/system/js_app/modules/js_storage.c index ee858b3928b..66d002f3377 100644 --- a/applications/system/js_app/modules/js_storage.c +++ b/applications/system/js_app/modules/js_storage.c @@ -68,7 +68,8 @@ static void js_storage_file_write(struct mjs* mjs) { static const JsValueDeclaration js_storage_file_write_arg_list[] = { JS_VALUE_SIMPLE(JsValueTypeAny), }; - static const JsValueArguments js_storage_file_write_args = JS_VALUE_ARGS(js_storage_file_write_arg_list); + static const JsValueArguments js_storage_file_write_args = + JS_VALUE_ARGS(js_storage_file_write_arg_list); mjs_val_t data; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_file_write_args, &data); @@ -125,7 +126,8 @@ static void js_storage_file_copy_to(struct mjs* mjs) { JS_VALUE_SIMPLE(JsValueTypeAny), JS_VALUE_SIMPLE(JsValueTypeInt32), }; - static const JsValueArguments js_storage_file_write_args = JS_VALUE_ARGS(js_storage_file_write_arg_list); + static const JsValueArguments js_storage_file_write_args = + JS_VALUE_ARGS(js_storage_file_write_arg_list); mjs_val_t dest_obj; int32_t bytes; @@ -166,12 +168,14 @@ static void js_storage_open_file(struct mjs* mjs) { JS_VALUE_ENUM(FS_AccessMode, js_storage_fsam_variants), JS_VALUE_ENUM(FS_OpenMode, js_storage_fsom_variants), }; - static const JsValueArguments js_storage_open_file_args = JS_VALUE_ARGS(js_storage_open_file_arg_list); + static const JsValueArguments js_storage_open_file_args = + JS_VALUE_ARGS(js_storage_open_file_arg_list); const char* path; FS_AccessMode access_mode; FS_OpenMode open_mode; - JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_open_file_args, &path, &access_mode, &open_mode); + JS_VALUE_PARSE_ARGS_OR_RETURN( + mjs, &js_storage_open_file_args, &path, &access_mode, &open_mode); Storage* storage = JS_GET_CONTEXT(mjs); File* file = storage_file_alloc(storage); @@ -353,7 +357,8 @@ static void js_storage_next_available_filename(struct mjs* mjs) { const char *dir_path, *file_name, *file_ext; int32_t max_len; - JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_naf_args, &dir_path, &file_name, &file_ext, &max_len); + JS_VALUE_PARSE_ARGS_OR_RETURN( + mjs, &js_storage_naf_args, &dir_path, &file_name, &file_ext, &max_len); Storage* storage = JS_GET_CONTEXT(mjs); FuriString* next_name = furi_string_alloc(); From f7f6856341d697092b23ef699073e6cbb19ccc5a Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 20 Mar 2025 15:00:40 +0400 Subject: [PATCH 24/26] feat: all js views done --- applications/services/gui/modules/popup.c | 5 +- applications/services/gui/modules/popup.h | 2 +- applications/system/js_app/application.fam | 48 +++ .../js_app/examples/apps/Scripts/gui.js | 120 ++++++- .../js_app/modules/js_gui/button_menu.c | 188 +++++++++++ .../js_app/modules/js_gui/button_panel.c | 292 ++++++++++++++++++ .../system/js_app/modules/js_gui/icon.c | 10 + .../system/js_app/modules/js_gui/js_gui.c | 21 +- .../system/js_app/modules/js_gui/js_gui.h | 5 + .../modules/js_gui/js_gui_api_table_i.h | 3 +- .../system/js_app/modules/js_gui/menu.c | 122 ++++++++ .../js_app/modules/js_gui/number_input.c | 130 ++++++++ .../system/js_app/modules/js_gui/popup.c | 111 +++++++ .../system/js_app/modules/js_gui/submenu.c | 33 +- .../system/js_app/modules/js_gui/vi_list.c | 180 +++++++++++ .../packages/fz-sdk/gui/button_menu.d.ts | 40 +++ .../packages/fz-sdk/gui/button_panel.d.ts | 49 +++ .../js_app/packages/fz-sdk/gui/icon.d.ts | 5 +- .../js_app/packages/fz-sdk/gui/index.d.ts | 18 +- .../js_app/packages/fz-sdk/gui/menu.d.ts | 38 +++ .../packages/fz-sdk/gui/number_input.d.ts | 44 +++ .../js_app/packages/fz-sdk/gui/popup.d.ts | 43 +++ .../js_app/packages/fz-sdk/gui/submenu.d.ts | 5 +- .../js_app/packages/fz-sdk/gui/vi_list.d.ts | 38 +++ .../js_app/packages/fz-sdk/package.json | 2 +- 25 files changed, 1504 insertions(+), 48 deletions(-) create mode 100644 applications/system/js_app/modules/js_gui/button_menu.c create mode 100644 applications/system/js_app/modules/js_gui/button_panel.c create mode 100644 applications/system/js_app/modules/js_gui/menu.c create mode 100644 applications/system/js_app/modules/js_gui/number_input.c create mode 100644 applications/system/js_app/modules/js_gui/popup.c create mode 100644 applications/system/js_app/modules/js_gui/vi_list.c create mode 100644 applications/system/js_app/packages/fz-sdk/gui/button_menu.d.ts create mode 100644 applications/system/js_app/packages/fz-sdk/gui/button_panel.d.ts create mode 100644 applications/system/js_app/packages/fz-sdk/gui/menu.d.ts create mode 100644 applications/system/js_app/packages/fz-sdk/gui/number_input.d.ts create mode 100644 applications/system/js_app/packages/fz-sdk/gui/popup.d.ts create mode 100644 applications/system/js_app/packages/fz-sdk/gui/vi_list.d.ts diff --git a/applications/services/gui/modules/popup.c b/applications/services/gui/modules/popup.c index 5c9c75e4a97..6b5cbfdd058 100644 --- a/applications/services/gui/modules/popup.c +++ b/applications/services/gui/modules/popup.c @@ -93,13 +93,12 @@ static bool popup_view_input_callback(InputEvent* event, void* context) { void popup_start_timer(void* context) { Popup* popup = context; if(popup->timer_enabled) { - uint32_t timer_period = - popup->timer_period_in_ms / (1000.0f / furi_kernel_get_tick_frequency()); + uint32_t timer_period = furi_ms_to_ticks(popup->timer_period_in_ms); if(timer_period == 0) timer_period = 1; if(furi_timer_start(popup->timer, timer_period) != FuriStatusOk) { furi_crash(); - }; + } } } diff --git a/applications/services/gui/modules/popup.h b/applications/services/gui/modules/popup.h index a3e8dc77321..d694dc45bbb 100644 --- a/applications/services/gui/modules/popup.h +++ b/applications/services/gui/modules/popup.h @@ -41,7 +41,7 @@ void popup_free(Popup* popup); */ View* popup_get_view(Popup* popup); -/** Set popup header text +/** Set popup timeout callback * * @param popup Popup instance * @param callback PopupCallback diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 66ec221ec75..4fec43c72b3 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -78,6 +78,54 @@ App( sources=["modules/js_gui/text_input.c"], ) +App( + appid="js_gui__number_input", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_number_input_ep", + requires=["js_app"], + sources=["modules/js_gui/number_input.c"], +) + +App( + appid="js_gui__button_panel", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_button_panel_ep", + requires=["js_app"], + sources=["modules/js_gui/button_panel.c"], +) + +App( + appid="js_gui__popup", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_popup_ep", + requires=["js_app"], + sources=["modules/js_gui/popup.c"], +) + +App( + appid="js_gui__button_menu", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_button_menu_ep", + requires=["js_app"], + sources=["modules/js_gui/button_menu.c"], +) + +App( + appid="js_gui__menu", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_menu_ep", + requires=["js_app"], + sources=["modules/js_gui/menu.c"], +) + +App( + appid="js_gui__vi_list", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_vi_list_ep", + requires=["js_app"], + sources=["modules/js_gui/vi_list.c"], +) + App( appid="js_gui__byte_input", apptype=FlipperAppType.PLUGIN, diff --git a/applications/system/js_app/examples/apps/Scripts/gui.js b/applications/system/js_app/examples/apps/Scripts/gui.js index 16673524aca..10d6d23aaec 100644 --- a/applications/system/js_app/examples/apps/Scripts/gui.js +++ b/applications/system/js_app/examples/apps/Scripts/gui.js @@ -9,6 +9,12 @@ let byteInputView = require("gui/byte_input"); let textBoxView = require("gui/text_box"); let dialogView = require("gui/dialog"); let filePicker = require("gui/file_picker"); +let buttonMenuView = require("gui/button_menu"); +let buttonPanelView = require("gui/button_panel"); +let menuView = require("gui/menu"); +let numberInputView = require("gui/number_input"); +let popupView = require("gui/popup"); +let viListView = require("gui/vi_list"); let widget = require("gui/widget"); let icon = require("gui/icon"); let flipper = require("flipper"); @@ -26,6 +32,11 @@ let stopwatchWidgetElements = [ { element: "button", button: "right", text: "Back" }, ]; +// icons for the button panel +let offIcons = [icon.getBuiltin("off_19x20"), icon.getBuiltin("off_hover_19x20")]; +let powerIcons = [icon.getBuiltin("power_19x20"), icon.getBuiltin("power_hover_19x20")]; +let settingsIcon = icon.getBuiltin("Settings_14"); + // declare view instances let views = { loading: loadingView.make(), @@ -47,19 +58,57 @@ let views = { text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.", }), stopwatchWidget: widget.makeWith({}, stopwatchWidgetElements), + buttonMenu: buttonMenuView.makeWith({ + header: "Header" + }, [ + { type: "common", label: "Test" }, + { type: "control", label: "Test2" }, + ]), + buttonPanel: buttonPanelView.makeWith({ + matrixSizeX: 2, + matrixSizeY: 2, + }, [ + { type: "button", x: 0, y: 0, matrixX: 0, matrixY: 0, icon: offIcons[0], iconSelected: offIcons[1] }, + { type: "button", x: 30, y: 30, matrixX: 1, matrixY: 1, icon: powerIcons[0], iconSelected: powerIcons[1] }, + { type: "label", x: 0, y: 50, text: "Label", font: "primary" }, + ]), + menu: menuView.makeWith({}, [ + { label: "One", icon: settingsIcon }, + { label: "Two", icon: settingsIcon }, + { label: "three", icon: settingsIcon }, + ]), + numberKbd: numberInputView.makeWith({ + header: "Number input", + defaultValue: 100, + minValue: 0, + maxValue: 200, + }), + popup: popupView.makeWith({ + header: "Hello", + text: "I'm going to be gone\nin 2 seconds", + }), + viList: viListView.makeWith({}, [ + { label: "One", variants: ["1", "1.0"] }, + { label: "Two", variants: ["2", "2.0"] }, + ]), demos: submenuView.makeWith({ header: "Choose a demo", - items: [ - "Hourglass screen", - "Empty screen", - "Text input & Dialog", - "Byte input", - "Text box", - "File picker", - "Widget", - "Exit app", - ], - }), + }, [ + "Hourglass screen", + "Empty screen", + "Text input & Dialog", + "Byte input", + "Text box", + "File picker", + "Widget", + "Button menu", + "Button panel", + "Menu", + "Number input", + "Popup", + "Var. item list", + "Exit app", + ]), }; // demo selector @@ -91,6 +140,19 @@ eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, v } else if (index === 6) { gui.viewDispatcher.switchTo(views.stopwatchWidget); } else if (index === 7) { + gui.viewDispatcher.switchTo(views.buttonMenu); + } else if (index === 8) { + gui.viewDispatcher.switchTo(views.buttonPanel); + } else if (index === 9) { + gui.viewDispatcher.switchTo(views.menu); + } else if (index === 10) { + gui.viewDispatcher.switchTo(views.numberKbd); + } else if (index === 11) { + views.popup.set("timeout", 2000); + gui.viewDispatcher.switchTo(views.popup); + } else if (index === 12) { + gui.viewDispatcher.switchTo(views.viList); + } else if (index === 13) { eventLoop.stop(); } }, gui, eventLoop, views); @@ -155,6 +217,42 @@ eventLoop.subscribe(eventLoop.timer("periodic", 500), function (_sub, _item, vie return [views, stopwatchWidgetElements, halfSeconds]; }, views, stopwatchWidgetElements, 0); +// go back after popup times out +eventLoop.subscribe(views.popup.timeout, function (_sub, _item, gui, views) { + gui.viewDispatcher.switchTo(views.demos); +}, gui, views); + +// button menu callback +eventLoop.subscribe(views.buttonMenu.input, function (_sub, input, gui, views) { + views.helloDialog.set("text", "You selected #" + input.index.toString()); + views.helloDialog.set("center", "Cool!"); + gui.viewDispatcher.switchTo(views.helloDialog); +}, gui, views); + +// button panel callback +eventLoop.subscribe(views.buttonPanel.input, function (_sub, input, gui, views) { + views.helloDialog.set("text", "You selected #" + input.index.toString()); + views.helloDialog.set("center", "Cool!"); + gui.viewDispatcher.switchTo(views.helloDialog); +}, gui, views); + +// menu callback +eventLoop.subscribe(views.menu.chosen, function (_sub, index, gui, views) { + views.helloDialog.set("text", "You selected #" + index.toString()); + views.helloDialog.set("center", "Cool!"); + gui.viewDispatcher.switchTo(views.helloDialog); +}, gui, views); + +// menu callback +eventLoop.subscribe(views.numberKbd.input, function (_sub, number, gui, views) { + views.helloDialog.set("text", "You typed " + number.toString()); + views.helloDialog.set("center", "Cool!"); + gui.viewDispatcher.switchTo(views.helloDialog); +}, gui, views); + +// ignore VI list +eventLoop.subscribe(views.viList.valueUpdate, function (_sub, _item) {}); + // run UI gui.viewDispatcher.switchTo(views.demos); eventLoop.run(); diff --git a/applications/system/js_app/modules/js_gui/button_menu.c b/applications/system/js_app/modules/js_gui/button_menu.c new file mode 100644 index 00000000000..c85bd857b28 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/button_menu.c @@ -0,0 +1,188 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +typedef struct { + int32_t next_index; + char** owned_strings; + size_t n_owned_strings; + + FuriMessageQueue* input_queue; + JsEventLoopContract contract; +} JsBtnMenuContext; + +typedef struct { + int32_t index; + InputType input_type; +} JsBtnMenuEvent; + +// not using mlib to conserve code size +static const char* js_button_menu_own_string(JsBtnMenuContext* context, const char* str) { + char* owned = strdup(str); + context->n_owned_strings++; + context->owned_strings = + realloc(context->owned_strings, context->n_owned_strings * sizeof(const char*)); + context->owned_strings[context->n_owned_strings - 1] = owned; + return owned; +} + +static void js_button_menu_free_owned_strings(JsBtnMenuContext* context) { + for(size_t i = 0; i < context->n_owned_strings; i++) { + free(context->owned_strings[i]); + } + free(context->owned_strings); + context->owned_strings = NULL; +} + +static const char* js_input_type_to_str(InputType type) { + switch(type) { + case InputTypePress: + return "press"; + case InputTypeRelease: + return "release"; + case InputTypeShort: + return "short"; + case InputTypeLong: + return "long"; + case InputTypeRepeat: + return "repeat"; + default: + furi_crash(); + } +} + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsBtnMenuContext* context) { + UNUSED(context); + JsBtnMenuEvent event; + furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk); + + mjs_val_t event_obj = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, event_obj) { + JS_FIELD("index", mjs_mk_number(mjs, event.index)); + JS_FIELD("type", mjs_mk_string(mjs, js_input_type_to_str(event.input_type), ~0, false)); + } + + return event_obj; +} + +static void input_callback(void* ctx, int32_t index, InputType type) { + JsBtnMenuContext* context = ctx; + JsBtnMenuEvent event = { + .index = index, + .input_type = type, + }; + furi_check(furi_message_queue_put(context->input_queue, &event, 0) == FuriStatusOk); +} + +static bool matrix_header_assign( + struct mjs* mjs, + ButtonMenu* menu, + JsViewPropValue value, + JsBtnMenuContext* context) { + UNUSED(mjs); + button_menu_set_header(menu, js_button_menu_own_string(context, value.string)); + return true; +} + +static bool js_button_menu_add_child( + struct mjs* mjs, + ButtonMenu* menu, + JsBtnMenuContext* context, + mjs_val_t child_obj) { + static const JsValueEnumVariant js_button_menu_item_type_variants[] = { + {"common", ButtonMenuItemTypeCommon}, + {"control", ButtonMenuItemTypeControl}, + }; + static const JsValueDeclaration js_button_menu_item_type = + JS_VALUE_ENUM(ButtonMenuItemType, js_button_menu_item_type_variants); + + static const JsValueDeclaration js_button_menu_string = JS_VALUE_SIMPLE(JsValueTypeString); + + static const JsValueObjectField js_button_menu_child_fields[] = { + {"type", &js_button_menu_item_type}, + {"label", &js_button_menu_string}, + }; + static const JsValueDeclaration js_button_menu_child = + JS_VALUE_OBJECT(js_button_menu_child_fields); + + ButtonMenuItemType item_type; + const char* label; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_button_menu_child), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &item_type, + &label); + if(status != JsValueParseStatusOk) return false; + + button_menu_add_item( + menu, + js_button_menu_own_string(context, label), + context->next_index++, + input_callback, + item_type, + context); + + return true; +} + +static void js_button_menu_reset_children(ButtonMenu* menu, JsBtnMenuContext* context) { + context->next_index = 0; + button_menu_reset(menu); + js_button_menu_free_owned_strings(context); +} + +static JsBtnMenuContext* ctx_make(struct mjs* mjs, ButtonMenu* menu, mjs_val_t view_obj) { + UNUSED(menu); + JsBtnMenuContext* context = malloc(sizeof(JsBtnMenuContext)); + *context = (JsBtnMenuContext){ + .next_index = 0, + .owned_strings = NULL, + .n_owned_strings = 0, + .input_queue = furi_message_queue_alloc(1, sizeof(JsBtnMenuEvent)), + }; + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->input_queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + .transformer_context = context, + }, + }; + mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(ButtonMenu* input, JsBtnMenuContext* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->input_queue); + furi_message_queue_free(context->input_queue); + js_button_menu_free_owned_strings(context); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)button_menu_alloc, + .free = (JsViewFree)button_menu_free, + .get_view = (JsViewGetView)button_menu_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .add_child = (JsViewAddChild)js_button_menu_add_child, + .reset_children = (JsViewResetChildren)js_button_menu_reset_children, + .prop_cnt = 1, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)matrix_header_assign}, + }}; + +JS_GUI_VIEW_DEF(button_menu, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/button_panel.c b/applications/system/js_app/modules/js_gui/button_panel.c new file mode 100644 index 00000000000..558549d091e --- /dev/null +++ b/applications/system/js_app/modules/js_gui/button_panel.c @@ -0,0 +1,292 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +typedef struct { + size_t matrix_x, matrix_y; + int32_t next_index; + char** owned_strings; + size_t n_owned_strings; + + FuriMessageQueue* input_queue; + JsEventLoopContract contract; +} JsBtnPanelContext; + +typedef struct { + int32_t index; + InputType input_type; +} JsBtnPanelEvent; + +// not using mlib to conserve code size +static const char* js_button_panel_own_string(JsBtnPanelContext* context, const char* str) { + char* owned = strdup(str); + context->n_owned_strings++; + context->owned_strings = + realloc(context->owned_strings, context->n_owned_strings * sizeof(const char*)); + context->owned_strings[context->n_owned_strings - 1] = owned; + return owned; +} + +static void js_button_panel_free_owned(JsBtnPanelContext* context) { + for(size_t i = 0; i < context->n_owned_strings; i++) { + free(context->owned_strings[i]); + } + free(context->owned_strings); + context->owned_strings = NULL; +} + +static const char* js_input_type_to_str(InputType type) { + switch(type) { + case InputTypePress: + return "press"; + case InputTypeRelease: + return "release"; + case InputTypeShort: + return "short"; + case InputTypeLong: + return "long"; + case InputTypeRepeat: + return "repeat"; + default: + furi_crash(); + } +} + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsBtnPanelContext* context) { + UNUSED(context); + JsBtnPanelEvent event; + furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk); + + mjs_val_t event_obj = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, event_obj) { + JS_FIELD("index", mjs_mk_number(mjs, event.index)); + JS_FIELD("type", mjs_mk_string(mjs, js_input_type_to_str(event.input_type), ~0, false)); + } + + return event_obj; +} + +static void input_callback(void* ctx, int32_t index, InputType type) { + JsBtnPanelContext* context = ctx; + JsBtnPanelEvent event = { + .index = index, + .input_type = type, + }; + furi_check(furi_message_queue_put(context->input_queue, &event, 0) == FuriStatusOk); +} + +static bool matrix_size_x_assign( + struct mjs* mjs, + ButtonPanel* panel, + JsViewPropValue value, + JsBtnPanelContext* context) { + UNUSED(mjs); + context->matrix_x = value.number; + button_panel_reserve(panel, context->matrix_x, context->matrix_y); + return true; +} + +static bool matrix_size_y_assign( + struct mjs* mjs, + ButtonPanel* panel, + JsViewPropValue value, + JsBtnPanelContext* context) { + UNUSED(mjs); + context->matrix_y = value.number; + button_panel_reserve(panel, context->matrix_x, context->matrix_y); + return true; +} + +static bool js_button_panel_add_child( + struct mjs* mjs, + ButtonPanel* panel, + JsBtnPanelContext* context, + mjs_val_t child_obj) { + typedef enum { + JsButtonPanelChildTypeButton, + JsButtonPanelChildTypeLabel, + JsButtonPanelChildTypeIcon, + } JsButtonPanelChildType; + static const JsValueEnumVariant js_button_panel_child_type_variants[] = { + {"button", JsButtonPanelChildTypeButton}, + {"label", JsButtonPanelChildTypeLabel}, + {"icon", JsButtonPanelChildTypeIcon}, + }; + static const JsValueDeclaration js_button_panel_child_type = + JS_VALUE_ENUM(JsButtonPanelChildType, js_button_panel_child_type_variants); + + static const JsValueDeclaration js_button_panel_number = JS_VALUE_SIMPLE(JsValueTypeInt32); + static const JsValueObjectField js_button_panel_common_fields[] = { + {"type", &js_button_panel_child_type}, + {"x", &js_button_panel_number}, + {"y", &js_button_panel_number}, + }; + static const JsValueDeclaration js_button_panel_common = + JS_VALUE_OBJECT(js_button_panel_common_fields); + + static const JsValueDeclaration js_button_panel_pointer = + JS_VALUE_SIMPLE(JsValueTypeRawPointer); + static const JsValueObjectField js_button_panel_button_fields[] = { + {"matrixX", &js_button_panel_number}, + {"matrixY", &js_button_panel_number}, + {"icon", &js_button_panel_pointer}, + {"iconSelected", &js_button_panel_pointer}, + }; + static const JsValueDeclaration js_button_panel_button = + JS_VALUE_OBJECT(js_button_panel_button_fields); + + static const JsValueDeclaration js_button_panel_string = JS_VALUE_SIMPLE(JsValueTypeString); + static const JsValueObjectField js_button_panel_label_fields[] = { + {"text", &js_button_panel_string}, + {"font", &js_gui_font_declaration}, + }; + static const JsValueDeclaration js_button_panel_label = + JS_VALUE_OBJECT(js_button_panel_label_fields); + + static const JsValueObjectField js_button_panel_icon_fields[] = { + {"icon", &js_button_panel_pointer}, + }; + static const JsValueDeclaration js_button_panel_icon = + JS_VALUE_OBJECT(js_button_panel_icon_fields); + + JsButtonPanelChildType child_type; + int32_t x, y; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_common), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &child_type, + &x, + &y); + if(status != JsValueParseStatusOk) return false; + + switch(child_type) { + case JsButtonPanelChildTypeButton: { + int32_t matrix_x, matrix_y; + const Icon *icon, *icon_selected; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_button), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &matrix_x, + &matrix_y, + &icon, + &icon_selected); + if(status != JsValueParseStatusOk) return false; + button_panel_add_item( + panel, + context->next_index++, + matrix_x, + matrix_y, + x, + y, + icon, + icon_selected, + (ButtonItemCallback)input_callback, + context); + break; + } + + case JsButtonPanelChildTypeLabel: { + const char* text; + Font font; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_label), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &text, + &font); + if(status != JsValueParseStatusOk) return false; + button_panel_add_label(panel, x, y, font, js_button_panel_own_string(context, text)); + break; + } + + case JsButtonPanelChildTypeIcon: { + const Icon* icon; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_icon), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &icon); + if(status != JsValueParseStatusOk) return false; + button_panel_add_icon(panel, x, y, icon); + break; + } + } + + return true; +} + +static void js_button_panel_reset_children(ButtonPanel* panel, JsBtnPanelContext* context) { + context->next_index = 0; + button_panel_reset(panel); + button_panel_reserve(panel, context->matrix_x, context->matrix_y); + js_button_panel_free_owned(context); +} + +static JsBtnPanelContext* ctx_make(struct mjs* mjs, ButtonPanel* panel, mjs_val_t view_obj) { + UNUSED(panel); + JsBtnPanelContext* context = malloc(sizeof(JsBtnPanelContext)); + *context = (JsBtnPanelContext){ + .matrix_x = 1, + .matrix_y = 1, + .next_index = 0, + .owned_strings = NULL, + .n_owned_strings = 0, + .input_queue = furi_message_queue_alloc(1, sizeof(JsBtnPanelEvent)), + }; + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->input_queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + .transformer_context = context, + }, + }; + mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(ButtonPanel* input, JsBtnPanelContext* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->input_queue); + furi_message_queue_free(context->input_queue); + js_button_panel_free_owned(context); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)button_panel_alloc, + .free = (JsViewFree)button_panel_free, + .get_view = (JsViewGetView)button_panel_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .add_child = (JsViewAddChild)js_button_panel_add_child, + .reset_children = (JsViewResetChildren)js_button_panel_reset_children, + .prop_cnt = 2, + .props = { + (JsViewPropDescriptor){ + .name = "matrixSizeX", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)matrix_size_x_assign}, + (JsViewPropDescriptor){ + .name = "matrixSizeY", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)matrix_size_y_assign}, + }}; + +JS_GUI_VIEW_DEF(button_panel, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/icon.c b/applications/system/js_app/modules/js_gui/icon.c index 4fc6da2e088..11e26d02fa8 100644 --- a/applications/system/js_app/modules/js_gui/icon.c +++ b/applications/system/js_app/modules/js_gui/icon.c @@ -14,9 +14,19 @@ typedef struct { .name = #icon, .data = &I_##icon \ } +#define ANIM_ICON_DEF(icon) \ + (IconDefinition) { \ + .name = #icon, .data = &A_##icon \ + } + static const IconDefinition builtin_icons[] = { ICON_DEF(DolphinWait_59x54), ICON_DEF(js_script_10px), + ICON_DEF(off_19x20), + ICON_DEF(off_hover_19x20), + ICON_DEF(power_19x20), + ICON_DEF(power_hover_19x20), + ANIM_ICON_DEF(Settings_14), }; // Firmware's Icon struct needs a frames array, and uses a small CompressHeader diff --git a/applications/system/js_app/modules/js_gui/js_gui.c b/applications/system/js_app/modules/js_gui/js_gui.c index c20d980aadb..2f782571748 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.c +++ b/applications/system/js_app/modules/js_gui/js_gui.c @@ -31,6 +31,14 @@ typedef struct { void* custom_data; } JsGuiViewData; +static const JsValueEnumVariant js_gui_font_variants[] = { + {"primary", FontPrimary}, + {"secondary", FontSecondary}, + {"keyboard", FontKeyboard}, + {"bit_numbers", FontBigNumbers}, +}; +const JsValueDeclaration js_gui_font_declaration = JS_VALUE_ENUM(Font, js_gui_font_variants); + /** * @brief Transformer for custom events */ @@ -273,9 +281,12 @@ static bool /** * @brief Sets the list of children. Not available from JS. */ -static bool - js_gui_view_internal_set_children(struct mjs* mjs, mjs_val_t children, JsGuiViewData* data) { - data->descriptor->reset_children(data->specific_view, data->custom_data); +static bool js_gui_view_internal_set_children( + struct mjs* mjs, + mjs_val_t children, + JsGuiViewData* data, + bool do_reset) { + if(do_reset) data->descriptor->reset_children(data->specific_view, data->custom_data); for(size_t i = 0; i < mjs_array_length(mjs, children); i++) { mjs_val_t child = mjs_array_get(mjs, children, i); @@ -357,7 +368,7 @@ static void js_gui_view_set_children(struct mjs* mjs) { if(!data->descriptor->add_child || !data->descriptor->reset_children) JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); - js_gui_view_internal_set_children(mjs, children, data); + js_gui_view_internal_set_children(mjs, children, data, true); } /** @@ -450,7 +461,7 @@ static void js_gui_vf_make_with(struct mjs* mjs) { if(!data->descriptor->add_child || !data->descriptor->reset_children) JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); - if(!js_gui_view_internal_set_children(mjs, children, data)) return; + if(!js_gui_view_internal_set_children(mjs, children, data, false)) return; } mjs_return(mjs, view_obj); diff --git a/applications/system/js_app/modules/js_gui/js_gui.h b/applications/system/js_app/modules/js_gui/js_gui.h index d9d98df39f0..552eaee7c17 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.h +++ b/applications/system/js_app/modules/js_gui/js_gui.h @@ -20,6 +20,11 @@ typedef union { mjs_val_t term; } JsViewPropValue; +/** + * JS-to-C font enum mapping + */ +extern const JsValueDeclaration js_gui_font_declaration; + /** * @brief Assigns a value to a view property * diff --git a/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h b/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h index 852b3d1071f..c83a38e30ca 100644 --- a/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h +++ b/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h @@ -1,4 +1,5 @@ #include "js_gui.h" static constexpr auto js_gui_api_table = sort(create_array_t( - API_METHOD(js_gui_make_view_factory, mjs_val_t, (struct mjs*, const JsViewDescriptor*)))); + API_METHOD(js_gui_make_view_factory, mjs_val_t, (struct mjs*, const JsViewDescriptor*)), + API_VARIABLE(js_gui_font_declaration, const JsValueDeclaration))); diff --git a/applications/system/js_app/modules/js_gui/menu.c b/applications/system/js_app/modules/js_gui/menu.c new file mode 100644 index 00000000000..abce47e04e5 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/menu.c @@ -0,0 +1,122 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +typedef struct { + int32_t next_index; + char** owned_strings; + size_t n_owned_strings; + + FuriMessageQueue* queue; + JsEventLoopContract contract; +} JsMenuCtx; + +// not using mlib to conserve code size +static const char* js_menu_own_string(JsMenuCtx* context, const char* str) { + char* owned = strdup(str); + context->n_owned_strings++; + context->owned_strings = + realloc(context->owned_strings, context->n_owned_strings * sizeof(const char*)); + context->owned_strings[context->n_owned_strings - 1] = owned; + return owned; +} + +static void js_menu_free_owned_strings(JsMenuCtx* context) { + for(size_t i = 0; i < context->n_owned_strings; i++) { + free(context->owned_strings[i]); + } + free(context->owned_strings); + context->owned_strings = NULL; +} + +static mjs_val_t choose_transformer(struct mjs* mjs, FuriMessageQueue* queue, void* context) { + UNUSED(context); + uint32_t index; + furi_check(furi_message_queue_get(queue, &index, 0) == FuriStatusOk); + return mjs_mk_number(mjs, (double)index); +} + +static void choose_callback(void* context, uint32_t index) { + JsMenuCtx* ctx = context; + furi_check(furi_message_queue_put(ctx->queue, &index, 0) == FuriStatusOk); +} + +static bool + js_menu_add_child(struct mjs* mjs, Menu* menu, JsMenuCtx* context, mjs_val_t child_obj) { + static const JsValueDeclaration js_menu_string = JS_VALUE_SIMPLE(JsValueTypeString); + static const JsValueDeclaration js_menu_pointer = JS_VALUE_SIMPLE(JsValueTypeRawPointer); + + static const JsValueObjectField js_menu_child_fields[] = { + {"icon", &js_menu_pointer}, + {"label", &js_menu_string}, + }; + static const JsValueDeclaration js_menu_child = JS_VALUE_OBJECT(js_menu_child_fields); + + const Icon* icon; + const char* label; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_menu_child), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &icon, + &label); + if(status != JsValueParseStatusOk) return false; + + menu_add_item( + menu, + js_menu_own_string(context, label), + icon, + context->next_index++, + choose_callback, + context); + + return true; +} + +static void js_menu_reset_children(Menu* menu, JsMenuCtx* context) { + context->next_index = 0; + menu_reset(menu); + js_menu_free_owned_strings(context); +} + +static JsMenuCtx* ctx_make(struct mjs* mjs, Menu* input, mjs_val_t view_obj) { + UNUSED(input); + JsMenuCtx* context = malloc(sizeof(JsMenuCtx)); + context->queue = furi_message_queue_alloc(1, sizeof(uint32_t)); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)choose_transformer, + }, + }; + mjs_set(mjs, view_obj, "chosen", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(Menu* input, JsMenuCtx* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->queue); + furi_message_queue_free(context->queue); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)menu_alloc, + .free = (JsViewFree)menu_free, + .get_view = (JsViewGetView)menu_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .add_child = (JsViewAddChild)js_menu_add_child, + .reset_children = (JsViewResetChildren)js_menu_reset_children, + .prop_cnt = 0, + .props = {}, +}; +JS_GUI_VIEW_DEF(menu, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/number_input.c b/applications/system/js_app/modules/js_gui/number_input.c new file mode 100644 index 00000000000..2a1aa6ee6de --- /dev/null +++ b/applications/system/js_app/modules/js_gui/number_input.c @@ -0,0 +1,130 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +typedef struct { + int32_t default_val, min_val, max_val; + FuriMessageQueue* input_queue; + JsEventLoopContract contract; +} JsNumKbdContext; + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsNumKbdContext* context) { + UNUSED(context); + int32_t number; + furi_check(furi_message_queue_get(queue, &number, 0) == FuriStatusOk); + return mjs_mk_number(mjs, number); +} + +static void input_callback(void* ctx, int32_t value) { + JsNumKbdContext* context = ctx; + furi_check(furi_message_queue_put(context->input_queue, &value, 0) == FuriStatusOk); +} + +static bool header_assign( + struct mjs* mjs, + NumberInput* input, + JsViewPropValue value, + JsNumKbdContext* context) { + UNUSED(mjs); + UNUSED(context); + number_input_set_header_text(input, value.string); + return true; +} + +static bool min_val_assign( + struct mjs* mjs, + NumberInput* input, + JsViewPropValue value, + JsNumKbdContext* context) { + UNUSED(mjs); + context->min_val = value.number; + number_input_set_result_callback( + input, input_callback, context, context->default_val, context->min_val, context->max_val); + return true; +} + +static bool max_val_assign( + struct mjs* mjs, + NumberInput* input, + JsViewPropValue value, + JsNumKbdContext* context) { + UNUSED(mjs); + context->max_val = value.number; + number_input_set_result_callback( + input, input_callback, context, context->default_val, context->min_val, context->max_val); + return true; +} + +static bool default_val_assign( + struct mjs* mjs, + NumberInput* input, + JsViewPropValue value, + JsNumKbdContext* context) { + UNUSED(mjs); + context->default_val = value.number; + number_input_set_result_callback( + input, input_callback, context, context->default_val, context->min_val, context->max_val); + return true; +} + +static JsNumKbdContext* ctx_make(struct mjs* mjs, NumberInput* input, mjs_val_t view_obj) { + JsNumKbdContext* context = malloc(sizeof(JsNumKbdContext)); + *context = (JsNumKbdContext){ + .default_val = 0, + .max_val = 100, + .min_val = 0, + .input_queue = furi_message_queue_alloc(1, sizeof(int32_t)), + }; + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->input_queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + .transformer_context = context, + }, + }; + number_input_set_result_callback( + input, input_callback, context, context->default_val, context->min_val, context->max_val); + mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(NumberInput* input, JsNumKbdContext* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->input_queue); + furi_message_queue_free(context->input_queue); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)number_input_alloc, + .free = (JsViewFree)number_input_free, + .get_view = (JsViewGetView)number_input_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 4, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "minValue", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)min_val_assign}, + (JsViewPropDescriptor){ + .name = "maxValue", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)max_val_assign}, + (JsViewPropDescriptor){ + .name = "defaultValue", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)default_val_assign}, + }}; + +JS_GUI_VIEW_DEF(number_input, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/popup.c b/applications/system/js_app/modules/js_gui/popup.c new file mode 100644 index 00000000000..c74e1c5ffc3 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/popup.c @@ -0,0 +1,111 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +typedef struct { + char** owned_strings; + size_t n_owned_strings; + + FuriSemaphore* semaphore; + JsEventLoopContract contract; +} JsPopupCtx; + +// not using mlib to conserve code size +static const char* js_popup_own_string(JsPopupCtx* context, const char* str) { + char* owned = strdup(str); + context->n_owned_strings++; + context->owned_strings = + realloc(context->owned_strings, context->n_owned_strings * sizeof(const char*)); + context->owned_strings[context->n_owned_strings - 1] = owned; + return owned; +} + +static void js_popup_free_owned_strings(JsPopupCtx* context) { + for(size_t i = 0; i < context->n_owned_strings; i++) { + free(context->owned_strings[i]); + } + free(context->owned_strings); + context->owned_strings = NULL; +} + +static void timeout_callback(JsPopupCtx* context) { + furi_check(furi_semaphore_release(context->semaphore) == FuriStatusOk); +} + +static bool + header_assign(struct mjs* mjs, Popup* popup, JsViewPropValue value, JsPopupCtx* context) { + UNUSED(mjs); + UNUSED(context); + popup_set_header( + popup, js_popup_own_string(context, value.string), 64, 0, AlignCenter, AlignTop); + return true; +} + +static bool + text_assign(struct mjs* mjs, Popup* popup, JsViewPropValue value, JsPopupCtx* context) { + UNUSED(mjs); + UNUSED(context); + popup_set_text( + popup, js_popup_own_string(context, value.string), 64, 32, AlignCenter, AlignCenter); + return true; +} + +static bool + timeout_assign(struct mjs* mjs, Popup* popup, JsViewPropValue value, JsPopupCtx* context) { + UNUSED(mjs); + UNUSED(context); + popup_set_timeout(popup, value.number); + popup_enable_timeout(popup); + return true; +} + +static JsPopupCtx* ctx_make(struct mjs* mjs, Popup* popup, mjs_val_t view_obj) { + JsPopupCtx* context = malloc(sizeof(JsPopupCtx)); + context->semaphore = furi_semaphore_alloc(1, 0); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeSemaphore, + .object = context->semaphore, + .non_timer = + { + .event = FuriEventLoopEventIn, + }, + }; + mjs_set(mjs, view_obj, "timeout", ~0, mjs_mk_foreign(mjs, &context->contract)); + popup_set_callback(popup, (PopupCallback)timeout_callback); + popup_set_context(popup, context); + return context; +} + +static void ctx_destroy(Popup* popup, JsPopupCtx* context, FuriEventLoop* loop) { + UNUSED(popup); + furi_event_loop_maybe_unsubscribe(loop, context->semaphore); + furi_semaphore_free(context->semaphore); + js_popup_free_owned_strings(context); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)popup_alloc, + .free = (JsViewFree)popup_free, + .get_view = (JsViewGetView)popup_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 3, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "text", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)text_assign}, + (JsViewPropDescriptor){ + .name = "timeout", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)timeout_assign}, + }}; + +JS_GUI_VIEW_DEF(popup, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/submenu.c b/applications/system/js_app/modules/js_gui/submenu.c index c142bcddb66..64ab1b90668 100644 --- a/applications/system/js_app/modules/js_gui/submenu.c +++ b/applications/system/js_app/modules/js_gui/submenu.c @@ -6,6 +6,7 @@ #define QUEUE_LEN 2 typedef struct { + int32_t next_index; FuriMessageQueue* queue; JsEventLoopContract contract; } JsSubmenuCtx; @@ -30,18 +31,24 @@ static bool return true; } -static bool items_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) { - UNUSED(mjs); - submenu_reset(submenu); - size_t len = mjs_array_length(mjs, value.term); - for(size_t i = 0; i < len; i++) { - mjs_val_t item = mjs_array_get(mjs, value.term, i); - if(!mjs_is_string(item)) return false; - submenu_add_item(submenu, mjs_get_string(mjs, &item, NULL), i, choose_callback, context); - } +static bool js_submenu_add_child( + struct mjs* mjs, + Submenu* submenu, + JsSubmenuCtx* context, + mjs_val_t child_obj) { + const char* str = mjs_get_string(mjs, &child_obj, NULL); + if(!str) return false; + + submenu_add_item(submenu, str, context->next_index++, choose_callback, context); + return true; } +static void js_submenu_reset_children(Submenu* submenu, JsSubmenuCtx* context) { + context->next_index = 0; + submenu_reset(submenu); +} + static JsSubmenuCtx* ctx_make(struct mjs* mjs, Submenu* input, mjs_val_t view_obj) { UNUSED(input); JsSubmenuCtx* context = malloc(sizeof(JsSubmenuCtx)); @@ -73,15 +80,13 @@ static const JsViewDescriptor view_descriptor = { .get_view = (JsViewGetView)submenu_get_view, .custom_make = (JsViewCustomMake)ctx_make, .custom_destroy = (JsViewCustomDestroy)ctx_destroy, - .prop_cnt = 2, + .add_child = (JsViewAddChild)js_submenu_add_child, + .reset_children = (JsViewResetChildren)js_submenu_reset_children, + .prop_cnt = 1, .props = { (JsViewPropDescriptor){ .name = "header", .type = JsViewPropTypeString, .assign = (JsViewPropAssign)header_assign}, - (JsViewPropDescriptor){ - .name = "items", - .type = JsViewPropTypeArr, - .assign = (JsViewPropAssign)items_assign}, }}; JS_GUI_VIEW_DEF(submenu, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/vi_list.c b/applications/system/js_app/modules/js_gui/vi_list.c new file mode 100644 index 00000000000..16eb8f726c2 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/vi_list.c @@ -0,0 +1,180 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +typedef struct { + char** owned_strings; + size_t n_owned_strings; + + // let mjs do the memory management heavy lifting, store children in a js array + struct mjs* mjs; + mjs_val_t children; + VariableItemList* list; + + FuriMessageQueue* input_queue; + JsEventLoopContract contract; +} JsViListContext; + +typedef struct { + int32_t item_index; + int32_t value_index; +} JsViListEvent; + +// not using mlib to conserve code size +static const char* js_vi_list_own_string(JsViListContext* context, const char* str) { + char* owned = strdup(str); + context->n_owned_strings++; + context->owned_strings = + realloc(context->owned_strings, context->n_owned_strings * sizeof(const char*)); + context->owned_strings[context->n_owned_strings - 1] = owned; + return owned; +} + +static void js_vi_list_free_owned_strings(JsViListContext* context) { + for(size_t i = 0; i < context->n_owned_strings; i++) { + free(context->owned_strings[i]); + } + free(context->owned_strings); + context->owned_strings = NULL; +} + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsViListContext* context) { + UNUSED(context); + JsViListEvent event; + furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk); + + mjs_val_t event_obj = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, event_obj) { + JS_FIELD("itemIndex", mjs_mk_number(mjs, event.item_index)); + JS_FIELD("valueIndex", mjs_mk_number(mjs, event.value_index)); + } + + return event_obj; +} + +static void js_vi_list_change_callback(VariableItem* item) { + JsViListContext* context = variable_item_get_context(item); + struct mjs* mjs = context->mjs; + uint8_t item_index = variable_item_list_get_selected_item_index(context->list); + uint8_t value_index = variable_item_get_current_value_index(item); + + // type safety ensured in add_child + mjs_val_t variants = mjs_array_get(mjs, context->children, item_index); + mjs_val_t variant = mjs_array_get(mjs, variants, value_index); + variable_item_set_current_value_text(item, mjs_get_string(mjs, &variant, NULL)); + + JsViListEvent event = { + .item_index = item_index, + .value_index = value_index, + }; + furi_check(furi_message_queue_put(context->input_queue, &event, 0) == FuriStatusOk); +} + +static bool js_vi_list_add_child( + struct mjs* mjs, + VariableItemList* list, + JsViListContext* context, + mjs_val_t child_obj) { + static const JsValueDeclaration js_vi_list_string = JS_VALUE_SIMPLE(JsValueTypeString); + static const JsValueDeclaration js_vi_list_arr = JS_VALUE_SIMPLE(JsValueTypeAnyArray); + static const JsValueDeclaration js_vi_list_int_default_0 = + JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 0); + + static const JsValueObjectField js_vi_list_child_fields[] = { + {"label", &js_vi_list_string}, + {"variants", &js_vi_list_arr}, + {"defaultSelected", &js_vi_list_int_default_0}, + }; + static const JsValueDeclaration js_vi_list_child = + JS_VALUE_OBJECT_W_DEFAULTS(js_vi_list_child_fields); + + JsValueParseStatus status; + const char* label; + mjs_val_t variants; + int32_t default_selected; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_vi_list_child), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &label, + &variants, + &default_selected); + if(status != JsValueParseStatusOk) return false; + + size_t variants_cnt = mjs_array_length(mjs, variants); + for(size_t i = 0; i < variants_cnt; i++) + if(!mjs_is_string(mjs_array_get(mjs, variants, i))) return false; + + VariableItem* item = variable_item_list_add( + list, + js_vi_list_own_string(context, label), + variants_cnt, + js_vi_list_change_callback, + context); + variable_item_set_current_value_index(item, default_selected); + mjs_val_t default_variant = mjs_array_get(mjs, variants, default_selected); + variable_item_set_current_value_text(item, mjs_get_string(mjs, &default_variant, NULL)); + + mjs_array_push(context->mjs, context->children, variants); + + return true; +} + +static void js_vi_list_reset_children(VariableItemList* list, JsViListContext* context) { + mjs_disown(context->mjs, &context->children); + context->children = mjs_mk_array(context->mjs); + mjs_own(context->mjs, &context->children); + + variable_item_list_reset(list); + js_vi_list_free_owned_strings(context); +} + +static JsViListContext* ctx_make(struct mjs* mjs, VariableItemList* list, mjs_val_t view_obj) { + JsViListContext* context = malloc(sizeof(JsViListContext)); + *context = (JsViListContext){ + .mjs = mjs, + .children = mjs_mk_array(mjs), + .list = list, + .input_queue = furi_message_queue_alloc(1, sizeof(JsViListEvent)), + }; + mjs_own(context->mjs, &context->children); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->input_queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + .transformer_context = context, + }, + }; + mjs_set(mjs, view_obj, "valueUpdate", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(VariableItemList* input, JsViListContext* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->input_queue); + furi_message_queue_free(context->input_queue); + js_vi_list_free_owned_strings(context); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)variable_item_list_alloc, + .free = (JsViewFree)variable_item_list_free, + .get_view = (JsViewGetView)variable_item_list_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .add_child = (JsViewAddChild)js_vi_list_add_child, + .reset_children = (JsViewResetChildren)js_vi_list_reset_children, + .prop_cnt = 0, + .props = {}, +}; + +JS_GUI_VIEW_DEF(vi_list, &view_descriptor); diff --git a/applications/system/js_app/packages/fz-sdk/gui/button_menu.d.ts b/applications/system/js_app/packages/fz-sdk/gui/button_menu.d.ts new file mode 100644 index 00000000000..3b5dd23591c --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/button_menu.d.ts @@ -0,0 +1,40 @@ +/** + * Displays a list of buttons. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let buttonMenuView = require("gui/button_menu"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the `gui.js` example script. + * + * # View props + * - `header`: Textual header above the buttons + * + * @version Added in JS SDK 0.4 + * @module + */ + +import type { View, ViewFactory, InputType } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string; +}; + +type Child = { type: "common" | "control", label: string }; + +declare class ButtonMenu extends View { + input: Contract<{ index: number, type: InputType }>; +} +declare class ButtonMenuFactory extends ViewFactory { } +declare const factory: ButtonMenuFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/button_panel.d.ts b/applications/system/js_app/packages/fz-sdk/gui/button_panel.d.ts new file mode 100644 index 00000000000..dcc956fed1f --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/button_panel.d.ts @@ -0,0 +1,49 @@ +/** + * Displays a button matrix. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let buttonPanelView = require("gui/button_panel"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the `gui.js` example script. + * + * # View props + * - `matrixSizeX`: Width of imaginary grid used for navigation + * - `matrixSizeY`: Height of imaginary grid used for navigation + * + * @version Added in JS SDK 0.4 + * @module + */ + +import type { View, ViewFactory, Font, InputType } from "."; +import type { Contract } from "../event_loop"; +import { IconData } from "./icon"; + +type Props = { + matrixSizeX: number, + matrixSizeY: number, +}; + +type Position = { x: number, y: number }; + +type ButtonChild = { type: "button", matrixX: number, matrixY: number, icon: IconData, iconSelected: IconData } & Position; +type LabelChild = { type: "label", font: Font, text: string } & Position; +type IconChild = { type: "icon", icon: IconData }; + +type Child = ButtonChild | LabelChild | IconChild; + +declare class ButtonPanel extends View { + input: Contract<{ index: number, type: InputType }>; +} +declare class ButtonPanelFactory extends ViewFactory { } +declare const factory: ButtonPanelFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts index 8d2b68bceec..1a0520ae92c 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts @@ -1,4 +1,7 @@ -export type BuiltinIcon = "DolphinWait_59x54" | "js_script_10px"; +export type BuiltinIcon = "DolphinWait_59x54" | "js_script_10px" + | "off_19x20" | "off_hover_19x20" + | "power_19x20" | "power_hover_19x20" + | "Settings_14"; export type IconData = symbol & { "__tag__": "icon" }; // introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist. diff --git a/applications/system/js_app/packages/fz-sdk/gui/index.d.ts b/applications/system/js_app/packages/fz-sdk/gui/index.d.ts index 969b6934eb2..f24e236fdff 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/index.d.ts @@ -24,24 +24,23 @@ * ### View * In Flipper's terminology, a "View" is a fullscreen design element that * assumes control over the entire viewport and all input events. Different - * types of views are available (not all of which are unfortunately currently - * implemented in JS): + * types of views are available: * | View | Has JS adapter? | * |----------------------|-----------------------| - * | `button_menu` | ❌ | - * | `button_panel` | ❌ | + * | `button_menu` | ✅ | + * | `button_panel` | ✅ | * | `byte_input` | ✅ | * | `dialog_ex` | ✅ (as `dialog`) | * | `empty_screen` | ✅ | * | `file_browser` | ✅ (as `file_picker`) | * | `loading` | ✅ | - * | `menu` | ❌ | - * | `number_input` | ❌ | - * | `popup` | ❌ | + * | `menu` | ✅ | + * | `number_input` | ✅ | + * | `popup` | ✅ | * | `submenu` | ✅ | * | `text_box` | ✅ | * | `text_input` | ✅ | - * | `variable_item_list` | ❌ | + * | `variable_item_list` | ✅ (as `vi_list`) | * | `widget` | ✅ | * * In JS, each view has its own set of properties (or just "props"). The @@ -119,6 +118,9 @@ import type { Contract } from "../event_loop"; +export type Font = "primary" | "secondary" | "keyboard" | "big_numbers"; +export type InputType = "press" | "release" | "short" | "long" | "repeat"; + type Properties = { [K: string]: any }; export declare class View { diff --git a/applications/system/js_app/packages/fz-sdk/gui/menu.d.ts b/applications/system/js_app/packages/fz-sdk/gui/menu.d.ts new file mode 100644 index 00000000000..a72fe4b770d --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/menu.d.ts @@ -0,0 +1,38 @@ +/** + * A list of selectable entries consisting of an icon and a label. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let submenuView = require("gui/menu"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the GUI example. + * + * # View props + * This view doesn't have any props. + * + * @version Added in JS SDK 0.1 + * @version API changed in JS SDK 0.4 + * @module + */ + +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; +import type { IconData } from "./icon"; + +type Props = {}; +type Child = { icon: IconData, label: string }; +declare class Submenu extends View { + chosen: Contract; +} +declare class SubmenuFactory extends ViewFactory { } +declare const factory: SubmenuFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/number_input.d.ts b/applications/system/js_app/packages/fz-sdk/gui/number_input.d.ts new file mode 100644 index 00000000000..4882c85fc19 --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/number_input.d.ts @@ -0,0 +1,44 @@ +/** + * Displays a number input keyboard. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let numberInputView = require("gui/number_input"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the `gui.js` example script. + * + * # View props + * - `header`: Text displayed at the top of the screen + * - `minValue`: Minimum allowed numeric value + * - `maxValue`: Maximum allowed numeric value + * - `defaultValue`: Default numeric value + * + * @version Added in JS SDK 0.4 + * @module + */ + +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string, + minValue: number, + maxValue: number, + defaultValue: number, +} +type Child = never; +declare class NumberInput extends View { + input: Contract; +} +declare class NumberInputFactory extends ViewFactory { } +declare const factory: NumberInputFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/popup.d.ts b/applications/system/js_app/packages/fz-sdk/gui/popup.d.ts new file mode 100644 index 00000000000..7f77b79f8fe --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/popup.d.ts @@ -0,0 +1,43 @@ +/** + * Like a Dialog, but with a built-in timer. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let popupView = require("gui/popup"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the `gui.js` example script. + * + * # View props + * - `header`: Text displayed in bold at the top of the screen + * - `text`: Text displayed in the middle of the string + * - `timeout`: Timeout, in milliseconds, after which the event will fire. The + * timer starts counting down when this property is assigned. + * + * @version Added in JS SDK 0.1 + * @module + */ + +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string, + text: string, + timeout: number, +} +type Child = never; +declare class Popup extends View { + timeout: Contract; +} +declare class PopupFactory extends ViewFactory { } +declare const factory: PopupFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts b/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts index e73856bee4c..9fb8d51cf3f 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts @@ -18,9 +18,9 @@ * * # View props * - `header`: Text displayed at the top of the screen in bold - * - `items`: Array of selectable textual items * * @version Added in JS SDK 0.1 + * @version API changed in JS SDK 0.4 * @module */ @@ -29,9 +29,8 @@ import type { Contract } from "../event_loop"; type Props = { header: string, - items: string[], }; -type Child = never; +type Child = string; declare class Submenu extends View { chosen: Contract; } diff --git a/applications/system/js_app/packages/fz-sdk/gui/vi_list.d.ts b/applications/system/js_app/packages/fz-sdk/gui/vi_list.d.ts new file mode 100644 index 00000000000..73826338c5d --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/vi_list.d.ts @@ -0,0 +1,38 @@ +/** + * Displays a list of settings-like variable items. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let viListView = require("gui/vi_list"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the `gui.js` example script. + * + * # View props + * This view doesn't have any props + * + * @version Added in JS SDK 0.4 + * @module + */ + +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = {}; + +type Child = { label: string, variants: string[] }; + +declare class ViList extends View { + valueUpdate: Contract<{ itemIndex: number, valueIndex: number }>; +} +declare class ViListFactory extends ViewFactory { } +declare const factory: ViListFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/package.json b/applications/system/js_app/packages/fz-sdk/package.json index 3ab108e48ad..3137b4b3743 100644 --- a/applications/system/js_app/packages/fz-sdk/package.json +++ b/applications/system/js_app/packages/fz-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@flipperdevices/fz-sdk", - "version": "0.3.0", + "version": "0.4.0", "description": "Type declarations and documentation for native JS modules available on Flipper Zero", "keywords": [ "flipper", From ff35eafbdcf9148407d7ec6f8a3a94916330fca9 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 29 Apr 2025 19:11:33 +0400 Subject: [PATCH 25/26] js, toolbox: generalize string owning --- .../js_app/modules/js_gui/button_menu.c | 33 +++---------- .../js_app/modules/js_gui/button_panel.c | 32 +++---------- .../system/js_app/modules/js_gui/menu.c | 27 ++--------- .../system/js_app/modules/js_gui/popup.c | 39 ++++++--------- .../system/js_app/modules/js_gui/vi_list.c | 29 +++--------- lib/toolbox/SConscript | 1 + lib/toolbox/str_buffer.c | 18 +++++++ lib/toolbox/str_buffer.h | 47 +++++++++++++++++++ targets/f18/api_symbols.csv | 7 ++- targets/f7/api_symbols.csv | 7 ++- 10 files changed, 116 insertions(+), 124 deletions(-) create mode 100644 lib/toolbox/str_buffer.c create mode 100644 lib/toolbox/str_buffer.h diff --git a/applications/system/js_app/modules/js_gui/button_menu.c b/applications/system/js_app/modules/js_gui/button_menu.c index c85bd857b28..e247e16358d 100644 --- a/applications/system/js_app/modules/js_gui/button_menu.c +++ b/applications/system/js_app/modules/js_gui/button_menu.c @@ -2,11 +2,11 @@ #include "js_gui.h" #include "../js_event_loop/js_event_loop.h" #include +#include typedef struct { int32_t next_index; - char** owned_strings; - size_t n_owned_strings; + StrBuffer str_buffer; FuriMessageQueue* input_queue; JsEventLoopContract contract; @@ -17,24 +17,6 @@ typedef struct { InputType input_type; } JsBtnMenuEvent; -// not using mlib to conserve code size -static const char* js_button_menu_own_string(JsBtnMenuContext* context, const char* str) { - char* owned = strdup(str); - context->n_owned_strings++; - context->owned_strings = - realloc(context->owned_strings, context->n_owned_strings * sizeof(const char*)); - context->owned_strings[context->n_owned_strings - 1] = owned; - return owned; -} - -static void js_button_menu_free_owned_strings(JsBtnMenuContext* context) { - for(size_t i = 0; i < context->n_owned_strings; i++) { - free(context->owned_strings[i]); - } - free(context->owned_strings); - context->owned_strings = NULL; -} - static const char* js_input_type_to_str(InputType type) { switch(type) { case InputTypePress: @@ -82,7 +64,7 @@ static bool matrix_header_assign( JsViewPropValue value, JsBtnMenuContext* context) { UNUSED(mjs); - button_menu_set_header(menu, js_button_menu_own_string(context, value.string)); + button_menu_set_header(menu, str_buffer_make_owned_clone(&context->str_buffer, value.string)); return true; } @@ -122,7 +104,7 @@ static bool js_button_menu_add_child( button_menu_add_item( menu, - js_button_menu_own_string(context, label), + str_buffer_make_owned_clone(&context->str_buffer, label), context->next_index++, input_callback, item_type, @@ -134,7 +116,7 @@ static bool js_button_menu_add_child( static void js_button_menu_reset_children(ButtonMenu* menu, JsBtnMenuContext* context) { context->next_index = 0; button_menu_reset(menu); - js_button_menu_free_owned_strings(context); + str_buffer_clear_all_clones(&context->str_buffer); } static JsBtnMenuContext* ctx_make(struct mjs* mjs, ButtonMenu* menu, mjs_val_t view_obj) { @@ -142,8 +124,7 @@ static JsBtnMenuContext* ctx_make(struct mjs* mjs, ButtonMenu* menu, mjs_val_t v JsBtnMenuContext* context = malloc(sizeof(JsBtnMenuContext)); *context = (JsBtnMenuContext){ .next_index = 0, - .owned_strings = NULL, - .n_owned_strings = 0, + .str_buffer = {0}, .input_queue = furi_message_queue_alloc(1, sizeof(JsBtnMenuEvent)), }; context->contract = (JsEventLoopContract){ @@ -165,7 +146,7 @@ static void ctx_destroy(ButtonMenu* input, JsBtnMenuContext* context, FuriEventL UNUSED(input); furi_event_loop_maybe_unsubscribe(loop, context->input_queue); furi_message_queue_free(context->input_queue); - js_button_menu_free_owned_strings(context); + str_buffer_clear_all_clones(&context->str_buffer); free(context); } diff --git a/applications/system/js_app/modules/js_gui/button_panel.c b/applications/system/js_app/modules/js_gui/button_panel.c index 558549d091e..ebd4edb57ad 100644 --- a/applications/system/js_app/modules/js_gui/button_panel.c +++ b/applications/system/js_app/modules/js_gui/button_panel.c @@ -2,12 +2,12 @@ #include "js_gui.h" #include "../js_event_loop/js_event_loop.h" #include +#include typedef struct { size_t matrix_x, matrix_y; int32_t next_index; - char** owned_strings; - size_t n_owned_strings; + StrBuffer str_buffer; FuriMessageQueue* input_queue; JsEventLoopContract contract; @@ -18,24 +18,6 @@ typedef struct { InputType input_type; } JsBtnPanelEvent; -// not using mlib to conserve code size -static const char* js_button_panel_own_string(JsBtnPanelContext* context, const char* str) { - char* owned = strdup(str); - context->n_owned_strings++; - context->owned_strings = - realloc(context->owned_strings, context->n_owned_strings * sizeof(const char*)); - context->owned_strings[context->n_owned_strings - 1] = owned; - return owned; -} - -static void js_button_panel_free_owned(JsBtnPanelContext* context) { - for(size_t i = 0; i < context->n_owned_strings; i++) { - free(context->owned_strings[i]); - } - free(context->owned_strings); - context->owned_strings = NULL; -} - static const char* js_input_type_to_str(InputType type) { switch(type) { case InputTypePress: @@ -206,7 +188,8 @@ static bool js_button_panel_add_child( &text, &font); if(status != JsValueParseStatusOk) return false; - button_panel_add_label(panel, x, y, font, js_button_panel_own_string(context, text)); + button_panel_add_label( + panel, x, y, font, str_buffer_make_owned_clone(&context->str_buffer, text)); break; } @@ -232,7 +215,7 @@ static void js_button_panel_reset_children(ButtonPanel* panel, JsBtnPanelContext context->next_index = 0; button_panel_reset(panel); button_panel_reserve(panel, context->matrix_x, context->matrix_y); - js_button_panel_free_owned(context); + str_buffer_clear_all_clones(&context->str_buffer); } static JsBtnPanelContext* ctx_make(struct mjs* mjs, ButtonPanel* panel, mjs_val_t view_obj) { @@ -242,8 +225,7 @@ static JsBtnPanelContext* ctx_make(struct mjs* mjs, ButtonPanel* panel, mjs_val_ .matrix_x = 1, .matrix_y = 1, .next_index = 0, - .owned_strings = NULL, - .n_owned_strings = 0, + .str_buffer = {0}, .input_queue = furi_message_queue_alloc(1, sizeof(JsBtnPanelEvent)), }; context->contract = (JsEventLoopContract){ @@ -265,7 +247,7 @@ static void ctx_destroy(ButtonPanel* input, JsBtnPanelContext* context, FuriEven UNUSED(input); furi_event_loop_maybe_unsubscribe(loop, context->input_queue); furi_message_queue_free(context->input_queue); - js_button_panel_free_owned(context); + str_buffer_clear_all_clones(&context->str_buffer); free(context); } diff --git a/applications/system/js_app/modules/js_gui/menu.c b/applications/system/js_app/modules/js_gui/menu.c index abce47e04e5..c8e23470f7a 100644 --- a/applications/system/js_app/modules/js_gui/menu.c +++ b/applications/system/js_app/modules/js_gui/menu.c @@ -2,34 +2,16 @@ #include "js_gui.h" #include "../js_event_loop/js_event_loop.h" #include +#include typedef struct { int32_t next_index; - char** owned_strings; - size_t n_owned_strings; + StrBuffer str_buffer; FuriMessageQueue* queue; JsEventLoopContract contract; } JsMenuCtx; -// not using mlib to conserve code size -static const char* js_menu_own_string(JsMenuCtx* context, const char* str) { - char* owned = strdup(str); - context->n_owned_strings++; - context->owned_strings = - realloc(context->owned_strings, context->n_owned_strings * sizeof(const char*)); - context->owned_strings[context->n_owned_strings - 1] = owned; - return owned; -} - -static void js_menu_free_owned_strings(JsMenuCtx* context) { - for(size_t i = 0; i < context->n_owned_strings; i++) { - free(context->owned_strings[i]); - } - free(context->owned_strings); - context->owned_strings = NULL; -} - static mjs_val_t choose_transformer(struct mjs* mjs, FuriMessageQueue* queue, void* context) { UNUSED(context); uint32_t index; @@ -68,7 +50,7 @@ static bool menu_add_item( menu, - js_menu_own_string(context, label), + str_buffer_make_owned_clone(&context->str_buffer, label), icon, context->next_index++, choose_callback, @@ -80,7 +62,7 @@ static bool static void js_menu_reset_children(Menu* menu, JsMenuCtx* context) { context->next_index = 0; menu_reset(menu); - js_menu_free_owned_strings(context); + str_buffer_clear_all_clones(&context->str_buffer); } static JsMenuCtx* ctx_make(struct mjs* mjs, Menu* input, mjs_val_t view_obj) { @@ -105,6 +87,7 @@ static void ctx_destroy(Menu* input, JsMenuCtx* context, FuriEventLoop* loop) { UNUSED(input); furi_event_loop_maybe_unsubscribe(loop, context->queue); furi_message_queue_free(context->queue); + str_buffer_clear_all_clones(&context->str_buffer); free(context); } diff --git a/applications/system/js_app/modules/js_gui/popup.c b/applications/system/js_app/modules/js_gui/popup.c index c74e1c5ffc3..65f827ab1b9 100644 --- a/applications/system/js_app/modules/js_gui/popup.c +++ b/applications/system/js_app/modules/js_gui/popup.c @@ -2,33 +2,14 @@ #include "js_gui.h" #include "../js_event_loop/js_event_loop.h" #include +#include typedef struct { - char** owned_strings; - size_t n_owned_strings; - + StrBuffer str_buffer; FuriSemaphore* semaphore; JsEventLoopContract contract; } JsPopupCtx; -// not using mlib to conserve code size -static const char* js_popup_own_string(JsPopupCtx* context, const char* str) { - char* owned = strdup(str); - context->n_owned_strings++; - context->owned_strings = - realloc(context->owned_strings, context->n_owned_strings * sizeof(const char*)); - context->owned_strings[context->n_owned_strings - 1] = owned; - return owned; -} - -static void js_popup_free_owned_strings(JsPopupCtx* context) { - for(size_t i = 0; i < context->n_owned_strings; i++) { - free(context->owned_strings[i]); - } - free(context->owned_strings); - context->owned_strings = NULL; -} - static void timeout_callback(JsPopupCtx* context) { furi_check(furi_semaphore_release(context->semaphore) == FuriStatusOk); } @@ -38,7 +19,12 @@ static bool UNUSED(mjs); UNUSED(context); popup_set_header( - popup, js_popup_own_string(context, value.string), 64, 0, AlignCenter, AlignTop); + popup, + str_buffer_make_owned_clone(&context->str_buffer, value.string), + 64, + 0, + AlignCenter, + AlignTop); return true; } @@ -47,7 +33,12 @@ static bool UNUSED(mjs); UNUSED(context); popup_set_text( - popup, js_popup_own_string(context, value.string), 64, 32, AlignCenter, AlignCenter); + popup, + str_buffer_make_owned_clone(&context->str_buffer, value.string), + 64, + 32, + AlignCenter, + AlignCenter); return true; } @@ -82,7 +73,7 @@ static void ctx_destroy(Popup* popup, JsPopupCtx* context, FuriEventLoop* loop) UNUSED(popup); furi_event_loop_maybe_unsubscribe(loop, context->semaphore); furi_semaphore_free(context->semaphore); - js_popup_free_owned_strings(context); + str_buffer_clear_all_clones(&context->str_buffer); free(context); } diff --git a/applications/system/js_app/modules/js_gui/vi_list.c b/applications/system/js_app/modules/js_gui/vi_list.c index 16eb8f726c2..bdea2fba152 100644 --- a/applications/system/js_app/modules/js_gui/vi_list.c +++ b/applications/system/js_app/modules/js_gui/vi_list.c @@ -2,10 +2,10 @@ #include "js_gui.h" #include "../js_event_loop/js_event_loop.h" #include +#include typedef struct { - char** owned_strings; - size_t n_owned_strings; + StrBuffer str_buffer; // let mjs do the memory management heavy lifting, store children in a js array struct mjs* mjs; @@ -21,24 +21,6 @@ typedef struct { int32_t value_index; } JsViListEvent; -// not using mlib to conserve code size -static const char* js_vi_list_own_string(JsViListContext* context, const char* str) { - char* owned = strdup(str); - context->n_owned_strings++; - context->owned_strings = - realloc(context->owned_strings, context->n_owned_strings * sizeof(const char*)); - context->owned_strings[context->n_owned_strings - 1] = owned; - return owned; -} - -static void js_vi_list_free_owned_strings(JsViListContext* context) { - for(size_t i = 0; i < context->n_owned_strings; i++) { - free(context->owned_strings[i]); - } - free(context->owned_strings); - context->owned_strings = NULL; -} - static mjs_val_t input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsViListContext* context) { UNUSED(context); @@ -111,7 +93,7 @@ static bool js_vi_list_add_child( VariableItem* item = variable_item_list_add( list, - js_vi_list_own_string(context, label), + str_buffer_make_owned_clone(&context->str_buffer, label), variants_cnt, js_vi_list_change_callback, context); @@ -130,12 +112,13 @@ static void js_vi_list_reset_children(VariableItemList* list, JsViListContext* c mjs_own(context->mjs, &context->children); variable_item_list_reset(list); - js_vi_list_free_owned_strings(context); + str_buffer_clear_all_clones(&context->str_buffer); } static JsViListContext* ctx_make(struct mjs* mjs, VariableItemList* list, mjs_val_t view_obj) { JsViListContext* context = malloc(sizeof(JsViListContext)); *context = (JsViListContext){ + .str_buffer = {0}, .mjs = mjs, .children = mjs_mk_array(mjs), .list = list, @@ -161,7 +144,7 @@ static void ctx_destroy(VariableItemList* input, JsViListContext* context, FuriE UNUSED(input); furi_event_loop_maybe_unsubscribe(loop, context->input_queue); furi_message_queue_free(context->input_queue); - js_vi_list_free_owned_strings(context); + str_buffer_clear_all_clones(&context->str_buffer); free(context); } diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 8a1c4a8c5d3..3a81441a271 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -25,6 +25,7 @@ env.Append( File("float_tools.h"), File("value_index.h"), File("tar/tar_archive.h"), + File("str_buffer.h"), File("stream/stream.h"), File("stream/file_stream.h"), File("stream/string_stream.h"), diff --git a/lib/toolbox/str_buffer.c b/lib/toolbox/str_buffer.c new file mode 100644 index 00000000000..060e3f6a243 --- /dev/null +++ b/lib/toolbox/str_buffer.c @@ -0,0 +1,18 @@ +#include "str_buffer.h" + +const char* str_buffer_make_owned_clone(StrBuffer* buffer, const char* str) { + char* owned = strdup(str); + buffer->n_owned_strings++; + buffer->owned_strings = + realloc(buffer->owned_strings, buffer->n_owned_strings * sizeof(const char*)); + buffer->owned_strings[buffer->n_owned_strings - 1] = owned; + return owned; +} + +void str_buffer_clear_all_clones(StrBuffer* buffer) { + for(size_t i = 0; i < buffer->n_owned_strings; i++) { + free(buffer->owned_strings[i]); + } + free(buffer->owned_strings); + buffer->owned_strings = NULL; +} diff --git a/lib/toolbox/str_buffer.h b/lib/toolbox/str_buffer.h new file mode 100644 index 00000000000..9623065c8de --- /dev/null +++ b/lib/toolbox/str_buffer.h @@ -0,0 +1,47 @@ +/** + * @file str_buffer.h + * + * Allows you to create an owned clone of however many strings that you need, + * then free all of them at once. Essentially the simplest possible append-only + * unindexable array of owned C-style strings. + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief StrBuffer instance + * + * Place this struct directly wherever you want, it doesn't have to be `alloc`ed + * and `free`d. + */ +typedef struct { + char** owned_strings; + size_t n_owned_strings; +} StrBuffer; + +/** + * @brief Makes a owned duplicate of the provided string + * + * @param[in] buffer StrBuffer instance + * @param[in] str Input C-style string + * + * @returns C-style string that contains to be valid event after `str` becomes + * invalid + */ +const char* str_buffer_make_owned_clone(StrBuffer* buffer, const char* str); + +/** + * @brief Clears all owned duplicates + * + * @param[in] buffer StrBuffer instance + */ +void str_buffer_clear_all_clones(StrBuffer* buffer); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 2074fa13120..7c50dc78ee3 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,82.1,, +Version,+,82.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -168,6 +168,7 @@ Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/simple_array.h,, +Header,+,lib/toolbox/str_buffer.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, @@ -2361,13 +2362,13 @@ Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" -Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" @@ -2611,6 +2612,8 @@ Function,+,storage_simply_remove,_Bool,"Storage*, const char*" Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*" Function,-,stpcpy,char*,"char*, const char*" Function,-,stpncpy,char*,"char*, const char*, size_t" +Function,+,str_buffer_clear_all_clones,void,StrBuffer* +Function,+,str_buffer_make_owned_clone,const char*,"StrBuffer*, const char*" Function,+,strcasecmp,int,"const char*, const char*" Function,-,strcasecmp_l,int,"const char*, const char*, locale_t" Function,+,strcasestr,char*,"const char*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1168a6eea53..0981ad82e8d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,82.1,, +Version,+,82.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -240,6 +240,7 @@ Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/simple_array.h,, +Header,+,lib/toolbox/str_buffer.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, @@ -2999,13 +3000,13 @@ Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" -Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" @@ -3299,6 +3300,8 @@ Function,+,storage_simply_remove,_Bool,"Storage*, const char*" Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*" Function,-,stpcpy,char*,"char*, const char*" Function,-,stpncpy,char*,"char*, const char*, size_t" +Function,+,str_buffer_clear_all_clones,void,StrBuffer* +Function,+,str_buffer_make_owned_clone,const char*,"StrBuffer*, const char*" Function,+,strcasecmp,int,"const char*, const char*" Function,-,strcasecmp_l,int,"const char*, const char*, locale_t" Function,+,strcasestr,char*,"const char*, const char*" From 93106609206e829ba22bafcdf3373f7f29bccf9e Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 29 Apr 2025 19:19:13 +0400 Subject: [PATCH 26/26] toolbox: silence pvs warning --- lib/toolbox/str_buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/toolbox/str_buffer.c b/lib/toolbox/str_buffer.c index 060e3f6a243..a46a3f27b46 100644 --- a/lib/toolbox/str_buffer.c +++ b/lib/toolbox/str_buffer.c @@ -4,7 +4,7 @@ const char* str_buffer_make_owned_clone(StrBuffer* buffer, const char* str) { char* owned = strdup(str); buffer->n_owned_strings++; buffer->owned_strings = - realloc(buffer->owned_strings, buffer->n_owned_strings * sizeof(const char*)); + realloc(buffer->owned_strings, buffer->n_owned_strings * sizeof(const char*)); // -V701 buffer->owned_strings[buffer->n_owned_strings - 1] = owned; return owned; }