
经过一番修整,bajdcc/clibjs逐渐支持了很多功能。
实现了虚拟机层面的to_primitive,以及调用API——call_api ,这使得可以在代码层面运行js函数。
要支持setTimeout,就需要全局保存回调和参数,这里有优先队列的内容,不过我使用了map来实现。
像是new/delete等就不说了,还有Rest特性也支持了,难点是语法层面支持。最主要的是通过了所有binop二元运算测试(严格来说,quickjs的结果也不是很完美)。
二元运算测试在https://github.com/bajdcc/clibjs/blob/master/test/test_all.js,结果在https://github.com/bajdcc/clibjs/blob/master/test/test_all_output.txt,过程是用nodejs跑的,在精度方面有不同。
"5" **(1/3) -> 1.709975946676697 "5" **(1/3) -> 1.7099759466766968 这段代码,左边为nodejs输出,右边为chrome输出,其他没毛病。
闭包的保存需要保存变量名以及变量所在ctx,这样在store_deref 时可以找到该变量在哪个object中,再进行修改。
二元运算
原先我是一个个switch判断的,代码量很大,硬编码,很不爽,直到:
js_value::ref cjsruntime::binop(int code, js_value::ref _op1, js_value::ref _op2) {
auto conv = js_value::conv_number;
switch (code) {
case COMPARE_EQUAL:
case COMPARE_FEQUAL:
case COMPARE_NOT_EQUAL:
case COMPARE_FNOT_EQUAL: {
if (!_op1->is_primitive() && !_op2->is_primitive()) {
switch (code) {
case COMPARE_EQUAL:
return new_boolean(_op1 == _op2);
case COMPARE_NOT_EQUAL:
return new_boolean(_op1 != _op2);
case COMPARE_FEQUAL:
return new_boolean(_op1 == _op2);
case COMPARE_FNOT_EQUAL:
return new_boolean(_op1 != _op2);
default:
break;
}
}
}
break;
default:
break;
}
auto op1 = _op1->to_primitive(*this, js_value::conv_default);
assert(op1);
auto op2 = _op2->to_primitive(*this, js_value::conv_default);
assert(op2);
if (code == BINARY_ADD) {
if (op1->get_type() == r_string || op2->get_type() == r_string)
conv = js_value::conv_string;
} else {
switch (code) {
case COMPARE_LESS:
case COMPARE_LESS_EQUAL:
case COMPARE_GREATER:
case COMPARE_GREATER_EQUAL:
if (op1->get_type() == r_string && op2->get_type() == r_string)
conv = js_value::conv_string;
break;
default:
break;
}
}
if (conv == js_value::conv_string) {
auto s1 = op1->to_string(this, op1->get_type() == r_number ? 1 : 0);
auto s2 = op2->to_string(this, op2->get_type() == r_number ? 1 : 0);
switch (code) {
case COMPARE_LESS:
return new_boolean(s1 < s2);
case COMPARE_LESS_EQUAL:
return new_boolean(s1 <= s2);
case COMPARE_GREATER:
return new_boolean(s1 > s2);
case COMPARE_GREATER_EQUAL:
return new_boolean(s1 >= s2);
case BINARY_ADD:
return new_string(s1 + s2);
default:
assert(!"invalid binop type");
break;
}
} else {
double s1, s2;
switch (code) {
case COMPARE_LESS:
s1 = op1->to_number(this);
s2 = op2->to_number(this);
return new_boolean(s1 < s2);
case COMPARE_LESS_EQUAL:
s1 = op1->to_number(this);
s2 = op2->to_number(this);
return new_boolean(s1 <= s2);
case COMPARE_GREATER:
s1 = op1->to_number(this);
s2 = op2->to_number(this);
return new_boolean(s1 > s2);
case COMPARE_GREATER_EQUAL:
s1 = op1->to_number(this);
s2 = op2->to_number(this);
return new_boolean(s1 >= s2);
case COMPARE_EQUAL: {
if (op1->get_type() != op2->get_type()) {
if (op1->get_type() == r_null) {
if (op2->get_type() == r_undefined)
return new_boolean(true);
return new_boolean(false);
}
if (op2->get_type() == r_null) {
if (op1->get_type() == r_undefined)
return new_boolean(true);
return new_boolean(false);
}
return new_boolean(op1->to_number(this) == op2->to_number(this));
}
switch (op1->get_type()) {
case r_string:
return new_boolean(JS_STR(op1) == JS_STR(op2));
case r_number:
return new_boolean(JS_NUM(op1) == JS_NUM(op2));
default:
return new_boolean(op1 == op2);
}
}
case COMPARE_NOT_EQUAL: {
if (op1->get_type() != op2->get_type()) {
if (op1->get_type() == r_null) {
if (op2->get_type() == r_undefined)
return new_boolean(false);
return new_boolean(true);
}
if (op2->get_type() == r_null) {
if (op1->get_type() == r_undefined)
return new_boolean(false);
return new_boolean(true);
}
return new_boolean(op1->to_number(this) != op2->to_number(this));
}
switch (op1->get_type()) {
case r_string:
return new_boolean(JS_STR(op1) != JS_STR(op2));
case r_number:
return new_boolean(JS_NUM(op1) != JS_NUM(op2));
default:
return new_boolean(op1 != op2);
}
}
case COMPARE_FEQUAL: {
if (_op1->get_type() != _op2->get_type()) {
return new_boolean(false);
}
switch (op1->get_type()) {
case r_string:
return new_boolean(JS_STR(op1) == JS_STR(op2));
case r_number:
return new_boolean(JS_NUM(op1) == JS_NUM(op2));
default:
return new_boolean(op1 == op2);
}
}
case COMPARE_FNOT_EQUAL: {
if (_op1->get_type() != _op2->get_type()) {
return new_boolean(true);
}
switch (op1->get_type()) {
case r_string:
return new_boolean(JS_STR(op1) != JS_STR(op2));
case r_number:
return new_boolean(JS_NUM(op1) != JS_NUM(op2));
default:
return new_boolean(op1 != op2);
}
}
case BINARY_POWER:
s1 = op1->to_number(this);
s2 = op2->to_number(this);
if (s2 == 0)
return new_number(1.0);
if (s2 == 0)
return new_number(s1);
if ((s1 == 1.0 || s1 == -1.0) && std::isinf(s2))
return new_number(NAN);
return new_number(pow(s1, s2));
case BINARY_MULTIPLY:
s1 = op1->to_number(this);
s2 = op2->to_number(this);
return new_number(s1 * s2);
case BINARY_MODULO:
s1 = op1->to_number(this);
s2 = op2->to_number(this);
if (std::isinf(s1) || s2 == 0)
return new_number(NAN);
if (std::isinf(s2))
return new_number(s1);
if (s1 == 0)
return new_number(std::isnan(s2) ? NAN : s1);
return new_number(fmod(s1, s2));
case BINARY_ADD:
s1 = op1->to_number(this);
s2 = op2->to_number(this);
return new_number(s1 + s2);
case BINARY_SUBTRACT:
s1 = op1->to_number(this);
s2 = op2->to_number(this);
return new_number(s1 - s2);
case BINARY_FLOOR_DIVIDE:
s1 = op1->to_number(this);
s2 = op2->to_number(this);
return new_number(s1 / s2);
case BINARY_TRUE_DIVIDE:
s1 = op1->to_number(this);
s2 = op2->to_number(this);
return new_number(s1 / s2);
case BINARY_LSHIFT: {
s1 = op1->to_number(this);
s2 = op2->to_number(this);
if (s2 == 0.0)
return new_number(fix(s1) == 0.0 ? 0.0 : fix(s1));
auto a = int(fix(s1));
auto b = fix(s2);
auto c = b > 0 ? (uint32_t(b) % 32) : uint32_t(int(fmod(b, 32)) + 32);
return new_number(double(int(a << c)));
}
case BINARY_RSHIFT: {
s1 = op1->to_number(this);
s2 = op2->to_number(this);
if (s2 == 0.0)
return new_number(fix(s1) == 0.0 ? 0.0 : fix(s1));
auto a = int(fix(s1));
auto b = fix(s2);
auto c = b > 0 ? (uint32_t(b) % 32) : uint32_t(int(fmod(b, 32)) + 32);
return new_number(double(int(a >> c)));
}
case BINARY_URSHIFT: {
s1 = op1->to_number(this);
s2 = op2->to_number(this);
if (s2 == 0.0)
return new_number(fix(s1) == 0.0 ? 0.0 : uint32_t(fix(s1)));
auto a = uint32_t(fix(s1));
auto b = fix(s2);
auto c = b > 0 ? (uint32_t(b) % 32) : uint32_t(int(fmod(b, 32)) + 32);
return new_number(double(uint32_t(a >> c)));
}
case BINARY_AND: {
s1 = op1->to_number(this);
s2 = op2->to_number(this);
auto a = uint32_t(fix(s1));
auto b = uint32_t(fix(s2));
return new_number(double(int(a & b)));
}
case BINARY_XOR: {
s1 = op1->to_number(this);
s2 = op2->to_number(this);
auto a = uint32_t(fix(s1));
auto b = uint32_t(fix(s2));
return new_number(double(int(a ^ b)));
}
case BINARY_OR: {
s1 = op1->to_number(this);
s2 = op2->to_number(this);
auto a = uint32_t(fix(s1));
auto b = uint32_t(fix(s2));
return new_number(double(int(a | b)));
}
default:
assert(!"invalid binop type");
break;
}
}
}
节省了许多代码,目前的各文件大小:

还是比较袖珍的。
转换成原始值
由于原始值返回自身,因此省略。
js_value::ref jsv_object::to_primitive(js_value_new &n, js_value::primitive_t t) {
if (t == conv_default)
t = conv_number;
switch (t) {
case conv_number: {
auto value = get("valueOf");
if (value && value->get_type() == r_function) {
std::vector<js_value::weak_ref> args;
js_value::weak_ref _this = std::const_pointer_cast<js_value>(shared_from_this());
auto ret = n.fast_api(JS_FUN(value), _this, args, 0);
if (ret->is_primitive())
return ret;
}
value = get("toString");
if (value && value->get_type() == r_function) {
std::vector<js_value::weak_ref> args;
js_value::weak_ref _this = std::const_pointer_cast<js_value>(shared_from_this());
auto ret = n.fast_api(JS_FUN(value), _this, args, 0);
if (ret->is_primitive())
return ret;
}
}
break;
case conv_string: {
auto value = get("toString");
if (value && value->get_type() == r_function) {
std::vector<js_value::weak_ref> args;
js_value::weak_ref _this = std::const_pointer_cast<js_value>(shared_from_this());
auto ret = n.fast_api(JS_FUN(value), _this, args, 0);
if (ret->is_primitive())
return ret;
}
value = get("valueOf");
if (value && value->get_type() == r_function) {
std::vector<js_value::weak_ref> args;
js_value::weak_ref _this = std::const_pointer_cast<js_value>(shared_from_this());
auto ret = n.fast_api(JS_FUN(value), _this, args, 0);
if (ret->is_primitive())
return ret;
}
}
break;
default:
break;
}
return nullptr;
}
fast_api很重要:
js_value::ref cjsruntime::fast_api(const jsv_function::ref &func, js_value::weak_ref &_this,
std::vector<js_value::weak_ref> &args, uint32_t attr) {
call_api(func, _this, args, attr);
return pop().lock();
}
int cjsruntime::call_api(const jsv_function::ref &func, js_value::weak_ref &_this,
std::vector<js_value::weak_ref> &args, uint32_t attr) {
auto stack_size = stack.size();
if (func->builtin) {
func->builtin(current_stack, _this, args, *this, 0);
} else {
stack.push_back(current_stack = new_stack(func->code));
auto env = current_stack->envs.lock();
current_stack->_this = _this;
current_stack->name = func->name;
if (!func->code->arrow && func->code->simpleName.front() != '<')
env->obj[func->code->simpleName] = func;
auto arg = new_object();
env->obj["arguments"] = arg;
size_t i = 0;
auto args_num = func->code->args_num;
auto n = args.size();
for (; i < n; i++) {
std::stringstream ss;
ss << i;
arg->obj[ss.str()] = args.at(i);
if (i < args_num)
env->obj[func->code->args.at(i)] = args.at(i);
}
for (; i < args_num; i++) {
env->obj[func->code->args.at(i)] = new_undefined();
}
if (func->code->rest) {
auto rest = new_array();
env->obj[func->code->args.at(args_num)] = rest;
auto j = 0;
for (i = args_num; i < n; i++) {
std::stringstream ss;
ss << j++;
rest->obj[ss.str()] = args.at(i);
}
rest->obj["length"] = new_number(j);
}
arg->obj["length"] = new_number(n);
if (func->closure.lock())
current_stack->closure = func->closure;
}
auto r = 0;
auto gc_period = 0;
while (stack.size() > stack_size) {
const auto &codes = current_stack->info->codes;
const auto &pc = current_stack->pc;
auto code = current_stack->info;
while (true) {
if (pc >= (int) codes.size()) {
r = 4;
break;
}
const auto &c = codes.at(pc);
if (pc + 1 == (int) codes.size() && c.code == POP_TOP) {
r = 2;
break;
}
r = run(c);
if (gc_period++ >= GC_PERIOD) {
gc_period = 0;
gc();
}
if (r != 0)
break;
}
if (r == 1) {
current_stack = stack.back();
continue;
}
if (r == 2) {
if (!current_stack->ret_value.lock())
current_stack->ret_value =
current_stack->stack.empty() ? new_undefined() : pop();
}
if (r == 3) {
continue;
}
if (r == 4) {
current_stack->ret_value = new_undefined();
}
if (stack.size() > 1) {
auto ret = stack.back()->ret_value;
delete_stack(current_stack);
stack.pop_back();
current_stack = stack.back();
push(ret);
} else {
delete_stack(current_stack);
stack.pop_back();
}
}
return 0;
}
里面也嵌入了运行时,和run函数一样,这里代码有些重复了,目前不必优化。
测试用例
test_8
function A(a, b) {
this.a = a;
this.b = b;
this.c = function () {
console.log(console.trace());
return this.a + ' ' + this.b;
};
}
var d = new A('123', 12.3);
console.log(d.c());
var obj = {0: 'a', 1: 'b', length: 2};
console.log([].slice.call(obj, 0).slice(1));
console.log([1].concat(1, [2], 3));
console.log([1, 2, 3, 4].map(x => x + 1).filter(x => x % 2 === 0));
console.log([1, 2, 3, 4].reduce((a, b) => a + b));
console.log([1, 2, 3, 4].reduce((a, b) => a + b, 1));
console.log([...[1, 2], ...[3, 4]].fill(5));
console.log.bind(null, 1, 2, 3)();
console.log(Array.prototype.concat.bind(0, 1, 2, 3)());
for (var i in {a: 1, ...{b: 2}}) console.log(i);
for (var i in [1,2]) console.log(i);
结果
============ TEST #8 ============
5: (..testtest_8.js:4:5) A.prototype.c
4: (..testtest_8.js:1:1) <entry>
3: (..testtest.js:1:1) test
2: (..testtest.js:1:1) <entry>
1: (<starter>) <entry>
123 12.3
[b]
[1, 1, 2, 3]
[2, 4]
10
11
[5, 5, 5, 5]
1 2 3
[0, 1, 2, 3]
a
b
0
1
test_9
(function () {
var i = 0;
console.log(i);
setTimeout(function a() {
if (++i > 4) return;
console.log(i);
setTimeout(a, 1000);
});
})();
结果(数字是延迟输出的,利用了sleep函数)
============ TEST #9 ============
0
undefined
1
2
3
4
库
目前多数builtin函数是js实现的,暂不考虑性能问题。因为c++去写库太繁琐了。
Function.prototype.bind = function (context, ...args) {
var _this = this;
return function () {
return _this.apply(context, args);
};
};
Array.prototype.push = function (...args) {
var len = this.length || 0;
for (var i in args) {
if (args.hasOwnProperty(i))
this[len++] = args[i];
}
this.length = len;
return this.length;
};
Array.prototype.slice = function (n) {
n = n || 0;
var len = this.length || 0;
if (len <= n)
return Array();
var arr = Array();
var j = 0;
for (var i = n; i < len; i++) {
if (this.hasOwnProperty(i))
arr[j++] = this[i];
}
arr.length = j;
return arr;
};
Array.prototype.concat = function (...args) {
var _this = this;
var unbox = (arr, o) => {
if (o === null || typeof o === "undefined")
return;
switch (typeof o) {
case "number":
return arr.push(Number(o));
case "string":
return arr.push(String(o));
case "boolean":
return arr.push(Boolean(o));
case "function":
return arr.push(Function(o));
case "object":
if (o instanceof Array)
return arr.push(...o);
return arr.push(o);
default:
break;
}
};
var unbox2 = (arr, o) => {
if (o instanceof Array)
return arr.push(...o);
return arr.push(o);
};
var arr = Array();
unbox(arr, _this);
for (var i in args) {
if (args.hasOwnProperty(i))
unbox2(arr, args[i]);
}
return arr;
};
Array.prototype.map = function (f) {
var arr = Array(this.length);
var _this = this instanceof Array ? this : [...this];
for (var i in _this) {
arr[i] = f(_this[i]);
}
return arr;
};
Array.prototype.filter = function (f) {
var arr = Array();
var _this = this instanceof Array ? this : [...this];
for (var i in _this) {
if (f(_this[i]))
arr.push(_this[i]);
}
return arr;
};
Array.prototype.reduce = function (f, init) {
var _this = this instanceof Array ? this : [...this];
if (!_this.length)
return init;
if (_this.length === 1)
return this[0];
var acc;
if (typeof init === 'undefined') {
var first = true;
for (var i in _this) {
if (first) {
first = false;
acc = _this[i];
continue;
}
acc = f(acc, _this[i]);
}
return acc;
} else {
acc = init;
for (var i in _this) {
acc = f(acc, _this[i]);
}
return acc;
}
};
Array.prototype.fill = function (init) {
var _this = this instanceof Array ? this : [...this];
if (!_this.length)
return _this;
var len = _this.length || 0;
for (var i = 0; i < len; i++) {
_this[i] = init;
}
return _this;
};
Array.prototype.join = function (s) {
var str = this.reduce((a, b) => a + s + b);
return typeof str !== "undefined" ? ("" + str) : "";
};
Array.prototype.toString = function (hint) {
if (!hint)
return "" + this.join(",");
return "[" + this.map(x => typeof x === "object" ? x.toString(hint) : x).join(", ") + "]";
};
return;
后续
目前完成了js的骨干功能,后面就是库的补充了。
214

被折叠的 条评论
为什么被折叠?



