/lisp

Primary LanguageJavaScript

LISP

Implementation in JavaScript that compiles to JavaScript and then evals the resulting code. Goal is maximum interoperability with JavaScript. Large parts are lifted from Scheme and Clojure.

and

(and "" 0) ; ""
(and 0 "") ; 0
(and "a" 1) ; 1
(and 1 "a") ; "a"

apply

(define nums [1 2 3 4])
(apply + nums) ; 10

array

Arrays are supported as literals.

(define primes [2 3 5 7 11])

(define key "foo")
(define value 42)
(define tuple [key value]) ; ["foo" 42]

boolean

true and false map directly to true and false in JavaScript.

calling convention

;; Calling a procedure compiles directly to a JavaScript function call
(f x y) ; f(x, y)

;; Applying a sequence as (the rest of the) arguments is possible
(f x y . z) ; f(x, y, ...z)
(f x y . (list 2 3)) ; doesn't work, results in f(x, y, list, 2, 3)

;; Defining a procedure compiles directly to a JavaScript function
(lambda (x y . z) z) ; (x, y, ...z) => z

concat

Conact two sequences.

(concat [1 2] `(3 4)) ; (1 2 3 4)

cond

(cond
  (= x 10) "x is 10"
  (> x 10) "x greater than 10"
  true "x less than 10")

define

Use define to create a reference to a value at the top level of a module.

(define SIZE 64)

Note that define can be reused to fix code when using the repl.

;; somewhere in a session
(define square (lambda (x) (+ x x)))

;; later in that session
(define square (lambda (x) (* x x)))

defmacro

Shorthand for defining a macro.

(defmacro infix (a op b) (list op a b))

defn

Shorthand for defining a function.

(defn square (x) (* x x))

do

(if flag
  (do
    (log "something")
    (log "something else")))

doto

(doto js/console (.log "Hello") (.log "World"))

fn

Create a function, with destructuring.

((fn ((x y) (u v)) [(+ x u) (+ y v)]) [1 2] [3 4]) ; [4 6]

if

Canonical way to make a choice.

(if (= x 10) "decimal" "other")

The predicate follows the truthy/falsy conventions of JavaScript, as the result from the compiler shows clearly.

(if x y z) ; (x ? y : z)

import

Used to import another module.

(import mandelbrot ../fractals/mandelbrot.clj)

(mandelbrot/render -2 -2 4)

The module can by any (commonjs) module.

(import fs fs)

(fs/readFileSync "hello.txt")

iterables

To enhance the interop with JavaScript, sequences implement the iterable interface (i.e. Symbol.iterator). So a lisp function returning a sequence can be used directly in JavaScript.

;; example.clj
(define getSeq (lambda () (seq 2 (seq 3 (seq 5 ())))))
const example = require("./example.clj")

console.log([...example.getSeq()]) // [2, 3, 5]

lambda

The core of any lisp is definitely lambda calculus.

((lambda (x y z) y) 1 2 3) ; 2
((lambda (x y . z) z) 1 2 3 4) ; [3 4]

;; the lambda body can be multiple expressions
(lambda add (a b)
  (log "adding...")
  (+ a b))

let

(let [x (+ 1 2) y (+ 3 4)]
  (* x y)) ; 21

list

List implements the traditional singly linked list in a lisp using cons cells.

(define lst (cons 2 (cons 3 ()))) ; (2 3)
(define lst2 (list 1 2 3 4)) ; (1 2 3 4)

Note that the cdr of a cons cell doesn't have to hold another cons, but the result won't be a sequence.

(define pair (cons 2 3))

log

Log JavaScript value directly to the console.

(log {1 2 3 4}) ; {"1": 2, "3": 4}

loop/recur

JavaScript is not so good with proper tail calls, so to achieve proper iterations loop/recur should be used.

(loop (x 6 s 0)
  (if (> x 0)
    (recur (- x 1) (+ s x))
    s)) ; 21

macros

A macro is a function that takes a data structure, presumable representing code, and return transformed code. Macros are invoked before compilation to JavaScript in a phase called macro expansion.

(define infix (macro (x f y)
  (list f x y)))

(infix 3 + 4) ; 7

A syntax quote and unquote are provided to make reading and writing macros simpler.

(define x 10)
`(= ~x y) ; (= 10 y)

map

(map (fn (x) (+ x 1)) [1 2 3]) ; (2 3 4)

numbers

Just like in JavaScript, number is the only numeric type known.

(+ 24 18) ; 42

Note there is no numeric tower.

null

null maps directly to null in JavaScript. Note that nil does not exist.

object

Objects are supported as literals.

(define age 6)
(define cat { "name" "snuggles" "age" age })

Note that keys will always end up as strings.

{ 42 42 } ; { "42" 42 }

or

(or 0 "") ; ""
(or "" 0) ; 0
(or 1 "a") ; 1
(or 0 "a") ; a

println

Write a value to the console using the lisp printer.

(println {1 2 3 4}) ; {"1" 2 "3" 4}

sequences

The ISeq interface comprises of three functions: empty?, first and rest. Data structures implementing are arrays, objects, lists and of course sequences.

seq can be used to build lazy sequences.

(define ones (seq 1 ones))

(first ones) ; 1
(first (rest ones)) ; 1

Note that an empty sequence is given by ().

(seq 3 ()) ; (3)

strings

String such as "hello world" map directly to string in JavaScript.

symbols

Lisp symbols are implemented using JavaScript Symbol. Should you need interop with lisp using symbols (unlikely) here is an example.

const equations = require("./equations.clj")

// solve x * x = 4
console.log(
  equations.solve([
    Symbol.for("="),
    4,
    [Symbol.for("*"), Symbol.for("x"), Symbol.for("x")]
  ])
)

take

(take 5 [1 2 3 4 5 6 7 8 9 10]) ; (1 2 3 4 5)

undefined

undefined maps directly to undefined in JavaScript.

whitespace

The usual whitespace

  • spaces
  • newlines
  • carriage returns
  • tabs

But also

  • commas!

So both are valid.

(define x [1 2 3])
(define y [1, 2, 3])

zip

(zip + [1 2 4] [8 16]) ; (9 18)

TODO

  • proper source maps for debugging
  • consistent namespacing: / for namespace (files really and static fields)
    • js/Math/PI
    • (. js/console log) or (. log js/console)?
  • avoid primitives being in env for every module
  • make loop/recur (loop [x 1 y 2]) using []?
  • Throw error on unknown symbol
    • make env richer?
    • env("list")?
  • macroexpand - (quote (let [x 10] x))
  • arity?
  • destructure {}
  • is ("foo" {}) a good idea?
  • should ("foo" null) return undefined?
  • define seq as macro and using deftype implementing ISeq protocol?
  • compile.js: clash between / (division) and namespaces (. js/Math PI)
  • implement syntax quote as macro expansion