Skip to content

Commit a4281e4

Browse files
committed
Change C/JS API
1 parent 6ae9e6e commit a4281e4

File tree

16 files changed

+5773
-4634
lines changed

16 files changed

+5773
-4634
lines changed

.github/workflows/test.yml

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,19 @@ jobs:
55
runs-on: ubuntu-latest
66
steps:
77
- uses: actions/checkout@v2
8-
- name: make
9-
run: make -C test test test++ elk EXTRA="-lm"
8+
- run: make -C test test test++ elk
109
MacOS:
1110
runs-on: macos-latest
1211
steps:
1312
- uses: actions/checkout@v2
14-
- name: make
15-
run: make -C test test++ elk upload-coverage
13+
- run: make -C test test++ elk upload-coverage
1614
Windows:
1715
runs-on: ubuntu-latest
1816
steps:
1917
- uses: actions/checkout@v2
20-
- name: vc2017
21-
run: make -C test vc2017
22-
- name: mingw
23-
run: make -C test mingw
18+
- run: make -C test vc2017 mingw vc22
2419
ArduinoUno:
2520
runs-on: ubuntu-latest
2621
steps:
2722
- uses: actions/checkout@v2
28-
- name: uno
29-
run: make -C test uno
23+
- run: make -C test uno

README.md

Lines changed: 58 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,19 @@ This demonstrates how JS code can import and call existing C functions:
7676
#include "elk.h"
7777
7878
// C function that adds two numbers. Will be called from JS
79-
int sum(int a, int b) {
80-
return a + b;
79+
jsval_t sum(struct js *js, jsval_t *args, int nargs) {
80+
if (nargs != 2) return js_err(js, "2 args expected");
81+
double a = js_getnum(args[0]); // Fetch 1st arg
82+
double b = js_getnum(args[1]); // Fetch 2nd arg
83+
return js_mknum(a + b);
8184
}
8285
8386
int main(void) {
8487
char mem[200];
85-
struct js *js = js_create(mem, sizeof(mem)); // Create JS instance
86-
jsval_t v = js_import(js, sum, "iii"); // Import C function "sum"
87-
js_set(js, js_glob(js), "f", v); // Under the name "f"
88-
jsval_t result = js_eval(js, "f(3, 4);", ~0); // Call "f"
89-
printf("result: %s\n", js_str(js, result)); // result: 7
88+
struct js *js = js_create(mem, sizeof(mem)); // Create JS instance
89+
js_set(js, js_glob(js), "sum", js_mkfun(sum))); // Import sum()
90+
jsval_t result = js_eval(js, "sum(3, 4);", ~0); // Call sum
91+
printf("result: %s\n", js_str(js, result)); // result: 7
9092
return 0;
9193
}
9294
```
@@ -146,6 +148,11 @@ $ xtensa-esp32-elf-gcc $CFLAGS elk.c -c elk.tmp
146148
$ xtensa-esp32-elf-objcopy --rename-section .text=.irom0.text elk.tmp elk.o
147149
```
148150

151+
Note: Elk uses `snprintf()` standard function to format numbers (double).
152+
On some architectures, for example AVR Arduino, that standard function does
153+
not support float formatting - therefore printing numbers may output nothing
154+
or `?` symbols.
155+
149156
## API reference
150157

151158
### js\_create()
@@ -199,122 +206,76 @@ The string is allocated in the "free" memory section. If there is no
199206
enough space there, an empty string is returned. The returned pointer
200207
is valid until the next `js_eval()` call.
201208
202-
203-
### js\_import()
209+
### js\_glob()
204210
205211
```c
206-
jsval_t js_import(struct js *js, uintptr_t funcaddr, const char *signature);
212+
jsval_t js_glob(struct js *);
207213
```
208214

209-
Import an existing C function with address `funcaddr` and signature `signature`.
210-
Return imported function, suitable for subsequent `js_set()`.
211-
212-
- `js`: JS instance
213-
- `funcaddr`: C function address: `(uintptr_t) &my_function`
214-
- `signature`: specifies C function signature that tells how JS engine
215-
should marshal JS arguments to the C function.
216-
First letter specifies return value type, following letters - parameters:
217-
- `b`: a C `bool` type
218-
- `d`: a C `double` type
219-
- `i`: a C integer type: `char`, `short`, `int`, `long`
220-
- `s`: a C string, a 0-terminated `char *`
221-
- `j`: a `jsval_t`
222-
- `m`: a current `struct js *`. In JS, pass `null`
223-
- `p`: any C pointer
224-
- `v`: valid only for the return value, means `void`
225-
226-
The imported C function must satisfy the following requirements:
227-
228-
- A function must have maximum 6 parameters
229-
- C `double` parameters could be only 1st or 2nd. For example, function
230-
`void foo(double x, double y, struct bar *)` could be imported, but
231-
`void foo(struct bar *, double x, double y)` could not
232-
- C++ functions must be declared as `extern "C"`
233-
- Functions with `float` params cannot be imported. Write wrappers with `double`
234-
235-
Here are some example of the import specifications:
236-
- `int sum(int)` -> `js_import(js, (uintptr_t) sum, "ii")`
237-
- `double sub(double a, double b)` -> `js_import(js, (uintptr_t) sub, "ddd")`
238-
- `int rand(void)` -> `js_import(js, (uintptr_t) rand, "i")`
239-
- `unsigned long strlen(char *s)` -> `js_import(js, (uintptr_t) strlen, "is")`
240-
- `char *js_str(struct js *, js_val_t)` -> `js_import(js, (uintptr_t) js_str, "smj")`
241-
242-
In some cases, C APIs use callback functions. For example, a timer C API could
243-
specify a time interval, a C function to call, and a function parameter. It is
244-
possible to marshal JS function as a C callback - in other words, it is
245-
possible to pass JS functions as C callbacks.
246-
247-
A C callback function should take between 1 and 6 arguments. One of these
248-
arguments must be a `void *` pointer, that is passed to the C callback by the
249-
imported function. We call this `void *` parameter a "userdata" parameter.
250-
251-
The C callback specification is enclosed into the square brackets `[...]`.
252-
In addition to the signature letters above, a new letter `u` is available
253-
that specifies userdata parameter. In JS, pass `null` for `u` param.
254-
Here is a complete example:
215+
Return global JS object, i.e. root namespace.
216+
217+
### js\_set()
255218

256219
```c
257-
#include <stdio.h>
258-
#include "elk.h"
220+
void js_set(struct js *, jsval_t obj, const char *key, jsval_t val);
221+
```
259222
260-
// C function that invokes a callback and returns the result of invocation
261-
int f(int (*fn)(int a, int b, void *userdata), void *userdata) {
262-
return fn(1, 2, userdata);
263-
}
223+
Assign an attribute `key` in in object `obj` to value `val`.
264224
265-
int main(void) {
266-
char mem[500];
267-
struct js *js = js_create(mem, sizeof(mem));
268-
js_set(js, js_glob(js), "f", js_import(js, f, "i[iiiu]u"));
269-
jsval_t v = js_eval(js, "f(function(a,b,c){return a + b;}, 0);", ~0);
270-
printf("result: %s\n", js_str(js, v)); // result: 3
271-
return 0;
272-
}
273-
```
274225
275-
### js\_set(), js\_glob(), js\_mkobj(), js\_mknum(), js\_mkstr()
226+
### js\_mk\*()
276227
277228
```c
278-
jsval_t js_glob(struct js *); // Return global object
279-
jsval_t js_mkobj(struct js *); // Create a new object
280-
jsval_t js_mkstr(struct js *, const void *, size_t); // Create a string
281-
jsval_t js_mknum(struct js *, double); // Create a number
282-
void js_set(struct js *, jsval_t obj, const char *key, jsval_t val); // Assign property to an object
229+
jsval_t js_mkval(int type); // Create undef, null, true, false
230+
jsval_t js_mkobj(struct js *); // Create object
231+
jsval_t js_mkstr(struct js *, const void *, size_t); // Create string
232+
jsval_t js_mknum(double); // Create number
233+
jsval_t js_mkerr(struct js *js, const char *fmt, ...); // Create error
234+
jsval_t js_mkfun(jsval_t (*fn)(struct js *, int)); // Create func
283235
```
284236

285-
These are helper functions for assigning properties to objects. The
286-
anticipated use case is to give names to imported C functions.
237+
Create JS values
238+
239+
### js\_get\*()
287240

288-
Importing a C function `sum` into the global namespace:
241+
### js\_type()
289242

290243
```c
291-
jsval_t global_namespace = js_glob(js);
292-
jsval_t imported_function = js_import(js, (uintptr_t) sum, "iii");
293-
js_set(js, global_namespace, "f", imported_function);
244+
enum { JS_UNDEF, JS_NULL, JS_TRUE, JS_FALSE, JS_STR, JS_NUM, JS_ERR, JS_PRIV };
245+
int js_type(jsval_t val); // Return JS value type
294246
```
295247
296-
Use `js_mkobj()` to create a dedicated object to hold groups of functions
297-
and keep a global namespace tidy. For example, all GPIO related functions
298-
can go into the `gpio` object:
248+
Return type of the JS value.
299249
300-
```c
301-
jsval_t gpio = js_mkobj(js); // Equivalent to:
302-
js_set(js, js_glob(js), "gpio", gpio); // let gpio = {};
250+
## js\_checkargs()
303251
304-
js_set(js, gpio, "mode", js_import(js, (uintptr_t) func1, "iii"); // Create gpio.mode(pin, mode)
305-
js_set(js, gpio, "read", js_import(js, (uintptr_t) func2, "ii"); // Create gpio.read(pin)
306-
js_set(js, gpio, "write", js_import(js, (uintptr_t) func3, "iii"); // Create gpio.write(pin, value)
252+
```c
253+
jsval_t js_checkargs(struct js *js, jsval_t *args, int nargs, const char *spec, ...);
307254
```
308255

309-
### js\_usage()
256+
A helper function that fetches JS arguments into C values, according to
257+
`spec` type specification. Return `JS_ERR` on error, or `JS_UNDEF` on success.
258+
Supported specifiers:
259+
- `b` for `bool`
260+
- `d` for `double`
261+
- `i` for `char`, `short`, `int`, `long`, and corresponding unsigned variants
262+
- `s` for `char *`
263+
- `j` for `jsval_t`
264+
265+
Usage example - a C function that implements a JS function
266+
`greater_than(number1, number2)`:
310267

311268
```c
312-
int js_usage(struct js *);
269+
jsval_t js_gt(struct js *js, jsval_t *args, int nargs) {
270+
double a, b;
271+
jsval_t res = js_checkargs(js, args, nargs, "dd", &a, &b);
272+
if (js_type(res) == JS_UNDEF) {
273+
res = js_mkval(a > b ? JS_TRUE : JS_FALSE);
274+
}
275+
return res;
276+
}
313277
```
314278
315-
Return memory usage percentage - a number between 0 and 100.
316-
317-
318279
## LICENSE
319280
320281
Dual license: AGPLv3 or commercial. For commercial licensing, technical support

0 commit comments

Comments
 (0)