SchemeJS: Scheme in JavaScript

JavaScript has dynamic typing, functions, closures, and a JIT with a small army of crack programmers optimizing its performance. It’s an ideal runtime for Scheme. All it lacks is cons cells, an s-expression parser/printer, eval/apply, and a handful of Lisp primitives.

Oh, and a compiler and JIT that compiles Scheme down to perfectly normal JavaScript functions that JavaScript JITs can go to town on.

SchemeJS fills that gap.

It’s hard to keep JavaScript objects from sneaking into the SchemeJS world, so I decided to invite them in as first-class. SchemeJS is fully Scheme and fully JavaScript.

This implementation aims for broad compatibility with SIOD, but inevitably, and in the grand tradition of lisp implementations, introduces a new dialect. Which is a sin but not a crime.

Run a demo by typing (load "demo.scm") or (load "gfxdemo.scm") or (load "mediademo.scm").

Learn about built-in Scheme definitions and implementation details.

Implementation Details

Key Features

JavaScript Integration and Interoperability

SchemeJS and JavaScript have full and fluent access to each other's objects. SchemeJS can call invoke any JavaScript function and every SchemeJS function is a JavaScript function, whether it is compiled or not.

Syntax for JavaScript Object and Array literals.

Core SchemeJS primitives are implemented directly on fundamental JavaScript primitives such as function closures and prototype resolution. These are the things that JavaScript JITs optimize most thouroughly.

Iterable-Aware

SchemeJS lists are iterable in JavaScript and JavaScript iterables are Scheme lists. SchemeJS functions that operate on lists also operate on JavaScript iterable such as Arrays.

N-ary Comparisons

Comparison functions such as "<" can be applied to arbitrarily many arguments. In this case it means that each argument is less than the previous. Evaluation ends as soon as the comparison fails.

Partial Function Application

Functions, whether user-defined, compiled or implemented in JavaScript, when invoked with fewer than their required number of arguments, result in a closure that binds the given arguments. For instance, (+ 5) results in a function (closure) that adds 5 to its argument(s).

Because the expression (< 10) evaluates to a function that's true if 10 is less than its argument, the expression (filter (< 10) list) returns a new list with elements from list that are ten or greater. (Seems backwards, perhaps, but that's Scheme for ya.)

Generalized Special Forms

Users can define "special forms" that take a specified number of evaluated parameters; the rest are unevaluated. In (\# 2 (a1 a3 a3 a5) forms...), a1 and a2 are evaluated and the rest are not.

Optional Parameters

A function defined as (def (fn p1 (p2 expr) (p3 expr) ) form ...) has optional parameters p2 and p3 with default values supplied by the expressions that follow them. The expressions are evaluated only if the parameter is absent.

Rest Parameters

A function defined as (def (fn p1 p2 ...p3) form ...) takes two normal parameters (p1 and p2) and receives a third parameter p3, that recieves a list of the remaining arguments.

Similarly, a lambda of the form (λ (p1 p2 ...p3) form ...) has a rest parameter p3.

Predicates as Conditionals

Predicates such as number? can be used to test a value's type and return a boolean, or as conditionals themselves.

Lazy Lists and Map Functions

JavaScript iterators are seen as lazy lists cells that don't fetch the next object until cdr is invoked.

Lazy "map" is the same, but in addition the mapping function is not applied until car is invoked on the mapped list.

Compiler

A compiler (a "transpiler," technically, but that sounds silly) that transforms Scheme into perfectly normal JavaScript that the JIT can go to town on.

An optional JIT that kicks in after a client-selectable number of invocations of a function. Because a JIT necessarily binds the current definitions of functions referenced in the compiled code, JIT-compiled code contains a guard that ensures those bindings have not changed and bails to the interpreter if they have. The function is then elligible to be JITted again if invoked sufficiently often.

Easly Embedded

SchemeJS is a JavaScript module designed to be easily embedded into other applications. Scheme JS can run in any web page (in a sufficiently modern browser; even on mobile devices) and can do anything JavaScript can do with as few as a half dozen lines of code to import it.

It comes with a Node.js-based CLI/REPL and this web-based REPL but they're just simple embeddings of the SchemeJS runtime.

SchemeJS has no dependencies beyond a modern JavaScript runtime.

Unicode

SchemeJS identifiers can use any Unicode "alphabetic" characters (including ideographs). People should be able to program in their own languages.

Sundry

Run unit tests; results display in the browser console. This Web REPL curently configures the runtime to log compiled code to the console. You can also see the compiled code of a funtion by typing (println (String fn)).

Copyright © 2021, Stan Switzer — (sjswitzer [at] gmail [dot] com)

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Find source on GitHub

Built-In Functions and Constants

 

Core Definitions

Mathematical Operations and Constants

Bitwise Operations

Other bitwise operations are listed avove under N-Ary Operations.

Comparison Operations

These operations take an arbitrary number of arguments and return false, ending evaluation, as soon as the relation fails to hold between successive elements.

Logical Operations

Control Flow Operations

Predicate Conditional Operations

These operations can function as simple predicates, returning true or false, or as conditional operations, evaluating the second argument if true and the third if false. You were probably going to us it for a condition anyway. (Actually, it always evaluates and returns the second or third argument; it's just that their default values are true and false.)

Utility

List Operations

JavaScript-Related

The implementations should explain what they do well enough.

Web Graphics

Web Graphics Taking Explicit Context

Uncategorized

Functions and Constants Imported from JavaScript

Euler's constant and the base of natural logarithms
Natural logarithm of 10
Base-10 logarithm of Euler's constant
Base-2 logarithm of Euler's constant
Ratio of the a circle's circumference to its diameter (π)
Square root of 1/2
Square root of 2
Natural logarithm of 2
(abs x)

Absolute value of x; x can be a number or a bigint.

(acos x)

Arccosine of x in radians.

(acosh x)

Hyperbolic arccosine of x in radians.

(asin x)

Arcsine of x in radians.

(asinh x)

Hyperbolic arcsine of x in radians.

(atan x)

Arctangent of x in radians.

(atanh x)

Hyperbolic arctangent of x in radians.

(atan2 y x)

Angle in radians from the positive x axis and the ray to the point (x, y). Does not suffer from divide-by-zero issues as tan would.

(sqrt x)

Square root of x.

(cbrt x)

Cube root of x.

(clz32 x)

Number of leading zeros in the binary representation of x considered as a 32-bit integer.

(cos x)

Cosine of x in radians.

(cosh x)

Hyperbolic cosine of x in radians.

(expm1 x)

The value (- (exp x) 1), avoiding numerical anomalies.

(log1p x)

The natural logarithm of x+1, avoiding numerical anomalies.

(exp x)

Euler's number to the power x.

(floor x)

The largest integer less than or equal to x.

(ceil x)

The smallest integer greater than or equal to x.

(fround x)

The closest single precision floating point representation of x.

(hypot value ...)

The square root of the sum of the squares of the arguments.

(imul x y)

Result of the multiplication of x and y considered as 32-bit integers.

(log x)

The natural logarithm of x.

(log x)

The base-10 logarithm of x.