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.
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.
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.
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.
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.)
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.
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.
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 such as number?
can be used to test a value's type
and return a boolean, or as conditionals themselves.
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.
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.
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.
SchemeJS identifiers can use any Unicode "alphabetic" characters (including ideographs). People should be able to program in their own languages.
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)
Other bitwise operations are listed avove under N-Ary 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.
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
.)
The implementations should explain what they do well enough.