V7 Documentation

V7 is deployed in two files v7.h and v7.c. v7.h defines V7 C/C++ embedding API. v7.c implements JavaScript engine. In order to embed V7 in your program,

  • Copy v7.c and v7.h to your project
  • Write code that uses V7 API, e.g. in my_app.c
  • Compile application: $ cc my_app.c mongoose.c
/* Include V7 API definitions */
#include "v7.h"

int main(int argc, char *argv[]) {
  const char *js_code = argc > 1 ? argv[1] : "";
  v7_val_t exec_result;

  /* Create V7 instance */
  struct v7 *v7 = v7_create();

  /* Execute a string given in a command line argument */
  v7_exec(v7, js_code, &exec_result);

  /* Destroy V7 instance */
  v7_destroy(v7);
  return 0;
}

Running the application above gives:

    ./my_app "var a = [1,2,3].splice(2); print(a)"

Call C/C++ function from JavaScript

edit

This example demonstrates how to export existing C/C++ function to the JavaScript environment. Here, C function sum() that calculates a sum of two numbers gets exported to JavaScript. A C application my_app.c executes JavaScript file my_js_code.js which call sum() that invokes C function sum().

#include 
#include 
#include "v7.h"

/*
 * a C function which we want to export to JavaScript
 */
static double sum(double a, double b) {
  return a + b;
}

/*
 * a C function that glues C function `sum()` and JavaScript function `sum()`
 * together
 */
static v7_val_t js_sum(struct v7 *v7, v7_val_t this_obj, v7_val_t args) {

  /*
   * When JavaScript function `sum()` is called, V7 creates arguments array
   * `args` and calls `js_sum()` glue function. Here we extract argument values
   * from the args array.
   */
  double arg0 = v7_get_double(v7_array_get(v7, args, 0));
  double arg1 = v7_get_double(v7_array_get(v7, args, 1));

  /* Call C function `sum()` */
  double result = sum(arg0, arg1);

  /* Return result to JavaScript */
  return v7_mk_number(result);
}

int main(void) {
  enum v7_err rcode = V7_OK;
  struct v7 *v7 = v7_create();
  v7_val_t result;

  /*
   * Export function `js_sum()` to JavaScript under the name `sum`. From that
   * point on, `v7` instance has global function `sum` defined.
   */
  v7_set_method(v7, v7_get_global(v7), "sum", &js_sum);

  /* Execute JavaScript code that calls `sum()` */
  rcode = v7_exec(v7, "print(sum(1.2, 3.4))", &result);
  if (rcode != V7_OK) {
    fprintf(stderr, "exec error: %d\n", (int)rcode);
  }
  v7_destroy(v7);
  return (int)rcode;
}

Call JavaScript function from C/C++

edit

This is the reverse example. Now we have a JavaScript function sum() that calculates sum of two numbers. Here is how we call that function from C/C++:

#include 
#include "v7.h"

static void call_sum(struct v7 *v7) {
  v7_val_t func, result, args;

  /* Lookup JavaScript function `sum()` */
  func = v7_get(v7, v7_get_global(v7), "sum", 3);

  /* Create arguments array with two numbers */
  args = v7_mk_array(v7);
  v7_array_push(v7, args, v7_mk_number(123.0));
  v7_array_push(v7, args, v7_mk_number(456.789));

  /* Call JavaScript function. Pass `undefined` as `this` */
  result = v7_apply(v7, func, v7_mk_undefined(), args);
  printf("Result: %g\n", v7_get_double(result));
}

int main(void) {
  enum v7_err rcode = V7_OK;
  v7_val_t result;
  struct v7 *v7 = v7_create();

  /* Define JavaScript function `sum()` */
  rcode = v7_exec(v7, "var sum = function(a, b) { return a + b; };", &result);
  if (rcode != V7_OK) {
    fprintf(stderr, "exec error: %d\n", (int)rcode);
  } else {
    call_sum(v7);
  }

  v7_destroy(v7);
  return (int)rcode;
}

Load JSON config

edit
enum v7_err rcode = V7_OK;
v7_val_t res;
struct v7 *v7 = v7_create();

rcode = v7_exec(v7, "config = File.loadJSON('config.json')", NULL);
if (rcode != V7_OK) {
  fprintf(stderr, "exec error: %d\n", (int)rcode);
  goto clean;
}

rcode = v7_exec(v7, "config.devices[2].name", &res);   // Lookup value
if (rcode != V7_OK) {
  fprintf(stderr, "exec error: %d\n", (int)rcode);
  goto clean;
}

printf("Device2 name: ");
v7_println(v7, res);

clean:
v7_destroy(v7);

Note: JavaScript configuration may contain not only static data, but also some dynamic logic in form of JavaScript function calls. That makes it possible to write adaptive configuration and offload configuration logic to JavaScript.

Options

edit
  • -DV7_ALLOW_ARGLESS_MAIN - When v7 is invoked without any arguments, continue working instead of printing usage help and exiting. This is useful if we invoke v7_main() with some pre_init() and/or post_init() functions, which may implement some kind of REPL, or whatever.
  • -DV7_FORCE_STRICT_MODE - Execute all scripts in the "strict mode", independently of the "use strict" literal.
  • -DV7_LARGE_AST - Use 32 bits for relative AST offsets instead of 16 bits. It allows v7 to handle much bigger scripts, but consumes more RAM to compile scripts (once script is compiled and is ready to execute, AST data is freed)
  • -DV7_MMAP_EXEC - If defined, v7 uses mmap() instead of fread() to read script files. Usage of mmap() saves RAM, but, obviously, it can be used if only mmap() is implemented on the target platform.
  • -DV7_TEMP_OFF - If defined, use manually-written double-to-string conversion; otherwise, use snprintf. TODO: find better name.

Features

edit
  • -DV7_BUILD_PROFILE=1 - Build minimal configuration: disable Date, UTF8, RegExp.
  • -DV7_BUILD_PROFILE=2 - Build medium-sized configuration: disable all Date functions but Date.now().
  • -DV7_BUILD_PROFILE=3 - Build full configuration.
  • -DV7_ENABLE_CRYPTO - Enable builtin Crypto API.
  • -DV7_ENABLE_FILE - Enable builtin File API.
  • -DV7_ENABLE_JS_GETTERS - Enabje JS getters.
  • -DV7_ENABLE_JS_SETTERS - Enable JS setters.
  • -DV7_ENABLE_SOCKET - Enable builtin Socket API.
  • -DV7_EXE - Export main() function, i.e. create V7 executable.
  • -DV7_NO_FS - Disable all functions that access filesystem, like v7_exec_file(), File, etc.

Error reporting

edit
  • -DV7_DISABLE_CALL_ERROR_CONTEXT - Unless defined, v7 will try to keep track of the couple of things that happened before any function call, it helps to improve error message if called object is not callable. When V7_DISABLE_CALL_ERROR_CONTEXT is defined, the error message v7 can give you is nothing more than "value is not a function".
  • -DV7_DISABLE_LINE_NUMBERS - Unless defined, v7 stores line numbers in AST and bcode, in order to provide nicer stack traces. It currently takes approx. 7-10% of memory needed to store AST and bcode. We plan to drop the overhead to < 1%.
  • -DV7_DISABLE_FILENAMES - Unless defined, each bcode has a pointer to the null-terminated string containing filename from which the bcode was generated. It is used in stack traces only. The overhead is:
    • Filename data itself, one string per file;
    • Pointer in each bcode, i.e. in each live script and function.

Bcode debug

edit
  • -DV7_BCODE_DUMP - Enable bcode dump (TODO: it is broken at the moment).
  • -DV7_BCODE_TRACE - Enable bcode trace (TODO: it is broken at the moment).
  • -DV7_BCODE_TRACE_STACK - Enable even more verbose bcode tracing: after each opcode, the data stack will be dumped. Caution: it generates lots of output.

AST debug

edit
  • -DV7_DISABLE_AST_TAG_NAMES - Unless defined, v7 has constant names of each AST tag, it is needed only for dumping generated AST (the -t flag).

GC debug

edit
  • -DV7_DISABLE_GC - Disable garbage collector. That will lead to memory leaks. Practical to debug garbage collector issues.
  • -DV7_DISABLE_STR_ALLOC_SEQ - Unless defined, v7 helps to spot bugs with "owning" of string values: it uses 16 bits of the v7_val_t to store number which increments every time GC compacts the owned string. We call this number ASN. So there always is a range of the ASNs which are valid, and if v7 encounters usage of the string with ASN outside of this range, it panics.
  • -DV7_GC_VERBOSE - Make GC verbose about every ASN assignment. It is useful to turn on this option when invalid ASN is detected, reproduce the problem and stare at logs.
  • -DV7_GC_AFTER_STRING_ALLOC - Invoke GC after every string allocation
  • -DV7_GC_PANIC_ON_ASN - When GC detects the GC rooting problem (usage of the string which was reallocated), call abort().
  • -DV7_MALLOC_GC - Instead of using a slab allocator for GC, use malloc.
  • -DV7_ENABLE_GC_CHECK - Use instrumentation (cyg_profile) in order to check GC owning. TODO: explain better.

Generic debug

edit
  • -DV7_ENABLE_ENTITY_IDS - Store magic numbers in each JS object, function and property, so that when v7 encounters any of the JS entity, it can check if magic number is present, and if not, it panics. This feature does not take any extra space.

Stack tracing

edit
  • -DV7_STACK_SIZE=8192 - If defined, v7 will try to measure the amount of consumed stack space. See V7_STACK_GUARD_MIN_SIZE.
  • -DV7_STACK_GUARD_MIN_SIZE - If V7_STACK_SIZE is defined, and amount of consumed stack space goes above the specified value, v7 panics.
  • -DV7_ENABLE_STACK_TRACKING - Allows to track stack usage of certain functions. Used to measure footprint. See cyg_profile.h for some details.

Call tracing

edit
  • -DV7_ENABLE_CALL_TRACE - If defined, v7 keeps track of every C function call. Used for example in heaplog; see tools/heaplog_viewer/README.md.

Freezing

edit

The "freezing" feature allows to save about 10 KB of RAM by moving JavaScript object hierarchy of standard library from RAM to ROM.

  • -DV7_FREEZE - This macro is defined by build: it causes v7 to generate a "snapshot" of the JS object hierarchy and save it as a C source file which will contain a pointer to struct v7_vals named fr_vals. The resulting v7 binary should not be used by clients. See also V7_THAW.
  • -DV7_FREEZE_NOT_READONLY - If defined, "frozen" objects will not be stored as readonly objects. TODO: explain when would it be appropriate to use it.
  • -DV7_THAW - If defined, the file generated at "freeze" step should be included in the binary, and then, instead of initializing all the standard library JS objects (and putting it in RAM), v7 will take the object hierarchy from the fr_vals pointer.

Other

edit
  • -DV7_CYG_PROFILE_ON - It is defined automatically if any of the features which use cyg_profile instrumentation is enabled; users don't need to define it manually. Enables generic code of the cyg_profile instrumentation.

v7_create_opt()

edit
struct v7 *v7_create_opt(struct v7_create_opts opts);

Like v7_create(), but allows to customize initial v7 state, see struct v7_create_opts.

v7_get_global()

edit
v7_val_t v7_get_global(struct v7 *v);

Return root level (global) object of the given V7 instance.

v7_arg()

edit
v7_val_t v7_arg(struct v7 *v, unsigned long i);

Return i-th argument

v7_argc()

edit
unsigned long v7_argc(struct v7 *v7);

Return the length of arguments

v7_own()

edit
void v7_own(struct v7 *v7, v7_val_t *v);

Tells the GC about a JS value variable/field owned by C code.

User C code should own v7_val_t variables if the value's lifetime crosses any invocation to the v7 runtime that creates new objects or new properties and thus can potentially trigger GC.

The registration of the variable prevents the GC from mistakenly treat the object as garbage. The GC might be triggered potentially allows the GC to update pointers

User code should also explicitly disown the variables with v7_disown once it goes out of scope or the structure containing the v7_val_t field is freed.

Example:

   struct v7_val cb;
   v7_own(v7, &cb);
   cb = v7_array_get(v7, args, 0);
   // do something with cb
   v7_disown(v7, &cb);

v7_disown()

edit
int v7_disown(struct v7 *v7, v7_val_t *v);

Returns 1 if value is found, 0 otherwise

v7_set_gc_enabled()

edit
void v7_set_gc_enabled(struct v7 *v7, int enabled);

Enable or disable GC.

Must be called before invoking v7_exec or v7_apply from within a cfunction unless you know what you're doing.

GC is disabled during execution of cfunctions in order to simplify memory management of simple cfunctions. However executing even small snippets of JS code causes a lot of memory pressure. Enabling GC solves that but forces you to take care of the reachability of your temporary V7 v7_val_t variables, as the GC needs to know where they are since objects and strings can be either reclaimed or relocated during a GC pass.

v7_interrupt()

edit
void v7_interrupt(struct v7 *v7);

Set an optional C stack limit.

It sets a flag that will cause the interpreter to throw an InterruptedError. It's safe to call it from signal handlers and ISRs on single threaded environments.

v7_get_parser_error()

edit
const char *v7_get_parser_error(struct v7 *v7);

Returns last parser error message. TODO: rename it to v7_get_error()

v7_stack_stat()

edit
int v7_stack_stat(struct v7 *v7, enum v7_stack_stat_what what);

Available if only V7_ENABLE_STACK_TRACKING is defined.

Returns stack metric specified by the metric id what. See v7_stack_stat_clean()

v7_stack_stat_clean()

edit
void v7_stack_stat_clean(struct v7 *v7);

Available if only V7_ENABLE_STACK_TRACKING is defined.

Clean all stack statistics gathered so far. See v7_stack_stat()

v7_val_t

edit
typedef uint64_t v7_val_t;

64-bit value, used to store JS values

v7_cfunction_t

edit
typedef enum v7_err(v7_cfunction_t)(struct v7 *v7, v7_val_t *res);

JavaScript -> C call interface

struct v7

edit
struct v7 {
  /* ... */
};

Opaque structure. V7 engine context.

struct v7_create_opts

edit
struct v7_create_opts {
  size_t object_arena_size;
  size_t function_arena_size;
  size_t property_arena_size;
#ifdef V7_STACK_SIZE
  void *c_stack_base;
#endif
#ifdef V7_FREEZE
  /* if not NULL, dump JS heap after init */
  char *freeze_file;
#endif
};

Customizations of initial V7 state; used by v7_create_opt().

enum v7_err

edit
enum v7_err {
  V7_OK,
  V7_SYNTAX_ERROR,
  V7_EXEC_EXCEPTION,
  V7_AST_TOO_LARGE,
  V7_INTERNAL_ERROR,
};

Code which is returned by some of the v7 functions. If something other than V7_OK is returned from some function, the caller function typically should either immediately cleanup and return the code further, or handle the error.

enum v7_stack_stat_what

edit
enum v7_stack_stat_what {
  /* max stack size consumed by `i_exec()` */
  V7_STACK_STAT_EXEC,
  /* max stack size consumed by `parse()` (which is called from `i_exec()`) */
  V7_STACK_STAT_PARSER,

  V7_STACK_STATS_CNT
};

Available if only V7_ENABLE_STACK_TRACKING is defined.

Stack metric id. See v7_stack_stat()

All primitive values but strings.

"foreign" values are also here, see v7_mk_foreign().

v7_mk_number()

edit
NOINSTR v7_val_t v7_mk_number(struct v7 *v7, double num);

Make numeric primitive value

v7_get_double()

edit
NOINSTR double v7_get_double(struct v7 *v7, v7_val_t v);

Returns number value stored in v7_val_t as double.

Returns NaN for non-numbers.

v7_get_int()

edit
NOINSTR int v7_get_int(struct v7 *v7, v7_val_t v);

Returns number value stored in v7_val_t as int. If the number value is not an integer, the fraction part will be discarded.

If the given value is a non-number, or NaN, the result is undefined.

v7_is_number()

edit
int v7_is_number(v7_val_t v);

Returns true if given value is a primitive number value

v7_mk_boolean()

edit
NOINSTR v7_val_t v7_mk_boolean(struct v7 *v7, int is_true);

Make boolean primitive value (either true or false)

v7_get_bool()

edit
NOINSTR int v7_get_bool(struct v7 *v7, v7_val_t v);

Returns boolean stored in v7_val_t: 0 for false or non-boolean, non-0 for true

v7_is_boolean()

edit
int v7_is_boolean(v7_val_t v);

Returns true if given value is a primitive boolean value

v7_mk_null()

edit
NOINSTR v7_val_t v7_mk_null(void);

Make null primitive value.

NOTE: this function is deprecated and will be removed in future releases. Use V7_NULL instead.

v7_is_null()

edit
int v7_is_null(v7_val_t v);

Returns true if given value is a primitive null value

v7_mk_undefined()

edit
NOINSTR v7_val_t v7_mk_undefined(void);

Make undefined primitive value.

NOTE: this function is deprecated and will be removed in future releases. Use V7_UNDEFINED instead.

v7_is_undefined()

edit
int v7_is_undefined(v7_val_t v);

Returns true if given value is a primitive undefined value

v7_mk_foreign()

edit
NOINSTR v7_val_t v7_mk_foreign(struct v7 *v7, void *ptr);

Make JavaScript value that holds C/C++ void * pointer.

A foreign value is completely opaque and JS code cannot do anything useful with it except holding it in properties and passing it around. It behaves like a sealed object with no properties.

NOTE: Only valid pointers (as defined by each supported architecture) will fully preserved. In particular, all supported 64-bit architectures (x86_64, ARM-64) actually define a 48-bit virtual address space. Foreign values will be sign-extended as required, i.e creating a foreign value of something like (void *) -1 will work as expected. This is important because in some 64-bit OSs (e.g. Solaris) the user stack grows downwards from the end of the address space.

If you need to store exactly sizeof(void) bytes of raw data where `sizeof(void)` >= 8, please use byte arrays instead.

v7_get_ptr()

edit
NOINSTR void *v7_get_ptr(struct v7 *v7, v7_val_t v);

Returns void * pointer stored in v7_val_t.

Returns NULL if the value is not a foreign pointer.

v7_is_foreign()

edit
int v7_is_foreign(v7_val_t v);

Returns true if given value holds void * pointer

v7_mk_string()

edit
v7_val_t v7_mk_string(struct v7 *v7, const char *str, size_t len, int copy);

Creates a string primitive value. str must point to the utf8 string of length len. If len is ~0, str is assumed to be NUL-terminated and strlen(str) is used.

If copy is non-zero, the string data is copied and owned by the GC. The caller can free the string data afterwards. Otherwise (copy is zero), the caller owns the string data, and is responsible for not freeing it while it is used.

v7_is_string()

edit
int v7_is_string(v7_val_t v);

Returns true if given value is a primitive string value

v7_get_string()

edit
const char *v7_get_string(struct v7 *v7, v7_val_t *v, size_t *len);

Returns a pointer to the string stored in v7_val_t.

String length returned in len, which is allowed to be NULL. Returns NULL if the value is not a string.

JS strings can contain embedded NUL chars and may or may not be NUL terminated.

CAUTION: creating new JavaScript object, array, or string may kick in a garbage collector, which in turn may relocate string data and invalidate pointer returned by v7_get_string().

Short JS strings are embedded inside the v7_val_t value itself. This is why a pointer to a v7_val_t is required. It also means that the string data will become invalid once that v7_val_t value goes out of scope.

v7_get_cstring()

edit
const char *v7_get_cstring(struct v7 *v7, v7_val_t *v);

Returns a pointer to the string stored in v7_val_t.

Returns NULL if the value is not a string or if the string is not compatible with a C string.

C compatible strings contain exactly one NUL char, in terminal position.

All strings owned by the V7 engine (see v7_mk_string()) are guaranteed to be NUL terminated. Out of these, those that don't include embedded NUL chars are guaranteed to be C compatible.

v7_is_object()

edit
int v7_is_object(v7_val_t v);

Returns true if the given value is an object or function. i.e. it returns true if the value holds properties and can be used as argument to v7_get, v7_set and v7_def.

v7_set_proto()

edit
v7_val_t v7_set_proto(struct v7 *v7, v7_val_t obj, v7_val_t proto);

Set object's prototype. Return old prototype or undefined on error.

v7_get()

edit
v7_val_t v7_get(struct v7 *v7, v7_val_t obj, const char *name, size_t name_len);

Lookup property name in object obj. If obj holds no such property, an undefined value is returned.

If name_len is ~0, name is assumed to be NUL-terminated and strlen(name) is used.

v7_get_throwing()

edit
enum v7_err v7_get_throwing(struct v7 *v7, v7_val_t obj, const char *name,
                            size_t name_len, v7_val_t *res);

Like v7_get(), but "returns" value through res pointer argument. res must not be NULL.

Caller should check the error code returned, and if it's something other than V7_OK, perform cleanup and return this code further.

v7_def()

edit
int v7_def(struct v7 *v7, v7_val_t obj, const char *name, size_t name_len,
           v7_prop_attr_desc_t attrs_desc, v7_val_t v);

Define object property, similar to JavaScript Object.defineProperty().

name, name_len specify property name, val is a property value. attrs_desc is a set of flags which can affect property's attributes, see comment of v7_prop_attr_desc_t for details.

If name_len is ~0, name is assumed to be NUL-terminated and strlen(name) is used.

Returns non-zero on success, 0 on error (e.g. out of memory).

See also v7_set().

v7_set()

edit
int v7_set(struct v7 *v7, v7_val_t obj, const char *name, size_t len,
           v7_val_t val);

Set object property. Behaves just like JavaScript assignment.

See also v7_def().

v7_set_method()

edit
int v7_set_method(struct v7 *, v7_val_t obj, const char *name,
                  v7_cfunction_t *func);

A helper function to define object's method backed by a C function func. name must be NUL-terminated.

Return value is the same as for v7_set().

v7_del()

edit
int v7_del(struct v7 *v7, v7_val_t obj, const char *name, size_t name_len);

Delete own property name of the object obj. Does not follow the prototype chain.

If name_len is ~0, name is assumed to be NUL-terminated and strlen(name) is used.

Returns 0 on success, -1 on error.

v7_next_prop()

edit
void *v7_next_prop(void *handle, v7_val_t obj, v7_val_t *name, v7_val_t *value,
                   v7_prop_attr_t *attrs);

Iterate over the obj's properties.

Usage example:

void *h = NULL;
v7_val_t name, val;
v7_prop_attr_t attrs;
while ((h = v7_next_prop(h, obj, &name, &val, &attrs)) != NULL) {
  ...
} 

v7_is_instanceof()

edit
int v7_is_instanceof(struct v7 *v7, v7_val_t o, const char *c);

Returns true if the object is an instance of a given constructor.

v7_is_instanceof_v()

edit
int v7_is_instanceof_v(struct v7 *v7, v7_val_t o, v7_val_t c);

Returns true if the object is an instance of a given constructor.

v7_set_user_data()

edit
void v7_set_user_data(struct v7 *v7, v7_val_t obj, void *ud);

Associates an opaque C value (anything that can be casted to a `void * ) with an object.

You can achieve a similar effect by just setting a special property with a foreign value (see v7_mk_foreign), except user data offers the following advantages:

  1. You don't have to come up with some arbitrary "special" property name.
  2. JS scripts cannot access user data by mistake via property lookup.
  3. The user data is available to the destructor. When the desctructor is invoked you cannot access any of its properties.
  4. Allows the implementation to use a more compact encoding

Does nothing if obj is not a mutable object.

v7_get_user_data()

edit
void *v7_get_user_data(struct v7 *v7, v7_val_t obj);

Get the opaque user data set with v7_set_user_data.

Returns NULL if there is no user data set or if obj is not an object.

v7_set_destructor_cb()

edit
void v7_set_destructor_cb(struct v7 *v7, v7_val_t obj, v7_destructor_cb_t *d);

Register a callback which will be invoked when a given object gets reclaimed by the garbage collector.

The callback will be invoked while garbage collection is still in progress and hence the internal state of the JS heap is in an undefined state. The callback thus cannot perform any calls to the V7 API and will receive only the user data associated with the destructed object.

The intended use case is to reclaim resources allocated by C code.

v7_prop_attr_t

edit
typedef unsigned short v7_prop_attr_t;
#define V7_PROPERTY_NON_WRITABLE (1 << 0)
#define V7_PROPERTY_NON_ENUMERABLE (1 << 1)
#define V7_PROPERTY_NON_CONFIGURABLE (1 << 2)
#define V7_PROPERTY_GETTER (1 << 3)
#define V7_PROPERTY_SETTER (1 << 4)
#define _V7_PROPERTY_HIDDEN (1 << 5)
/* property not managed by V7 HEAP */
#define _V7_PROPERTY_OFF_HEAP (1 << 6)
/* special property holding user data and destructor cb */
#define _V7_PROPERTY_USER_DATA_AND_DESTRUCTOR (1 << 7)
/*
 * not a property attribute, but a flag for `v7_def()`. It's here in order to
 * keep all offsets in one place
 */
#define _V7_DESC_PRESERVE_VALUE (1 << 8)

Property attributes bitmask

v7_prop_attr_desc_t

edit
typedef unsigned long v7_prop_attr_desc_t;
#define V7_DESC_WRITABLE(v) _V7_MK_DESC_INV(v, V7_PROPERTY_NON_WRITABLE)
#define V7_DESC_ENUMERABLE(v) _V7_MK_DESC_INV(v, V7_PROPERTY_NON_ENUMERABLE)
#define V7_DESC_CONFIGURABLE(v) _V7_MK_DESC_INV(v, V7_PROPERTY_NON_CONFIGURABLE)
#define V7_DESC_GETTER(v) _V7_MK_DESC(v, V7_PROPERTY_GETTER)
#define V7_DESC_SETTER(v) _V7_MK_DESC(v, V7_PROPERTY_SETTER)
#define V7_DESC_PRESERVE_VALUE _V7_DESC_PRESERVE_VALUE

Property attribute descriptors that may be given to v7_def(): for each attribute (v7_prop_attr_t), there is a corresponding macro, which takes param: either 1 (set attribute) or 0 (clear attribute). If some particular attribute isn't mentioned at all, it's left unchanged (or default, if the property is being created)

There is additional flag: V7_DESC_PRESERVE_VALUE. If it is set, the property value isn't changed (or set to undefined if the property is being created)

v7_is_array()

edit
int v7_is_array(struct v7 *v7, v7_val_t v);

Returns true if given value is an array object

v7_array_length()

edit
unsigned long v7_array_length(struct v7 *v7, v7_val_t arr);

Returns length on an array. If arr is not an array, 0 is returned.

v7_array_push()

edit
int v7_array_push(struct v7 *, v7_val_t arr, v7_val_t v);

Insert value v in array arr at the end of the array.

v7_array_push_throwing()

edit
enum v7_err v7_array_push_throwing(struct v7 *v7, v7_val_t arr, v7_val_t v,
                                   int *res);

Like v7_array_push(), but "returns" value through the res pointer argument. res is allowed to be NULL.

Caller should check the error code returned, and if it's something other than V7_OK, perform cleanup and return this code further.

v7_array_get()

edit
v7_val_t v7_array_get(struct v7 *, v7_val_t arr, unsigned long index);

Return array member at index index. If index is out of bounds, undefined is returned.

v7_array_set()

edit
int v7_array_set(struct v7 *v7, v7_val_t arr, unsigned long index, v7_val_t v);

Insert value v into arr at index index.

v7_array_set_throwing()

edit
enum v7_err v7_array_set_throwing(struct v7 *v7, v7_val_t arr,
                                  unsigned long index, v7_val_t v, int *res);

Like v7_array_set(), but "returns" value through the res pointer argument. res is allowed to be NULL.

Caller should check the error code returned, and if it's something other than V7_OK, perform cleanup and return this code further.

v7_array_del()

edit
void v7_array_del(struct v7 *v7, v7_val_t arr, unsigned long index);

Delete value in array arr at index index, if it exists.

v7_mk_function()

edit
v7_val_t v7_mk_function(struct v7 *, v7_cfunction_t *func);

Make a JS function object backed by a cfunction.

func is a C callback.

A function object is JS object having the Function prototype that holds a cfunction value in a hidden property.

The function object will have a prototype property holding an object that will be used as the prototype of objects created when calling the function with the new operator.

v7_mk_function_with_proto()

edit
v7_val_t v7_mk_function_with_proto(struct v7 *v7, v7_cfunction_t *f,
                                   v7_val_t proto);

Make f a JS function with specified prototype proto, so that the resulting function is better suited for the usage as a constructor.

v7_mk_cfunction()

edit
v7_val_t v7_mk_cfunction(v7_cfunction_t *func);

Make a JS value that holds C/C++ callback pointer.

CAUTION: This is a low-level function value. It's not a real object and cannot hold user defined properties. You should use v7_mk_function unless you know what you're doing.

v7_is_callable()

edit
int v7_is_callable(struct v7 *v7, v7_val_t v);

Returns true if given value is callable (i.e. it's either a JS function or cfunction)

v7_mk_regexp()

edit
enum v7_err v7_mk_regexp(struct v7 *v7, const char *regex, size_t regex_len,
                         const char *flags, size_t flags_len, v7_val_t *res);

Make RegExp object. regex, regex_len specify a pattern, flags and flags_len specify flags. Both utf8 encoded. For example, regex is (.+), flags is gi. If regex_len is ~0, regex is assumed to be NUL-terminated and strlen(regex) is used.

v7_is_regexp()

edit
int v7_is_regexp(struct v7 *v7, v7_val_t v);

Returns true if given value is a JavaScript RegExp object

v7_stringify()

edit
char *v7_stringify(struct v7 *v7, v7_val_t v, char *buf, size_t len,
                   enum v7_stringify_mode mode);

Generate string representation of the JavaScript value val into a buffer buf, len. If len is too small to hold a generated string, v7_stringify() allocates required memory. In that case, it is caller's responsibility to free the allocated buffer. Generated string is guaranteed to be 0-terminated.

Available stringification modes are:

  • V7_STRINGIFY_DEFAULT: Convert JS value to string, using common JavaScript semantics:

    • If value is an object:
      • call toString();
      • If toString() returned non-primitive value, call valueOf();
      • If valueOf() returned non-primitive value, throw TypeError.
    • Now we have a primitive, and if it's not a string, then stringify it.
  • V7_STRINGIFY_JSON: Generate JSON output

  • V7_STRINGIFY_DEBUG: Mostly like JSON, but will not omit non-JSON objects like functions.

Example code:

char buf[100], *p;
p = v7_stringify(v7, obj, buf, sizeof(buf), V7_STRINGIFY_DEFAULT);
printf("JSON string: [%s]\n", p);
if (p != buf) {
  free(p);
} 

v7_stringify_throwing()

edit
enum v7_err v7_stringify_throwing(struct v7 *v7, v7_val_t v, char *buf,
                                  size_t size, enum v7_stringify_mode mode,
                                  char **res);

Like v7_stringify(), but "returns" value through the res pointer argument. res must not be NULL.

Caller should check the error code returned, and if it's something other than V7_OK, perform cleanup and return this code further.

v7_to_json()

edit
#define v7_to_json(a, b, c, d);

A shortcut for v7_stringify() with V7_STRINGIFY_JSON

v7_is_truthy()

edit
int v7_is_truthy(struct v7 *v7, v7_val_t v);

Returns true if given value evaluates to true, as in if (v) statement.

enum v7_stringify_mode

edit
enum v7_stringify_mode {
  V7_STRINGIFY_DEFAULT,
  V7_STRINGIFY_JSON,
  V7_STRINGIFY_DEBUG,
};

Stringify mode, see v7_stringify() and v7_stringify_throwing()

v7_exec()

edit
enum v7_err v7_exec(struct v7 *v7, const char *js_code, v7_val_t *result);

Execute JavaScript js_code. The result of evaluation is stored in the result variable.

Return:

  • V7_OK on success. result contains the result of execution.
  • V7_SYNTAX_ERROR if js_code in not a valid code. result is undefined.
  • V7_EXEC_EXCEPTION if js_code threw an exception. result stores an exception object.
  • V7_AST_TOO_LARGE if js_code contains an AST segment longer than 16 bit. result is undefined. To avoid this error, build V7 with V7_LARGE_AST.

v7_exec_opt()

edit
enum v7_err v7_exec_opt(struct v7 *v7, const char *js_code,
                        const struct v7_exec_opts *opts, v7_val_t *res);

Customizable version of v7_exec(): allows to specify various options, see struct v7_exec_opts.

v7_exec_file()

edit
enum v7_err v7_exec_file(struct v7 *v7, const char *path, v7_val_t *result);

Same as v7_exec(), but loads source code from path file.

v7_parse_json()

edit
enum v7_err v7_parse_json(struct v7 *v7, const char *str, v7_val_t *res);

Parse str and store corresponding JavaScript object in res variable. String str should be '\0'-terminated. Return value and semantic is the same as for v7_exec().

v7_parse_json_file()

edit
enum v7_err v7_parse_json_file(struct v7 *v7, const char *path, v7_val_t *res);

Same as v7_parse_json(), but loads JSON string from path.

v7_compile()

edit
enum v7_err v7_compile(const char *js_code, int generate_binary_output,
                       int use_bcode, FILE *fp);

Compile JavaScript code js_code into the byte code and write generated byte code into opened file stream fp. If generate_binary_output is 0, then generated byte code is in human-readable text format. Otherwise, it is in the binary format, suitable for execution by V7 instance. NOTE: fp must be a valid, opened, writable file stream.

v7_apply()

edit
enum v7_err v7_apply(struct v7 *v7, v7_val_t func, v7_val_t this_obj,
                     v7_val_t args, v7_val_t *res);

Call function func with arguments args, using this_obj as this. args should be an array containing arguments or undefined.

res can be NULL if return value is not required.

struct v7_exec_opts

edit
struct v7_exec_opts {
  /* Filename, used for stack traces only */
  const char *filename;

  /*
   * Object to be used as `this`. Note: when it is zeroed out, i.e. it's a
   * number `0`, the `undefined` value is assumed. It means that it's
   * impossible to actually use the number `0` as `this` object, but it makes
   * little sense anyway.
   */
  v7_val_t this_obj;

  /* Whether the given `js_code` should be interpreted as JSON, not JS code */
  unsigned is_json : 1;
};

Options for v7_exec_opt(). To get default options, like v7_exec() uses, just zero out this struct.

v7_throw()

edit
enum v7_err v7_throw(struct v7 *v7, v7_val_t v);

Throw an exception with an already existing value.

v7_throwf()

edit
enum v7_err v7_throwf(struct v7 *v7, const char *typ, const char *err_fmt, ...);

Throw an exception with given formatted message.

Pass "Error" as typ for a generic error.

v7_rethrow()

edit
enum v7_err v7_rethrow(struct v7 *v7);

Rethrow the currently thrown object. In fact, it just returns V7_EXEC_EXCEPTION.

v7_get_thrown_value()

edit
v7_val_t v7_get_thrown_value(struct v7 *v7, unsigned char *is_thrown);

Returns the value that is being thrown at the moment, or undefined if nothing is being thrown. If is_thrown is not NULL, it will be set to either 0 or 1, depending on whether something is thrown at the moment.

v7_heap_stat()

edit
int v7_heap_stat(struct v7 *v7, enum v7_heap_stat_what what);

Returns a given heap statistics

v7_gc()

edit
void v7_gc(struct v7 *v7, int full);

Perform garbage collection. Pass true to full in order to reclaim unused heap back to the OS.

enum v7_heap_stat_what

edit
enum v7_heap_stat_what {
  V7_HEAP_STAT_HEAP_SIZE,
  V7_HEAP_STAT_HEAP_USED,
  V7_HEAP_STAT_STRING_HEAP_RESERVED,
  V7_HEAP_STAT_STRING_HEAP_USED,
  V7_HEAP_STAT_OBJ_HEAP_MAX,
  V7_HEAP_STAT_OBJ_HEAP_FREE,
  V7_HEAP_STAT_OBJ_HEAP_CELL_SIZE,
  V7_HEAP_STAT_FUNC_HEAP_MAX,
  V7_HEAP_STAT_FUNC_HEAP_FREE,
  V7_HEAP_STAT_FUNC_HEAP_CELL_SIZE,
  V7_HEAP_STAT_PROP_HEAP_MAX,
  V7_HEAP_STAT_PROP_HEAP_FREE,
  V7_HEAP_STAT_PROP_HEAP_CELL_SIZE,
  V7_HEAP_STAT_FUNC_AST_SIZE,
  V7_HEAP_STAT_BCODE_OPS_SIZE,
  V7_HEAP_STAT_BCODE_LIT_TOTAL_SIZE,
  V7_HEAP_STAT_BCODE_LIT_DESER_SIZE,
  V7_HEAP_STAT_FUNC_OWNED,
  V7_HEAP_STAT_FUNC_OWNED_MAX
};

Heap metric id, see v7_heap_stat()

v7_print()

edit
void v7_print(struct v7 *v7, v7_val_t v);

Output a string representation of the value to stdout. V7_STRINGIFY_DEBUG mode is used.

v7_println()

edit
void v7_println(struct v7 *v7, v7_val_t v);

Output a string representation of the value to stdout followed by a newline. V7_STRINGIFY_DEBUG mode is used.

v7_fprint()

edit
void v7_fprint(FILE *f, struct v7 *v7, v7_val_t v);

Output a string representation of the value to a file. V7_STRINGIFY_DEBUG mode is used.

v7_fprintln()

edit
void v7_fprintln(FILE *f, struct v7 *v7, v7_val_t v);

Output a string representation of the value to a file followed by a newline. V7_STRINGIFY_DEBUG mode is used.

v7_fprint_stack_trace()

edit
void v7_fprint_stack_trace(FILE *f, struct v7 *v7, v7_val_t e);

Output stack trace recorded in the exception e to file f

v7_print_error()

edit
void v7_print_error(FILE *f, struct v7 *v7, const char *ctx, v7_val_t e);

Output error object message and possibly stack trace to f

v7_main()

edit
int v7_main(int argc, char *argv[], void (*pre_freeze_init);

V7 executable main function.

There are various callbacks available:

pre_freeze_init() and pre_init() are optional intialization functions, aimed to export any extra functionality into vanilla v7 engine. They are called after v7 initialization, before executing given files or inline expressions. pre_freeze_init() is called before "freezing" v7 state; whereas pre_init called afterwards.

post_init(), if provided, is called after executing files and expressions, before destroying v7 instance and exiting.

V7 has several non-standard extensions for String.prototype in order to give a compact and fast API to access raw data obtained from File, Socket, and hardware input/output such as I2C. V7 IO API functions return string data as a result of read operations, and that string data is a raw byte array. ECMA6 provides ArrayBuffer and DataView API for dealing with raw bytes, because strings in JavaScript are Unicode. That standard API is too bloated for the embedded use, and does not allow to use handy String API (e.g. .match()) against data. * V7 internally stores strings as byte arrays. All strings created by the String API are UTF8 encoded. Strings that are the result of input/output API calls might not be a valid UTF8 strings, but nevertheless they are represented as strings, and the following API allows to access underlying byte sequence:

String.prototype.at()

edit
String.prototype.at(position) -> number or NaN

Return byte at index position. Byte value is in 0,255 range. If position is out of bounds (either negative or larger then the byte array length), NaN is returned. Example: "ы".at(0) returns 0xd1.

String.prototype.blen

edit
String.prototype.blen -> number

Return string length in bytes.

Example: "ы".blen returns 2. Note that "ы".length is 1, since that string consists of a single Unicode character (2-byte).

Crypto.md5()

edit
Crypto.md5(str)
Crypto.md5_hex(str)

Generate MD5 hash from input string str. Return 16-byte hash (md5()), or stringified hexadecimal representation of the hash (md5_hex).

Crypto.sha1()

edit
Crypto.sha1(str)
Crypto.sha1_hex(str)

Generate SHA1 hash from input string str. Return 20-byte hash (sha1()), or stringified hexadecimal representation of the hash (sha1_hex).

File.eval()

edit
File.eval(file_name)

Parse and run file_name.

Throws an exception if the file doesn't exist, cannot be parsed or if the script throws any exception.

File.list()

edit
File.list(dir_name) -> array_of_names

Return a list of files in a given directory, or undefined on error.

File.open()

edit
File.open(file_name [, mode]) -> file_object or null

Open a file path. For list of valid mode values, see fopen() documentation. If mode is not specified, mode rb is used, i.e. file is opened in read-only mode. Return an opened file object, or null on error.

Example:

var f = File.open('/etc/passwd'); f.close();

File.read()

edit
File.read(file_name) -> string or undefined

Open a file path, read until EOF and return. In case of errors undefined is returned.

var passwd = File.read('/etc/passwd');

File.remove()

edit
File.remove(file_name) -> errno

Delete file file_name.

Return 0 on success, or errno value on error.

File.rename()

edit
File.rename(old_name, new_name) -> errno

Rename file old_name to new_name. Return 0 on success, or errno value on error.

File.write()

edit
File.write(file_name, body) -> boolean

Write body into file_name, creating the file if it doesn't exist, or truncating it otherwise. Return true in case of success.

Example:

File.write('/tmp/foo, 'bar');

file_obj.close()

edit
file_obj.close() -> undefined

Close opened file object.

NOTE: it is user's responsibility to close all opened file streams. V7 does not do that automatically.

file_obj.read()

edit
file_obj.read() -> string

Read portion of data from an opened file stream. Return string with data, or an empty string on EOF or error.

file_obj.write()

edit
file_obj.write(str) -> num_bytes_written

Write string str to the opened file object. Return number of bytes written.

Builtin API provides additional JavaScript interfaces available for V7 scripts.

File API is a wrapper around standard C calls fopen(), fclose(), fread(), fwrite(), rename(), remove().

Crypto API provides functions for base64, md5, and sha1 encoding/decoding.

Socket API provides low-level socket API.

Socket.connect()

edit
Socket.connect(host, port [, is_udp]) -> socket_obj

Connect to a given host. host can be a string IP address, or a host name. Optional is_udp parameter, if true, indicates that socket should be UDP. Return socket object on success, null on error.

Socket.listen()

edit
Socket.listen(port [, ip_address [,is_udp]]) -> socket_obj

Create a listening socket on a given port. Optional ip_address argument specifies and IP address to bind to. Optional is_udp parameter, if true, indicates that socket should be UDP. Return socket object on success, null on error.

socket_obj.accept()

edit
socket_obj.accept() -> socket_obj

Sleep until new incoming connection is arrived. Return accepted socket object on success, or null on error.

socket_obj.close()

edit
socket_obj.close() -> numeric_errno

Close socket object. Return 0 on success, or system errno on error.

socket_obj.recv()

edit
socket_obj.recv() -> string

Read data from socket. Return data string, or empty string if peer has disconnected, or null on error.

socket_obj.recvAll()

edit
socket_obj.recvAll() -> string

Same as recv(), but keeps reading data until socket is closed.

sock.send(string)

edit
sock.send(string) -> num_bytes_sent

Send string to the socket. Return number of bytes sent, or 0 on error. Simple HTTP client example:

var s = Socket.connect("google.com", 80);
s.send("GET / HTTP/1.0\n\n");
var reply = s.recv();

V7 has four loosely coupled layers:

  • Tokenizer. Reads input source and returns next token.
  • Parser. Calls tokenizer to parse the source code, and builds an AST
  • Abstract Syntax Tree (AST) - a representation of a program's syntax tree in a compact and portable binary form
  • Interpreter - an actual VM that executes AST

Each layer is described in a respective section below.

NaN packing

edit

V7 uses a well-known NaN packing technique for values. This is a method of storing values in the IEEE 754 double-precision floating-point number. double type is 64-bit, comprised of 1 sign bit, 11 exponent bits and 52 mantissa bits:

    7         6        5        4        3        2        1        0
seeeeeee|eeeemmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm

If an exponent is all-1 and mantissa is non-0, then double value represents NaN (not-a-number). V7 uses first 4 bits of mantissa to store value type, and the rest 48 bits of mantissa goes to storing values:

11111111|1111tttt|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv
 NaN marker |type|  48-bit placeholder for values: pointers, strings

On 64-bit platforms, pointers occupy only 48 bits, so they can fit into the 48-bit placeholder - provided they are sign extended.

Mbufs

edit

Mbufs are mutable/growing memory buffers, kinda like C++ strings:

struct mbuf {
  char *buf;
  size_t len;
  size_t size;
};

Mbuf can append data to the end of a buffer, or insert data into arbitrary position in the middle of a buffer. The buffer grows automatically when needed. Mbuf is used as a backing store for the AST and strings.

Strings

edit

Strings in V7 are stored as UTF8 chunks of memory. They are always 0-terminated, in order to be suitable for standard C string functions. V7 has four distinct string types:

  • #define V7_TAG_STRING_I ((uint64_t) 0xFFFA << 48) - this is an inlined string. Inlined string are fully contained in the NaN payload. NaN payload is 6 bytes in total. First byte of the payload specifies string length. Another byte is 0 terminator. Therefore, inlined strings could only be 4 bytes or less. For example, inlined string hi! looks like this:
11111111|1111tttt|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv
 NaN marker |0xa |   3    |    h   |    i   |    !   |  \0    |
  • #define V7_TAG_STRING_5 ((uint64_t) 0xFFF9 << 48) - this is an inlined string exactly 5 bytes in size: 6 bytes of string payload + 1 zero byte. For example, string hello looks like this:
11111111|1111tttt|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv
 NaN marker |0x9 |   h    |    e   |    l   |    l   |   o    |  \0
  • #define V7_TAG_STRING_O ((uint64_t) 0xFFF8 << 48) - this is an owned string, i.e. string data is owned by V7. This type is for larger strings. NaN payload is a pointer to a memory chunk containing varint-encoded string length followed by string data, 0-terminated. All such memory chunks are stored in struct v7::owned_strings mbuf. Example of a string Mukacheve looks like this:
11111111|1111tttt|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv
 NaN marker |0x8 |    0    |    0   |  0xa  |  0xb   |   0xc   |  0xd
        pointer 0xabcd points inside struct v7::owned_strings mbuf
                   v
|------------------|0x9|Mukacheve\0|-----...other strings...
  • #define V7_TAG_STRING_F ((uint64_t) 0xFFF7 << 48) - this string type is used for long (>5 bytes) strings, whose data is not owned by V7. NaN payload is a pointer to a memory chunk containing varint-encoded string length followed by a pointer to string data outside V7. V7 cannot guarantee that the foreign string is 0-terminated. Example of a foreign string Marko The Great:
11111111|1111tttt|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv
 NaN marker |0x7 |    0    |    0   |  0xa  |  0xb   |   0xc   |  0xd
        pointer 0xabcd points inside struct v7::foreign_strings mbuf
                   v
|------------------|0xf|0x123456|-----...other strings...
 pointer 0x12345 points to a foreign string
           v
-----------|Marko The Great|------

Tokenizer

edit

Tokenizer reads input source and returns the next token. V7 uses hand-written tokenizer. All possible tokens are described as enum v7_tok. Tokenizer exports following interface:

Skip to next token

int skip_to_next_tok(const char **ptr);

Move source code pointer ptr to the next token, skipping whitespaces and comments. Return number of new lines skipped.

Skip to next token

enum v7_tok get_tok(const char **s, double *n, enum v7_tok prev_tok);

Return a token pointed by source code pointer s. get_tok() expects skip_to_next_tok() called, i.e. that s points to the valid next token. If the returned token is TOK_NUMBER, then n will contain the parsed number. prev_tok is required to parse RegExp literals, otherwise it would be impossible to correctly parse strings like: a /= b; c /= d;: when the tokenizer sees first /, it wouldn't know if it is a division or a regexp literal.

get_tok() is basically a giant switch statement that looks at the first character of the input string. For each letter, there is a short number of tokens that begins with that letter, and get_tok() iterates over all of them. Usually compilers generate jump table for the switch statement, which makes get_tok() quite fast.

Is reserved word

int is_reserved_word_token(enum v7_tok tok);

Return non-zero if the token is one of the reserved words. To make this function fast, all reserved words tokens are grouped together in alphabetical order, thus enabling this implementation:

int is_reserved_word_token(enum v7_tok tok) {
  return tok >= TOK_BREAK && tok <= TOK_WITH;
}

Parser

edit

V7 parser is a hand-written recursive-descent parser. It calls tokenizer to get the next token in the stream, and generates an abstract syntax tree (AST) that is represented as contiguous chunk of memory.

AST

edit

AST represents program's syntax tree in a compact and portable binary form.

VM

edit

The VM operates on several core data structures:

  • struct v7 - describes V7 instance itself
  • struct v7_object, struct v7_property - describes object and their properties
  • struct ast - describes AST, which VM executes