npm package: @kiniro/lang
Description: A Lisp-like language interpreter.
Author: klntsky
Latest version: 2.0.3

![npm bundle size (minified)](https://img.shields.io/bundlephobia/min/@kiniro/lang.svg)
![NpmLicense](https://img.shields.io/npm/l/@kiniro/lang.svg)

# kiniro-lang

`kiniro-lang` ([gitlab](https://gitlab.com/kiniro/lang) / [npm](https://www.npmjs.com/package/@kiniro/lang) / [jsdoc](https://kiniro.gitlab.io/lang/)) is a toy LISP-like programming language interpreter written in JavaScript suitable for use in both node.js and the browser.

It can be a viable solution as an embedded language for a web application that needs an isolated computational environment with a limited set of interfaces made available, e.g. for game scripting or as a configuration processing DSL.

## Cautions

The interpreter is not optimised for performance, and performance is not the main goal, so don't expect it to be blazingly fast.

**It is not recommended for use in production.**

## Language features and properties

- strictness
- dynamic typing (types are assigned to values, not variables)
- lexical scoping

## API

See [the docs](https://kiniro.gitlab.io/lang/).

## Performance

The interpreter is written with a heavy use of `Promise`s, so there is a little overhead due to async functions being pushed to [message queue](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop) on *some* `eval` calls. But the use of deferred computations solves the problem of which most of naive language interpreter implementations written in JS suffer - `too much recursion` error.

## How to use

### Installing

```sh
npm install @kiniro/lang
```

### Testing

In order to run tests, you need 'grunt' binary installed in your environment.

```sh
grunt test
```

### Using

#### Evaluating expressions

```javascript
const { Kiniro } = require('@kiniro/lang');
const kiniro = new Kiniro();
kiniro.exec('(+ 2 2)').then(result => {
    console.log(result); // 4
});
```

#### Calling JS form kiniro

It is possible to lift a JS function into kiniro world:

```javascript
const { Kiniro } = require('@kiniro/lang');
const kiniro = new Kiniro();
kiniro.load({ raw: { fun: (x, y) => x + y } });
console.log(await kiniro.exec('(fun 3 4)'));
```

[try on RunKit](https://runkit.com/klntsky/js-from-kiniro)

Or using define:

```javascript
const { Kiniro } = require('@kiniro/lang');
const kiniro = new Kiniro();

kiniro.define('sayHi', name => console.log(`Hi, ${name}!`));
await kiniro.exec('(sayHi "JavaScript")');
```

[try on RunKit](https://runkit.com/klntsky/calling-js-form-kiniro-with-define)

#### Using kiniro functions within JS

And getting kiniro functions back is possible too:

```javascript
const { Kiniro } = require("@kiniro/lang")
const kiniro = new Kiniro();

await kiniro.exec(
    '(def reduce (f e xs)\
        (if (empty xs) \
            e \
          (f (head xs) (reduce f e (tail xs)))))'
);

console.log(await kiniro.lookup('reduce')(
    (x, y) => x + y,
    0,
    [1,2,3,4]
)); // 10
```

[try on RunKit](https://runkit.com/klntsky/using-kiniro-functions-within-js)

Note that they are always asynchronous.

## Language basics

The following names are reserved keywords. It is impossible to define a variable named with a keyword or pass a keyword to a higher-order function as argument.

### do

Sequential execution. Evaluates all arguments returning the value of the last one.

### begin

Evaluates all arguments within a new scope and returns the value of the last argument.

Variable definitions never leave the scope they are defined in, so the ones wrapped in a `begin` statement will not be visible from outer scope. However, it is possible to change the value of a variable from outer scope using [set](#set). Think of `begin` blocks as of [IIFEs](https://developer.mozilla.org/en-US/docs/Glossary/IIFE).

Differences between `do` and `begin` can be illustrated with the following code snippet:

```lisp
;; define initial value
(def x 0)

;; modify the variable within a new scope
(begin
  (def x 1)
  ;; x is now 1
  x)

;; in the outer scope x remains unchanged (0)
x

(do (def x 1))

;; but now it is set to 1
x
```

### let

Bind a list of variables to the corresponding values. Variable definitions can refer to other variables defined earlier in the same `let`-statement.

Example:
```
;; evaluates to 2
(let ((x 1)
      (y (+ 1 x))
     )
  y)
```

### def

Define a variable or a function in the current scope. Shoul be used before [set](#set). Think of `def` as of `var` in strict-mode javascript.

Example:
```
;; A function that sums 3 arguments
(def f (a b c)
  (+ a b c))

;; A variable
(def x
  9)
```

### set

Assignment operator (equivalent to `=` in JS) , changes variable values or object properties.

```lisp
;; variable definition is required before first set use
(def x 0)
(set x 1)
(def y { a: { b: 0 } })
(set y.a.b { c: 1 })
(set y.a "d" 3)
```

When modifying object properties, `set` returns object values, thus allowing to chain calls:

```lisp
(def x {})
(set (set (set x "a" 0)
                 "b" 1)
                 "c" 3)
```

### lambda

A lambda-abstraction, or an anonymous function. Arguments list, which may be empty, can be accessed through the `args` variable (same as `arguments` in JS).

```
(\x y ->
  (* x y))
```

### if

If the expression right after the keyword evaluates to `cons`, `nil` or a wrapped JS value that is [*truthy*](https://developer.mozilla.org/en-US/docs/Glossary/Truthy), then the next expression will be evaluated, otherwise the last will be chosen.

```
(if []
     0
   1) ;; 1
```

Note that such behavior is unusual for LISP-like languages, where `nil` is usually *falsy*. The reason behind this is that `unlift` converts `nil` to [], and [] is truthy in JS.

### cond

Sequentially searches for a clause with condition expression that evaluates to truthy value. If no such expression is found, throws an error.

`cond` expressions translate to sequences of nested `if`s.

```
(cond
   ((foo bar) baz)
   (bar (foo baz)))
```

is equivalent to

```
(if (foo bar) baz
   (if bar (foo baz)
      (throw "cond: none of the conditions is true")))
```

### . (dot)

`.`-operator looks up a property of given object. It can be used as infix operator.

The following two expressions are equivalent (note the double parentheses and quotes):

```
(a.b).c

(. ((. a "b")) "c")
```

### apply

Call a function with arguments given as a list. Similar to [Function.prototype.apply](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply), except for the absence of `this`. It is not possible to use `apply` with keywords as first argument.

```
(apply + [1 2 3])
;; 6
```

### throw

Throws first argument (or `nil` if none given) as exception.

### try

Tries to evaluate the first argument, if it fails, applies second argument (that must be a function) to the thrown value. The exception does not propagate until rethrown again.

```lisp
(try (throw 1)
     (\err -> (+ 1 err))) ;; 2
```

### par

Evaluates given arguments in parallel.

### async

Evaluates given arguments asynchronously. Returns a promise that resolves to the last expression's value.

### await

Freezes evaluation, waiting until the given promise resolve. If the first argument is not a promise, returns it's value.

```lisp
(def promise (async (+ 3 4)))
(await promise) ;; 7
```

### sleep

```
(sleep t)
```

Delay evaluation for `t` milliseconds. Returns `t` value. When called without arguments, returns `nil`.

## Language primitives

### List primitives

#### cons

`Cons`truct a new list by given element and another list (i.e. put the element to the beginning of the list).

#### empty

Check whether given list is empty.

#### head

Get the first element of a list. Throws if `nil` given.

#### tail

Get the tail of a list. Throws if `nil` given.

#### list

Construct a list from arguments.

```lisp
(list 1 2 3) = (cons 1 (cons 2 (cons 3 nil))) = [1 2 3]
```

### quote

Accepts a single expression and converts it to a list. When called with an atomic expression (i.e. not parentheses), returns it untouched.

### eval

Evaluates a list as if it were an expression.

```lisp
(eval [+ 1 [- 9 4]]) ;; 6

(def x (tail (quote (1 2))))
(set x (cons 3 x))
(set x (cons + x))
(eval x) ;; 5
```

### Arithmetics

The following arithmetic functions are supported: `+`, `-`, `*`, `/`, `^`, `>`, `<`, `>=`, `<=`, `==`.

`+` and `*` accept arbitrary number of arguments.

[Math](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math) object is also available.

### Logical functions

#### `or`, `and`.

Both can be called with arbitrary number of arguments.

#### not

Negates the first argument.

#### `true`, `false`

Logical constants.

## Syntactic sugar

### List notation

Bracketed lists translate into `list` function calls.

```
[a b c] => (list a b c)
```

### Object notation

Object syntax is similar to the one used in JS, however, commas are optional.

### Dollar notation

`$` is a right-associative infix application operator, allows to flatten nested parentheses.

```lisp
(a $ b $ c) => (a (b (c)))
(a $ b c d) => (a (b c d))
((a $ b) c) => ((a (b)) c)
```