Skip to main content
libant is the C API that lets you embed the Ant JavaScript engine directly into your own application. You create a runtime instance, optionally expose C functions as JavaScript globals, evaluate scripts, and drive the async event loop — all from C. This is how you add scriptable behavior to a game engine, a CLI tool, a config system, or any other C program without shipping a separate runtime binary.
Build the embedded library and the example with the scripts in libant/: run ./libant/build.sh to compile libant, then ./libant/example.sh to build and run examples/embed/embed.c.

Getting started

Include <ant.h> and link against libant. Every program that embeds Ant follows the same three-step lifecycle: create a runtime, do work, destroy it.
1

Create and initialize a runtime

Call js_create_dynamic() to allocate a new runtime. Then set the stack base so the garbage collector can scan the native stack, and call ant_runtime_init to wire up the standard built-ins.
#include <ant.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
  volatile char stack_base;

  ant_t *js = js_create_dynamic();
  js_setstackbase(js, (void *)&stack_base);

  static char *argv[] = { "my_app", NULL };
  ant_runtime_init(js, 1, argv, NULL);

  // ... do work ...

  js_destroy(js);
  return EXIT_SUCCESS;
}
Pass NULL as the last argument to ant_runtime_init when you do not need persistent localStorage. Pass a file path string to enable persistence.
2

Evaluate JavaScript

Use js_eval_bytecode_eval(js, code, len) to compile and run a string of JavaScript. It returns an ant_value_t — check its type with vtype() before reading the value.
const char *code = "1 + 2 * 3";
ant_value_t result = js_eval_bytecode_eval(js, code, strlen(code));

if (vtype(result) == T_NUM) {
  printf("Result: %g\n", js_getnum(result));
} else if (vtype(result) == T_ERR) {
  printf("Error: %s\n", js_str(js, result));
}
3

Destroy the runtime

Call js_destroy(js) when you are done. This frees all memory allocated by the runtime including the heap, interned strings, and any pending microtasks.
js_destroy(js);

Core API reference

Runtime lifecycle

js_create_dynamic()
ant_t *
Allocate and return a new Ant runtime instance. Returns NULL on failure. Each instance is fully independent — you can create multiple runtimes in the same process.
js_setstackbase(js, ptr)
void
Set the GC stack-base pointer. Pass the address of a local variable declared near the top of the frame that owns the runtime. The GC uses this to find the bottom of the native stack when scanning for roots.
js_setstacklimit(js, size)
void
Override the JavaScript stack size in bytes. Call before any evaluation if you need a non-default stack limit.
ant_runtime_init(js, argc, argv, localstorage_file)
void
Initialize the runtime with command-line arguments and optional localStorage persistence. Pass NULL for localstorage_file to disable persistence.
js_eval_bytecode_eval(js, code, len)
ant_value_t
Compile and evaluate the JavaScript string code of length len. State (variables, function definitions) accumulates across multiple calls on the same runtime — see Stateful sessions.
js_run_event_loop(js)
void
Drive the async event loop to completion. Call this after js_eval_bytecode_eval if your script schedules timers, resolves promises, or enqueues microtasks.
js_destroy(js)
void
Destroy the runtime and free all associated memory.

Value types

Every JavaScript value is represented as ant_value_t. Use vtype(val) to inspect its type before reading it.
Type constantJavaScript type
T_NUMnumber
T_STRstring
T_OBJobject
T_ARRarray
T_BOOLboolean
T_ERRError
T_UNDEFundefined
T_NULLnull

Creating values

js_mknum(double)
ant_value_t
Create a number value.
js_mkstr(js, ptr, len)
ant_value_t
Create a string value from a C string and length. Ant copies the bytes — you do not need to keep ptr alive after the call.
js_mkobj(js)
ant_value_t
Create an empty object ({}).
js_mkarr(js)
ant_value_t
Create an empty array ([]).
js_mkfun(fn_ptr)
ant_value_t
Wrap a C function pointer as a JavaScript function value. The function must have the signature ant_value_t fn(ant_t *js, ant_value_t *args, int nargs).
js_true / js_false
ant_value_t
Boolean constants. Use directly — no function call needed.
js_mkundef()
ant_value_t
Create an undefined value.
js_mknull()
ant_value_t
Create a null value.
js_mkerr(js, msg)
ant_value_t
Create an Error value with the given message string.

Reading values

js_getnum(val)
double
Extract a double from a number value.
js_getstr(js, val, &len)
char *
Extract a C string from a string value, writing its length into len.
js_str(js, val)
const char *
Convert any value to a display string, equivalent to calling String(val) in JavaScript. Useful for printing and logging.

Exposing C functions to JavaScript

Declare your C functions with the required signature, register them as globals using js_set and js_mkfun, then call them from any evaluated script.
static ant_value_t my_add(ant_t *js, ant_value_t *args, int nargs) {
  if (!js_chkargs(args, nargs, "dd")) {
    return js_mkerr(js, "add() expects two numbers");
  }

  double a = js_getnum(args[0]);
  double b = js_getnum(args[1]);

  return js_mknum(a + b);
}

static ant_value_t my_greet(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1 || vtype(args[0]) != T_STR) {
    return js_mkerr(js, "greet() expects a string");
  }

  size_t len;
  char *name = js_getstr(js, args[0], &len);

  char buf[256];
  snprintf(buf, sizeof(buf), "Hello, %s!", name);

  return js_mkstr(js, buf, strlen(buf));
}

static ant_value_t my_create_point(ant_t *js, ant_value_t *args, int nargs) {
  if (!js_chkargs(args, nargs, "dd")) {
    return js_mkerr(js, "createPoint() expects two numbers");
  }

  ant_value_t obj = js_mkobj(js);
  js_set(js, obj, "x", args[0]);
  js_set(js, obj, "y", args[1]);

  return obj;
}
Register these functions on the global object before evaluating any script that uses them:
ant_value_t global = js_glob(js);
js_set(js, global, "add", js_mkfun(my_add));
js_set(js, global, "greet", js_mkfun(my_greet));
js_set(js, global, "createPoint", js_mkfun(my_create_point));

const char *code1 = "add(10, 32)";
ant_value_t r1 = js_eval_bytecode_eval(js, code1, strlen(code1));
printf("  add(10, 32) = %g\n", js_getnum(r1));

const char *code2 = "greet('World')";
ant_value_t r2 = js_eval_bytecode_eval(js, code2, strlen(code2));
printf("  greet('World') = %s\n", js_str(js, r2));

const char *code3 = "let p = createPoint(3, 4); p.x * p.x + p.y * p.y";
ant_value_t r3 = js_eval_bytecode_eval(js, code3, strlen(code3));
printf("  distance² = %g\n", js_getnum(r3));
js_chkargs(args, nargs, fmt) validates that the argument list matches a format string. Use "d" for number, "s" for string. It returns false if validation fails, at which point you should return an error value.

Object and array operations

js_glob(js)
ant_value_t
Return the global object. Use js_set and js_get on this to expose values to all evaluated scripts.
js_get(js, obj, key)
ant_value_t
Read a property from an object by key string.
js_set(js, obj, key, val)
void
Set a property on an object.
js_arr_push(js, arr, val)
void
Append a value to the end of an array.
js_arr_get(js, arr, idx)
ant_value_t
Read an element from an array by zero-based index.
js_arr_len(js, arr)
ant_offset_t
Return the length of an array.
Create objects and arrays in C and pass them into scripts as globals:
ant_value_t config = js_mkobj(js);
js_set(js, config, "debug", js_true);
js_set(js, config, "version", js_mknum(1.0));
js_set(js, config, "name", js_mkstr(js, "MyApp", 5));
js_set(js, js_glob(js), "config", config);

ant_value_t arr = js_mkarr(js);
js_arr_push(js, arr, js_mknum(10));
js_arr_push(js, arr, js_mknum(20));
js_arr_push(js, arr, js_mknum(30));
js_set(js, js_glob(js), "numbers", arr);

const char *code = "config.name + ' v' + config.version + ' - sum: ' + numbers.reduce((a,b) => a+b, 0)";
ant_value_t result = js_eval_bytecode_eval(js, code, strlen(code));
printf("  Result: %s\n", js_str(js, result));

Calling JavaScript functions from C

Evaluate the function definitions first, then retrieve the function values from the global object and call them with sv_vm_call.
const char *code =
  "function multiply(a, b) {"
  "    return a * b;"
  "}"
  "function formatName(first, last) {"
  "    return last + ', ' + first;"
  "}";

js_eval_bytecode_eval(js, code, strlen(code));

ant_value_t glob = js_glob(js);
ant_value_t multiply_fn = js_get(js, glob, "multiply");
ant_value_t format_fn   = js_get(js, glob, "formatName");

ant_value_t args1[] = { js_mknum(6), js_mknum(7) };
ant_value_t result1 = sv_vm_call(js->vm, js, multiply_fn, js_mkundef(), args1, 2, NULL, false);
printf("  multiply(6, 7) = %g\n", js_getnum(result1));

ant_value_t args2[] = {
  js_mkstr(js, "John", 4),
  js_mkstr(js, "Doe", 3)
};
ant_value_t result2 = sv_vm_call(js->vm, js, format_fn, js_mkundef(), args2, 2, NULL, false);
printf("  formatName('John', 'Doe') = %s\n", js_str(js, result2));
sv_vm_call(vm, js, fn, this, args, nargs, NULL, false) — pass js->vm for the first argument, the function value, a this value (js_mkundef() for a plain call), and the argument array with its length.

Iterating object properties

Use js_prop_iter_begin, js_prop_iter_next, and js_prop_iter_end to walk the enumerable properties of an object:
const char *code = "({ name: 'Alice', age: 30, city: 'NYC' })";
ant_value_t obj = js_eval_bytecode_eval(js, code, strlen(code));

ant_iter_t iter = js_prop_iter_begin(js, obj);
const char *key;
size_t key_len;
ant_value_t value;

while (js_prop_iter_next(&iter, &key, &key_len, &value)) {
  printf("  %.*s = %s\n", (int)key_len, key, js_str(js, value));
}
js_prop_iter_end(&iter);
Always call js_prop_iter_end even if you exit the loop early, to release resources held by the iterator.

Stateful sessions

State accumulates across multiple js_eval_bytecode_eval calls on the same runtime. You can define variables and functions in one call and use them in later calls:
const char *scripts[] = {
  "let counter = 0;",
  "function increment() { return ++counter; }",
  "function getCount() { return counter; }",
  "increment(); increment(); increment();",
  "getCount()"
};

ant_value_t result = js_mkundef();
for (int i = 0; i < 5; i++) {
  result = js_eval_bytecode_eval(js, scripts[i], strlen(scripts[i]));
  if (vtype(result) == T_ERR) {
    printf("Error in script %d: %s\n", i, js_str(js, result));
    break;
  }
}

printf("Final count: %g\n", js_getnum(result)); // 3

Async code and the event loop

Scripts that use setTimeout, Promise, or queueMicrotask schedule work on the event loop. Call js_run_event_loop(js) after evaluating the script to drain all pending work:
init_symbol_module();
init_builtin_module();
init_timer_module();

const char *code =
  "let results = [];"
  "setTimeout(() => { results.push('timer'); }, 10);"
  "Promise.resolve('p').then(v => { results.push(v); });"
  "results.push('sync');";

js_eval_bytecode_eval(js, code, strlen(code));
js_run_event_loop(js);

ant_value_t results = js_get(js, js_glob(js), "results");
ant_offset_t len = js_arr_len(js, results);

for (ant_offset_t i = 0; i < len; i++) {
  ant_value_t item = js_arr_get(js, results, i);
  printf("  %llu. %s\n", (unsigned long long)i + 1, js_str(js, item));
}
Call init_console_module() before evaluating any script that uses console.log, console.warn, or console.error. Similarly, call init_timer_module() before using setTimeout or setInterval.

Error handling

js_eval_bytecode_eval never throws — it returns an ant_value_t with type T_ERR on failure. Always check before using the result:
const char *bad_code = "let x = {";
ant_value_t r = js_eval_bytecode_eval(js, bad_code, strlen(bad_code));
if (vtype(r) == T_ERR) {
  printf("Syntax error: %s\n", js_str(js, r));
}
C functions exposed to JavaScript should return js_mkerr(js, msg) on failure rather than calling abort or exit. Ant turns the returned error value into a JavaScript exception that the script can catch.