diff --git a/README.md b/README.md index c34f5a3..ca56e56 100644 --- a/README.md +++ b/README.md @@ -239,32 +239,24 @@ char *js_getstr(struct js *js, jsval_t val, size_t *len); // Get string Extract C values from JS values -### js\_checkargs() +### js\_chkargs() ```c -jsval_t js_checkargs(struct js *js, jsval_t *args, int nargs, const char *spec, ...); +bool js_chkargs(jsval_t *args, int nargs, const char *spec); ``` -A helper function that fetches JS arguments into C values, according to -`spec` type specification. Return `JS_ERR` on error, or `JS_UNDEF` on success. -Supported specifiers: -- `b` for `bool` -- `d` for `double` -- `i` for `char`, `short`, `int`, and corresponding unsigned variants -- `s` for `char *` -- `j` for `jsval_t` +A helper function that checks a validity of the arguments passed to a function. +A `spec` is a 0-terminated string where each character represents a type of +the expected argument: `b` for `bool`, `d` for number, `s` for string, `j` +for any other JS value. Usage example - a C function that implements a JS function `greater_than(number1, number2)`: ```c -jsval_t js_gt(struct js *js, jsval_t *args, int nargs) { - double a, b; - jsval_t res = js_checkargs(js, args, nargs, "dd", &a, &b); - if (js_type(res) == JS_UNDEF) { - res = a > b ? js_mktrue() : js_mkfalse(); - } - return res; +static jsval_t js_gt(struct js *js, jsval_t *args, int nargs) { + if (!js_chkargs(args, nargs, "dd")) return js_mkerr(js, "bad args!"); + return js_getnum(args[0]) > js_getnum(args[1]) ? js_mktrue() : js_mkfalse(); } ``` diff --git a/elk.c b/elk.c index 94dd348..9f629a8 100644 --- a/elk.c +++ b/elk.c @@ -21,10 +21,8 @@ #endif #include -#include #include #include -#include #include #include #include @@ -866,14 +864,20 @@ static jsval_t js_obj_literal(struct js *js) { if (is_err(obj)) return obj; js->consumed = 1; while (next(js) != TOK_RBRACE) { - EXPECT(TOK_IDENTIFIER, ); - size_t koff = js->toff, klen = js->tlen; + jsval_t key = 0; + if (js->tok == TOK_IDENTIFIER) { + if (exe) key = js_mkstr(js, js->code + js->toff, js->tlen); + } else if (js->tok == TOK_STRING) { + if (exe) key = js_str_literal(js); + } else { + return js_mkerr(js, "parse error"); + } + js->consumed = 1; EXPECT(TOK_COLON, ); jsval_t val = js_expr(js); if (exe) { // printf("XXXX [%s] scope: %lu\n", js_str(js, val), vdata(js->scope)); if (is_err(val)) return val; - jsval_t key = js_mkstr(js, js->code + koff, klen); if (is_err(key)) return key; jsval_t res = setprop(js, obj, key, resolveprop(js, val)); if (is_err(res)) return res; @@ -1369,27 +1373,15 @@ void js_stats(struct js *js, size_t *total, size_t *lwm, size_t *css) { } // clang-format on -jsval_t js_checkargs(struct js *js, jsval_t *args, int nargs, const char *spec, - ...) { - va_list ap; - int i = 0; - va_start(ap, spec); - for (i = 0; i < nargs; i++) { - uint8_t t = vtype(args[i]); - switch (spec[i]) { // clang-format off - case 'b': if (t != T_BOOL) goto fail; *(va_arg(ap, bool *)) = js_getbool(args[i]); break; - case 'd': if (t != T_NUM) goto fail; *(va_arg(ap, double *)) = js_getnum(args[i]); break; - case 'i': if (t != T_NUM) goto fail; *(va_arg(ap, int *)) = (int) js_getnum(args[i]); break; - case 's': if (t != T_STR) goto fail; *(va_arg(ap, char **)) = js_getstr(js, args[i], NULL); break; - case 'j': *(va_arg(ap, jsval_t *)) = args[i]; break; - default: goto fail; - } // clang-format on +bool js_chkargs(jsval_t *args, int nargs, const char *spec) { + int i = 0, ok = 1; + for (; ok && i < nargs && spec[i]; i++) { + uint8_t t = vtype(args[i]), c = (uint8_t) spec[i]; + ok = (c == 'b' && t == T_BOOL) || (c == 'd' && t == T_NUM) || + (c == 's' && t == T_STR) || (c == 'j'); } - va_end(ap); - if (spec[i] != '\0') return js_mkerr(js, "arg count"); - return js_mkundef(); -fail: - return js_mkerr(js, "arg %d", i); + if (spec[i] != '\0' || i != nargs) ok = 0; + return ok; } jsval_t js_eval(struct js *js, const char *buf, size_t len) { diff --git a/elk.h b/elk.h index edb7875..711634a 100644 --- a/elk.h +++ b/elk.h @@ -17,6 +17,7 @@ #define JS_VERSION "3.0.0" #pragma once +#include #include #include @@ -31,9 +32,9 @@ struct js *js_create(void *buf, size_t len); // Create JS instance jsval_t js_eval(struct js *, const char *, size_t); // Execute JS code jsval_t js_glob(struct js *); // Return global object const char *js_str(struct js *, jsval_t val); // Stringify JS value -jsval_t js_checkargs(struct js *, jsval_t *, int, const char *, ...); // Check -void js_setmaxcss(struct js *, size_t); // Set max C stack size -void js_setgct(struct js *, size_t); // Set GC trigger threshold +bool js_chkargs(jsval_t *, int, const char *); // Check args validity +void js_setmaxcss(struct js *, size_t); // Set max C stack size +void js_setgct(struct js *, size_t); // Set GC trigger threshold void js_stats(struct js *, size_t *total, size_t *min, size_t *cstacksize); void js_dump(struct js *); // Print debug info. Requires -DJS_DUMP diff --git a/examples/Esp32JS/JS.h b/examples/Esp32JS/JS.h index 1effbf9..23a6270 100644 --- a/examples/Esp32JS/JS.h +++ b/examples/Esp32JS/JS.h @@ -55,33 +55,26 @@ static void logstats(void) { // These functions below will be imported into the JS engine. // Note that they are inside the extern "C" section. static jsval_t gpio_write(struct js *js, jsval_t *args, int nargs) { - int pin, val; - jsval_t res = js_checkargs(js, args, nargs, "ii", &pin, &val); - if (js_type(res) == JS_UNDEF) { - MG_INFO(("gpio.write %d -> %d", pin, val)); - digitalWrite(pin, val); - } - return res; + if (!js_chkargs(args, nargs, "dd")) return js_mkerr(js, "bad args"); + int pin = js_getnum(args[0]), val = js_getnum(args[1]); + MG_INFO(("gpio.write %d -> %d", pin, val)); + digitalWrite(pin, val); + return js_mknull(); } static jsval_t gpio_read(struct js *js, jsval_t *args, int nargs) { - int pin; - jsval_t res = js_checkargs(js, args, nargs, "i", &pin); - if (js_type(res) == JS_UNDEF) { - MG_INFO(("gpio.read %d", pin)); - res = js_mknum(digitalRead(pin)); - } - return res; + if (!js_chkargs(args, nargs, "d")) return js_mkerr(js, "bad args"); + int pin = js_getnum(args[0]); + MG_INFO(("gpio.read %d", pin)); + return js_mknum(digitalRead(pin)); } static jsval_t gpio_mode(struct js *js, jsval_t *args, int nargs) { - int pin, mode; - jsval_t res = js_checkargs(js, args, nargs, "ii", &pin, &mode); - if (js_type(res) == JS_UNDEF) { - MG_INFO(("gpio.mode %d -> %d", pin, mode)); - pinMode(pin, mode); - } - return res; + if (!js_chkargs(args, nargs, "dd")) return js_mkerr(js, "bad args"); + int pin = js_getnum(args[0]), mode = js_getnum(args[1]); + MG_INFO(("gpio.mode %d -> %d", pin, mode)); + pinMode(pin, mode); + return js_mknull(); } void timer_cleanup(void *data) { @@ -105,26 +98,20 @@ static void js_timer_fn(void *userdata) { } static jsval_t mktimer(struct js *js, jsval_t *args, int nargs) { - int milliseconds = 0; - const char *funcname = NULL; - jsval_t res = js_checkargs(js, args, nargs, "is", &milliseconds, &funcname); - if (js_type(res) == JS_UNDEF) { - struct mg_timer *t = mg_timer_add(&s_mgr, milliseconds, MG_TIMER_REPEAT, - js_timer_fn, strdup(funcname)); - MG_INFO(("mktimer %lu, %d ms, fn %s", t->id, milliseconds, funcname)); - addresource(timer_cleanup, (void *) t->id); - res = js_mknum(t->id); - } - return res; + if (!js_chkargs(args, nargs, "ds")) return js_mkerr(js, "bad args"); + int milliseconds = js_getnum(args[0]); + const char *funcname = js_getstr(js, args[1], NULL); + struct mg_timer *t = mg_timer_add(&s_mgr, milliseconds, MG_TIMER_REPEAT, + js_timer_fn, strdup(funcname)); + MG_INFO(("mktimer %lu, %d ms, fn %s", t->id, milliseconds, funcname)); + addresource(timer_cleanup, (void *) t->id); + return js_mknum(t->id); } static jsval_t deltimer(struct js *js, jsval_t *args, int nargs) { - unsigned long id; - jsval_t res = js_checkargs(js, args, nargs, "i", &id); - if (js_type(res) == JS_UNDEF) { - delresource(timer_cleanup, (void *) id); - } - return res; + if (!js_chkargs(args, nargs, "d")) return js_mkerr(js, "bad args"); + delresource(timer_cleanup, (void *) (unsigned long) js_getnum(args[0])); + return js_mknull(); } static jsval_t js_log(struct js *js, jsval_t *args, int nargs) { diff --git a/test/unit_test.c b/test/unit_test.c index 507e2c7..cbbd743 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -126,6 +126,8 @@ static void test_basic(void) { assert(ev(js, "a = {b:2}", "{\"b\":2}")); assert(ev(js, "a", "{\"b\":2}")); assert(ev(js, "a.b", "2")); + assert(ev(js, "({a:3}).a", "3")); + assert(ev(js, "({\"a\":4})", "{\"a\":4}")); assert(ev(js, "a.b = {c:3}", "{\"c\":3}")); assert(ev(js, "a", "{\"b\":{\"c\":3}}")); assert(ev(js, "a.b.c", "3")); @@ -419,6 +421,12 @@ static void test_funcs(void) { assert(ev(js, "f() + 2;", "3")); assert(ev(js, "f=function (x){return x+1;}; f(1);", "2")); + + assert(ev(js, "f = function(x){return x;};", "function(x){return x;}")); + assert(ev(js, "f(2)", "2")); + assert(ev(js, "f({})", "{}")); + assert(ev(js, "f({a:5,b:3}).b", "3")); + assert(ev(js, "f({\"a\":5,\"b\":3}).b", "3")); } static void test_bool(void) { @@ -516,12 +524,8 @@ static jsval_t js_set_timer(struct js *js, jsval_t *args, int nargs) { } static jsval_t js_gt(struct js *js, jsval_t *args, int nargs) { - double a, b; - jsval_t res = js_checkargs(js, args, nargs, "dd", &a, &b); - if (js_type(res) == JS_UNDEF) { - res = a > b ? js_mktrue() : js_mkfalse(); - } - return res; + if (!js_chkargs(args, nargs, "dd")) return js_mkerr(js, "doh"); + return js_getnum(args[0]) > js_getnum(args[1]) ? js_mktrue() : js_mkfalse(); } static void test_c_funcs(void) { @@ -530,9 +534,9 @@ static void test_c_funcs(void) { assert((js = js_create(mem, sizeof(mem))) != NULL); js_set(js, js_glob(js), "gt", js_mkfun(js_gt)); - assert(ev(js, "gt()", "ERROR: arg count")); - assert(ev(js, "gt(1,null)", "ERROR: arg 1")); - assert(ev(js, "gt(null, 1)", "ERROR: arg 0")); + assert(ev(js, "gt()", "ERROR: doh")); + assert(ev(js, "gt(1,null)", "ERROR: doh")); + assert(ev(js, "gt(null, 1)", "ERROR: doh")); assert(ev(js, "gt(1,2)", "false")); assert(ev(js, "gt(1,1)", "false")); assert(ev(js, "gt(2,1)", "true")); @@ -547,6 +551,15 @@ static void test_c_funcs(void) { if (s_timer_fn) s_timer_fn(1, s_timer_fn_data); // C code calls timer assert(ev(js, "v", "8")); // printf("--> [%s]\n", js_str(js, js_glob(js))); + + jsval_t args[] = {0, js_mktrue(), js_mkstr(js, "a", 1), js_mknull()}; + assert(js_chkargs(args, 4, "dbsj") == true); + assert(js_chkargs(args, 4, "dbsjb") == false); + assert(js_chkargs(args, 4, "bbsj") == false); + assert(js_chkargs(args, 4, "ddsj") == false); + assert(js_chkargs(args, 4, "dbdj") == false); + assert(js_chkargs(args, 4, "dbss") == false); + assert(js_chkargs(args, 4, "d") == false); } static void test_ternary(void) {