pebble/applib-targets/emscripten/emscripten_jerry_api.c
2025-01-27 11:38:16 -08:00

720 lines
24 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "jerry-api.h"
#include <emscripten/emscripten.h>
#include <string.h>
#define TYPE_ERROR \
jerry_create_error(JERRY_ERROR_TYPE, NULL);
#define TYPE_ERROR_ARG \
jerry_create_error(JERRY_ERROR_TYPE, (const jerry_char_t *)"wrong type of argument");
#define TYPE_ERROR_FLAG \
jerry_create_error(JERRY_ERROR_TYPE, (const jerry_char_t *)"argument cannot have an error flag");
////////////////////////////////////////////////////////////////////////////////
// Parser and Executor Function
////////////////////////////////////////////////////////////////////////////////
// Note that `is_strict` is currently unsupported by emscripten
jerry_value_t jerry_eval(const jerry_char_t *source_p, size_t source_size, bool is_strict) {
return (jerry_value_t)EM_ASM_INT({
// jerry_eval() uses an indirect eval() call,
// so the global execution context is used.
// Also see ECMA 5.1 -- 10.4.2 Entering Eval Code.
var indirectEval = eval;
try {
return __jerryRefs.ref(indirectEval(Module.Pointer_stringify($0, $1)));
} catch (e) {
var error_ref = __jerryRefs.ref(e);
__jerryRefs.setError(error_ref, true);
return error_ref;
}
}, source_p, source_size);
}
jerry_value_t jerry_acquire_value(jerry_value_t value) {
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.acquire($0);
}, value);
}
void jerry_release_value(jerry_value_t value) {
EM_ASM_INT({
__jerryRefs.release($0);
}, value);
}
////////////////////////////////////////////////////////////////////////////////
// Get the global context
////////////////////////////////////////////////////////////////////////////////
jerry_value_t jerry_get_global_object(void) {
return ((jerry_value_t)EM_ASM_INT_V({ \
return __jerryRefs.ref(Function('return this;')()); \
}));
}
jerry_value_t jerry_get_global_builtin(const jerry_char_t *builtin_name) {
return ((jerry_value_t)EM_ASM_INT({ \
return __jerryRefs.ref(Function('return this;')()[Module.Pointer_stringify($0)]); \
}, builtin_name));
}
////////////////////////////////////////////////////////////////////////////////
// Jerry Value Type Checking
////////////////////////////////////////////////////////////////////////////////
#define JERRY_VALUE_HAS_TYPE(ref, typename) \
((bool)EM_ASM_INT({ \
return typeof __jerryRefs.get($0) === (typename); \
}, (ref)))
#define JERRY_VALUE_IS_INSTANCE(ref, type) \
((bool)EM_ASM_INT({ \
return __jerryRefs.get($0) instanceof (type); \
}, (ref)))
bool jerry_value_is_array(const jerry_value_t value) {
return JERRY_VALUE_IS_INSTANCE(value, Array);
}
bool jerry_value_is_boolean(const jerry_value_t value) {
return JERRY_VALUE_HAS_TYPE(value, 'boolean');
}
bool jerry_value_is_constructor(const jerry_value_t value) {
return jerry_value_is_function(value);
}
bool jerry_value_is_function(const jerry_value_t value) {
return JERRY_VALUE_HAS_TYPE(value, 'function');
}
bool jerry_value_is_number(const jerry_value_t value) {
return JERRY_VALUE_HAS_TYPE(value, 'number');
}
bool jerry_value_is_null(const jerry_value_t value) {
return ((bool)EM_ASM_INT({
return __jerryRefs.get($0) === null;
}, value));
}
bool jerry_value_is_object(const jerry_value_t value) {
return !jerry_value_is_null(value) &&
(JERRY_VALUE_HAS_TYPE(value, 'object') || jerry_value_is_function(value));
}
bool jerry_value_is_string(const jerry_value_t value) {
return JERRY_VALUE_HAS_TYPE(value, 'string');
}
bool jerry_value_is_undefined(const jerry_value_t value) {
return JERRY_VALUE_HAS_TYPE(value, 'undefined');
}
////////////////////////////////////////////////////////////////////////////////
// Jerry Value Getter Functions
////////////////////////////////////////////////////////////////////////////////
bool jerry_get_boolean_value(const jerry_value_t value) {
if (!jerry_value_is_boolean(value)) {
return false;
}
return (bool)EM_ASM_INT({
return (__jerryRefs.get($0) === true);
}, value);
}
double jerry_get_number_value(const jerry_value_t value) {
if (!jerry_value_is_number(value)) {
return 0.0;
}
return EM_ASM_DOUBLE({
return __jerryRefs.get($0);
}, value);
}
////////////////////////////////////////////////////////////////////////////////
// Functions for UTF-8 encoded string values
////////////////////////////////////////////////////////////////////////////////
jerry_size_t jerry_get_utf8_string_size(const jerry_value_t value) {
if (!jerry_value_is_string(value)) {
return 0;
}
return (jerry_size_t)EM_ASM_INT({
return Module.lengthBytesUTF8(__jerryRefs.get($0));
}, value);
}
jerry_size_t jerry_string_to_utf8_char_buffer(const jerry_value_t value,
jerry_char_t *buffer_p,
jerry_size_t buffer_size) {
const jerry_size_t str_size = jerry_get_utf8_string_size(value);
if (str_size == 0 || buffer_size < str_size || buffer_p == NULL) {
return 0;
}
EM_ASM_INT({
var str = __jerryRefs.get($0);
// Add one onto the buffer size, since Module.stringToUTF8 adds a null
// character at the end. This will lead to truncation if we just use
// buffer_size. Since the actual jerry-api does not do this, we are
// always careful to allocate space for a null character at the end.
// Allow stringToUTF8 to write that extra null beyond the passed in
// buffer_length.
Module.stringToUTF8(str, $1, $2 + 1);
}, value, buffer_p, buffer_size);
return strlen((const char *)buffer_p);
}
////////////////////////////////////////////////////////////////////////////////
// Functions for array object values
////////////////////////////////////////////////////////////////////////////////
uint32_t jerry_get_array_length(const jerry_value_t value) {
if (!jerry_value_is_array(value)) {
return 0;
}
return (uint32_t)EM_ASM_INT({
return __jerryRefs.get($0).length;
}, value);
}
////////////////////////////////////////////////////////////////////////////////
// Jerry Value Creation API
////////////////////////////////////////////////////////////////////////////////
#define JERRY_CREATE_VALUE(value) \
((jerry_value_t)EM_ASM_INT_V({ \
return __jerryRefs.ref((value)); \
}))
jerry_value_t jerry_create_array(uint32_t size) {
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(new Array($0));
}, size);
}
jerry_value_t jerry_create_boolean(bool value) {
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(Boolean($0));
}, value);
}
jerry_value_t jerry_create_error(jerry_error_t error_type, const jerry_char_t *message_p) {
return jerry_create_error_sz(error_type, message_p, strlen((const char *)message_p));
}
#define JERRY_ERROR(type, msg, sz) (jerry_value_t)(EM_ASM_INT({ \
return __jerryRefs.ref(new (type)(Module.Pointer_stringify($0, $1))) \
}, (msg), (sz)))
jerry_value_t jerry_create_error_sz(jerry_error_t error_type,
const jerry_char_t *message_p,
jerry_size_t message_size) {
jerry_value_t error_ref = 0;
switch (error_type) {
case JERRY_ERROR_COMMON:
error_ref = JERRY_ERROR(Error, message_p, message_size);
break;
case JERRY_ERROR_EVAL:
error_ref = JERRY_ERROR(EvalError, message_p, message_size);
break;
case JERRY_ERROR_RANGE:
error_ref = JERRY_ERROR(RangeError, message_p, message_size);
break;
case JERRY_ERROR_REFERENCE:
error_ref = JERRY_ERROR(ReferenceError, message_p, message_size);
break;
case JERRY_ERROR_SYNTAX:
error_ref = JERRY_ERROR(SyntaxError, message_p, message_size);
break;
case JERRY_ERROR_TYPE:
error_ref = JERRY_ERROR(TypeError, message_p, message_size);
break;
case JERRY_ERROR_URI:
error_ref = JERRY_ERROR(URIError, message_p, message_size);
break;
default:
EM_ASM_INT({
abort('Cannot create error type: ' + $0);
}, error_type);
break;
}
jerry_value_set_error_flag(&error_ref);
return error_ref;
}
jerry_value_t jerry_create_external_function(jerry_external_handler_t handler_p) {
return (jerry_value_t)EM_ASM_INT({
return __jerry_create_external_function($0);
}, handler_p);
}
jerry_value_t jerry_create_number(double value) {
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref($0);
}, value);
}
jerry_value_t jerry_create_number_infinity(bool negative) {
if (negative) {
return JERRY_CREATE_VALUE(-Infinity);
} else {
return JERRY_CREATE_VALUE(Infinity);
}
}
jerry_value_t jerry_create_number_nan(void) {
return JERRY_CREATE_VALUE(NaN);
}
jerry_value_t jerry_create_null(void) {
return JERRY_CREATE_VALUE(null);
}
jerry_value_t jerry_create_object(void) {
return JERRY_CREATE_VALUE(new Object());
}
jerry_value_t jerry_create_string(const jerry_char_t *str_p) {
if (!str_p) {
return jerry_create_undefined();
}
return jerry_create_string_utf8_sz(str_p, strlen((const char *)str_p));
}
jerry_value_t jerry_create_string_sz(const jerry_char_t *str_p, jerry_size_t str_size) {
return jerry_create_string_utf8_sz(str_p, str_size);
}
jerry_value_t jerry_create_string_utf8(const jerry_char_t *str_p) {
if (!str_p) {
return jerry_create_undefined();
}
return jerry_create_string_utf8_sz(str_p, strlen((const char *)str_p));
}
jerry_value_t jerry_create_string_utf8_sz(const jerry_char_t *str_p, jerry_size_t str_size) {
if (!str_p) {
return jerry_create_undefined();
}
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(Module.Pointer_stringify($0, $1));
}, str_p, str_size);
}
jerry_value_t jerry_create_undefined(void) {
return JERRY_CREATE_VALUE(undefined);
}
////////////////////////////////////////////////////////////////////////////////
// General API Functions of JS Objects
////////////////////////////////////////////////////////////////////////////////
bool jerry_has_property(const jerry_value_t obj_val, const jerry_value_t prop_name_val) {
if (!jerry_value_is_object(obj_val) || !jerry_value_is_string(prop_name_val)) {
return false;
}
return (bool)EM_ASM_INT({
var obj = __jerryRefs.get($0);
var name = __jerryRefs.get($1);
return (name in obj);
}, obj_val, prop_name_val);
}
bool jerry_has_own_property(const jerry_value_t obj_val, const jerry_value_t prop_name_val) {
if (!jerry_value_is_object(obj_val) || !jerry_value_is_string(prop_name_val)) {
return false;
}
return (bool)EM_ASM_INT({
var obj = __jerryRefs.get($0);
var name = __jerryRefs.get($1);
return obj.hasOwnProperty(name);
}, obj_val, prop_name_val);
}
bool jerry_delete_property(const jerry_value_t obj_val, const jerry_value_t prop_name_val) {
if (!jerry_value_is_object(obj_val) || !jerry_value_is_string(prop_name_val)) {
return false;
}
return (bool)EM_ASM_INT({
var obj = __jerryRefs.get($0);
var name = __jerryRefs.get($1);
try {
return delete obj[name];
} catch (e) {
// In strict mode, delete throws SyntaxError if the property is an
// own non-configurable property.
return false;
}
return true;
}, obj_val, prop_name_val);
}
jerry_value_t jerry_get_property(const jerry_value_t obj_val, const jerry_value_t prop_name_val) {
if (!jerry_value_is_object(obj_val) || !jerry_value_is_string(prop_name_val)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
var obj = __jerryRefs.get($0);
var name = __jerryRefs.get($1);
return __jerryRefs.ref(obj[name]);
}, obj_val, prop_name_val);
}
jerry_value_t jerry_get_property_by_index(const jerry_value_t obj_val, uint32_t index) {
if (!jerry_value_is_object(obj_val)) {
return TYPE_ERROR;
}
return (jerry_value_t)EM_ASM_INT({
var obj = __jerryRefs.get($0);
return __jerryRefs.ref(obj[$1]);
}, obj_val, index);
}
jerry_value_t jerry_set_property(const jerry_value_t obj_val,
const jerry_value_t prop_name_val,
const jerry_value_t value_to_set) {
if (jerry_value_has_error_flag(value_to_set) ||
!jerry_value_is_object(obj_val) ||
!jerry_value_is_string(prop_name_val)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
var obj = __jerryRefs.get($0);
var name = __jerryRefs.get($1);
var to_set = __jerryRefs.get($2);
obj[name] = to_set;
return __jerryRefs.ref(true);
}, obj_val, prop_name_val, value_to_set);
}
jerry_value_t jerry_set_property_by_index(const jerry_value_t obj_val,
uint32_t index,
const jerry_value_t value_to_set) {
if (jerry_value_has_error_flag(value_to_set) ||
!jerry_value_is_object(obj_val)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
var obj = __jerryRefs.get($0);
var to_set = __jerryRefs.get($2);
obj[$1] = to_set;
return __jerryRefs.ref(true);
}, obj_val, index, value_to_set);
}
void jerry_init_property_descriptor_fields(jerry_property_descriptor_t *prop_desc_p) {
*prop_desc_p = (jerry_property_descriptor_t) {
.value = jerry_create_undefined(),
.getter = jerry_create_undefined(),
.setter = jerry_create_undefined(),
};
}
jerry_value_t jerry_define_own_property(const jerry_value_t obj_val,
const jerry_value_t prop_name_val,
const jerry_property_descriptor_t *pdp) {
if (!jerry_value_is_object(obj_val) && !jerry_value_is_string(obj_val)) {
return TYPE_ERROR_ARG;
}
if ((pdp->is_writable_defined || pdp->is_value_defined)
&& (pdp->is_get_defined || pdp->is_set_defined)) {
return TYPE_ERROR_ARG;
}
if (pdp->is_get_defined && !jerry_value_is_function(pdp->getter)) {
return TYPE_ERROR_ARG;
}
if (pdp->is_set_defined && !jerry_value_is_function(pdp->setter)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)(EM_ASM_INT({
var obj = __jerryRefs.get($12 /* obj_val */);
var name = __jerryRefs.get($13 /* prop_name_val */);
var desc = {};
if ($0 /* is_value_defined */) {
desc.value = __jerryRefs.get($9);
}
if ($1 /* is_get_defined */) {
desc.get = __jerryRefs.get($10);
}
if ($2 /* is_set_defined */) {
desc.set = __jerryRefs.get($11);
}
if ($3 /* is_writable_defined */) {
desc.writable = Boolean($4 /* is_writable */);
}
if ($5 /* is_enumerable_defined */) {
desc.enumerable = Boolean($6 /* is_enumerable */);
}
if ($7 /* is_configurable */) {
desc.configurable = Boolean($8 /* is_configurable */);
}
Object.defineProperty(obj, name, desc);
return __jerryRefs.ref(Boolean(true));
}, pdp->is_value_defined, /* $0 */
pdp->is_get_defined, /* $1 */
pdp->is_set_defined, /* $2 */
pdp->is_writable_defined, /* $3 */
pdp->is_writable, /* $4 */
pdp->is_enumerable_defined, /* $5 */
pdp->is_enumerable, /* $6 */
pdp->is_configurable_defined, /* $7 */
pdp->is_configurable, /* $8 */
pdp->value, /* $9 */
pdp->getter, /* $10 */
pdp->setter, /* $11 */
obj_val, /* $12 */
prop_name_val /* $13 */
));
}
jerry_value_t emscripten_call_jerry_function(jerry_external_handler_t func_obj_p,
const jerry_value_t func_obj_val,
const jerry_value_t this_val,
const jerry_value_t args_p[],
jerry_size_t args_count) {
return (func_obj_p)(func_obj_val, this_val, args_p, args_count);
}
jerry_value_t jerry_call_function(const jerry_value_t func_obj_val,
const jerry_value_t this_val,
const jerry_value_t args_p[],
jerry_size_t args_count) {
if (!jerry_value_is_function(func_obj_val)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
var func_obj = __jerryRefs.get($0);
var this_val = __jerryRefs.get($1);
var args = [];
for (var i = 0; i < $3; ++i) {
args.push(__jerryRefs.get(getValue($2 + i*4, 'i32')));
}
try {
var rv = func_obj.apply(this_val, args);
} catch (e) {
var error_ref = __jerryRefs.ref(e);
__jerryRefs.setError(error_ref, true);
return error_ref;
}
return __jerryRefs.ref(rv);
}, func_obj_val, this_val, args_p, args_count);
}
jerry_value_t jerry_construct_object(const jerry_value_t func_obj_val,
const jerry_value_t args_p[],
jerry_size_t args_count) {
if (!jerry_value_is_constructor(func_obj_val)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
var func_obj = __jerryRefs.get($0);
var args = [];
for (var i = 0; i < $2; ++i) {
args.push(__jerryRefs.get(getValue($1 + i*4, 'i32')));
}
// Call the constructor with new object as `this`
var bindArgs = [null].concat(args);
var boundConstructor = func_obj.bind.apply(func_obj, bindArgs);
var rv = new boundConstructor();
return __jerryRefs.ref(rv);
}, func_obj_val, args_p, args_count);
}
jerry_size_t jerry_string_to_char_buffer(const jerry_value_t value,
jerry_char_t *buffer_p,
jerry_size_t buffer_size) {
return jerry_string_to_utf8_char_buffer(value, buffer_p, buffer_size);
}
jerry_size_t jerry_object_to_string_to_utf8_char_buffer(const jerry_value_t object,
jerry_char_t *buffer_p,
jerry_size_t buffer_size) {
jerry_value_t str_ref = (jerry_value_t)EM_ASM_INT({
var str = __jerryRefs.ref(String(__jerryRefs.get($0)));
return str;
}, object);
jerry_size_t len = jerry_string_to_utf8_char_buffer(str_ref, buffer_p, buffer_size);
jerry_release_value(str_ref);
return len;
}
// FIXME: PBL-43551 Propery CESU-8 => UTF-8 conversion.
jerry_size_t jerry_object_to_string_to_char_buffer(const jerry_value_t object,
jerry_char_t *buffer_p,
jerry_size_t buffer_size) {
return jerry_object_to_string_to_utf8_char_buffer(object,
buffer_p,
buffer_size);
}
jerry_value_t jerry_get_object_keys(const jerry_value_t value) {
if (!jerry_value_is_object(value)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(Object.keys(__jerryRefs.get($0)));
}, value);
}
jerry_value_t jerry_get_prototype(const jerry_value_t value) {
if (!jerry_value_is_object(value)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(__jerryRefs.get($0).prototype);
}, value);
}
jerry_value_t jerry_set_prototype(const jerry_value_t obj_val, const jerry_value_t proto_obj_val) {
return 0; // FIXME: Not sure what to do here
}
bool jerry_get_object_native_handle(const jerry_value_t obj_val, uintptr_t *out_handle_p) {
return EM_ASM_INT({
var ptr = __jerryRefs.getNativeHandle($0);
if (ptr === undefined) {
return false;
}
Module.setValue($1, ptr, '*');
return true;
}, obj_val, out_handle_p);
}
void jerry_set_object_native_handle(const jerry_value_t obj_val, uintptr_t handle_p,
jerry_object_free_callback_t freecb_p) {
EM_ASM_INT({
__jerryRefs.setNativeHandle($0, $1, $2);
}, obj_val, handle_p, freecb_p);
}
void emscripten_call_jerry_object_free_callback(jerry_object_free_callback_t freecb_p,
uintptr_t handle_p) {
if (freecb_p) {
freecb_p(handle_p);
}
}
////////////////////////////////////////////////////////////////////////////////
// Error flag manipulation functions
////////////////////////////////////////////////////////////////////////////////
//
// The error flag is stored alongside the value in __jerryRefs.
// This allows for us to keep a valid value, like jerryscript does, and be able
// to add / remove a flag specifying whether there was an error or not.
bool jerry_value_has_error_flag(const jerry_value_t value) {
return (bool)(EM_ASM_INT({
return __jerryRefs.getError($0);
}, value));
}
void jerry_value_clear_error_flag(jerry_value_t *value_p) {
EM_ASM_INT({
return __jerryRefs.setError($0, false);
}, *value_p);
}
void jerry_value_set_error_flag(jerry_value_t *value_p) {
EM_ASM_INT({
return __jerryRefs.setError($0, true);
}, *value_p);
}
////////////////////////////////////////////////////////////////////////////////
// Converters of `jerry_value_t`
////////////////////////////////////////////////////////////////////////////////
bool jerry_value_to_boolean(const jerry_value_t value) {
if (jerry_value_has_error_flag(value)) {
return false;
}
return (bool)EM_ASM_INT({
return Boolean(__jerryRefs.get($0));
}, value);
}
jerry_value_t jerry_value_to_number(const jerry_value_t value) {
if (jerry_value_has_error_flag(value)) {
return TYPE_ERROR_FLAG;
}
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(Number(__jerryRefs.get($0)));
}, value);
}
jerry_value_t jerry_value_to_object(const jerry_value_t value) {
if (jerry_value_has_error_flag(value)) {
return TYPE_ERROR_FLAG;
}
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(new Object(__jerryRefs.get($0)));
}, value);
}
jerry_value_t jerry_value_to_primitive(const jerry_value_t value) {
if (jerry_value_has_error_flag(value)) {
return TYPE_ERROR_FLAG;
}
return (jerry_value_t)EM_ASM_INT({
var val = __jerryRefs.get($0);
var rv;
if ((typeof val === 'object' && val != null)
|| (typeof val === 'function')) {
rv = val.valueOf(); // unbox
} else {
rv = val; // already a primitive
}
return __jerryRefs.ref(rv);
}, value);
}
jerry_value_t jerry_value_to_string(const jerry_value_t value) {
if (jerry_value_has_error_flag(value)) {
return TYPE_ERROR_FLAG;
}
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(String(__jerryRefs.get($0)));
}, value);
}
int jerry_obj_refcount(jerry_value_t o) {
return EM_ASM_INT({
try {
return __jerryRefs.getRefCount($0);
} catch (e) {
return 0;
}
}, o);
}
////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
void jerry_init(jerry_init_flag_t flags) {
EM_ASM(__jerryRefs.reset());
}
void jerry_cleanup(void) {
}