Skip to content

Commit

Permalink
Change chkargs. Support strings as keys in obj literals
Browse files Browse the repository at this point in the history
  • Loading branch information
cpq committed Jul 31, 2022
1 parent cd9ac73 commit 9112ce0
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 92 deletions.
26 changes: 9 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
```

Expand Down
42 changes: 17 additions & 25 deletions elk.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@
#endif

#include <assert.h>
#include <inttypes.h>
#include <math.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
7 changes: 4 additions & 3 deletions elk.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#define JS_VERSION "3.0.0"
#pragma once

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

Expand All @@ -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

Expand Down
63 changes: 25 additions & 38 deletions examples/Esp32JS/JS.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
31 changes: 22 additions & 9 deletions test/unit_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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"));
Expand All @@ -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) {
Expand Down

0 comments on commit 9112ce0

Please sign in to comment.