cosmopolitan/third_party/quickjs/gen.c
Justine Tunney 10fd8bdb70 Unbloat the build
This change resurrects ae5d06dc53
2022-08-11 00:15:29 -07:00

510 lines
19 KiB
C

/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2021 Fabrice Bellard
* Copyright (c) 2017-2021 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/runtime/runtime.h"
#include "third_party/quickjs/internal.h"
asm(".ident\t\"\\n\\n\
QuickJS (MIT License)\\n\
Copyright (c) 2017-2021 Fabrice Bellard\\n\
Copyright (c) 2017-2021 Charlie Gordon\"");
asm(".include \"libc/disclaimer.inc\"");
/* clang-format off */
typedef enum JSGeneratorStateEnum {
JS_GENERATOR_STATE_SUSPENDED_START,
JS_GENERATOR_STATE_SUSPENDED_YIELD,
JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR,
JS_GENERATOR_STATE_EXECUTING,
JS_GENERATOR_STATE_COMPLETED,
} JSGeneratorStateEnum;
typedef struct JSGeneratorData {
JSGeneratorStateEnum state;
JSAsyncFunctionState func_state;
} JSGeneratorData;
static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s)
{
if (s->state == JS_GENERATOR_STATE_COMPLETED)
return;
async_func_free(rt, &s->func_state);
s->state = JS_GENERATOR_STATE_COMPLETED;
}
void js_generator_finalizer(JSRuntime *rt, JSValue obj)
{
JSGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_GENERATOR);
if (s) {
free_generator_stack_rt(rt, s);
js_free_rt(rt, s);
}
}
static void free_generator_stack(JSContext *ctx, JSGeneratorData *s)
{
free_generator_stack_rt(ctx->rt, s);
}
void js_generator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
{
JSObject *p = JS_VALUE_GET_OBJ(val);
JSGeneratorData *s = p->u.generator_data;
if (!s || s->state == JS_GENERATOR_STATE_COMPLETED)
return;
async_func_mark(rt, &s->func_state, mark_func);
}
static JSValue js_generator_next(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv,
BOOL *pdone, int magic)
{
JSGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_GENERATOR);
JSStackFrame *sf;
JSValue ret, func_ret;
*pdone = TRUE;
if (!s)
return JS_ThrowTypeError(ctx, "not a generator");
sf = &s->func_state.frame;
switch(s->state) {
default:
case JS_GENERATOR_STATE_SUSPENDED_START:
if (magic == GEN_MAGIC_NEXT) {
goto exec_no_arg;
} else {
free_generator_stack(ctx, s);
goto done;
}
break;
case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR:
case JS_GENERATOR_STATE_SUSPENDED_YIELD:
/* cur_sp[-1] was set to JS_UNDEFINED in the previous call */
ret = JS_DupValue(ctx, argv[0]);
if (magic == GEN_MAGIC_THROW &&
s->state == JS_GENERATOR_STATE_SUSPENDED_YIELD) {
JS_Throw(ctx, ret);
s->func_state.throw_flag = TRUE;
} else {
sf->cur_sp[-1] = ret;
sf->cur_sp[0] = JS_NewInt32(ctx, magic);
sf->cur_sp++;
exec_no_arg:
s->func_state.throw_flag = FALSE;
}
s->state = JS_GENERATOR_STATE_EXECUTING;
func_ret = async_func_resume(ctx, &s->func_state);
s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD;
if (JS_IsException(func_ret)) {
/* finalize the execution in case of exception */
free_generator_stack(ctx, s);
return func_ret;
}
if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) {
/* get the returned yield value at the top of the stack */
ret = sf->cur_sp[-1];
sf->cur_sp[-1] = JS_UNDEFINED;
if (JS_VALUE_GET_INT(func_ret) == FUNC_RET_YIELD_STAR) {
s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR;
/* return (value, done) object */
*pdone = 2;
} else {
*pdone = FALSE;
}
} else {
/* end of iterator */
ret = sf->cur_sp[-1];
sf->cur_sp[-1] = JS_UNDEFINED;
JS_FreeValue(ctx, func_ret);
free_generator_stack(ctx, s);
}
break;
case JS_GENERATOR_STATE_COMPLETED:
done:
/* execution is finished */
switch(magic) {
default:
case GEN_MAGIC_NEXT:
ret = JS_UNDEFINED;
break;
case GEN_MAGIC_RETURN:
ret = JS_DupValue(ctx, argv[0]);
break;
case GEN_MAGIC_THROW:
ret = JS_Throw(ctx, JS_DupValue(ctx, argv[0]));
break;
}
break;
case JS_GENERATOR_STATE_EXECUTING:
ret = JS_ThrowTypeError(ctx, "cannot invoke a running generator");
break;
}
return ret;
}
JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj,
JSValueConst this_obj, int argc,
JSValueConst *argv, int flags)
{
JSValue obj, func_ret;
JSGeneratorData *s;
s = js_mallocz(ctx, sizeof(*s));
if (!s)
return JS_EXCEPTION;
s->state = JS_GENERATOR_STATE_SUSPENDED_START;
if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
s->state = JS_GENERATOR_STATE_COMPLETED;
goto fail;
}
/* execute the function up to 'OP_initial_yield' */
func_ret = async_func_resume(ctx, &s->func_state);
if (JS_IsException(func_ret))
goto fail;
JS_FreeValue(ctx, func_ret);
obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_GENERATOR);
if (JS_IsException(obj))
goto fail;
JS_SetOpaque(obj, s);
return obj;
fail:
free_generator_stack_rt(ctx->rt, s);
js_free(ctx, s);
return JS_EXCEPTION;
}
static JSValue js_async_generator_resolve_function(JSContext *ctx,
JSValueConst this_obj,
int argc, JSValueConst *argv,
int magic, JSValue *func_data);
static int js_async_generator_resolve_function_create(JSContext *ctx,
JSValueConst generator,
JSValue *resolving_funcs,
BOOL is_resume_next)
{
int i;
JSValue func;
for(i = 0; i < 2; i++) {
func = JS_NewCFunctionData(ctx, js_async_generator_resolve_function, 1,
i + is_resume_next * 2, 1, &generator);
if (JS_IsException(func)) {
if (i == 1)
JS_FreeValue(ctx, resolving_funcs[0]);
return -1;
}
resolving_funcs[i] = func;
}
return 0;
}
static int js_async_generator_await(JSContext *ctx,
JSAsyncGeneratorData *s,
JSValueConst value)
{
JSValue promise, resolving_funcs[2], resolving_funcs1[2];
int i, res;
promise = js_promise_resolve(ctx, ctx->promise_ctor,
1, &value, 0);
if (JS_IsException(promise))
goto fail;
if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator),
resolving_funcs, FALSE)) {
JS_FreeValue(ctx, promise);
goto fail;
}
/* Note: no need to create 'thrownawayCapability' as in
the spec */
for(i = 0; i < 2; i++)
resolving_funcs1[i] = JS_UNDEFINED;
res = perform_promise_then(ctx, promise,
(JSValueConst *)resolving_funcs,
(JSValueConst *)resolving_funcs1);
JS_FreeValue(ctx, promise);
for(i = 0; i < 2; i++)
JS_FreeValue(ctx, resolving_funcs[i]);
if (res)
goto fail;
return 0;
fail:
return -1;
}
static void js_async_generator_resolve_or_reject(JSContext *ctx,
JSAsyncGeneratorData *s,
JSValueConst result,
int is_reject)
{
JSAsyncGeneratorRequest *next;
JSValue ret;
next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link);
list_del(&next->link);
ret = JS_Call(ctx, next->resolving_funcs[is_reject], JS_UNDEFINED, 1,
&result);
JS_FreeValue(ctx, ret);
JS_FreeValue(ctx, next->result);
JS_FreeValue(ctx, next->promise);
JS_FreeValue(ctx, next->resolving_funcs[0]);
JS_FreeValue(ctx, next->resolving_funcs[1]);
js_free(ctx, next);
}
static void js_async_generator_resolve(JSContext *ctx,
JSAsyncGeneratorData *s,
JSValueConst value,
BOOL done)
{
JSValue result;
result = js_create_iterator_result(ctx, JS_DupValue(ctx, value), done);
/* XXX: better exception handling ? */
js_async_generator_resolve_or_reject(ctx, s, result, 0);
JS_FreeValue(ctx, result);
}
static void js_async_generator_reject(JSContext *ctx,
JSAsyncGeneratorData *s,
JSValueConst exception)
{
js_async_generator_resolve_or_reject(ctx, s, exception, 1);
}
static void js_async_generator_complete(JSContext *ctx,
JSAsyncGeneratorData *s)
{
if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) {
s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
async_func_free(ctx->rt, &s->func_state);
}
}
static int js_async_generator_completed_return(JSContext *ctx,
JSAsyncGeneratorData *s,
JSValueConst value)
{
JSValue promise, resolving_funcs[2], resolving_funcs1[2];
int res;
promise = js_promise_resolve(ctx, ctx->promise_ctor,
1, (JSValueConst *)&value, 0);
if (JS_IsException(promise))
return -1;
if (js_async_generator_resolve_function_create(ctx,
JS_MKPTR(JS_TAG_OBJECT, s->generator),
resolving_funcs1,
TRUE)) {
JS_FreeValue(ctx, promise);
return -1;
}
resolving_funcs[0] = JS_UNDEFINED;
resolving_funcs[1] = JS_UNDEFINED;
res = perform_promise_then(ctx, promise,
(JSValueConst *)resolving_funcs1,
(JSValueConst *)resolving_funcs);
JS_FreeValue(ctx, resolving_funcs1[0]);
JS_FreeValue(ctx, resolving_funcs1[1]);
JS_FreeValue(ctx, promise);
return res;
}
void js_async_generator_resume_next(JSContext *ctx, JSAsyncGeneratorData *s)
{
JSAsyncGeneratorRequest *next;
JSValue func_ret, value;
for(;;) {
if (list_empty(&s->queue))
break;
next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link);
switch(s->state) {
case JS_ASYNC_GENERATOR_STATE_EXECUTING:
/* only happens when restarting execution after await() */
goto resume_exec;
case JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN:
goto done;
case JS_ASYNC_GENERATOR_STATE_SUSPENDED_START:
if (next->completion_type == GEN_MAGIC_NEXT) {
goto exec_no_arg;
} else {
js_async_generator_complete(ctx, s);
}
break;
case JS_ASYNC_GENERATOR_STATE_COMPLETED:
if (next->completion_type == GEN_MAGIC_NEXT) {
js_async_generator_resolve(ctx, s, JS_UNDEFINED, TRUE);
} else if (next->completion_type == GEN_MAGIC_RETURN) {
s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN;
js_async_generator_completed_return(ctx, s, next->result);
goto done;
} else {
js_async_generator_reject(ctx, s, next->result);
}
goto done;
case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD:
case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR:
value = JS_DupValue(ctx, next->result);
if (next->completion_type == GEN_MAGIC_THROW &&
s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) {
JS_Throw(ctx, value);
s->func_state.throw_flag = TRUE;
} else {
/* 'yield' returns a value. 'yield *' also returns a value
in case the 'throw' method is called */
s->func_state.frame.cur_sp[-1] = value;
s->func_state.frame.cur_sp[0] =
JS_NewInt32(ctx, next->completion_type);
s->func_state.frame.cur_sp++;
exec_no_arg:
s->func_state.throw_flag = FALSE;
}
s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING;
resume_exec:
func_ret = async_func_resume(ctx, &s->func_state);
if (JS_IsException(func_ret)) {
value = JS_GetException(ctx);
js_async_generator_complete(ctx, s);
js_async_generator_reject(ctx, s, value);
JS_FreeValue(ctx, value);
} else if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) {
int func_ret_code;
value = s->func_state.frame.cur_sp[-1];
s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
func_ret_code = JS_VALUE_GET_INT(func_ret);
switch(func_ret_code) {
case FUNC_RET_YIELD:
case FUNC_RET_YIELD_STAR:
if (func_ret_code == FUNC_RET_YIELD_STAR)
s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR;
else
s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD;
js_async_generator_resolve(ctx, s, value, FALSE);
JS_FreeValue(ctx, value);
break;
case FUNC_RET_AWAIT:
js_async_generator_await(ctx, s, value);
JS_FreeValue(ctx, value);
goto done;
default:
abort();
}
} else {
assert(JS_IsUndefined(func_ret));
/* end of function */
value = s->func_state.frame.cur_sp[-1];
s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
js_async_generator_complete(ctx, s);
js_async_generator_resolve(ctx, s, value, TRUE);
JS_FreeValue(ctx, value);
}
break;
default:
abort();
}
}
done: ;
}
static JSValue js_async_generator_resolve_function(JSContext *ctx,
JSValueConst this_obj,
int argc, JSValueConst *argv,
int magic, JSValue *func_data)
{
BOOL is_reject = magic & 1;
JSAsyncGeneratorData *s = JS_GetOpaque(func_data[0], JS_CLASS_ASYNC_GENERATOR);
JSValueConst arg = argv[0];
/* XXX: what if s == NULL */
if (magic >= 2) {
/* resume next case in AWAITING_RETURN state */
assert(s->state == JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN ||
s->state == JS_ASYNC_GENERATOR_STATE_COMPLETED);
s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
if (is_reject) {
js_async_generator_reject(ctx, s, arg);
} else {
js_async_generator_resolve(ctx, s, arg, TRUE);
}
} else {
/* restart function execution after await() */
assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING);
s->func_state.throw_flag = is_reject;
if (is_reject) {
JS_Throw(ctx, JS_DupValue(ctx, arg));
} else {
/* return value of await */
s->func_state.frame.cur_sp[-1] = JS_DupValue(ctx, arg);
}
js_async_generator_resume_next(ctx, s);
}
return JS_UNDEFINED;
}
void js_async_generator_free(JSRuntime *rt, JSAsyncGeneratorData *s)
{
struct list_head *el, *el1;
JSAsyncGeneratorRequest *req;
list_for_each_safe(el, el1, &s->queue) {
req = list_entry(el, JSAsyncGeneratorRequest, link);
JS_FreeValueRT(rt, req->result);
JS_FreeValueRT(rt, req->promise);
JS_FreeValueRT(rt, req->resolving_funcs[0]);
JS_FreeValueRT(rt, req->resolving_funcs[1]);
js_free_rt(rt, req);
}
if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED &&
s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) {
async_func_free(rt, &s->func_state);
}
js_free_rt(rt, s);
}
static const JSCFunctionListEntry js_generator_function_proto_funcs[] = {
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "GeneratorFunction", JS_PROP_CONFIGURABLE),
};
static const JSCFunctionListEntry js_generator_proto_funcs[] = {
JS_ITERATOR_NEXT_DEF("next", 1, js_generator_next, GEN_MAGIC_NEXT ),
JS_ITERATOR_NEXT_DEF("return", 1, js_generator_next, GEN_MAGIC_RETURN ),
JS_ITERATOR_NEXT_DEF("throw", 1, js_generator_next, GEN_MAGIC_THROW ),
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Generator", JS_PROP_CONFIGURABLE),
};
void JS_AddIntrinsicGenerator(JSContext *ctx)
{
JSValue obj1;
ctx->class_proto[JS_CLASS_GENERATOR] = JS_NewObjectProto(ctx, ctx->iterator_proto);
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_GENERATOR],
js_generator_proto_funcs,
countof(js_generator_proto_funcs));
ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION] = JS_NewObjectProto(ctx, ctx->function_proto);
obj1 = JS_NewCFunctionMagic(ctx, js_function_constructor,
"GeneratorFunction", 1,
JS_CFUNC_constructor_or_func_magic, JS_FUNC_GENERATOR);
JS_SetPropertyFunctionList(ctx,
ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION],
js_generator_function_proto_funcs,
countof(js_generator_function_proto_funcs));
JS_SetConstructor2(ctx, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION],
ctx->class_proto[JS_CLASS_GENERATOR],
JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE);
JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION],
0, JS_PROP_CONFIGURABLE);
JS_FreeValue(ctx, obj1);
}