Cumalis Lisp is a stack-based implementation of R7RS Scheme Language in Typescript.
Can be used as a library in web browsers or Node.js environment.
$ yarn add cumalis-lisp
# or
$ npm install cumalis-lisp
- Almost full implementation of R7RS (small) except complex/fraction numbers, including:
- call-with-current-continuation (call/cc).
- guard / with-exception-handler / raise / raise-continuable.
- dynamic-wind.
- define-library / import.
- make-parameter / parameterize.
- define-record-type.
- let-values / values.
- syntax-rules with nested patterns.
- quasiquote.
- nested multiline comments.
- datum tags.
- etc.
- Standard libraries in R7RS (small) except (scheme complex) are implemented.
- (scheme base) -- imported by default.
- (scheme read)
- (scheme write)
- (scheme promise)
- (scheme time)
- (scheme inexact)
- (scheme case-lambda)
- (scheme char)
- (scheme cxr)
- (scheme process-context)
- (scheme file)
- (scheme eval)
- (scheme repl)
- (scheme load)
- (scheme r5rs)
- Proper tail recursion. (tail call optimization)
- Javascript interfaces
- Adding built-in syntaxes, procedures, libraries.
- Able to write expressions as Javascript Arrays and evaluate.
- Able to contain Javascript objects in AST.
- All objects, AST, and call-frames consist of pure JSON objects.
- Continuations can be serialized to JSON strings. (Circular references need to be resolved.)
- Simple JSON serializer/deserializer utility is bundled. (toReferentialJSON / fromReferentialJSON)
- No dependency.
With these features, The following application fields can be considered.
- Macro system for online applications. (Programs can be built as simple Javascript objects.)
- Mobile agent systems. (Send running application via network and continue to run on another machine.)
- Games that need to save the running status.
- Work-flow systems.
- Backend of Web-based visual programming environment, like scratch-blocks or Blockly.
- Note: Cumalis Lisp is expected to be the backend of "Cumalis" in the next major version.
- etc.
Note: Implementations of Scheme are not required to implement fraction and complex numbers. See section "6.2.3. Implementation restrictions" on R7RS.
import { Interpreter, toJS } from "cumalis-lisp";
const itrp = new Interpreter(); // Create interpreter.
const ret = itrp.eval(`
(define (fib n)
(if (<= n 2)
1
(+ (fib (- n 1)) (fib (- n 2)))))
(fib 10)
`); // Evaluate S-expression.
const num = toJS(ret); // returns 55.
import { Interpreter, is, create, toJS, fromJS, defineBuiltInProcedure } from "cumalis-lisp";
const itrp = new Interpreter(); // Create interpreter.
const helloProc = defineBuiltInProcedure("hello", [ // Define procedure
{ name: "obj" }
], function ({obj}) {
if (!is.Object(obj)) {
throw new Error("Not a object");
}
console.log(`Hello ${toJS(obj)}`);
return create.Number(42);
});
const hello2Proc = defineBuiltInProcedure("hello2", [ // Define macro
{ name: "obj" }
], function ({obj}) {
if (!is.Object(obj)) {
throw new Error("Not a object");
}
return fromJS(["string-append", `"HELLO "`, obj]); // Write LISP as JS array.
}, true); // <-- this "true" indicates macro.
itrp.setBuiltInProcedure(helloProc); // Set the procedure to the interpreter.
itrp.setBuiltInProcedure(hello2Proc); // Set the procedure to the interpreter.
console.log(toJS(itrp.eval(`(hello "world")`))); // => 42
console.log(toJS(itrp.eval(`(hello2 "WORLD")`))); // => HELLO WORLD
import {
Interpreter, LISP, create, toJS,
SuspendEnvelope, isSuspendEnvelope, suspendValueFromEnvelope,
toReferentialJSON, fromReferentialJSON,
} from "cumalis-lisp";
const itrp = new Interpreter(); // Create interpreter.
// Suspend
let suspend: SuspendEnvelope | null = null;
try {
itrp.eval(`(+ 11 (suspend "SUSPEND HERE"))`);
} catch (e) {
if (isSuspendEnvelope(e)) {
suspend = e;
} else {
throw e;
}
}
if (suspend) {
console.log(toJS(suspendValueFromEnvelope(suspend))); // => "SUSPEND HERE"
// Serialize/Deserialize
const json = toReferentialJSON(suspend, "$$$");
const revived: LISP.Suspend = fromReferentialJSON(json, "$$$");
// Resume
const ret = itrp.resume(revived, create.Number(31));
console.log(toJS(ret)); // => 42
}
To handle files in Cumalis Lisp on Node.js, "fs" object must be passed to the interpreter as a constructor's option when you create a Interpreter instance.
import { Interpreter } from "cumalis-lisp";
import fs from "fs";
const itrp = new Interpreter({fs}); // <= set "fs" object as option.
itrp.eval(`
(import (scheme file))
(if (file-exists? "some-file.txt")
(with-input-from-file "some-file.txt"
(define x (read-line))
...
(with-output-to-file "some-other-file.txt"
(lambda ()
(write-char #\a)
(write-string "ABC")
(newline)
...
(delete-file "some-file.txt")
`);
Note: If you want to serialize / deserialize suspended continuations, open files status (seek position, open/close status, etc) can't be recovered when you deserialize / resume. It will cause unexpected behaviour. Be sure to close files before suspend / serialization.
Revised7 Report on the Algorithmic Language Scheme
- Only integer and real number is supported. Complex / fraction number is not implemented.
- Standard library (scheme complex) is not implemented.
- 1.0 and 1 is same value. (like Javascript's number primitive).
- "exact" means Number.isSafeInteger is true in Javascript.
- "exact" procedure trys to convert float numbers to safe-integer. It raises an error if it fails.
- "inexact" procedure does nothing than returning the given value.
- In S-expressions, hexadecimal, octal, binary literals can't have digits.
- Patterns with vectors are not supported.
- Procedure call must be a proper list. The last cdr of procedure call will be ignored.
- Defining syntax-rules pattern to call procedure with improper list raises syntax-error.
- (scheme base) library is imported by default. Importing "(scheme base)" is just ignored.
- (scheme base) is imported by default even if (environment) (null-environment) (scheme-report-environment 5).
- (environment) doesn't make immutable bindings.
- "import" doesn't make immutable bindings.
- (eqv? "aaa" "aaa") returns #t. (like Javascript's "aaa" === "aaa" returns true).
- char-ready? u8-ready? raise errors for file ports. Because Node.js doesn't seem to have any ftell(3) equivalent.
- read-char read-line etc. may block until complete reading.
- include / include-cli (or load etc.) always read files from the path relative to the current working directory.
- toReferentialJSON / fromReferentialJSON don't respect "toJSON" property of class instances. If you want to include class instances in serialization, please consider other serializers like js-yaml, etc.
- exit / emergency-exit does't do process.exit() but throws an Envelope object that isExitEnvelope() returns true, so that users can catch it and perform proper finalizations.
Contributions are welcome.
Bugs are tracked in the project's issue tracker.
Please read Limitations on this README before you submit.
Please include informaton:
- Expected outcome
- Actual outcome.
- Your running environment. At least:
- Cumalis Lisp's version.
- Node.js version or/and browser name and version.
- Operating system's name and version.
- If possible, URL of CodeSandbox that can reproduce the bug.
Pull requests are accepted on Github.
- Better documentation (especially Javascript interfaces more).
- REPL for Node.js.
- Expose all items in create, forms, part of functions and LISP that can be called directly for usability.
- Review the parameter names of functions. (to match R7RS)
- Add JSDocs.
- Add async/await feature to handle Javascript's async functions.
- Add some built-in library to handle objects.
- It will be nice if there are Regular expressions(SRFI-115), Hashtables(SRFI-69), Handling date and time(SRFI-19), Sorting lists and vectors, etc.
- Fraction/Complex might be implemented using Fraction.js and Complex.js.
MIT