js string转number_【虚拟机系列】JS虚拟机——实现setTimeout

5795639ac441874411afc711bf4f3506.png

经过一番修整,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;
        }
    }
}

节省了许多代码,目前的各文件大小:

56864b231e6b3b2b3e8d330a90890a89.png
项目文件大小

还是比较袖珍的。

转换成原始值

由于原始值返回自身,因此省略。

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的骨干功能,后面就是库的补充了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值