npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@kiniro/lang

v2.0.3

Published

A Lisp-like language interpreter.

Downloads

18

Readme

npm bundle size (minified) NpmLicense

kiniro-lang

kiniro-lang (gitlab / npm / jsdoc) 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.

Performance

The interpreter is written with a heavy use of Promises, so there is a little overhead due to async functions being pushed to message queue 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

npm install @kiniro/lang

Testing

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

grunt test

Using

Evaluating expressions

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:

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

Or using define:

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

Using kiniro functions within JS

And getting kiniro functions back is possible too:

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

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. Think of begin blocks as of IIFEs.

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

;; 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. 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.

;; 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:

(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, 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 ifs.

(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, 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.

(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.

(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

Construct 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.

(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.

(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 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.

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