- ×
Scheme based powerful lisp interpreter written in JavaScript
Filed under application tools › frameworksShow AllLIPS is a powerful Scheme-based, Lisp language written in JavaScript. It is based on the Scheme dialect of lisp and the R5RS/R7RS specifications. It has extensions to make it easier to interact with JavaScript and extend the language. It works both in the browser and with Node.js.
The aim of the project is to support full R7RS specification and be compatible with Scheme programming language.
The name is a recursive acronym which stands for LIPS Is Pretty Simple.
Demo
Features
- Literal regular expression.
- Asynchronous execution (auto resolving of promises).
- Possibility to add new syntax (similar to vectors and object).
- Numerical tower and Big Integer support.
- Powerful introspection.
- Great integration with JavaScript.
- Auto formatting lisp of code (pretty print)
- Lisp and hygienic Scheme macros and macroexpand.
- Builtin help system.
Installation
To install you can use npm (or yarn)
NOTE: The version that is on NPM is heavily outdated, use beta version:npm install @jcubic/lips@beta
or yarn:
yarn add @jcubic/lips@beta
then include the file in the script tag. You can grab the version from unpkg.com
https://unpkg.com/@jcubic/lips@beta
or from jsDelivr (that's seems a bit faster)
https://cdn.jsdelivr.net/npm/@jcubic/lips@beta/dist/lips.min.js
Bookmarklet REPL
You can also run the REPL on any page while you learn Scheme using the bookmarklet:
https://github.com/jcubic/lips/blob/master/lib/js/bookmark.js
Create any link in your bookmarks, edit it and copy-paste the content of that file. After you click on the link it will create the REPL at the bottom of the page. (NOTE: It may not work on every page because of content security policy; e.g. google.com or gihub.com)
If you have trouble with creating the bookmarklet, you can open LISP Scheme home page where you can find a link that you can drag to your bookmarks.
Usage
The simplest way is to include the lips code in the script tag:
<script type="text/x-scheme" bootstrap> (let ((what "world") (greet "hello")) (display (string-append greet " " what))) </script>
or use the
src
attribute:<script type="text/x-scheme" bootstrap src="example.scm"></script>
Bootstrapping Scheme system
Big part of LIPS is written in LIPS itself, but to use full power of LIPS you need to load those additional Scheme files. The easiest way is to add
bootstrap
attribute on first script tag withtext/x-scheme
type. By default, it will use CDN from jsdelivr. To load each file using builtin load function (that will fetch the file using AJAX and evaluate it).<script src="https://cdn.jsdelivr.net/npm/@jcubic/lips@beta/dist/lips.min.js" bootstrap></script>
You can also specify the path where LIPS should search for standard library.
<script src="https://cdn.jsdelivr.net/npm/@jcubic/lips@beta/dist/lips.min.js" bootstrap="https://cdn.jsdelivr.net/npm/@jcubic/lips@beta/dist/std.xcb"> </script>
You can use
bootstrap="./std.xcb"
if there isstd.xcb
file in local directory. You can also bootstrap withstd.scm
orstd.min.scm
but xcb file is the fastest, because it's already parsed and compiled into binary format.Running LIPS programmatically
var {exec} = require('@jcubic/lips'); // node // or var {exec} = lips; // browser exec(string).then(function(results) { results.forEach(function(result) { console.log(result.toString()); }); });
When running exec you will also need to bootstrap the language and loaded files from
/lib/
directory.Documentation about beta version can be found in Wiki.
Standalone executable
NOTE: Executable don't require bootstrapping lib files.
If you install lips globally with:
npm install -g @jcubic/lips@beta
you can run the interpreter from the terminal:
You can also run code in a string with:
lips -c '(let ((what "World")) (display (string-append "Hello " what)))'
and you can run a file using:
cat > foo.scm <<EOF (let ((what "World")) (display (string-append "Hello " what)) (newline)) EOF lips foo.scm
You can also write executable files that use lips using shebang (SRFI-22)
cat foo.scm #!/usr/bin/env lips (let ((what "World")) (display (string-append "Hello " what)) (newline)) chmod a+x foo.scm ./foo.scm
Executables also return a S-Expression according to SRFI-176 use
lips --version
orlips -V
.FOSDEM'23 Presentation [Video]
Limitations
Performance
Because LIPS is tree walking interpreter, sometimes it may be slow. Especially if you want to process long arrays and use callback function. If the array is quite large each piece of code inside the callback may slow down the processing. For example see:
script reference.scm
That generates reference documentation for all builtin functions and macros. The slow part is
(names.sort name-compare)
(Array::sort
) that take quite time to calculate, because the array with functions and macros is quite large. If you came into performance issue, you can write the part of the code in JavaScript. If you want to do this in LIPS Scheme you can use something like this:(let ((fn (self.eval "(function(a, b) { /* any complex code in JS */ return a.localeCompare(b); })"))) (arr.sort fn))
Another example of slow performance is using LIPS with React, the more code you put into components the slower the app will become.
Examples:
- Preact app that update SVG - it requires using debounce.
- React with Hooks - on click the UI freezes for ~300ms, you can see warnings in dev tools.
The issue with performance is tracked in #197.
JavaScript callbacks
Another limitation is when using JavaScript libraries that require normal values but get a Promise instead. This can happen with React/Preact and when the component returns a Promise. Some macros can be async (return a Promise), which will break the React app when used in components. An example of a macro that is async is
do
macro. So when using React/Preact and when you need to use a promise, use promise quotation anduseEffect
.Supported SRFI
built-in
description spec Feature-based conditional expansion construct SRFI-0 Homogeneous numeric vector datatypes SRFI-4 Basic String Ports SRFI-6 Running Scheme Scripts on Unix SRFI-22 Error reporting mechanism SRFI-23 Basic Format Strings SRFI-28 Basic Syntax-rules Extensions SRFI-46 An interface to access environment variables SRFI-98 Syntax parameters SRFI-139 Custom macro transformers SRFI-147 Version flag SRFI-176 Command line SRFI-193 require
(load "./lib/srfi/<number>.scm")
They should be loaded as R7RS libraries in final 1.0.0 version
description spec List Library SRFI-1 AND-LET*
: an AND with local bindings, a guardedLET*
special formSRFI-2 receive: Binding to multiple values SRFI-8 #,
external formSRFI-10 Notation for Specializing Parameters without Currying SRFI-26 Basic hash tables SRFI-69 Boxes SRFI-111 Syntactic combiners for binary predicates SRFI-156 Multiple-value boxes SRFI-195 Procedures and Syntax for Multiple Values SRFI-210 Evaluating expressions in an unspecified order SRFI-236 in Web (e.g. in Web REPL) you can use URL:
(load "https://cdn.jsdelivr.net/npm/@jcubic/lips@beta/lib/srfi/<NUMBER>.scm")
Links
Articles
- How to start learning Lisp (PL with translator widget)
Press
- JavaScript Weekly
- Hacker News
- Brian Lovin (HN clone)
Projects that use LIPS
- Conzept - Topic exploration system for the 21st century (see also their Twitter account and Command API Documentation)
Roadmap
1.0
- [x] Full support for R5RS
- [ ] Full support for R7RS
- [ ] R7RS libraries (
import
/export
/define-library
). - [ ] Continuations.
- [ ] Tail Call Optimization (TCO).
- [ ] Fully tested Numerical Tower.
- [ ] R7RS libraries (
- [x] Fully working binary compiler (for faster parsing and loading std lib).
- [ ] Finish
syntax-rules
(ignore limitations of current approach).- [ ] Objects.
- [ ] Vectors.
Future Plans
- [ ] Picture language (possibly inspired by P5.js, see SRFI-203).
- [ ] Stepper/Debugger.
- [ ] Allow to use read/port in syntax extensions (similar to CL reader macros).
- [ ] Proper expansion time for both macro systems.
- [ ] Fully working and tested R7RS hygienic Macros (
syntax-rules
). - [ ] All recursive function in JS don't consume stack.
WIP Side projects
How you can help
I'm working on version 1.0. If you find any bugs, you can help by reporting them. If you have some Scheme code that doesn't work (note about the limitations) don't hesitate to report an issue.
You can also propose a feature or improvement to the library, it doesn't always have to be defects.
If you enjoy the library, you can write about it on a blog post and share information about it or write on Social Media. Don't forget to link to the project website (it's good for SEO).
Of course, if you want you can also contribute with code, but there are way easier ways to help.
I would also love to see if you use the library, I may even share the links of projects that use it.
Acknowledgments
- Font used in logo is Telegrafico by ficod.
- Part of the current Parser is inspired by implementation in BiwaScheme by Yutaka HARA (yhara).
fetch
polyfill use unfetch by Jason Miller.- Browser
init
function use ContentLoaded. - The rationalize algorithm is based on Kawa Scheme by Per M.A. Bothner, Alan Bawden and Marc Feeley.
ucs2decode
function taken from punycode.js by Mathias Bynens.- Rosetta Code was used for:
- StackOverlow code was used for functions:
- Code formatter is roughly based on scheme-style and GNU Emacs scheme mode.
- Some helpers in standard library are inspired by same functions from RamdaJS library.
Special thanks to Lassi Kortela for helping with Scheme code.
License
Released under MIT license
Copyright (c) 2018-2024 Jakub T. Jankiewicz