A language that for easily building cross-language language
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.vscode
core
highlight
lib
parser
test
.gitignore
.travis.yml
nofile.js
package.json
readme.md
tsconfig.json

readme.md

nisp

NPM version Build Status Deps Up to Date

The interesting part is that nisp is designed to be Turing incomplete.

You have the full control of the vm and ast, you can decide what the language will have, or how lazy the expression will be. for example, if you don't expose the if exp the user can never express if logic.

By default nisp only presents the meta data of the program itself. So by default it is used to exchange plain data.

You may ask what it really does? Yes, it does nothing. And that is exactly what a composable permission protocol needs. I use it to expose composable api.

The ast of nisp is plain JSON, the js implementation is only around 50 lines of code, so it will be very easy to port nisp to other languages. No closure or complex data type is required, even plain C can implement nisp easily.

Everything inside nisp is just function, so it's very easy to keep everything type safe, and with the composable nature, nisp is an ideal middle layer to carry query or RPC. Such as the Websocket RPC lib based on nisp: https://github.com/ysmood/nisper.

Quick Start

For more examples, read the unit test of this project.

Try it online: https://runkit.com/ysmood/nisp-demo

Implementaions

Define your own function

For a simple example, here you can use it as a sum-only-calculator.

import nisp from 'nisp'

var sandbox = {
    '+': (a, b) => a + b
};

var exp = ['+', 1, ['+', 1, 1]];

nisp(exp, sandbox); // => 3

Encode and escape data

Sometimes you may want to separate nisp code and raw data, Here we provide encode function to reduce the pain of typing quotes and commas. The grammar is a subset of lisp.

import nisp from 'nisp'
import encode from 'nisp/lib/encode'
import $ from 'nisp/lib/$'

var sandbox = {
    $, // raw data
    "+": ns => ns.reduce((a, b) => a + b),
    "++": ns => ns.map(a => a + 1)
};

var data = [1, 2, 3]

var exp = encode`(+ (++ ${data}))`

nisp(exp, sandbox); // => 9

Composable RPC and safe by design

Commonly used RPC libs such as json-rpc, thrift and GRPC can only handle function per call, if you want to handle large amount of data with two procedures, you have to load all of them on each procedure, that is a big waste. With nisp, you can seamlessly compose multiple functions into one remote call.

We even used nisp to create a simple db query language, and it automatically helps us defense insertion attach by its nature.

import nisp, { error } from 'nisp'

var sandbox = {
    concat (...args) {
        return args.reduce((a, b) => a.concat(b));
    },

    getAnimals () {
        if (this.env.isZooKeeper)
            return ['cat', 'dog'];
        else
            error(this, "Not Allowed"); // it will log error stacks
    },

    getFruits () {
        return ['apple', 'banana'];
    }
};

var session = {
    user: 'Jack',
    isZooKeeper: true
}

var exp = ["concat", ["getAnimals"], ["getFruits"]];

nisp(exp, sandbox, session);

Full control the ast with macro

Here we implementation a if expression The if expression is very special, it cannot be achieved without ast manipulation.

import nisp from 'nisp'
import encode from 'nisp/lib/encode'
import $ from 'nisp/lib/$'
import args from 'nisp/lib/args'
import $do from 'nisp/lib/do'

var sandbox = {
    $,

    // Most times you don't want to use it.
    "non-lazy-if": (cond, a, b) => cond ? a : b,

    // Full lazy.
    if: args(v => v(0) ? v(1) : v(2)),

    // Even half lazy, you have the full control to how lazy the program will be.
    // No matter v(0) is true or false, v(1) will be calculated.
    "half-lazy-if": args(function (v) {
        var v1 = v(1);
        return v(0) ? v1 : v(2);
    }),

    do: $do,

    "+" (...args) {
        console.log('calc:', args);
        return args.reduce(function (s, n) { return s + n; });
    })
};

var exp = encode`(do
    (if           true  (+ 1 1) (+ 2 2))
    (non-lazy-if  false (+ 1 1) (+ 2 2))
    (half-lazy-if true  (+ 1 1) (+ 2 2))
)`;

nisp(exp, sandbox);

Make a complete async language

var nisp = require("nisp");
var async = require("nisp/lib/async");

function waitNumber (val) {
    return new Promise(function (r) {
        return setTimeout((function () {
            return r(val);
        }), 1000);
    });
};

var sandbox = {
    download: async(() => waitNumber(1))

    "+": async((a, b) => a + b)
};

// Here we can write async code a sync way.
var exp = ["+", ["download"], ["download"]];

nisp(exp, sandbox).then(function (out) {
    // It will take about 1 seconds to log out.
    console.log(out) // => 2
});

API

The project is written in typescript, all main APIs are typed and commented.