JSX implementation

This commit is contained in:
Andrew 2020-10-27 13:27:28 -07:00
parent a60f3b0ac2
commit 43198ce9ec
10 changed files with 6117 additions and 1953 deletions

4
.gitignore vendored
View file

@ -4,8 +4,8 @@
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# Premake files
.bin/*
.build/*
.bin/
.build/
# User-specific files
*.suo

View file

@ -17,7 +17,11 @@ workspace "quickjs-msvc"
-- Premake output folder
location(path.join(".build", _ACTION))
defines {"JS_STRICT_NAN_BOXING"} -- this option enables x64 build
defines {
"JS_STRICT_NAN_BOXING", -- this option enables x64 build on Windows/MSVC
"CONFIG_BIGNUM",
"CONFIG_JSX", -- native JSX support - enables JSX literals
}
platforms { "x86", "x64", "arm32", "arm64" }
@ -71,6 +75,7 @@ project "quickjs"
"libunicode.c",
"quickjs.c",
"quickjs-libc.c",
"libbf.c",
"libregexp.h",
"libregexp-opcode.h",
"libunicode.h",
@ -79,7 +84,8 @@ project "quickjs"
"quickjs.h",
"quickjs-atom.h",
"quickjs-libc.h",
"quickjs-opcode.h"
"quickjs-opcode.h",
"quickjs-jsx.h",
}
-----------------------------------------------------------------------------------------------------------------------
@ -102,8 +108,11 @@ project "qjs"
files {
"qjs.c",
"repl.js",
"repl.c"
"repl.c",
"qjscalc.js",
"qjscalc.c"
}
-- Compile repl.js and save bytecode into repl.c
prebuildcommands { "\"%{cfg.buildtarget.directory}/qjsc.exe\" -c -o \"../../repl.c\" -m \"../../repl.js\"" }
prebuildcommands { "\"%{cfg.buildtarget.directory}/qjsc.exe\" -c -o \"../../qjscalc.c\" -m \"../../qjscalc.js\"" }

3788
qjscalc.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -223,6 +223,9 @@ DEF(BigDecimal, "BigDecimal")
DEF(OperatorSet, "OperatorSet")
DEF(Operators, "Operators")
#endif
#ifdef CONFIG_JSX
DEF(JSX, "JSX")
#endif
DEF(Map, "Map")
DEF(Set, "Set") /* Map + 1 */
DEF(WeakMap, "WeakMap") /* Map + 2 */

224
quickjs-jsx.h Normal file
View file

@ -0,0 +1,224 @@
/* JSX translates XML literals like this
*
* var jsx = <div foo="bar">some text</div>;
*
* into calls of "JSX driver function":
*
* __jsx__("div", {foo:"bar"}, ["some text"]);
*
* note:
* a) the call always have 3 arguments: string, object|null, array|null
* b) __jsx__ can be redefined, e.g. for https://mithril.js.org it will be just
*
* __jsx__ = m; // using mithril as JSX driver
*/
static __exception int next_web_token(JSParseState *s) {
s->allow_web_name_token = 1;
int r = next_token(s);
s->allow_web_name_token = 0;
return r;
}
static int js_parse_jsx_expr(JSParseState *s, int level)
{
int atts_count = 0;
int kids_count = 0;
// NOTE: caller already consumed '<'
if (next_web_token(s)) goto fail;
if (s->token.val != TOK_IDENT)
return js_parse_error(s, "Expecting tag name");
//tag
JSAtom tag_atom = s->token.u.ident.atom;
JSValue tag = JS_AtomToString(s->ctx,tag_atom);
// load JSX function - driver of JSX expressions:
#if 1 // load it as a global function
emit_op(s, OP_get_var);
emit_atom(s, JS_ATOM_JSX);
#else // load it as a local/scope function - do we need that?
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_JSX);
emit_u16(s, s->cur_func->scope_level);
#endif
// #0 #1 #2
// JSX(tag, atts ,kids); where
// - atts - object {...}, can be empty
// - kids - array [...], can be empty
emit_push_const(s, tag, 0);
JS_FreeValue(s->ctx, tag);
// parse attributes
JSAtom attr_name = JS_ATOM_NULL;
JSValue attr_value = JS_UNINITIALIZED;
#if defined(CONFIG_JSX_SCITER) // HTML shortcuts used by Sciter
char class_buffer[512] = {0};
#endif
if (next_web_token(s)) goto fail;
emit_op(s, OP_object);
while (s->token.val != '>') {
if (s->token.val == '/') {
next_token(s);
//json_parse_expect(s, '>');
if(s->token.val != '>')
js_parse_error(s, "expecting '>'");
goto GENERATE_KIDS;
}
#if defined(CONFIG_JSX_SCITER) // HTML shortcuts used by Sciter
if (s->token.val == '#') { // <div #some> -> <div id="some">
if (next_web_token(s)) goto fail;
if (s->token.val != TOK_IDENT) { js_parse_error(s, "expecting identifier"); goto fail; }
attr_name = JS_NewAtom(s->ctx,"id");
attr_value = JS_AtomToString(s->ctx, s->token.u.ident.atom);
goto PUSH_ATTR_VALUE;
}
if (s->token.val == '|') { // <input|text> -> <input type="text">
if (next_web_token(s)) goto fail;
if (s->token.val != TOK_IDENT) { js_parse_error(s, "expecting identifier"); goto fail; }
attr_name = JS_NewAtom(s->ctx, "type");
attr_value = JS_AtomToString(s->ctx, s->token.u.ident.atom);
goto PUSH_ATTR_VALUE;
}
if (s->token.val == '(') { // <input(foo)> -> <input name="foo">
if (next_web_token(s)) goto fail;
if (s->token.val != TOK_IDENT) { js_parse_error(s, "expecting identifier"); goto fail; }
attr_name = JS_NewAtom(s->ctx, "name");
attr_value = JS_AtomToString(s->ctx, s->token.u.ident.atom);
if (next_token(s)) goto fail;
if (s->token.val != ')') { js_parse_error(s, "expecting ')'"); goto fail; }
goto PUSH_ATTR_VALUE;
}
if (s->token.val == '.') { // <div.some> -> <div class="some">
if (next_web_token(s)) goto fail;
if (s->token.val != TOK_IDENT) { js_parse_error(s, "expecting identifier"); goto fail; }
char cls1[256];
const char *name = JS_AtomGetStr(s->ctx, cls1, countof(cls1), s->token.u.ident.atom);
if (strlen(class_buffer) + strlen(name) + 2 < countof(class_buffer)) {
if(class_buffer[0]) strcat(class_buffer, " ");
strcat(class_buffer, name);
}
next_web_token(s);
continue;
}
#endif
if (token_is_ident(s->token.val)) {
/* keywords and reserved words have a valid atom */
attr_name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
if (next_token(s))
goto fail;
}
if (js_parse_expect(s, '='))
goto fail;
if (s->token.val == TOK_STRING) {
attr_value = JS_DupValue(s->ctx,s->token.u.str.str);
PUSH_ATTR_VALUE:
if (emit_push_const(s, attr_value, 0))
goto fail;
JS_FreeValue(s->ctx, attr_value);
}
else if (s->token.val == '{')
{
if (next_token(s))
goto fail;
if (js_parse_assign_expr(s, TRUE))
goto fail;
if(s->token.val != '}')
return js_parse_error(s, "expecting '}'");
}
set_object_name(s, attr_name);
emit_op(s, OP_define_field);
emit_atom(s, attr_name);
JS_FreeAtom(s->ctx, attr_name);
if (next_web_token(s))
return -1;
}
#if defined(CONFIG_JSX_SCITER) // HTML shortcuts used by Sciter
if (class_buffer[0]) { // add remaining classes
attr_value = JS_NewString(s->ctx, class_buffer);
emit_push_const(s, JS_NewString(s->ctx, class_buffer), 0);
JS_FreeValue(s->ctx, attr_value);
attr_name = JS_NewAtom(s->ctx, "class");
set_object_name(s, attr_name);
emit_op(s, OP_define_field);
emit_atom(s, attr_name);
JS_FreeAtom(s->ctx, attr_name);
}
#endif
// parse content of the element
for (;;) {
const uint8_t *p;
p = s->last_ptr = s->buf_ptr;
s->last_line_num = s->token.line_num;
if (js_parse_string(s, '<', TRUE, p, &s->token, &p))
goto fail;
if (s->buf_ptr != p) {
s->buf_ptr = p;
if (emit_push_const(s, s->token.u.str.str, 1))
goto fail;
++kids_count;
}
next_token(s);
if (s->token.val == '<') {
if (*s->buf_ptr == '/') {
next_token(s); // skip '/'
next_web_token(s); // get tail tag name
if (token_is_ident(s->token.val)) { /* keywords and reserved words have a valid atom */
if(s->token.u.ident.atom != tag_atom)
return js_parse_error(s, "head and tail tags do not match");
next_token(s);
if (s->token.val != '>')
return js_parse_error(s, "expecting '>' in tail tag");
break;
}
}
else {
js_parse_jsx_expr(s, level + 1);
++kids_count;
}
}
else if (s->token.val == '{') {
if (next_token(s))
goto fail;
if (js_parse_assign_expr(s, TRUE))
goto fail;
if(s->token.val != '}')
return js_parse_error(s, "expected '}'");
++kids_count;
}
}
GENERATE_KIDS:
emit_op(s, OP_array_from);
emit_u16(s, kids_count);
emit_op(s, OP_call);
emit_u16(s, 3);
if (level == 0)
next_token(s);
return 0;
fail:
JS_FreeValue(s->ctx, tag);
JS_FreeAtom(s->ctx, attr_name);
JS_FreeValue(s->ctx, attr_value);
return -1;
}

View file

@ -1 +1 @@
#define QUICKJS_VERSION "2020-09-06"
#define QUICKJS_VERSION "2020-09-06"

623
quickjs.c

File diff suppressed because it is too large Load diff

View file

@ -520,6 +520,8 @@ void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt);
JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len);
JSAtom JS_NewAtom(JSContext *ctx, const char *str);
JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n);
JSAtom JS_NewAtomLenRT(JSRuntime *rt, const char *str, int len);
const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size, JSAtom atom);
JSAtom JS_DupAtom(JSContext *ctx, JSAtom v);
void JS_FreeAtom(JSContext *ctx, JSAtom v);
void JS_FreeAtomRT(JSRuntime *rt, JSAtom v);
@ -535,6 +537,8 @@ typedef struct JSPropertyEnum {
JSAtom atom;
} JSPropertyEnum;
void js_free_prop_enum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len);
typedef struct JSPropertyDescriptor {
int flags;
JSValue value;
@ -897,6 +901,7 @@ int JS_DefinePropertyGetSet(JSContext *ctx, JSValueConst this_obj,
void JS_SetOpaque(JSValue obj, void *opaque);
void *JS_GetOpaque(JSValueConst obj, JSClassID class_id);
void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id);
JSClassID JS_GetClassID(JSValueConst obj, void** ppopaque);
/* 'buf' must be zero terminated i.e. buf[buf_len] = '\0'. */
JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len,
@ -999,6 +1004,9 @@ JSAtom JS_GetScriptOrModuleName(JSContext *ctx, int n_stack_levels);
JSModuleDef *JS_RunModule(JSContext *ctx, const char *basename,
const char *filename);
JSValue JS_GetModuleExportItemStr(JSContext *ctx, JSModuleDef *m, const char *name);
JSValue JS_GetModuleExportItem(JSContext *ctx, JSModuleDef *m, JSAtom atom);
/* C function definition */
typedef enum JSCFunctionEnum { /* XXX: should rename for namespace isolation */
JS_CFUNC_generic,

3340
repl.c

File diff suppressed because it is too large Load diff

63
tests/JSX/test-jsx.js Normal file
View file

@ -0,0 +1,63 @@
// "driver" of JSX expressions
JSX = function(tag,atts,kids) {
return [tag,atts,kids]; // just produce "vnode" tuple
}
function isObject(object) {
return object != null && typeof object === 'object';
}
function deepEqual(object1, object2) {
const keys1 = Object.keys(object1);
const keys2 = Object.keys(object2);
if (keys1.length !== keys2.length) {
return false;
}
for (const key of keys1) {
const val1 = object1[key];
const val2 = object2[key];
const areObjects = isObject(val1) && isObject(val2);
if (
areObjects && !deepEqual(val1, val2) ||
!areObjects && val1 !== val2
) {
return false;
}
}
return true;
}
function assert(v1,v2) {
if(!deepEqual(v1,v2)) {
//console.log(JSON.stringify(v1,v2));
throw "problem in JSX construct"
}
}
var t1 = <div>test</div>;
var t1a = ["div",{},["test"]];
assert(t1,t1a);
var t2 = <h1 id="foo">test</h1>;
var t2a = ["h1",{id:"foo"},["test"]];
assert(t2,t2a);
var t3 = <div><h1/></div>;
var t3a = ["div",{},[["h1",{},[]]]];
assert(t3,t3a);
var t4 = <div id="foo" class="bar"><h1>header</h1><button>clicks</button></div>;
var t4a = ["div",{id:"foo",class:"bar"},[["h1",{},["header"]],["button",{},["clicks"]]]];
assert(t3,t3a);
console.log("JSX test passed!");