# Node.js Evaluation Loop (NEL)

`NEL` is an [npm module](https://www.npmjs.com/) for running
[Node.js](https://nodejs.org/) REPL sessions.

`NEL` is a spin-off library from
[IJavascript](https://n-riesco.github.io/ijavascript). This fact explains some
of the design decisions in `NEL`, such as returning results in MIME format, and
the functionality provided for completion and inspection of Javascript
expressions.  See the section on usage for more details.

`NEL` is used by the following Jupyter kernels:
- [IJavascript](https://n-riesco.github.io/ijavascript)
- [ITypescript](https://www.npmjs.com/package/itypescript)
- [jp-babel](https://github.com/n-riesco/jp-babel)
- [jp-coffeescript](https://github.com/n-riesco/jp-coffeescript)

## Main Features

- Run Javascript code within a `Node.js` session. The result can be formatted
  as:
  - [plain text](http://n-riesco.github.io/ijavascript/doc/hello.ipynb.html)
  - [other MIME
    formats](http://n-riesco.github.io/ijavascript/doc/custom.ipynb.html)
    (`HTML`, `SVG`, `PNG`...)
- Run Javascript code
  [asynchronously](http://n-riesco.github.io/ijavascript/doc/async.ipynb.html).
- Generate a list of [completion
  options](http://n-riesco.github.io/ijavascript/doc/complete.md.html) for an
  incomplete piece of Javascript code.
- [Inspect a Javascript
  expression](http://n-riesco.github.io/ijavascript/doc/inspect.md.html) and
  return information such as type or even documentation (currently, only for
  Javascript builtins).

## Announcements

- `NEL v1.3.0`: API enhancement (added option awaitExecution)
- `NEL v1.2.0`: API enhancement (Session#transpile may return a Promise)
- `NEL v1.1.0`: API enhancement (added $$.clear({wait}))
- `NEL v1.0.0`: Stable API
- `NEL v0.5.6`: API enhancement (added $$.input() and onRequest callback)
- `NEL v0.5.5`: Accept Promises as output
- `NEL v0.5.4`: API enhancement (added $$.display() and onDisplay callback)
- `NEL v0.5`: API enhancement (added transpile option)
- `NEL v0.4`: API enhancement (added onStdout and onStderr callbacks)
- `NEL v0.3`: New API (simplify API by hiding type module:nel~Task)
- `NEL v0.2`: API change (removed Session#executionCount)
- `NEL v0.1.1`: API enhancement (experimental `$$mimer$$` and `$$defaultMimer$$`)
- `NEL v0.1`: Output change (changed function output)
- `NEL v0.0`: Initial release

## Install

```sh
npm install nel
```

## Usage

The documentation generated by JSDoc can be found
[here](http://n-riesco.github.io/nel/).

### Hello, World!

```js
// Load `nel` module
var nel = require("nel");

// Setup a new Javascript session
var session = new nel.Session();

// Example of an execution request
// Output:
// { mime: { 'text/plain': '\'Hello, World!\'' } }
var code = "['Hello', 'World!'].join(', ');";
session.execute(code, {
    onSuccess: console.log,
    onError: console.error,
});
```

### Exceptions

```js
// Example of throwing an exception
// Output:
// { error:
//  { ename: 'Error',
//    evalue: 'Hello, World!',
//    traceback:
//     [ 'Error: Hello, World!',
//       '    at evalmachine.<anonymous>:1:7',
//       '    at run ([eval]:182:19)',
//       '    at onMessage ([eval]:63:41)',
//       '    at process.EventEmitter.emit (events.js:98:17)',
//       '    at handleMessage (child_process.js:318:10)',
//       '    at Pipe.channel.onread (child_process.js:345:11)' ] } }
code = "throw new Error('Hello, World!');";
session.execute(code, {
    onSuccess: console.log,
    onError: console.error,
});
```

### `stdout` and `stderr`

```js
// Example of use of console.log()
// Output:
// Hello, World!
//
// { mime: { 'text/plain': 'undefined' } }
code = "console.log('Hello, World!');";
session.execute(code, {
    onSuccess: console.log,
    onError: console.error,
    onStdout: console.log,
    onStderr: console.error,
});
```

### `onDisplay` callback

The [Jupyter messaging protocol](http://jupyter-client.readthedocs.io/en/latest/messaging.html#display-data)
introduces the concept of display. A display is very much like an execution
result. It is associated with an execution request and in protocol version 5.1
and above it can be assigned an ID for subsequent updates. Here's an example of
the support provided by `NEL`:

```js
// Example using onDisplay callback
// Output:
// { mime: { 'text/plain': 'Hello, World!' } }
code = "$$.display().text('Hello, World!');";
session.execute(code, {
    onDisplay: console.log,
});

// Example using a display ID
// Output:
// { display_id: 'test', mime: { 'text/plain': 'Hello, World!' } }
code = "$$.display('test').text('Hello, World!');";
session.execute(code, {
    onDisplay: console.log,
});
```

### `onRequest` callback and `$$.input(options, callback)`

The [Jupyter messaging protocol](http://jupyter-client.readthedocs.io/en/latest/messaging.html#messages-on-the-stdin-router-dealer-channel)
defines an stdin socket, so that a kernel can request an input from the user.
`NEL` defines `$$.input(options, callback)` to create such a request.

Here are two examples (first one passing a callback to `$$.input`; second one
using a `Promise` returned by `$$.input()`):

```js
// Example passing a callback to $$.input()
// Output:
// { mime: { 'text/plain': '\'opensesame\'' } }
code = "$$.input({prompt:'?', password: true}, function(error, reply) {$$.done(reply)});";

session.execute(code, {
    onRequest: function(request, onReply) {
        assert(request.input.prompt === "?");
        assert(request.input.password === true);

        onReply({input: "opensesame"});
    },
    onSuccess: console.log,
});

// Example using the Promise returned by $$.input()
// Output:
// { mime: { 'text/plain': '\'opensesame\'' } }
code = "(function($$) {$$.input({prompt:'?', password: true}).then($$.done);})($$);";

session.execute(code, {
    onRequest: function(request, onReply) {
        assert(request.input.prompt === "?");
        assert(request.input.password === true);

        onReply({input: "opensesame"});
    },
    onSuccess: console.log,
});
```

### `onRequest` callback and `$$.clear(options)`

The [Jupyter messaging protocol](http://jupyter-client.readthedocs.io/en/latest/messaging.html#clear_output)
defines the message `clear_output` for kernels to request the output of a cell
to be cleared. `NEL` provides `$$.clear(options)` to implement such a request.

Here's an example showing the use:

```js
// Example using $$.clear(options)
// Output:
// { clear: { wait: true } }
code = "$$.clear({wait: true});";

session.execute(code, {
    onRequest: console.log
});
```

### MIME output

A session may return results in MIME formats other than 'text/plain'.

```js
// HTML example
// Output:
// { mime: { 'text/html': '<div style=\'background-color:olive;width:50px;height:50px\'></div>' } }
code = "$$html$$ = \"<div style='background-color:olive;width:50px;height:50px'></div>\";";
session.execute(code, {
    onSuccess: console.log,
    onError: console.error,
});

// SVG example
// Output:
// { mime: { 'image/svg+xml': '<svg><rect width=80 height=80 style=\'fill: orange;\'/></svg>' } }
code = "$$svg$$ = \"<svg><rect width=80 height=80 style='fill: orange;'/></svg>\";";
session.execute(code, {
    onSuccess: console.log,
    onError: console.error,
});

// PNG example
code = "$$png$$ = require('fs').readFileSync('image.png').toString('base64');";
session.execute(code, {
    onSuccess: console.log,
    onError: console.error,
});

// JPEG example
code = "$$jpeg$$ = require('fs').readFileSync('image.jpg').toString('base64');";
session.execute(code, {
    onSuccess: console.log,
    onError: console.error,
});

// MIME example
code = "$$mime$$ = {\"text/html\": \"<div style='background-color:olive;width:50px;height:50px'></div>\"};";
session.execute(code, {
    onSuccess: console.log,
    onError: console.error,
});
```

### Promises are accepted as output

When the result of an execution request is a `Promise`, `NEL` enables
asynchronous execution automatically and waits for the `Promise` to resolve:

```js
// Example returning a Promise
// Output:
// { mime: { 'text/plain': '\'Hello, World!\'' } }
code = "Promise.resolve('Hello, World!');";

session.execute(code, {
    onSuccess: console.log,
});
```

### Generate a completion list

`NEL` can parse simple Javascript variable expressions and generate a list of
completion options:

```js
session.complete(
    "set",     // code
    3,         // cursorPos
    {
        onSuccess: console.log,
        onError: console.error,
    }
);

// Output:
// { completion:
//    { list: [ 'setImmediate', 'setInterval', 'setTimeout' ],
//      code: 'set',
//      cursorPos: 3,
//      matchedText: 'set',
//      cursorStart: 0,
//      cursorEnd: 3 } }
```

Note that the cursor position can be located anywhere within the Javascript
code:

```js
session.complete(
    "set",     // code
    2,         // cursorPos
    {
        onSuccess: console.log,
        onError: console.error,
    }
);

// Output:
// { completion:
//    { list: [ 'setImmediate', 'setInterval', 'setTimeout' ],
//      code: 'set',
//      cursorPos: 2,
//      matchedText: 'se',
//      cursorStart: 0,
//      cursorEnd: 3 } }
```

### Inspect an expression

`NEL` can parse simple Javascript variable expressions and inspect their value:

```js
code = "var a = [1, 2, 3];";
session.execute(code, null, onError);
session.inspect(
    code,      // code
    5,         // cursorPos
    {
        onSuccess: console.log,
        onError: console.error,
    }
);

// Output:
// { inspection:
//    { string: '[ 1, 2, 3 ]',
//      type: 'Array',
//      constructorList: [ 'Array', 'Object' ],
//      length: 3,
//      code: 'var a = [1, 2, 3];',
//      cursorPos: 5,
//      matchedText: 'a' } }
```

`NEL` can also provide relevant documentation (currently only available for
Javascript builtins):

```js
session.inspect(
    "parseInt", // code
    8,          // cursorPos
    {
        onSuccess: console.log,
        onError: console.error,
    }
);

// Output:
// { inspection:
//    { string: '[Function: parseInt]',
//      type: 'Object',
//      constructorList: [ 'Function', 'Object' ],
//      length: 2,
//      code: 'parseInt',
//      cursorPos: 8,
//      matchedText: 'parseInt' },
//   doc:
//    { description: 'The parseInt() function parses a string argument and returns an integer of the specified radix (the base in mathematical numeral systems).',
//      url: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt',
//      usage: 'parseInt(string, radix);' } }
```

### Callbacks `beforeRun` and `afterRun`

```js
var beforeRun = function() { console.log("This callback runs first"); }
code = "'I run next'";
var afterRun = function() { console.log("This callback runs last"); }
session.execute(code, {
    onSuccess: console.log,
    onError: console.error,
    beforeRun: beforeRun,
    afterRun: afterRun,
});

// Output:
// This callback runs first
// { mime: { 'text/plain': '\'I run next\'' } }
// This callback runs last
```

## Contributions

First of all, thank you for taking the time to contribute. Please, read
[CONTRIBUTING.md](https://github.com/n-riesco/nel/blob/master/CONTRIBUTING.md)
and use the [issue tracker](https://github.com/n-riesco/nel/issues) for
any contributions: support requests, bug reports, enhancement requests, pull
requests, ...

## TODO

- Add tests for customising output
- Add `Node.js` documentation
