1
mirror of https://github.com/flipperdevices/flipperzero-firmware.git synced 2025-12-12 04:41:26 +04:00

js: value destructuring and tests

This commit is contained in:
Anna Antonenko
2025-03-03 06:44:08 +04:00
parent 0d99e54a17
commit 20294a82b9
6 changed files with 689 additions and 0 deletions

View File

@@ -6,9 +6,12 @@
#include <storage/storage.h>
#include <applications/system/js_app/js_thread.h>
#include <applications/system/js_app/js_value.h>
#include <stdint.h>
#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);

View File

@@ -8,6 +8,7 @@
#include <rpc/rpc_i.h>
#include <flipper.pb.h>
#include <applications/system/js_app/js_thread.h>
#include <applications/system/js_app/js_value.h>
static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)),
@@ -38,4 +39,16 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
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)));

View File

@@ -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",

View File

@@ -2,6 +2,7 @@
#include <stdint.h>
#include "js_thread_i.h"
#include "js_value.h"
#include <flipper_application/flipper_application.h>
#include <flipper_application/plugins/plugin_manager.h>
#include <flipper_application/plugins/composite_resolver.h>

View File

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

View File

@@ -0,0 +1,136 @@
#pragma once
#include <furi.h>
#include "js_modules.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
JsValueTypeArgs, //<! Function arguments
JsValueTypeAny, //<! Literal term
JsValueTypeAnyArray, //<! Literal term, after ensuring that it's an array
JsValueTypeAnyObject, //<! Literal term, after ensuring that it's an object
JsValueTypeFunction, //<! Literal term, after ensuring that it's a function
JsValueTypeRawPointer, //<! Unchecked `void*`
JsValueTypeInt32, //<! Number cast to `int32_t`
JsValueTypeDouble, //<! Number cast to `double`
JsValueTypeString, //<! Any string cast to `const char*`
JsValueTypeBool, //<! Bool cast to `bool`
JsValueTypeEnum, //<! String with predefined possible values cast to the desired C enum
JsValueTypeObject, //<! Object with predefined recursive fields cast to several C values
JsValueTypeEnumValue, //<! String-to-number enum mapping
} JsValueType;
typedef union {
void* ptr_val;
int32_t int32_val;
double double_val;
const char* str_val;
uint32_t enum_val;
bool bool_val;
} JsValueDefaultValue;
typedef struct JsValueDeclaration {
JsValueType type;
union {
const char* object_field_name;
const char* enum_string_value;
};
bool permit_null;
JsValueDefaultValue default_value;
union {
uint8_t enum_size;
uint32_t enum_value;
};
size_t n_children;
const struct JsValueDeclaration* children;
} JsValueDeclaration;
#define JS_VALUE_ENUM_VARIANT(str_value, int_value) \
{.type = JsValueTypeEnumValue, .enum_string_value = str_value, .enum_value = int_value}
#define JS_VALUE_CHILDREN(variants_or_fields) \
.n_children = COUNT_OF(variants_or_fields), .children = variants_or_fields
typedef enum {
JsValueParseFlagNone = 0,
JsValueParseFlagReturnOnError =
(1
<< 0), //<! Sets mjs error string to a description of the parsing error and returns from the JS function
} JsValueParseFlag;
typedef enum {
JsValueParseStatusOk, //<! Parsing completed successfully
JsValueParseStatusJsError, //<! Parsing failed due to incorrect JS input
} JsValueParseStatus;
/**
* @brief Magic value that tells `js_value_parse` to source the JS values from
* function arguments.
*/
#define JS_VAL_PARSE_SOURCE_ARGS NULL
/**
* @brief Determines the size of the buffer array of `mjs_val_t`s that needs to
* be passed to `js_value_parse`.
*/
size_t js_value_buffer_size(const JsValueDeclaration* declaration);
/**
* @brief Converts a JS value into a series of C values.
*
* @param[in] mjs mJS instance pointer
* @param[in] declaration Type declaration for the input value
* @param[in] flags See the corresponding enum
* @param[out] buffer Temporary buffer for values that need to live
* longer than the function call. To determine the
* size of the buffer, use `js_value_buffer_size`.
* Values parsed by this function will become invalid
* when this buffer goes out of scope.
* @param[in] buf_size Number of entries in the temporary buffer (i.e.
* `COUNT_OF`, not `sizeof`).
* @param[in] source Source JS value that needs to be converted. Set to
* `JS_VAL_PARSE_SOURCE_ARGS` to fetch the values from
* arguments.
* @param[in] n_c_vals Number of output C values
* @param[out] ... Pointers to output C values. The order in which
* these values are populated corresponds to the order
* in which the values are defined in the declaration.
*
* @returns Parsing status
*/
JsValueParseStatus js_value_parse(
struct mjs* mjs,
const JsValueDeclaration* declaration,
JsValueParseFlag flags,
mjs_val_t* buffer,
size_t buf_size,
mjs_val_t* source,
size_t n_c_vals,
...);
#define JS_VALUE_PARSE(mjs, declaration, flags, status_ptr, value_ptr, ...) \
void* _args[] = {__VA_ARGS__}; \
size_t _n_args = COUNT_OF(_args); \
size_t _temp_buf_len = js_value_buffer_size(declaration); \
mjs_val_t _temp_buffer[_temp_buf_len]; \
*(status_ptr) = js_value_parse( \
mjs, declaration, flags, _temp_buffer, _temp_buf_len, value_ptr, _n_args, __VA_ARGS__);
#define JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, declaration, ...) \
JsValueParseStatus _status; \
JS_VALUE_PARSE( \
mjs, \
declaration, \
JsValueParseFlagReturnOnError, \
&_status, \
JS_VAL_PARSE_SOURCE_ARGS, \
__VA_ARGS__); \
if(_status != JsValueParseStatusOk) return;
#ifdef __cplusplus
}
#endif