Skip to content

Commit

Permalink
feat: add quickjs 20210307 support.
Browse files Browse the repository at this point in the history
  • Loading branch information
andycall committed Sep 9, 2021
1 parent 1c2cdf3 commit 6d60390
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 6 deletions.
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Apply the following rules to these file types
[*]
end_of_line = lf #Unix type line endings
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
max_line_length = 120
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,5 @@ dist
compile_commands.json

build/
.clangd
.clangd
.idea/
28 changes: 27 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
var bindings = require('bindings')('qjsc_20210307.node');
const supportedVersion = ['20210307'];

class Qjsc {
constructor(version = '20210307') {
if (supportedVersion.indexOf(version) === -1) {
throw new Error('Unsupported quickjs version: ' + version);
}
this._bindings = require('bindings')(`qjsc_${version}.node`);
}
help() {
console.log('supported version: ' + supportedVersion.join(', '));
}

dumpByteCode(code) {
return this._bindings.dumpByteCode(code);
}

version() {
return this._bindings.version();
}

evalByteCode(buffer) {
return this._bindings.evalByteCode(buffer);
}
}

module.exports = Qjsc;
104 changes: 104 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Node.js addon for the Quickjs compiler",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "./node_modules/.bin/jasmine test.js"
},
"repository": {
"type": "git",
Expand All @@ -20,5 +20,8 @@
"bindings": "^1.5.0",
"nan": "^2.15.0",
"node-addon-api": "^4.1.0"
},
"devDependencies": {
"jasmine": "^3.9.0"
}
}
109 changes: 106 additions & 3 deletions src/qjsc_20210307.cc
Original file line number Diff line number Diff line change
@@ -1,8 +1,108 @@
#include <napi.h>
#include <iostream>
#include "../deps/quickjs_2021_03_27/quickjs.h"

// ...
static void reportError(Napi::Env &env, JSContext *ctx, JSValue error) {
const char *title = JS_ToCString(ctx, error);
const char *stack = nullptr;
JSValue stackValue = JS_GetPropertyStr(ctx, error, "stack");
if (!JS_IsUndefined(stackValue)) {
stack = JS_ToCString(ctx, stackValue);
}

uint32_t messageLength = strlen(title) + 2;
if (stack != nullptr) {
messageLength += strlen(stack);
char message[messageLength];
sprintf(message, "%s\n%s", title, stack);
Napi::TypeError::New(env, message).ThrowAsJavaScriptException();
} else {
char message[messageLength];
sprintf(message, "%s", title);
Napi::TypeError::New(env, message).ThrowAsJavaScriptException();
}

};

/// Convert javascript source code to quickjs bytecode.
Napi::Value DumpByteCode(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();

if (info.Length() == 0) {
Napi::TypeError::New(env, "1 arguments required").ThrowAsJavaScriptException();
return env.Null();
}

if (!info[0].IsString()) {
Napi::TypeError::New(env, "1st arguments should be string.").ThrowAsJavaScriptException();
return env.Null();
}

JSRuntime *runtime = JS_NewRuntime();
JSContext *ctx = JS_NewContext(runtime);

std::string code = info[0].As<Napi::String>().Utf8Value();

JSValue object = JS_Eval(ctx, code.c_str(), code.size(), "internal://",
JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_COMPILE_ONLY);
if (JS_IsException(object)) {
JSValue error = JS_GetException(ctx);
reportError(env, ctx, error);
return env.Null();
}

size_t byteCodeLength;
uint8_t *bytes = JS_WriteObject(ctx, &byteCodeLength, object, JS_WRITE_OBJ_BYTECODE);
Napi::Buffer<uint8_t> buffer = Napi::Buffer<uint8_t>::New(env, bytes, byteCodeLength);

JS_FreeValue(ctx, object);
JS_FreeContext(ctx);
JS_FreeRuntime(runtime);
return buffer;
}

Napi::Value evalByteCode(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();

if (info.Length() == 0) {
Napi::TypeError::New(env, "1 arguments required").ThrowAsJavaScriptException();
return env.Null();
}

if (!info[0].IsBuffer()) {
Napi::TypeError::New(env, "1st arguments must be buffer").ThrowAsJavaScriptException();
return env.Null();
}

JSRuntime *runtime = JS_NewRuntime();
JSContext *ctx = JS_NewContext(runtime);

Napi::Buffer<uint8_t> buffer = info[0].As<Napi::Buffer<uint8_t>>();
uint8_t *bytes = buffer.Data();
size_t byteLength = buffer.ByteLength();

JSValue object;
object = JS_ReadObject(ctx, bytes, byteLength, JS_READ_OBJ_BYTECODE);
if (JS_IsException(object)) {
JSValue error = JS_GetException(ctx);
reportError(env, ctx, error);
return env.Null();
}

JSValue value = JS_EvalFunction(ctx, object);
if (JS_IsException(value)) {
JSValue error = JS_GetException(ctx);
reportError(env, ctx, error);
return env.Null();
}

const char* returnString = JS_ToCString(ctx, value);
JS_FreeValue(ctx, value);
Napi::String result = Napi::String::New(env, returnString);
JS_FreeCString(ctx, returnString);
JS_FreeContext(ctx);
JS_FreeRuntime(runtime);
return result;
}

/**
* This code is our entry-point. We receive two arguments here, the first is the
Expand All @@ -13,6 +113,9 @@
* the exports for the module when you return from the Init function.
*/
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "dumpByteCode"), Napi::Function::New(env, DumpByteCode));
exports.Set(Napi::String::New(env, "version"), Napi::String::New(env, "2021-03-07"));
exports.Set(Napi::String::New(env, "evalByteCode"), Napi::Function::New(env, evalByteCode));
return exports;
}

Expand All @@ -24,4 +127,4 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
* node-gyp (which is the usual way of building modules). The second argument
* points to the function to invoke. The function must not be namespaced.
*/
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
28 changes: 28 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const Qjsc = require('./index');

const qjsc = new Qjsc();

describe('qjsc', () => {
it('throw error when empty arguments', () => {
expect(() => qjsc.dumpByteCode()).toThrowError('1st arguments should be string.');
});

it('throw error when js syntax not correct', () => {
const code = `
function f() {
console.log(111;
`;
expect(() => qjsc.dumpByteCode(code)).toThrowError();
});

it('return bytecode binary', () => {
const code = `
function f() { return 1 + '1234'; }
f();
`;
let buffer = qjsc.dumpByteCode(code);
expect(qjsc.evalByteCode(buffer)).toBe('11234');
});
});

0 comments on commit 6d60390

Please sign in to comment.