V7 - embedded JavaScript engine version: master

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

  • Copy v7.c and v7.h in to your project
  • Write code that uses V7 API, e.g. in my_app.c
  • Compile the 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++ functions to the JavaScript environment. Here, the C function sum() that calculates a sum of two numbers gets exported to JavaScript. The C application my_app.c executes a JavaScript file my_js_code.js which calls sum() that invokes the 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 the 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: The 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 something similar.
  • -DV7_FORCE_STRICT_MODE - Executes all scripts in the "strict mode", independently of the "use strict" literal.
  • -DV7_LARGE_AST - Uses 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, uses manually-written double-to-string conversion; otherwise, uses 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.
  • -DV7_NO_COMPILER - Disable JavaScript compiler and all related modules: tokenizer, parser, AST. V7 will only be able to evaluate a precompiled JS bytecode, not a real JS code. NOTE: currently it's only possible to use this option together with V7_THAW (see "Freezing" section below), since normal v7 initialization routine currently includes evaluating some real JavaScript code.

Error reporting

edit
  • -DV7_DISABLE_CALL_ERROR_CONTEXT - Unless defined, V7 will try to keep track of a couple of things that happen before any function call. It helps to improve any error message if the 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 the 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 - Enables bcode dump (TODO: it is broken at the moment).
  • -DV7_BCODE_TRACE - Enables bcode trace (TODO: it is broken at the moment).
  • -DV7_BCODE_TRACE_STACK - Enables 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 only needed for dumping generated AST (the -t flag).

GC debug

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

Generic debug

edit
  • -DV7_ENABLE_ENTITY_IDS - Stores magic numbers in each JS object, function and property, so that when V7 encounters any of the JS entity, it can check if a 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 you to save about 10 KB of RAM by moving the JavaScript object hierarchy of the 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 the "freeze" stage should be included in the binary, and then, instead of initialising 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 automatically defined if any of the features which use cyg_profile instrumentation are 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_proto()

edit
v7_val_t v7_get_proto(struct v7 *v7, v7_val_t obj);

Get object's prototype.

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_init_prop_iter_ctx()

edit
enum v7_err v7_init_prop_iter_ctx(struct v7 *v7, v7_val_t obj,
                                  struct prop_iter_ctx *ctx);

Initialize the property iteration context ctx, see v7_next_prop() for usage example.

v7_destruct_prop_iter_ctx()

edit
void v7_destruct_prop_iter_ctx(struct v7 *v7, struct prop_iter_ctx *ctx);

Destruct the property iteration context ctx, see v7_next_prop() for usage example

v7_next_prop()

edit
int v7_next_prop(struct v7 *v7, struct prop_iter_ctx *ctx, v7_val_t *name,
                 v7_val_t *value, v7_prop_attr_t *attrs);

Iterate over the obj's properties.

Usage example (here we assume we have some v7_val_t obj):

struct prop_iter_ctx ctx;
v7_val_t name, val;
v7_prop_attr_t attrs;

v7_init_prop_iter_ctx(v7, obj, &ctx);
while (v7_next_prop(v7, &ctx, &name, &val, &attrs)) {
  ...
}
v7_destruct_prop_iter_ctx(v7, &ctx); 

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 only v7 API which is safe to use in this callback is v7_disown(), that's why v7 pointer is given to it. Calls to any other v7 functions are illegal here.

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)

struct prop_iter_ctx

edit
struct prop_iter_ctx {
#if V7_ENABLE__Proxy
  struct prop_iter_proxy_ctx *proxy_ctx;
#endif
  struct v7_property *cur_prop;

  unsigned init : 1;
};

Context for property iteration, see v7_next_prop().

Clients should not interpret contents of this structure, it's here merely to allow clients to allocate it not from the heap.

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_get_own_prop_desc_cb_t

edit
typedef int(v7_get_own_prop_desc_cb_t)(struct v7 *v7, v7_val_t target,
                                       v7_val_t name, v7_prop_attr_t *attrs,
                                       v7_val_t *value);

C callback, analogue of JS callback getOwnPropertyDescriptor(). Callbacks of this type are used for C API only, see m7_mk_proxy().

name is the name of the property, and the function should fill attrs and value with the property data. Before this callback is called, attrs is set to 0, and value is V7_UNDEFINED.

It should return non-zero if the property should be considered existing, or zero otherwise.

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

Returns bytes at index position. The byte value is in the 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

Returns string length in bytes.

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

Builtin API provides additional JavaScript interfaces available for V7 scripts.

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

The Crypto API provides functions for Base64, MD5, and SHA1 encoding/decoding.

The Socket API provides a low-level socket API.

Crypto.md5()

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

Generates an MD5 hash from the input string str. Returns 16-byte hash (md5()) or a stringified hexadecimal representation of the hash (md5_hex).

Crypto.sha1()

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

Generates a SHA1 hash from the input string str. Returns 20-byte hash (sha1()) or a stringified hexadecimal representation of the hash (sha1_hex).

File.eval()

edit
File.eval(file_name)

Parses and runs file_name.

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

File.list()

edit
File.list(dir_name) -> array_of_names

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

File.open()

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

Opens a file path. For a 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. Returns 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

Opens a file path, reads until EOF and returns. In case of errors undefined is returned.

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

File.remove()

edit
File.remove(file_name) -> errno

Deletes the file file_name.

Returns 0 on success or errno value on error.

File.rename()

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

Renames the file old_name to new_name. Returns 0 on success or errno value on error.

File.write()

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

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

Example:

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

file_obj.close()

edit
file_obj.close() -> undefined

Closes an opened file object.

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

file_obj.read()

edit
file_obj.read() -> string

Reads a portion of data from an opened file stream. Returns a string with data or an empty string on EOF or error.

file_obj.write()

edit
file_obj.write(str) -> num_bytes_written

Writes a string str to the opened file object. Returns the number of bytes written.

Socket.connect()

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

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

Socket.listen()

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

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

socket_obj.accept()

edit
socket_obj.accept() -> socket_obj

Sleeps until new incoming connection has arrived. Returns the accepted socket object on success or null on error.

socket_obj.close()

edit
socket_obj.close() -> numeric_errno

Closes the socket object. Returns 0 on success or a system errno on error.

socket_obj.recv()

edit
socket_obj.recv() -> string

Reads data from socket. Returns 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 the socket is closed.

sock.send(string)

edit
sock.send(string) -> num_bytes_sent

Sends a string to the socket. Returns the 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 the 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 the first 4 bits of mantissa to store the value type and the remaining 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, similar to 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 an 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 strings are fully contained in the NaN payload. The NaN payload is 6 bytes in total. The first byte of the payload specifies the string length. Another byte is the 0 terminator. Therefore, inlined strings can 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. The NaN payload is a pointer to a memory chunk containing the varint-encoded string length followed by string data, 0-terminated. All these 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. The NaN payload is a pointer to a memory chunk containing the 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 the input source and returns the next token. V7 uses the 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 the source code pointer ptr to the next token, skipping whitespaces and comments. Returns the number of new lines skipped.

Skip to next token

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

Returns a token pointed to by the source code pointer s. get_tok() expects skip_to_next_tok() to be 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 the 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 the get_tok() iterates over all of them. Usually, compilers generate a jump table for the switch statement, which makes get_tok() quite fast.

Is reserved word

int is_reserved_word_token(enum v7_tok tok);

Returns 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 a contiguous chunk of memory.

AST

edit

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

VM

edit

The VM operates on several core data structures:

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