@thi.ng/lispy
TypeScript icon, indicating that this package has built-in type declarations

0.4.6 • Public • Published

@thi.ng/lispy

npm version npm downloads Mastodon Follow

[!NOTE] This is one of 210 standalone projects, maintained as part of the @thi.ng/umbrella monorepo and anti-framework.

🚀 Please help me to work full-time on these projects by sponsoring me on GitHub. Thank you! ❤️

About

Lightweight, extensible, interpreted Lisp-style DSL for embedding in other projects.

[!NOTE] This DSL implementation has been extracted as standalone package from existing work/examples bundled in the thi.ng/umbrella monorepo (i.e. lispy-repl)

Core language features

The core language is intentionally kept minimal, aimed at data transformations, configuration, user code snippets/expressions, and currently only contains the following:

Constants

  • T: true
  • F: false
  • null: null
  • PI
  • HALF_PI
  • TAU
  • INF: ∞
  • -INF: -∞

Predicates

  • (zero? x): zero check
  • (nan? x): NaN check
  • (neg? x): < 0 check
  • (null? x): Null(ish) check
  • (pos? x): > 0 check

Basic math ops

  • (+ x y...)
  • (- x y...)
  • (* x y...)
  • (/ x y...)
  • (inc x): increment (+1)
  • (dec x): decrement (-1)

Boolean logic operators

  • (and x y ...)
  • (or x y ...)
  • (not x)

Bitwise operators

  • (<< x y)
  • (>> x y)
  • (>>> x y)
  • (bit-and x y)
  • (bit-or x y)
  • (bit-xor x y)
  • (bit-not x)

Comparison operators

  • (< x y)
  • (<= x y)
  • (> x y)
  • (>= x y)
  • (== x y)
  • (!= x y)

Constants & functions from JS-native Math

All included...

Selected utilities from thi.ng/math package

  • (clamp x min max): clamp value to interval
  • (deg x): convert radians to degrees
  • (fit x a b c d): fit value from interval [a..b] into [c..d]
  • (mix a b t): linear interpolation (same as GLSL mix())
  • (rad x): convert degrees to radians
  • (smoothstep e1 e2 x): same as GLSL smoothstep()
  • (step edge x): same as GLSL step()

Functional programming

  • (always ...): function which always returns true
  • (comp f g): functional composition
  • (def name val): define global symbol
  • (defn name (...args) body): define global function
  • (fnull? x fn): function which returns (fn) if x is nullish
  • (identity x): function which always returns its arg
  • (never ...): function which always returns false
  • (partial fn arg): partial application (currying)

Control flow

  • (env! (sym val ...)): modify bindings in current env
  • (if test truthy falsey?): conditional with optional false branch
  • (let (sym val ...) body): locally scoped var bindings/expression
  • (while test body...): loop while test is truthy
  • (-> ...): Clojure-style thread-first S-expression re-writing
    • (-> a (+ b) (* c))(* (+ a b) c)
  • (->> ...): Clojure-style thread-last S-expression re-writing
    • (->> a (+ b) (* c))(* c (+ b a))

List/array/object processing

  • (get arr index): get array/object value at index/key
  • (set! arr index value): set array/object value for index/key
  • (concat x y ...): same as Array.prototype.concat(...)
  • (count x): number of items (also for strings)
  • (filter fn list): list/array filtering
  • (first x): first item of array
  • (map fn list): list/array transformation
  • (next x): remaining array items (after first)
  • (push arr x...): same as Array.prototype.push(...)
  • (reduce fn init list): list/array reduction

String processing

  • (capitalize x)
  • (float x): same as parseFloat(x)
  • (int x radix?): same as parseInt(x, radix)
  • (join sep arr): same as Array.prototype.join(sep)
  • (lower x)
  • (pad-left x width fill)
  • (pad-right x width fill)
  • (str x...): coerce values to string, concat results
  • (substr x from to?)
  • (trim x)
  • (upper x)

Regexp

  • (re-match re x)
  • (re-test re x)
  • (regexp str flags?)
  • (replace str regexp replacement)

Misc utilities

  • (env): JSON stringified version of current env
  • (syms): Array of symbol names in current env
  • (print x...): aka console.log(...)

Extensibility

The core language can be easily customized/extended by defining new symbols (or redefining existing ones) in the root environment ENV (see example below) or passing a custom environment to evalSource().

Status

BETA - possibly breaking changes forthcoming

Search or submit any issues for this package

Installation

yarn add @thi.ng/lispy

ESM import:

import * as lispy from "@thi.ng/lispy";

Browser ESM import:

<script type="module" src="https://esm.run/@thi.ng/lispy"></script>

JSDelivr documentation

For Node.js REPL:

const lispy = await import("@thi.ng/lispy");

Package sizes (brotli'd, pre-treeshake): ESM: 1.99 KB

Dependencies

Note: @thi.ng/api is in most cases a type-only import (not used at runtime)

Usage examples

One project in this repo's /examples directory is using this package:

Screenshot Description Live demo Source
Browser REPL for a Lispy S-expression based mini language Demo Source

API

Generated API docs

[!NOTE] Please also see /tests for more small code examples..

import { evalSource, ENV } from "@thi.ng/lispy";

// define custom root environment
const CUSTOM_ENV = {
    ...ENV,
    // re-define print fn (actually the same as default)
    print: console.log,
    // pre-define new global variable
    name: "lispy"
};

const SRC = `
(print (+ 1 2 3 4))
;; 10

;; local variables
(let (a 23 b 42) (print (+ a b)))
;; 65

;; define global var/fn
;; here, a curried version of the built-in print fn
(def greetings! (partial print "hello,"))

;; print greeting ('name' symbol provided via custom env)
(greetings! name)
;; hello, lispy!

;; basic loop w/ local var
(let (i 0)
  (while (< i 5)
    (print i)
    (env! (i (inc i)))))
;; 0
;; 1
;; 2
;; 3
;; 4

;; threading/rewriting operators
(->> name (str "hello, ") (print "result:"))
;; result: hello, lispy

;; print contents of default environment
(print (env))
`;

// execute with customized environment
evalSource(SRC, CUSTOM_ENV);

// output:
// 10
// 65
// hello, lispy
// 0
// 1
// 2
// 3
// 4
// result: hello, lispy
// {
//   "+": "<function>",
//   "*": "<function>",
//   "-": "<function>",
//   "/": "<function>",
//   "inc": "<function>",
//   "dec": "<function>",
//   "null?": "<function>",
//   "zero?": "<function>",
//   "neg?": "<function>",
//   "pos?": "<function>",
//   "nan?": "<function>",
//   "=": "<function>",
//   "!=": "<function>",
//   "<": "<function>",
//   "<=": "<function>",
//   ">=": "<function>",
//   ">": "<function>",
//   "T": true,
//   "F": false,
//   "null": null,
//   "and": "<function>",
//   "or": "<function>",
//   "not": "<function>",
//   "<<": "<function>",
//   ">>": "<function>",
//   ">>>": "<function>",
//   "bit-and": "<function>",
//   "bit-or": "<function>",
//   "bit-xor": "<function>",
//   "bit-not": "<function>",
//   "E": 2.718281828459045,
//   "LN10": 2.302585092994046,
//   "LN2": 0.6931471805599453,
//   "LOG10E": 0.4342944819032518,
//   "LOG2E": 1.4426950408889634,
//   "PI": 3.141592653589793,
//   "SQRT1_2": 0.7071067811865476,
//   "SQRT2": 1.4142135623730951,
//   "abs": "<function>",
//   "acos": "<function>",
//   "acosh": "<function>",
//   "asin": "<function>",
//   "asinh": "<function>",
//   "atan": "<function>",
//   "atan2": "<function>",
//   "atanh": "<function>",
//   "cbrt": "<function>",
//   "ceil": "<function>",
//   "clz32": "<function>",
//   "cos": "<function>",
//   "cosh": "<function>",
//   "exp": "<function>",
//   "expm1": "<function>",
//   "floor": "<function>",
//   "fround": "<function>",
//   "hypot": "<function>",
//   "imul": "<function>",
//   "log": "<function>",
//   "log10": "<function>",
//   "log1p": "<function>",
//   "log2": "<function>",
//   "max": "<function>",
//   "min": "<function>",
//   "pow": "<function>",
//   "random": "<function>",
//   "round": "<function>",
//   "sign": "<function>",
//   "sin": "<function>",
//   "sinh": "<function>",
//   "sqrt": "<function>",
//   "tan": "<function>",
//   "tanh": "<function>",
//   "trunc": "<function>",
//   "HALF_PI": 1.5707963267948966,
//   "TAU": 6.283185307179586,
//   "clamp": "<function>",
//   "deg": "<function>",
//   "fit": "<function>",
//   "mix": "<function>",
//   "rad": "<function>",
//   "step": "<function>",
//   "smoothstep": "<function>",
//   "get": "<function>",
//   "set!": "<function>",
//   "push": "<function>",
//   "concat": "<function>",
//   "count": "<function>",
//   "first": "<function>",
//   "next": "<function>",
//   "str": "<function>",
//   "join": "<function>",
//   "lower": "<function>",
//   "upper": "<function>",
//   "capitalize": "<function>",
//   "pad-left": "<function>",
//   "pad-right": "<function>",
//   "substr": "<function>",
//   "trim": "<function>",
//   "regexp": "<function>",
//   "re-test": "<function>",
//   "re-match": "<function>",
//   "replace": "<function>",
//   "identity": "<function>",
//   "always": "<function>",
//   "never": "<function>",
//   "int": "<function>",
//   "float": "<function>",
//   "print": "<function>",
//   "partial": "<function>",
//   "partial2": "<function>",
//   "comp": "<function>",
//   "comp2": "<function>",
//   "fnull?": "<function>",
//   "reduce": "<function>",
//   "map": "<function>",
//   "filter": "<function>",
//   "name": "lispy",
//   "greetings!": "<function>"
// }

Authors

If this project contributes to an academic publication, please cite it as:

@misc{thing-lispy,
  title = "@thi.ng/lispy",
  author = "Karsten Schmidt",
  note = "https://thi.ng/lispy",
  year = 2023
}

License

© 2023 - 2025 Karsten Schmidt // Apache License 2.0

/@thi.ng/lispy/

    Package Sidebar

    Install

    npm i @thi.ng/lispy

    Homepage

    thi.ng/lispy

    Weekly Downloads

    13

    Version

    0.4.6

    License

    Apache-2.0

    Unpacked Size

    40.1 kB

    Total Files

    8

    Last publish

    Collaborators

    • thi.ng