Tasmota/lib/libesp32/Berry/src/be_vm.c
2021-06-17 08:28:55 +02:00

1147 lines
37 KiB
C

/********************************************************************
** Copyright (c) 2018-2020 Guan Wenliang
** This file is part of the Berry default interpreter.
** skiars@qq.com, https://github.com/Skiars/berry
** See Copyright Notice in the LICENSE file or at
** https://github.com/Skiars/berry/blob/master/LICENSE
********************************************************************/
#include "be_vm.h"
#include "be_decoder.h"
#include "be_string.h"
#include "be_strlib.h"
#include "be_class.h"
#include "be_func.h"
#include "be_vector.h"
#include "be_map.h"
#include "be_module.h"
#include "be_mem.h"
#include "be_var.h"
#include "be_gc.h"
#include "be_exec.h"
#include "be_debug.h"
#include "be_libs.h"
#include <string.h>
#include <math.h>
#define NOT_METHOD BE_NONE
#define vm_error(vm, except, ...) \
be_raise(vm, except, be_pushfstring(vm, __VA_ARGS__))
#define RA() (reg + IGET_RA(ins))
#define RKB() ((isKB(ins) ? ktab : reg) + KR2idx(IGET_RKB(ins)))
#define RKC() ((isKC(ins) ? ktab : reg) + KR2idx(IGET_RKC(ins)))
#define var2cl(_v) cast(bclosure*, var_toobj(_v))
#define var2real(_v) (var_isreal(_v) ? (_v)->v.r : (breal)(_v)->v.i)
#define val2bool(v) ((v) ? btrue : bfalse)
#define ibinop(op, a, b) ((a)->v.i op (b)->v.i)
#if BE_USE_DEBUG_HOOK
#define DEBUG_HOOK() \
if (vm->hookmask & BE_HOOK_LINE) { \
do_linehook(vm); \
reg = vm->reg; \
}
#else
#define DEBUG_HOOK()
#endif
#define vm_exec_loop() \
loop: \
DEBUG_HOOK(); \
switch (IGET_OP(ins = *vm->ip++))
#if BE_USE_SINGLE_FLOAT
#define mathfunc(func) func##f
#else
#define mathfunc(func) func
#endif
#define opcase(opcode) case OP_##opcode
#define dispatch() goto loop
#define equal_rule(op, iseq) \
bbool res; \
if (var_isint(a) && var_isint(b)) { \
res = ibinop(op, a, b); \
} else if (var_isnumber(a) && var_isnumber(b)) { \
res = var2real(a) op var2real(b); \
} else if (var_isinstance(a)) { \
res = object_eqop(vm, #op, iseq, a, b); \
} else if (var_type(a) == var_type(b)) { /* same types */ \
if (var_isnil(a)) { /* nil op nil */ \
res = 1 op 1; \
} else if (var_isbool(a)) { /* bool op bool */ \
res = var_tobool(a) op var_tobool(b); \
} else if (var_isstr(a)) { /* string op string */ \
res = 1 op be_eqstr(a->v.s, b->v.s); \
} else if (var_isclass(a) || var_isfunction(a)) { \
res = var_toobj(a) op var_toobj(b); \
} else { \
binop_error(vm, #op, a, b); \
res = bfalse; /* will not be executed */ \
} \
} else { /* different types */ \
res = 1 op 0; \
} \
return res
#define relop_rule(op) \
bbool res; \
if (var_isint(a) && var_isint(b)) { \
res = ibinop(op, a, b); \
} else if (var_isnumber(a) && var_isnumber(b)) { \
res = var2real(a) op var2real(b); \
} else if (var_isstr(a) && var_isstr(b)) { \
bstring *s1 = var_tostr(a), *s2 = var_tostr(b); \
res = be_strcmp(s1, s2) op 0; \
} else if (var_isinstance(a)) { \
binstance *obj = var_toobj(a); \
object_binop(vm, #op, *a, *b); \
check_bool(vm, obj, #op); \
res = var_tobool(vm->top); \
} else { \
binop_error(vm, #op, a, b); \
res = bfalse; /* will not be executed */ \
} \
return res
#define bitwise_block(op) \
bvalue *dst = RA(), *a = RKB(), *b = RKC(); \
if (var_isint(a) && var_isint(b)) { \
var_setint(dst, ibinop(op, a, b)); \
} else if (var_isinstance(a)) { \
ins_binop(vm, #op, ins); \
} else { \
binop_error(vm, #op, a, b); \
}
#define push_native(_vm, _f, _ns, _t) { \
precall(_vm, _f, _ns, _t); \
_vm->cf->status = PRIM_FUNC; \
}
static void attribute_error(bvm *vm, const char *t, bvalue *b, bvalue *c)
{
const char *attr = var_isstr(c) ? str(var_tostr(c)) : be_vtype2str(c);
vm_error(vm, "attribute_error",
"'%s' value has no %s '%s'", be_vtype2str(b), t, attr);
}
static void binop_error(bvm *vm, const char *op, bvalue *a, bvalue *b)
{
vm_error(vm, "type_error",
"unsupported operand type(s) for %s: '%s' and '%s'",
op, be_vtype2str(a), be_vtype2str(b));
}
static void unop_error(bvm *vm, const char *op, bvalue *a)
{
vm_error(vm, "type_error",
"unsupported operand type(s) for %s: '%s'",
op, be_vtype2str(a));
}
static void call_error(bvm *vm, bvalue *v)
{
vm_error(vm, "type_error",
"'%s' value is not callable", be_vtype2str(v));
}
static void check_bool(bvm *vm, binstance *obj, const char *method)
{
if (!var_isbool(vm->top)) {
const char *name = str(be_instance_name(obj));
vm_error(vm, "type_error",
"`%s::%s` return value error, the expected type is 'bool'",
strlen(name) ? name : "<anonymous>", method);
}
}
#if BE_USE_DEBUG_HOOK
static void do_linehook(bvm *vm)
{
bcallframe *cf = vm->cf;
bclosure *cl = var_toobj(cf->func);
int pc = cast_int(vm->ip - cl->proto->code);
if (!pc || pc > cf->lineinfo->endpc) {
while (pc > cf->lineinfo->endpc)
cf->lineinfo++;
be_callhook(vm, BE_HOOK_LINE);
} else {
blineinfo *linfo = cf->lineinfo;
blineinfo *base = cl->proto->lineinfo;
while (linfo > base && pc <= linfo[-1].endpc)
linfo--;
if (cf->lineinfo != linfo) {
cf->lineinfo = linfo;
be_callhook(vm, BE_HOOK_LINE);
}
}
}
#endif
static void precall(bvm *vm, bvalue *func, int nstack, int mode)
{
bcallframe *cf;
int expan = nstack + BE_STACK_FREE_MIN;
if (vm->stacktop < func + expan) {
size_t fpos = func - vm->stack;
be_stack_expansion(vm, expan);
func = vm->stack + fpos;
}
be_stack_push(vm, &vm->callstack, NULL);
cf = be_stack_top(&vm->callstack);
cf->func = func - mode;
cf->top = vm->top;
cf->reg = vm->reg;
vm->reg = func + 1;
vm->top = vm->reg + nstack;
vm->cf = cf;
}
static void push_closure(bvm *vm, bvalue *func, int nstack, int mode)
{
bclosure *cl = var_toobj(func);
precall(vm, func, nstack, mode);
vm->cf->ip = vm->ip;
vm->cf->status = NONE_FLAG;
vm->ip = cl->proto->code;
#if BE_USE_DEBUG_HOOK
vm->cf->lineinfo = cl->proto->lineinfo;
be_callhook(vm, BE_HOOK_CALL);
#endif
}
static void ret_native(bvm *vm)
{
bcallframe *_cf = vm->cf;
vm->reg = _cf->reg;
vm->top = _cf->top;
be_stack_pop(&vm->callstack);
vm->cf = be_stack_top(&vm->callstack);
}
static bbool obj2bool(bvm *vm, bvalue *var)
{
binstance *obj = var_toobj(var);
bstring *tobool = str_literal(vm, "tobool");
/* get operator method */
if (be_instance_member(vm, obj, tobool, vm->top)) {
vm->top[1] = *var; /* move self to argv[0] */
be_dofunc(vm, vm->top, 1); /* call method 'tobool' */
/* check the return value */
check_bool(vm, obj, "tobool");
return var_tobool(vm->top);
}
return btrue;
}
bbool be_value2bool(bvm *vm, bvalue *v)
{
switch (var_basetype(v)) {
case BE_NIL:
return bfalse;
case BE_BOOL:
return var_tobool(v);
case BE_INT:
return val2bool(v->v.i);
case BE_REAL:
return val2bool(v->v.r);
case BE_INSTANCE:
return obj2bool(vm, v);
default:
return btrue;
}
}
static void obj_method(bvm *vm, bvalue *o, bstring *attr, bvalue *dst)
{
binstance *obj = var_toobj(o);
int type = be_instance_member(vm, obj, attr, dst);
if (basetype(type) != BE_FUNCTION) {
vm_error(vm, "attribute_error",
"the '%s' object has no method '%s'",
str(be_instance_name(obj)), str(attr));
}
}
static int obj_attribute(bvm *vm, bvalue *o, bvalue *c, bvalue *dst)
{
bvalue instance = *o; /* save instance to send it later to member */
bstring *attr = var_tostr(c);
binstance *obj = var_toobj(o);
int type = be_instance_member(vm, obj, attr, dst);
if (basetype(type) == BE_NIL) { /* if no method found, try virtual */
/* get method 'member' */
int type2 = be_instance_member(vm, obj, str_literal(vm, "member"), vm->top);
if (basetype(type2) == BE_FUNCTION) {
bvalue *top = vm->top;
top[1] = instance; /* move instance to argv[0] */
top[2] = *c; /* move method name to argv[1] */
vm->top += 3; /* prevent collection results */
be_dofunc(vm, top, 2); /* call method 'member' */
vm->top -= 3;
*dst = *vm->top; /* copy result to R(A) */
type = var_type(dst);
}
}
if (basetype(type) == BE_NIL) {
vm_error(vm, "attribute_error",
"the '%s' object has no attribute '%s'",
str(be_instance_name(obj)), str(attr));
}
return type;
}
static bbool object_eqop(bvm *vm,
const char *op, bbool iseq, bvalue *a, bvalue *b)
{
binstance *o = var_toobj(a);
bvalue self = *a, other = *b;
bbool isself = var_isinstance(b) && o == var_toobj(b);
/* first, try to call the overloaded operator of the object */
int type = be_instance_member(vm, o, be_newstr(vm, op), vm->top);
if (basetype(type) == BE_FUNCTION) { /* call method */
bvalue *top = vm->top;
top[1] = self; /* move self to argv[0] */
top[2] = other; /* move other to argv[1] */
be_incrtop(vm); /* prevent collection results */
be_dofunc(vm, top, 2); /* call method 'item' */
be_stackpop(vm, 1);
check_bool(vm, o, op); /* check return value */
return var_tobool(vm->top); /* copy result to dst */
}
/* the default equal operation rule */
return iseq == isself; /* check object self */
}
static void object_binop(bvm *vm, const char *op, bvalue self, bvalue other)
{
bvalue *top = vm->top;
/* get operator method (possible GC) */
obj_method(vm, &self, be_newstr(vm, op), vm->top);
top[1] = self; /* move self to argv[0] */
top[2] = other; /* move other to argv[1] */
be_incrtop(vm); /* prevent collection results */
be_dofunc(vm, top, 2); /* call method 'item' */
be_stackpop(vm, 1);
}
#define ins_binop(vm, op, ins) { \
object_binop(vm, op, *RKB(), *RKC()); \
reg = vm->reg; \
*RA() = *vm->top; /* copy result to dst */ \
}
static void ins_unop(bvm *vm, const char *op, bvalue self)
{
bvalue *top = vm->top;
/* get operator method (possible GC) */
obj_method(vm, &self, be_newstr(vm, op), vm->top);
top[1] = self; /* move self to argv[0] */
be_dofunc(vm, top, 1); /* call method 'item' */
}
bbool be_vm_iseq(bvm *vm, bvalue *a, bvalue *b)
{
equal_rule(==, btrue);
}
bbool be_vm_isneq(bvm *vm, bvalue *a, bvalue *b)
{
equal_rule(!=, bfalse);
}
bbool be_vm_islt(bvm *vm, bvalue *a, bvalue *b)
{
relop_rule(<);
}
bbool be_vm_isle(bvm *vm, bvalue *a, bvalue *b)
{
relop_rule(<=);
}
bbool be_vm_isgt(bvm *vm, bvalue *a, bvalue *b)
{
relop_rule(>);
}
bbool be_vm_isge(bvm *vm, bvalue *a, bvalue *b)
{
relop_rule(>=);
}
static void make_range(bvm *vm, bvalue lower, bvalue upper)
{
/* get method 'item' (possible GC) */
int idx = be_builtin_find(vm, str_literal(vm, "range"));
bvalue *top = vm->top;
top[0] = *be_global_var(vm, idx);
top[1] = lower; /* move lower to argv[0] */
top[2] = upper; /* move upper to argv[1] */
vm->top += 3; /* prevent collection results */
be_dofunc(vm, top, 2); /* call method 'item' */
vm->top -= 3;
}
static void connect_str(bvm *vm, bstring *a, bvalue *b)
{
bstring *s;
if (var_isstr(b)) {
s = be_strcat(vm, a, var_tostr(b));
var_setstr(vm->top, s);
} else {
*vm->top++ = *b;
be_val2str(vm, -1);
b = vm->top - 1;
s = be_strcat(vm, a, var_tostr(b));
var_setstr(b, s);
vm->top -= 1;
}
}
BERRY_API bvm* be_vm_new(void)
{
bvm *vm = be_os_malloc(sizeof(bvm));
be_assert(vm != NULL);
memset(vm, 0, sizeof(bvm)); /* clear all members */
be_gc_init(vm);
be_string_init(vm);
be_stack_init(vm, &vm->callstack, sizeof(bcallframe));
be_stack_init(vm, &vm->refstack, sizeof(binstance*));
be_stack_init(vm, &vm->exceptstack, sizeof(struct bexecptframe));
be_stack_init(vm, &vm->tracestack, sizeof(bcallsnapshot));
vm->stack = be_malloc(vm, sizeof(bvalue) * BE_STACK_FREE_MIN);
vm->stacktop = vm->stack + BE_STACK_FREE_MIN;
vm->reg = vm->stack;
vm->top = vm->reg;
be_globalvar_init(vm);
be_gc_setpause(vm, 1);
be_loadlibs(vm);
#if BE_USE_OBSERVABILITY_HOOK
vm->obshook = NULL;
#endif
return vm;
}
BERRY_API void be_vm_delete(bvm *vm)
{
be_gc_deleteall(vm);
be_string_deleteall(vm);
be_stack_delete(vm, &vm->callstack);
be_stack_delete(vm, &vm->refstack);
be_stack_delete(vm, &vm->exceptstack);
be_stack_delete(vm, &vm->tracestack);
be_free(vm, vm->stack, (vm->stacktop - vm->stack) * sizeof(bvalue));
be_globalvar_deinit(vm);
#if BE_USE_DEBUG_HOOK
/* free native hook */
if (var_istype(&vm->hook, BE_COMPTR))
be_free(vm, var_toobj(&vm->hook), sizeof(struct bhookblock));
#endif
/* free VM structure */
be_os_free(vm);
}
static void vm_exec(bvm *vm)
{
bclosure *clos;
bvalue *ktab, *reg;
binstruction ins;
vm->cf->status |= BASE_FRAME;
newframe: /* a new call frame */
be_assert(var_isclosure(vm->cf->func));
clos = var_toobj(vm->cf->func);
ktab = clos->proto->ktab;
reg = vm->reg;
vm_exec_loop() {
opcase(LDNIL): {
var_setnil(RA());
dispatch();
}
opcase(LDBOOL): {
bvalue *v = RA();
var_setbool(v, IGET_RKB(ins));
if (IGET_RKC(ins)) { /* skip next instruction */
vm->ip += 1;
}
dispatch();
}
opcase(LDINT): {
bvalue *v = RA();
var_setint(v, IGET_sBx(ins));
dispatch();
}
opcase(LDCONST): {
bvalue *dst = RA();
*dst = ktab[IGET_Bx(ins)];
dispatch();
}
opcase(GETGBL): {
bvalue *v = RA();
int idx = IGET_Bx(ins);
*v = *be_global_var(vm, idx);
dispatch();
}
opcase(SETGBL): {
bvalue *v = RA();
int idx = IGET_Bx(ins);
*be_global_var(vm, idx) = *v;
dispatch();
}
opcase(GETUPV): {
bvalue *v = RA();
int idx = IGET_Bx(ins);
be_assert(*clos->upvals != NULL);
*v = *clos->upvals[idx]->value;
dispatch();
}
opcase(SETUPV): {
bvalue *v = RA();
int idx = IGET_Bx(ins);
be_assert(*clos->upvals != NULL);
*clos->upvals[idx]->value = *v;
dispatch();
}
opcase(MOVE): {
bvalue *dst = RA();
*dst = *RKB();
dispatch();
}
opcase(ADD): {
bvalue *dst = RA(), *a = RKB(), *b = RKC();
if (var_isint(a) && var_isint(b)) {
var_setint(dst, ibinop(+, a, b));
} else if (var_isnumber(a) && var_isnumber(b)) {
breal x = var2real(a), y = var2real(b);
var_setreal(dst, x + y);
} else if (var_isstr(a) && var_isstr(b)) { /* strcat */
bstring *s = be_strcat(vm, var_tostr(a), var_tostr(b));
reg = vm->reg;
dst = RA();
var_setstr(dst, s);
} else if (var_isinstance(a)) {
ins_binop(vm, "+", ins);
} else {
binop_error(vm, "+", a, b);
}
dispatch();
}
opcase(SUB): {
bvalue *dst = RA(), *a = RKB(), *b = RKC();
if (var_isint(a) && var_isint(b)) {
var_setint(dst, ibinop(-, a, b));
} else if (var_isnumber(a) && var_isnumber(b)) {
breal x = var2real(a), y = var2real(b);
var_setreal(dst, x - y);
} else if (var_isinstance(a)) {
ins_binop(vm, "-", ins);
} else {
binop_error(vm, "-", a, b);
}
dispatch();
}
opcase(MUL): {
bvalue *dst = RA(), *a = RKB(), *b = RKC();
if (var_isint(a) && var_isint(b)) {
var_setint(dst, ibinop(*, a, b));
} else if (var_isnumber(a) && var_isnumber(b)) {
breal x = var2real(a), y = var2real(b);
var_setreal(dst, x * y);
} else if (var_isinstance(a)) {
ins_binop(vm, "*", ins);
} else {
binop_error(vm, "*", a, b);
}
dispatch();
}
opcase(DIV): {
bvalue *dst = RA(), *a = RKB(), *b = RKC();
if (var_isint(a) && var_isint(b)) {
bint x = var_toint(a), y = var_toint(b);
if (y == 0) {
vm_error(vm, "divzero_error", "division by zero");
} else {
var_setint(dst, x / y);
}
} else if (var_isnumber(a) && var_isnumber(b)) {
breal x = var2real(a), y = var2real(b);
if (y == cast(breal, 0)) {
vm_error(vm, "divzero_error", "division by zero");
}
var_setreal(dst, x / y);
} else if (var_isinstance(a)) {
ins_binop(vm, "/", ins);
} else {
binop_error(vm, "/", a, b);
}
dispatch();
}
opcase(MOD): {
bvalue *dst = RA(), *a = RKB(), *b = RKC();
if (var_isint(a) && var_isint(b)) {
var_setint(dst, ibinop(%, a, b));
} else if (var_isnumber(a) && var_isnumber(b)) {
var_setreal(dst, mathfunc(fmod)(var_toreal(a), var_toreal(b)));
} else if (var_isinstance(a)) {
ins_binop(vm, "%", ins);
} else {
binop_error(vm, "%", a, b);
}
dispatch();
}
opcase(LT): {
bbool res = be_vm_islt(vm, RKB(), RKC());
bvalue *dst;
reg = vm->reg;
dst = RA();
var_setbool(dst, res);
dispatch();
}
opcase(LE): {
bbool res = be_vm_isle(vm, RKB(), RKC());
bvalue *dst;
reg = vm->reg;
dst = RA();
var_setbool(dst, res);
dispatch();
}
opcase(EQ): {
bbool res = be_vm_iseq(vm, RKB(), RKC());
bvalue *dst;
reg = vm->reg;
dst = RA();
var_setbool(dst, res);
dispatch();
}
opcase(NE): {
bbool res = be_vm_isneq(vm, RKB(), RKC());
bvalue *dst;
reg = vm->reg;
dst = RA();
var_setbool(dst, res);
dispatch();
}
opcase(GT): {
bbool res = be_vm_isgt(vm, RKB(), RKC());
bvalue *dst;
reg = vm->reg;
dst = RA();
var_setbool(dst, res);
dispatch();
}
opcase(GE): {
bbool res = be_vm_isge(vm, RKB(), RKC());
bvalue *dst;
reg = vm->reg;
dst = RA();
var_setbool(dst, res);
dispatch();
}
opcase(CONNECT): {
bvalue *a = RKB(), *b = RKC();
if (var_isint(a) && var_isint(b)) {
make_range(vm, *RKB(), *RKC());
} else if (var_isstr(a)) {
connect_str(vm, var_tostr(a), b);
} else if (var_isinstance(a)) {
object_binop(vm, "..", *RKB(), *RKC());
} else {
binop_error(vm, "..", RKB(), RKC());
}
reg = vm->reg;
*RA() = *vm->top; /* copy result to R(A) */
dispatch();
}
opcase(AND): {
bitwise_block(&);
dispatch();
}
opcase(OR): {
bitwise_block(|);
dispatch();
}
opcase(XOR): {
bitwise_block(^);
dispatch();
}
opcase(SHL): {
bitwise_block(<<);
dispatch();
}
opcase(SHR): {
bitwise_block(>>);
dispatch();
}
opcase(NEG): {
bvalue *dst = RA(), *a = RKB();
if (var_isint(a)) {
var_setint(dst, -a->v.i);
} else if (var_isreal(a)) {
var_setreal(dst, -a->v.r);
} else if (var_isinstance(a)) {
ins_unop(vm, "-*", *RKB());
reg = vm->reg;
*RA() = *vm->top; /* copy result to dst */
} else {
unop_error(vm, "-", a);
}
dispatch();
}
opcase(FLIP): {
bvalue *dst = RA(), *a = RKB();
if (var_isint(a)) {
var_setint(dst, -a->v.i);
} else if (var_isinstance(a)) {
ins_unop(vm, "~", *RKB());
reg = vm->reg;
*RA() = *vm->top; /* copy result to dst */
} else {
unop_error(vm, "~", a);
}
dispatch();
}
opcase(JMP): {
vm->ip += IGET_sBx(ins);
dispatch();
}
opcase(JMPT): {
if (be_value2bool(vm, RA())) {
vm->ip += IGET_sBx(ins);
}
dispatch();
}
opcase(JMPF): {
if (!be_value2bool(vm, RA())) {
vm->ip += IGET_sBx(ins);
}
dispatch();
}
opcase(CLOSURE): {
bvalue *dst;
bproto *p = clos->proto->ptab[IGET_Bx(ins)];
bclosure *cl = be_newclosure(vm, p->nupvals);
cl->proto = p;
reg = vm->reg;
dst = RA();
var_setclosure(dst, cl);
be_initupvals(vm, cl);
dispatch();
}
opcase(CLASS): {
bclass *c = var_toobj(ktab + IGET_Bx(ins));
be_class_upvalue_init(vm, c);
dispatch();
}
opcase(GETMBR): {
bvalue *a = RA(), *b = RKB(), *c = RKC();
if (var_isinstance(b) && var_isstr(c)) {
obj_attribute(vm, b, c, a);
reg = vm->reg;
} else if (var_ismodule(b) && var_isstr(c)) {
bstring *attr = var_tostr(c);
bmodule *module = var_toobj(b);
bvalue *v = be_module_attr(vm, module, attr);
if (v) {
*a = *v;
} else {
bvalue *member = be_module_attr(vm, module, str_literal(vm, "member"));
var_setnil(a);
if (member && var_basetype(member) == BE_FUNCTION) {
bvalue *top = vm->top;
top[0] = *member;
top[1] = *c; /* move name to argv[0] */
vm->top += 2; /* prevent collection results */
be_dofunc(vm, top, 1); /* call method 'method' */
vm->top -= 2;
*a = *vm->top; /* copy result to R(A) */
}
if (var_basetype(a) == BE_NIL) {
vm_error(vm, "attribute_error",
"module '%s' has no attribute '%s'",
be_module_name(module), str(attr));
}
}
} else {
attribute_error(vm, "attribute", b, c);
}
dispatch();
}
opcase(GETMET): {
bvalue *a = RA(), *b = RKB(), *c = RKC();
if (var_isinstance(b) && var_isstr(c)) {
bvalue self = *b;
bstring *attr = var_tostr(c);
binstance *obj = var_toobj(b);
int type = obj_attribute(vm, b, c, a);
reg = vm->reg;
if (basetype(type) == BE_FUNCTION) {
/* check if the object is a superinstance, if so get the lowest possible subclass */
while (obj->sub) {
obj = obj->sub;
}
var_setobj(&self, var_type(&self), obj); /* replace superinstance by lowest subinstance */
a[1] = self;
} else {
vm_error(vm, "attribute_error",
"class '%s' has no method '%s'",
str(be_instance_name(obj)), str(attr));
}
} else if (var_ismodule(b) && var_isstr(c)) {
bstring *attr = var_tostr(c);
bmodule *module = var_toobj(b);
bvalue *src = be_module_attr(vm, module, attr);
if (src) {
var_settype(a, NOT_METHOD);
a[1] = *src;
} else {
bvalue *member = be_module_attr(vm, module, str_literal(vm, "member"));
var_setnil(a);
if (member && var_basetype(member) == BE_FUNCTION) {
bvalue *top = vm->top;
top[0] = *member;
top[1] = *c; /* move name to argv[0] */
vm->top += 2; /* prevent collection results */
be_dofunc(vm, top, 1); /* call method 'method' */
vm->top -= 2;
var_settype(a, NOT_METHOD);
a[1] = *vm->top; /* copy result to R(A) */
}
if (var_basetype(a) == BE_NIL) {
vm_error(vm, "attribute_error",
"module '%s' has no method '%s'",
be_module_name(module), str(attr));
}
}
} else {
attribute_error(vm, "method", b, c);
}
dispatch();
}
opcase(SETMBR): {
bvalue *a = RA(), *b = RKB(), *c = RKC();
if (var_isinstance(a) && var_isstr(b)) {
binstance *obj = var_toobj(a);
bstring *attr = var_tostr(b);
if (!be_instance_setmember(vm, obj, attr, c)) {
vm_error(vm, "attribute_error",
"class '%s' cannot assign to attribute '%s'",
str(be_instance_name(obj)), str(attr));
}
dispatch();
}
if (var_ismodule(a) && var_isstr(b)) {
bmodule *obj = var_toobj(a);
bstring *attr = var_tostr(b);
bvalue tmp = *c; /* stack may change */
bvalue *v = be_module_bind(vm, obj, attr);
if (v != NULL) {
*v = tmp;
dispatch();
}
/* if it failed, try 'setmeemner' */
bvalue *member = be_module_attr(vm, obj, str_literal(vm, "setmember"));
if (member && var_basetype(member) == BE_FUNCTION) {
bvalue *top = vm->top;
top[0] = *member;
top[1] = *b; /* move name to argv[0] */
top[2] = tmp; /* move value to argv[1] */
vm->top += 3; /* prevent collection results */
be_dofunc(vm, top, 2); /* call method 'setmember' */
vm->top -= 3;
dispatch();
}
}
attribute_error(vm, "writable attribute", a, b);
dispatch();
}
opcase(GETIDX): {
bvalue *b = RKB(), *c = RKC();
if (var_isinstance(b)) {
bvalue *top = vm->top;
/* get method 'item' */
obj_method(vm, b, str_literal(vm, "item"), vm->top);
top[1] = *b; /* move object to argv[0] */
top[2] = *c; /* move key to argv[1] */
vm->top += 3; /* prevent collection results */
be_dofunc(vm, top, 2); /* call method 'item' */
vm->top -= 3;
reg = vm->reg;
*RA() = *vm->top; /* copy result to R(A) */
} else if (var_isstr(b)) {
bstring *s = be_strindex(vm, var_tostr(b), c);
reg = vm->reg;
var_setstr(RA(), s);
} else {
vm_error(vm, "type_error",
"value '%s' does not support subscriptable",
be_vtype2str(b));
}
dispatch();
}
opcase(SETIDX): {
bvalue *a = RA(), *b = RKB(), *c = RKC();
if (var_isinstance(a)) {
bvalue *top = vm->top;
/* get method 'setitem' */
obj_method(vm, a, str_literal(vm, "setitem"), vm->top);
top[1] = *a; /* move object to argv[0] */
top[2] = *b; /* move key to argv[1] */
top[3] = *c; /* move src to argv[2] */
vm->top += 4;
be_dofunc(vm, top, 3); /* call method 'setitem' */
vm->top -= 4;
reg = vm->reg;
} else {
vm_error(vm, "type_error",
"value '%s' does not support index assignment",
be_vtype2str(a));
}
dispatch();
}
opcase(SETSUPER): {
bvalue *a = RA(), *b = RKB();
if (var_isclass(a) && var_isclass(b)) {
bclass *obj = var_toobj(a);
be_class_setsuper(obj, var_toobj(b));
} else {
vm_error(vm, "type_error",
"value '%s' does not support set super",
be_vtype2str(b));
}
dispatch();
}
opcase(CLOSE): {
be_upvals_close(vm, RA());
dispatch();
}
opcase(IMPORT): {
bvalue *b = RKB();
if (var_isstr(b)) {
bstring *name = var_tostr(b);
int res = be_module_load(vm, name);
reg = vm->reg;
switch (res) {
case BE_OK: /* find the module */
be_stackpop(vm, 1);
*RA() = *vm->top;
break;
case BE_EXCEPTION: /* pop the exception value and message */
be_pop(vm, 2);
be_throw(vm, BE_EXCEPTION);
break;
default:
vm_error(vm, "import_error", "module '%s' not found", str(name));
}
} else {
vm_error(vm, "type_error",
"import '%s' does not support import",
be_vtype2str(b));
}
dispatch();
}
opcase(CATCH): {
bvalue *base = RA(), *top = vm->top;
int i = 0, ecnt = IGET_RKB(ins), vcnt = IGET_RKC(ins);
while (i < ecnt && !be_vm_iseq(vm, top, base + i)) {
++i;
}
if (!ecnt || i < ecnt) { /* exception catched */
base = RA(), top = vm->top;
for (i = 0; i < vcnt; ++i) {
*base++ = *top++;
}
vm->ip += 1; /* skip next instruction */
}
dispatch();
}
opcase(RAISE): {
if (IGET_RA(ins) < 2) {
bvalue *top = vm->top;
top[0] = *RKB(); /* push the exception value to top */
if (IGET_RA(ins)) { /* has exception argument? */
top[1] = *RKC(); /* push the exception argument to top + 1 */
} else {
var_setnil(top + 1);
}
be_save_stacktrace(vm);
}
be_throw(vm, BE_EXCEPTION); /* throw / rethrow the exception */
dispatch();
}
opcase(EXBLK): {
if (!IGET_RA(ins)) {
be_except_block_setup(vm);
if (be_setjmp(vm->errjmp->b)) {
be_except_block_resume(vm);
goto newframe;
}
reg = vm->reg;
} else {
be_except_block_close(vm, IGET_Bx(ins));
}
dispatch();
}
opcase(CALL): {
bvalue *var = RA();
int mode = 0, argc = IGET_RKB(ins);
recall: /* goto: instantiation class and call constructor */
switch (var_type(var)) {
case NOT_METHOD:
var[0] = var[1];
++var, --argc, mode = 1;
goto recall;
case BE_CLASS:
if (be_class_newobj(vm, var_toobj(var), var, ++argc, mode)) {
reg = vm->reg + mode;
mode = 0;
var = RA() + 1; /* to next register */
goto recall; /* call constructor */
}
break;
case BE_INSTANCE: {
bvalue *v = var + argc++, temp;
/* load the '()' method to `temp' */
obj_method(vm, var, str_literal(vm, "()"), &temp);
for (; v >= var; --v) v[1] = v[0];
*var = temp;
goto recall; /* call '()' method */
}
case BE_CLOSURE: {
bvalue *v, *end;
bproto *proto = var2cl(var)->proto;
push_closure(vm, var, proto->nstack, mode);
reg = vm->reg;
v = reg + argc;
end = reg + proto->argc;
for (; v < end; ++v) {
var_setnil(v);
}
goto newframe;
}
case BE_NTVCLOS: {
bntvclos *f = var_toobj(var);
push_native(vm, var, argc, mode);
f->f(vm); /* call C primitive function */
ret_native(vm);
break;
}
case BE_NTVFUNC: {
bntvfunc f = var_tontvfunc(var);
push_native(vm, var, argc, mode);
f(vm); /* call C primitive function */
ret_native(vm);
break;
}
case BE_MODULE: {
bmodule *f = var_toobj(var);
bvalue *member = be_module_attr(vm, f, str_literal(vm, "()"));
if (member && var_basetype(member) == BE_FUNCTION) {
*var = *member;
goto recall; /* call '()' method */
} else {
call_error(vm, var);
}
break;
}
default:
call_error(vm, var);
}
reg = vm->reg;
dispatch();
}
opcase(RET): {
bcallframe *cf;
bvalue *ret;
#if BE_USE_DEBUG_HOOK
be_callhook(vm, BE_HOOK_RET);
#endif
cf = vm->cf;
ret = vm->cf->func;
/* copy return value */
if (IGET_RA(ins)) {
*ret = *RKB();
} else {
var_setnil(ret);
}
vm->reg = cf->reg;
vm->top = cf->top;
vm->ip = cf->ip;
be_stack_pop(&vm->callstack); /* pop don't delete */
if (cf->status & BASE_FRAME) { /* entrance function */
bstack *cs = &vm->callstack;
if (!be_stack_isempty(cs)) {
vm->cf = be_stack_top(cs);
}
return;
}
vm->cf = be_stack_top(&vm->callstack);
goto newframe;
}
}
}
static void do_closure(bvm *vm, bvalue *reg, int argc)
{
bvalue *v, *end;
bproto *proto = var2cl(reg)->proto;
push_closure(vm, reg, proto->nstack, 0);
v = vm->reg + argc;
end = vm->reg + proto->argc;
for (; v <= end; ++v) {
var_setnil(v);
}
vm_exec(vm);
}
static void do_ntvclos(bvm *vm, bvalue *reg, int argc)
{
bntvclos *f = var_toobj(reg);
push_native(vm, reg, argc, 0);
f->f(vm); /* call C primitive function */
ret_native(vm);
}
static void do_ntvfunc(bvm *vm, bvalue *reg, int argc)
{
bntvfunc f = var_tontvfunc(reg);
push_native(vm, reg, argc, 0);
f(vm); /* call C primitive function */
ret_native(vm);
}
static void do_class(bvm *vm, bvalue *reg, int argc)
{
if (be_class_newobj(vm, var_toobj(reg), reg, ++argc, 0)) {
be_incrtop(vm);
be_dofunc(vm, reg + 1, argc);
be_stackpop(vm, 1);
}
}
void be_dofunc(bvm *vm, bvalue *v, int argc)
{
be_assert(vm->reg <= v && v < vm->stacktop);
be_assert(vm->stack <= vm->reg && vm->reg < vm->stacktop);
switch (var_type(v)) {
case BE_CLASS: do_class(vm, v, argc); break;
case BE_CLOSURE: do_closure(vm, v, argc); break;
case BE_NTVCLOS: do_ntvclos(vm, v, argc); break;
case BE_NTVFUNC: do_ntvfunc(vm, v, argc); break;
default: call_error(vm, v);
}
}
BERRY_API void be_set_obs_hook(bvm *vm, bobshook hook)
{
(void)vm; /* avoid comiler warning */
(void)hook; /* avoid comiler warning */
#if BE_USE_OBSERVABILITY_HOOK
vm->obshook = hook;
#endif
}