Introduction to Rampart

Preface

Acknowledgement

Rampart uses the Duktape JavaScript Engine. Duktape is an embeddable JavaScript engine, with a focus on portability and compact footprint. The developers of Rampart are extremely grateful for the excellent api and ease of use of this library.

License

Duktape and the Core Rampart program are MIT licensed.

Also included in Rampart is linenoise.c (under the BSD 2 Clause License), setproctitle.c (under the MIT license) and whereami.c (under the MIT license or the WTFPLv2). The developers of Rampart wish to extend their thanks for the excellent code.

What does it do?

Rampart uses a low memory footprint JavaScript interpreter to bring together several high performance tools and useful utilities for use in Web and information management applications. At its core is the Duktape JavaScript library and added to it is a SQL database, full text search engine, a memory map NOSQL database, a fast multi-threaded webserver, client functionality via the Curl, crypto functions via OpenSSL and more. It attempts to provide performance, maximum flexibility and ease of use through the marriage of C code and JavaScript scripting.

Features

Core features of Duktape

A partial list of Duktape features:

  • Partial support for ECMAScript 2015 (E6) and ECMAScript 2016 (E7).
  • ES2015 TypedArray and Node.js Buffer bindings
  • CBOR bindings
  • Encoding API bindings based on the WHATWG Encoding Living Standard
  • performance.now()
  • Built-in regular expression engine
  • Built-in Unicode support
  • Combined reference counting and mark-and-sweep garbage collection with finalization
  • Property virtualization using a subset of ECMAScript ES2015 Proxy object
  • Bytecode dump/load for caching compiled functions

See full list Here

Rampart additions

In addition to the standard features in Duktape JavaScript, Rampart adds the following:

  • Standard module support for C and JavaScript modules via the require() function.
  • File and C-functions utilities such as printf, fseek, and exec.
  • Included C modules (rampart-sql, rampart-server, rampart-curl, rampart-crypto, rampart-html, rampart-lmdb, rampart-redis, rampart-cmark, rampart-net, rampart-python and rampart-robots).
  • Event loop using libevent2.
  • ECMA 2015, 2016, 2017 and 2018 support using the Babel JavaScript transpiler.
  • Full Text Search and SQL databasing via rampart-sql.
  • Generic threading, locking and variable sharing via rampart.thread.
  • Multi-threaded http(s) server from libevhtp_ws via rampart-server.
  • HTTP, FTP, etc. client functionality via rampart-curl.
  • Cryptography functions from OpenSSL via rampart-crypto.
  • HTML parsing and and error correcting via rampart-html.
  • Fast NOSQL database via rampart-lmdb.
  • Redis Client via rampart-redis.
  • Asynchronous networking and socket functions via rampart-net.
  • Python interpreter, running python functions and sharing variables via rampart-python
  • Simple Event functions via rampart.event.
  • Extra JavaScript Functionality.

Rampart philosophy

Rampart uses the Duktape JavaScript Engine and API as a gateway for high performance functions written in C. JavaScript execution with Duktape is more memory efficient, but interpertation is slower than with, e.g., node.js. However, the functionality and speed of the available C functions provide comparable efficacy, excellent performance and is a viable alternative to LAMP, MEAN or other stacks, all in a single product, while consuming considerably less resources than the aforementioned.

Rampart Global Variable and Functions

Rampart provides global variables beyond what is available in Duktape: rampart and process, as well as the require function. Below is a listing of these added functions.

rampart.globalize

Put all or named properties of an Object in the global namespace.

rampart.globalize(var_obj [, prop_names]);
Argument Type Description
var_obj Object The Object with the properties to be globalized
prop_names Array optional Array of property names to be put into the global namespace. If specified, only the named properties will be copied.

Without prop_names, this is equivalent to Object.assign(global, var_obj);.

With prop_names, this is equivalent to for (var k in prop_names) global[[prop_names[k]]] = var_obj[[prop_names[k]]];

Return value:
undefined.

Example:

 rampart.globalize(rampart.utils);
 printf("rampart.utils.* are now global vars!\n");

 /* or */

rampart.globalize(rampart.utils, ["printf"]);
printf("only printf is a global var\n");

rampart.utils

A collection of utility functions. See Rampart Utility Functions for full description of functions.

rampart.event

Rampart can execute functions from within its event loop using its own event-on-trigger syntax. When used across threads, a registered function is executed in the thread in which it was registered and may be triggered from any thread.

rampart.event.on()

Register a named function to be run upon triggering a named event. If the named event does not exist, it will be created.

Usage:

rampart.event.on(eventName, funcName, callback, callbackUserVar);

Where:

  • eventName is an arbitrary String used to identify, trigger and remove the event using the rampart.event.trigger() and rampart.event.remove() function below.
  • funcName is an arbitrary String used to identify and remove the callback function using the rampart.event.off() function below.
  • callback is a Function to be executed when the event is triggered. It is called, when triggered, as such: callback(callbackUserVar, callbackTriggerVar).
  • callbackUserVar is an arbitrary variable which will be passed to the callback Function as its first parameter.

rampart.event.trigger()

Trigger a named event, calling all the callbacks registered under the given name.

rampart.event.trigger(eventName, callbackTriggerVar);

Where:

  • eventName is the String used when registering the event with rampart.event.on().
  • callbackTriggerVar is the second parameter passed to the callback function specified when the event and function were registered with rampart.event.on().
  • Caveat, the callbackTriggerVar must be a variable which can be serialized using CBOR. Because this function may trigger events that span several threads and Duktape stacks, when used with the rampart-server or rampart-thread modules, special variables such as req (see: The Request Object) may contain functions and hidden state variables which cannot be moved from stack to stack. In most cases, it will not be limiting since each callback is run on its own thread/stack and can take a callbackUserVar which does not have the above limitations.

rampart.event.off()

Remove a named function from the list of functions for the given event.

rampart.event.off(eventName, funcName);

Where:

  • eventName is a String, the eventName passed to the rampart.utils.on() function above.
  • funcName is a String, the funcName passed to the rampart.utils.on() function above.

rampart.event.remove()

Remove all function from the list of functions for the given event. This effectively removes the event.

rampart.event.remove(eventName);

Where:

  • eventName is a String, the eventName passed to the rampart.utils.on() function above.

rampart.event.scopeToModule()

Scope rampart.event functions set with rampart.event.on from inside a module to that module only. If set, rampart.event.trigger will only trigger the named event from inside a module if it was set in the same module. This is useful for long lived scripts such as used with the rampart server module.

This setting also separates events set and triggered in modules from those in the main script.

See Using the require Function to Import Modules below for information on modules.

Note: This should be set before any events are created. Once this is turned on, it cannot be turned off in the same invocation of the script.

Example

var usr_var = "I'm a user variable.";

function myCallback (uservar,triggervar){

    console.log(uservar, "Triggervar = "+triggervar);
    rampart.utils.sleep(0.5);

    if(triggervar>4)
        rampart.event.remove("myev");

    rampart.event.trigger("myev", triggervar+1);
}

rampart.event.on("myev", "myfunc", myCallback, usr_var);

rampart.event.trigger("myev", 1);

/* expected output:
I'm a user variable. Triggervar = 1
I'm a user variable. Triggervar = 2
I'm a user variable. Triggervar = 3
I'm a user variable. Triggervar = 4
I'm a user variable. Triggervar = 5
*/

See also: the Echo/Chat Server Example.

rampart.include

Include the source of a file in the current script as global code.

Usage:

rampart.include(jsfile);

Where jsfile is the path of the script to be included.

If jsfile is not a absolute path name it will be searched for in the same manner as with Module Search Path except that in addition to the current directory and the process.scriptPath directory, it will search in /usr/local/rampart/includes/ and ~/.rampart/includes/ rather than the equivalent */modules/ paths.

The rampart.include function is similar to the following code:

var icode = rampart.utils.readFile({file: jsfile, returnString:true});
eval(icode);

With the exception that it:

Return Value: undefined

rampart.import

csvFile

The csvFile Function imports csv data from a file. It takes a String containing a file name and optionally an Object of options and/or a callback Function. The parameters may be specified in any order.

Usage:

var res = rampart.import.csvFile(filename [, options] [, callback]);
Argument Type Description
filename String The csv file to import
options Object Options described below
callback Function a function to handle data one row at a time.
filename:
The name of the csv file to be opened;
options:

The options Object may contain any of the following.

  • stripLeadingWhite - Boolean (default true): Remove leading whitespace characters from cells.
  • stripTrailingWhite - Boolean (default true): Remove trailing whitespace characters from cells.
  • doubleQuoteEscape - Boolean (default false): "" within strings is used to embed " characters.
  • singleQuoteNest - Boolean (default true): Strings may be bounded by ' pairs and " characters within are ignored.
  • backslashEscape - Boolean (default true): Characters preceded by ‘' are translated and escaped.
  • allEscapes - Boolean (default true): All \ escape sequences known by the ‘C’ compiler are translated, if false only backslash, single quote, and double quote are escaped.
  • europeanDecimal - Boolean (default false): Numbers like 123 456,78 will be parsed as 123456.78.
  • tryParsingStrings - Boolean (default false): Look inside quoted strings for dates and numbers to parse, if false anything quoted is a string.
  • delimiter - String (default ","): Use the first character of string as a column delimiter (e.g \t).
  • timeFormat - String (default "%Y-%m-%d %H:%M:%S"): Set the format for parsing a date/time. See man page for strptime().
  • returnType- String (default "array", optionally "object"): Whether to return an Array or an Object for each row.
  • hasHeaderRow - - Boolean (default false): Whether to treat the first row as column names. If false, the first row is imported as csv data and the column names will default to col_1, col_2, ..., col_n.
  • normalize - Boolean (default false): If true, examine each column in the parsed CSV object to find the majority type of that column. It then casts all the members of that column to the majority type, or set it to null if it is unable to do so. If false, each cell is individually normalized.
  • includeRawString - Boolean (default false): if true, return each cell as an object containing {value: normalized value, raw: originalString}. If false, each cell value is the primitive normalized value.
  • progressFunc - Function: A function to monitor the progress of the passes over the csv data. It takes as arguments function(i, stage) The variable stage is 0 for the initial counting of rows, 1 for the parsing of the cells in each row and 2+ optionally if normalize is true for the two stages of the analysis of each column in the csv (e.g. 2 for column 0 first pass, 3 for column 0 second pass, etc.). The variable i is the current row number.
  • progressStep Number: Where number is n, execute progresFunc callback, if provided, for every nth row in each stage.
callback:

A Function taking as parameters (result_row, index, columns). The callback is executed once for each row in the csv file:

  • result_row: (Array/Object): depending on the setting of returnType in Options above, a single row is passed to the callback as an Object or an Array.
  • index: (Number) The ordinal number of the current search result.
  • columns: an Array corresponding to the column names or aliases selected and returned in results.
Return Value:

Number/Object.

With no callback, an Object is returned. The Object contains three key/value pairs:

  • Key: results - Value: an Array of Arrays. Each outer Array corresponds to a row in the csv file and each inner Array corresponds to the columns in that row. If returnType is set to "object", an Array of Objects with keys set to the corresponding column names and the values set to the corresponding column values of the imported row.
  • Key: rowCount - Value: a Number corresponding to the number of rows returned.
  • Key: columns - Value: an Array corresponding to the column names or aliases selected and returned in results.

With a callback, the return value is set to number of rows in the csv file (not including the Header if hasHeaderRow is true).

Note: In the callback, the loop can be canceled at any point by returning false. The return value (number of rows) will still be the total number of rows in the csv file.

csv

Usage:

var res = rampart.import.csv(csvData [, options] [, callback]);

Same as csvFile() except instead of a file name, a String or Buffer containing the csv data is passed as a parameter.

Example:

var csvdata =
"column 1, column 2, column 3, column 4\n"+
"1.0, val2, val3, val4\n" +
"valx, val5, val6, value 7\n";

/* no callback */
console.log(
  JSON.stringify(
    rampart.import.csv(csvdata,
        {
            hasHeaderRow: true,
            normalize: true
        }
    ),null,3
  )
);

/* with callback */
var rows=rampart.import.csv(
   csvdata,
   {
      hasHeaderRow: true,
      normalize: true,
      returnType:'object',
      includeRawString:true
   },
   function(res,i,col){
        console.log(i,res,col);
   }
);

console.log("rows:", rows);

/* expected output:
{
   "results": [
      [
         1,
         "val2",
         "val3",
         "val4"
      ],
      [
         null,
         "val5",
         "val6",
         "value 7"
      ]
   ],
   "columns": [
      "column 1",
      "column 2",
      "column 3",
      "column 4"
   ],
   "rowCount": 2
}
0 {"column 1":{value:1,raw:"1.0"},"column 2":{value:"val2",raw:"val2"},"column 3":{value:"val3",raw:"val3"},"column 4":{value:"val4",raw:"val4"}} ["column 1","column 2","column 3","column 4"]
1 {"column 1":{value:null,raw:"valx"},"column 2":{value:"val5",raw:"val5"},"column 3":{value:"val6",raw:"val6"},"column 4":{value:"value 7",raw:"value 7"}} ["column 1","column 2","column 3","column 4"]
rows: 2
*/

Process Global Variable and Functions

The process global variable has the following properties:

exit

The exit function terminates the execution of the current script.

Usage:

process.exit([exitcode]);

Where the optional exitcode is a Number, the status that Rampart returns to its parent (default: 0);

env

The value of process.env is an Object containing properties and values corresponding to the environment variables available to Rampart upon execution.

argv

The value of process.argv is an Array of the arguments passed to rampart upon execution. The first member is always the name of the rampart executable. The second is usually the filename of the script provided on the command line. However if flags are present (arguments starting with -), the script name may be a later argument. Subsequent members occur in the order they were given on the command line.

installPath

The value of process.installPath is a String containing the canonical path (directory) of the rampart install directory. It is derived from the path of the rampart executable, removing ‘/bin’ from the end of the path if exists. Example: if /usr/local/bin/rampart is run (and is the actual location of the executable and not a symlink), process.installPath will be /usr/local. However if the executable is in a path that does not end in bin/ (e.g. ~/mytestfiles/rampart), process.installPath will be the location of the executable (and the same as installPathBin below). process.installPath is used internally to locate modules and other files used by rampart. See Module Search Path below.

installPathBin

The value of process.installPathBin is a String containing the canonical path of the directory containing the rampart executable.

modulesPath

The value of process.modulesPath is a String containing the canonical path (directory) in which the standard installed modules can be found.

scriptPath

The value of process.scriptPath is a String containing the canonical path (directory) in which the currently executing script can be found (e.g. if rampart /path/to/my/script.js is run, process.scriptPath will be /path/to/my).

scriptName

The value of process.scriptName is a String, the name of the currently executing script (e.g. if rampart /path/to/my/script.js is run, process.scriptName will be script.js).

script

The value of process.script is a String containing the canonical path (file) of the currently executing script (e.g. if rampart /path/to/my/script.js is run, process.script will be /path/to/my/script.js).

getpid

Get the process id of the current process.

Usage:

var pid = process.getpid();
Return Value:
Number. The pid of the current process.

getppid

Get the process id of the parent of the current process.

Usage:

var ppid = process.getppid();
Return Value:
Number. The pid of the parent process.

setProcTitle

Set the name of the current process (as seen by the command line utilities such as ps and top).

Usage:

process.setProcTitle(newname);

Where newname is the new name for the current process.

Return Value:
undefined.

Using the require Function to Import Modules

Scripts may reference function stored in external files. These files are known as modules. A module is a compiled C program or a JavaScript file which exports an Object or Function when the require("module-name") syntax is used.

Example for the SQL C Module:

var Sql = require("rampart-sql");

This will search the current directory and the rampart modules directories for a module named rampart-sql.so or rampart-sql.js and use the first one found. In this case rampart-sql.so will be found and the SQL module and its functions will be usable via the named variable Sql. See, e.g, The rampart-sql documentation for full details.

Example creating a JavaScript module

If you have an often used function, or a function used for serving web pages with The rampart-server HTTP module, it can be placed in a separate file (here the file is named times2.js):

function timestwo (num) {
   return num * 2;
}

module.exports=timestwo;

The module.exports variable is set to the Object or Function being exported.

In another script, the exported timestwo function could be accessed as such:

var x2 = require("times2");
/* alternatively
  var x2 = require("times2.js");
*/

var res = x2(5);

/* res == 10 */

Note also that from within a module, the module object contains some useful information. An example module named mod.js and loaded with the statement require("mod.js") will have module set to a value similar to the following:

{
   "id": "/path/to/my/mod.js",
   "path": "/path/to/my",
   "exports": {},
   "mtime": 1624904227,
   "atime": 1624904227
}

Example creating a C module

A module can also be written in C. Below is an example where the filename is times3.c:

#include "rampart.h"

static duk_ret_t timesthree(duk_context *ctx)
{
    double num = duk_get_number_default(ctx, 0, 0.0);

    duk_push_number(ctx, num * 3.0 );

    return 1;
}


/* **************************************************
   Initialize module
   ************************************************** */
duk_ret_t duk_open_module(duk_context *ctx)
{
  duk_push_c_function(ctx, timesthree, 1);

  return 1;
}

In this example, the item on the top of the value stack (when the C function returns 1) in the timesthree() function will be the return value of the exported function.

The timesthree function is made available to JavaScript in a function that must be named duk_open_module. The C function pushed to the top of the stack (when duk_open_module() returns 1) will be the return value of the require() function in JavaScript.

The duk_open_module alternatively can push an Object which contains functions and/or other JavaScript variables.

This could be compiled with GCC as follows:

cc -I/usr/local/rampart/include -fPIC -shared -Wl,-soname,times3.so -o times3.so times3.c

On MacOs, the following might be used:

cc -I/usr/local/rampart/include -dynamiclib -undefined dynamic_lookup -install_name times3.so -o times3.so times3.c

The module could then be imported using the require() function.

var x3 = require("times3");

var res = x3(5);

/* res == 15 */

See The Duktape API Documentation for a detailed listing of available Duktape C API functions.

Module Search Path

Modules are searched for in the following order:

  1. If /path/to/module.js is given, the absolute path is checked first. If absolute and not found, the search stops there.
  2. If included from within a module, in the module’s path.
  3. In process.scriptPath.
  4. In the directory or modules/ or lib/rampart_modules/ subdirectory of process.scriptPath.
  5. In the ~/.rampart/modules or /lib/rampart_modules/ directory of current user’s home directory as provided by the $HOME environment variable. If $HOME is not set, /tmp is used.
  6. If set, in the directory as provided by the $RAMPART_PATH environment variable.
  7. In process.modulesPath, i.e. the modules or /lib/rampart_modules/ subdirectory of process.installPath.

Extra JavaScript Functionality

A subset of post ES5 JavaScript syntax is supported when not using babel below. It is provided experimentally (unsupported) and is limited in scope.

Object.values()

Return an Array containing the values of an object.

var obj = {
   key1: "val1",
   key2: "val2"
}

console.log(Object.values(obj));
/* expected output:
   ["val1","val2"]              */

Template Literals

These may be used in the same manner as in standard ES6 JavaScript:

var type, color;

var out = `I'm a ${color? color: `black`} ${ type ? `${type} ` : `tea`}pot`;
/* out = "I'm a black teapot" */

type = "coffee";
color = "red";
out = `I'm a ${color? color: `black`} ${ type ? `${type} ` : `tea`}pot`;
/* out = "I'm a red coffee pot" */

Tagged Functions

These may be used in the same manner as in standard ES6 JavaScript:

function aboutMe(strings) {
   var keys = Object.values(arguments).slice(1);
   console.log(strings);
   console.log(keys);
}

var name="Francis", age=31;

aboutMe`My name is ${name} and I am ${age} years old`;
/* expected output:
   ["My name is "," and I am "," years old"]
   ["Francis",31]
*/

Rest Parameters

Rest Parameter syntax may also be used for arguments to functions.

function aboutMe(strings, ...keys) {
   console.log(strings);
   console.log(keys);
}

var name="Francis", age=31;

aboutMe`My name is ${name} and I am ${age} years old`;
/* expected output:
   ["My name is "," and I am "," years old"]
   ["Francis",31]
*/

Template Literals and sprintf

A non-standard (and unique to Rampart) shortcut syntax may be used in template literals in place of rampart.utils.sprintf by specifying a format string followed by a colon : in a substituted variable (${}). If the string begins with a %, or if the string is quoted with single or double quotes rampart.utils.sprintf is called.

Example:

var myhtml = `
<div>
    my contents
</div>
`;

/* same as:
console.log("Here is the html:<br>\n<pre>"+rampart.utils.sprintf("%H",myhtml)+"</pre>");
*/
console.log(`Here is the html:<br>\n<pre>${%H:myhtml}</pre>`);

/* or */

/* same as:
console.log("Here is the html:<br>\n"+rampart.utils.sprintf("<pre>%H</pre>",myhtml));
*/
console.log(`Here is the html<br>\n${"<pre>%H</pre>":myhtml}`);

/* expected output:
Here is the html:<br>
<pre>
&lt;div&gt;
    my contents
&lt;&#47;div&gt;
</pre>
*/

Note that this non-standard syntax is not available when using babel below.

Unescaped Literals

A non-standard (and unique to Rampart) shortcut syntax using triple backticks may be used in place of normal Strings where \ have no special meaning. It also accepts multi-lined strings.

Example:

var unescaped = ```here is a single backslash:
\
```;
/* equiv to ""here is a single backslash:\n\\\n"; */

Note that this non-standard syntax is not available when using babel below.

Duktape/Node.js Buffer Binding Extras

The Duktape JavaScript engine provides basic node.js Buffer support. Rampart adds the following:

Buffer.alloc()

Allocate a new node.js style Buffer.

Usage:

var newBuf = Buffer.alloc(size[, fill]);

Where:

  • size is a Number - The size of the Buffer to be created.
  • fill is a String or Buffer - If provided, buffer will be initialized with this data. If smaller than buffer, data will repeat. If not provided, the Buffer will be initialized with 0;

Buffer.from()

Create a new node.js style Buffer from existing data.

Usage:

var newBuf = Buffer.from(data);

Where from is a String or Buffer, the data which will be copied into the new Buffer.

setTimeout()

Also added to Rampart is the setTimeout() function. It supports the asynchronous calling of functions from within Rampart’s event loop in the same manner as setTimeout in node.js or a browser such as Firefox or Chrome.

Usage:

var id = setTimeout(callback, timeOut[, arg1, arg2, ..., argn]);

Where:

  • callback is a Function to be run when the elapsed time is reached.
  • timeOut is the amount of time in milliseconds to wait before the callback function is called.
  • argX are arguments to be passed to the callback function.
Return Value:
An id which may be used with clearTimeout().

Example:

/* print message after 2 seconds */
setTimeout(function(){ console.log("Hi from a timeout callback"); }, 2000);

Note that Rampart JavaScript executes all global code before entering its event loop. Thus if a script uses synchronous functions that take longer than timeOut, the callback will be run immediately after the global code is executed. Consider the following:

setTimeout(function(){ console.log("Hi from a timeout callback"); }, 2000);

rampart.utils.sleep(3);

The callback function will not be executed until after the sleep function returns. At that time, the clock will have expired and the setTimeout callback will be run immediately. The net effect is that console.log will be executed after approximately 3 seconds.

clearTimeout()

Clear a pending setTimeout() timer before it has executed.

Usage:

var id = setTimeout(callback, timeOut);

clearTimeout(id);

Where:

Return Value:
undefined

setInterval()

Similar to setTimeout() except it repeats every interval milliseconds until canceled via clearInterval().

Usage:

var id = setInterval(callback, interval[, arg1, arg2, ..., argn]);

Where:

  • callback is a Function to be run when the elapsed time is reached.
  • interval is the amount of time in milliseconds between calls to callback.
  • argX are arguments to be passed to the callback function.
Return Value:
An id which may be used with clearInterval().

Example:

var x=0;

/* print message every second, 10 times */
var id = setInterval(function(){
     x++;
     console.log("loop " + x);
     if(x>9) {
         clearInterval(id);
         console.log("all done");
     }
}, 1000);

clearInterval()

Clear a pending setInterval() timer, breaking the loop.

Usage:

var id = setInterval(callback, interval);

clearInterval(id);

Where:

Return Value:
undefined

setMetronome()

Similar to setInterval() except it repeats every interval milliseconds as close to the scheduled time as possible, possibly skipping intervals (aims for the absolute value of starttime + count * interval and skips if past that time).

Usage:

var id = setMetronome(callback, interval[, arg1, arg2, ..., argn]);

Where:

  • callback is a Function to be run when the elapsed time is reached.
  • interval is the amount of time in milliseconds between calls to callback.
  • argX are arguments to be passed to the callback function.
Return Value:
An id which may be used with clearMetronome().

Example:

rampart.globalize(rampart.utils);

var x=0;
var id=setMetronome(function(){
    var r = Math.random()*2;

    printf("%d %.3f %.3f\n", x++, r, (performance.now()/1000)%100);

    if(x>9)
        clearMetronome(id);

    sleep(r); //sleep a random amount of time between 0 and 2 seconds
},1000);

/* Output will be similar to:

0 0.884 45.759
1 0.574 46.759
2 1.737 47.759
3 0.810 49.759
4 0.792 50.759
5 1.616 51.759
6 1.989 53.759
7 1.959 55.759
8 1.275 57.758
9 0.324 59.760

NOTE: where the sleep time is greater than 1 second, that
      second is skipped in order to keep the timing.
*/

clearMetronome()

Clear a pending setMetronome() timer, breaking the loop.

Usage:

var id = setMetronome(callback, interval);

clearMetronome(id);

Where:

Return Value:
undefined
NOTE:
clearTimeout(), clearInterval() and clearMetronome() internally are aliases for the same function and will clear whichever id is specified, regardless of type.

Additional Global Variables and Functions

Other global variables are provided by the Duktape JavaScript engine and include:

For more information, see the Duktape Guide

ECMAScript 2015+ and Babel.js

Babel Acknowledgement

Rampart experimentally uses Babel.js to support a greater breath of JavaScript syntax and functionality. Babel.js is a toolchain that converts ECMAScript 2015+ (and optionally TypeScript) code into a version of JavaScript compatible with Duktape. The authors of Rampart are extremely grateful to the Babel development team.

Babel License

Babel.js is MIT licensed.

Usage

A slightly modified version of babel.js (currently babel-standalone v 7.11.1) and the associated collection of polyfills (babel-polyfill.js) are included in the Rampart distribution. To use ECMA 2015+ features of JavaScript, simply include the following at the beginning of the script:

"use babel"

 /* or */

 "use babelGlobally" //run included modules through babel as well

Note that the "use babel" or "use babelGlobally" string should be the first JavaScript text in the script. However it may come after any comments or a hash-bang line. It also should be the only text on the line, other than an optional comment.

Example:

#!/usr/local/bin/rampart
// above is ignored by rampart.

/* My first ECMA 2015 Script using Rampart/Duktape/Babel */

"use babel" /* a comment on this line is ok */

console.log(`a multi-line string
using backticks is much easier than
using
console.log(
             "string\\n" +
             "string2\\n"
           );
`);

The "use babel" directive optionally takes a : followed by babel options. Without options "use babel" is equivalent to "use babel:{ presets: ['env'], retainLines: true }". See babel documentation for more information on possible options.

A simple example in TypeScript:

/* note that filename is required for 'typescript'
   and that 'env' is also included to allow for ECMA 2015+  */

"use babel:{ filename: 'myfile.ts', presets: ['typescript','env'], retainLines: true }"

interface Point {
  x: number;
  y: number;
}

function printPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}

// prints "12, 26"
const point = { x: 12, y: 26 };
printPoint(point);

Note that babel does not actually do any type checking. See this caveat.

For a list of tested and supported syntax, see the /usr/local/rampart/tests/babel-test.js file (also available here.

How it works

When the "use babel" string is found, Rampart automatically loads babel.js and uses it to transpile the script into JavaScript compatible with the Duktape JavaScript engine. A cache copy of the transpiled script will be saved in the same directory, and will be named by removing .js from the original script name and replacing it with .babel.js. Thus if, e.g., the original script was named myfile.js, the transpiled version will be named myfile.babel.js.

When the original script is run again, Rampart will check the date on the script, and if it was not modified after the modification date of the *.babel.js file, the transpile stage will be skipped and the transpiled script will be run directly.

Caveats

For a complicated script, the transpile stage can be very slow. However if the script has not changed since last run, the execution speed will be normal as the cached/transpiled code will be used and thus no traspiling will occur.

Asynchronous code may also be used with babel. For example, the following code produces the same output in Rampart and Node.js.

"use babel" /* ignored in node */

function resolveme() {
  return new Promise(resolve => {

    setTimeout(() => {
      console.log("**I'm async in a Timeout!!**");
    },5);

    resolve("**I'm async!!**");

  });
}

async function asyncCall() {
  const result = await resolveme();
  console.log(result);
}

asyncCall();

console.log(
`a multiline string
using backticks`
);

/* expect output:
a multiline string
using backticks
**I'm async!!**
**I'm async in a Timeout!!**
*/