LIPS Scheme v. 1.0.0-beta.20
Powerful Scheme based Lisp in JavaScript
Scheme Language Online REPL
;; &() is object literal used with quasiquote (let ((obj `&(:name "LIPS Scheme" :version ,lips.version))) ;; you can access JavaScript properties ;; with dot notation, print is display + newline (print (string-append obj.name " " obj.version)) ;; you can mix scheme and JavaScript (setTimeout (lambda () (alert (JSON.stringify obj))) 1000) #void)
With LIPS you can mix Scheme and JavaScript. You can access all JavaScript objects. You can also call any functions from Scheme.
NOTE: you can hover over Scheme symbols and get tooltip with documentation.
(print (filter number? '(foo 10 bar 20 baz 30))) ;; ==> (10 20 30) (print (filter #/foo|bar/ '(foo bar baz quux))) ;; ==> (foo bar) (define foo_or_bar (curry filter #/foo|bar/)) (print (foo_or_bar '(foo bar baz))) ;; ==> (foo bar) ;; &() is object literal used with quasiquote (define (make-person first last) `&(:fist ,first :last ,last)) (define beatles (map make-person '("John" "Paul" "Ringo" "George") '("Lennon" "McCartney" "Starr" "Harrison"))) ;; pluck will extract properties from objects (write (map (pluck "fist") beatles)) (newline) ;; ==> ("John" "Paul" "Ringo" "George")
Filter function accepts: regex or function. Curry is higher order function that creates a new function with default arguments. Pluck returns a function that extract fields from an object.
;; JavaScript regular expression literal (define re #/<h1>([^>]+)<\/h1>/) ;; --> is a macro that allow chaining ;; of JavaScript methods calls ;; no need to use Promises because of automagic ;; promise resolving (let ((msg (--> (fetch "https://scheme.org.pl/test/") (text) (match re) 1))) (print msg)) ;; explicit promise handling with quotation (let ((promise (--> '>(fetch "https://scheme.org.pl/test/") (then (lambda (res) (res.text))) (then (lambda (x) (. (x.match re) 1)))))) (print (await promise)))
LIPS do automagic async/await. It resolve all promises by default, but you can quote a promise and handle it like in JavaScript.
;; show hygienic macro prints expression ;; and the result value (define-syntax show (syntax-rules () [(_ expr ...) (begin (begin (write 'expr) (display " = ") (write expr) (newline)) ...)])) ;; few example of Numeric Tower (show (/ 1 2) (expt 1/4 1/2) (expt 10+10i 1/2) (log 2+1/2i) (acos -1) (+ 1/2 1/4) (* 3/4 1/10))
You can use hygienic syntax-rules macro, here are also a few examples of Numeric Tower.
(define-macro (λ . body) `(lambda ($1 $2 $3 $4 $5 $6 $7 $8 $8 $9) ,@body)) (print (map (λ (+ $1 1)) (range 10))) ;; ==> (1 2 3 4 5 6 7 8 9 10) (let ((x 10)) (print (map (λ (+ $1 $2)) (range x) (cdr (range (+ x 1)))))) ;; ==> (1 3 5 7 9 11 13 15 17 19)
LIPS also supports classic lisp macros. Here is example of anaphoric macro (a lambda shortcut)
;; Create new class using define-class macro (define-class Person Object (constructor (lambda (self name) (set! self.name name)))) ;; add syntax extension (set-special! "P:" 'make-person lips.specials.SPLICE) ;; add class representation (set-repr! Person (lambda (x q) (string-append "P:(" (repr x.name q) ")"))) ;; function that create new object ;; for the syntax extension (define (make-person name) (new Person name)) ;; we can use new syntax (write P:("jon")) (newline) ;; ==> P:("jon") (print (. P:("jon") 'name)) ;; ==> "jon"
Syntax extensions and custom repr allow to create new homoiconic data types.
;; this will query the DOM and invoke click method (let ((selector "[class*=\"colorModeToggle\"] button")) (--> (document.querySelector selector) (click))) ;; accessing jQuery Terminal, ignore works like begin ;; but the return value is ignored so the terminal ;; is not paused when it find a Promise from ;; Terminal typing animation (ignore (term.animation (lambda () (let (($button ($ ".run"))) ($button.attr "disabled" #t) (term.css "--background" "#2E2E2E") (term.echo "This is LIPS Scheme" &(:typing #t)) ($button.removeAttr "disabled")))))
With LIPS you can interact with JavaScript DOM and jQuery Terminal (REPL).
;; define new dynamic parameter (define x (make-parameter 10)) (define (double-x) (* (x) (x))) ;; use default value (print (double-x)) ;; ==> 100 ;; change the value dynamically (parameterize ((x 20)) (print (double-x))) ;; ==> 400
Dynamic variables with R7RS parameterize
;; macro that swap first two variables ;; with the last two expressions (define-macro (swap! a b x y) (let ((g_b (gensym))) `(let ((,g_b ,y)) (set! ,a ,b) (set! ,b ,g_b)))) ;; example taken from Go website ;; fib creates a function ;; that return fibonacci numbers (define (fib) (let ((a 0) (b 1)) (lambda () (swap! a b b (+ a b)) a))) (let ((f (fib))) (list (f) (f) (f) (f) (f)))
Here is a fibonacci Closure with swap! lisp style macro.
;; recursive hygienic syntax-rules macro (define-syntax alist (syntax-rules () ((_) ()) ((_ x y z ...) (cons (cons x y) (alist z ...))))) (print (alist "foo" 10 "bar" 20 "baz" 30)) ;; ==> ((foo . 10) (bar . 20) (baz . 30)) (macroexpand (alist "foo" 10 "bar" 20)) ;; ==> (#:cons (#:cons "foo" 10) ;; (#:cons (#:cons "bar" 20) ;; ()))
Scheme hygienic macro that creates an assoc list, with macroexpand.
(define (repeater x) "(repeater value) Function prints the value 1 time and modifies itself to repeat (+ n 1) times on the next call." (for-each (lambda () (print x)) (range 1)) (let ((r (cadr (cdadddr (. repeater '__code__))))) (set-cdr! r (list (+ (cadr r) 1))))) (print "1") (repeater 'hello) (print "2") (repeater 'hello) (print "3") (repeater 'hello)
Function that modify its source code when run
;; define new syntax parameter (define-syntax-parameter it (syntax-rules () ((_ . _) (syntax-error "abort used outside of a loop")))) ;; syntax-rules macro aif adds (it) parameter ;; to access tested value. (define-syntax aif (syntax-rules () ((_ cond rest ...) (let ((test cond)) (syntax-parameterize ((it (syntax-rules () ((_) test)))) (if test rest ...)))))) ;; no need to use assoc two times ;; or using a variable to hold the value (let ((alist '((a . 10) (b . 20)))) (aif (assoc 'a alist) (print (cdr (it)))))
Built in SRFI-139 syntax-parameterize allows creating anamorphic hygienic macros.
;; JavaScript generator created using JS eval (define gen (self.eval " (async function* gen(time, ...args) { function delay(time) { return new Promise((resolve) => { setTimeout(resolve, time); }); } for (let x of args) { await delay(time); yield x; } })")) ;; iteration over iterator/generator (do-iterator (i (apply gen 100 (range 10))) () (print i)) (print (iterator->array (gen 100 1 2 3 4 5))) ;; strings and lists are JavaScript iterators (write (iterator->array "hello")) (newline) (print (iterator->array '(1 2 3 4)))
You can iterate over JavaScript generators (objects that implement iterator protocol)
(define Y (lambda (h) ((lambda (x) (x x)) (lambda (g) (h (lambda args (apply (g g) args))))))) ((Y (lambda (f) (lambda (n) (cond ((< n 0) (throw (new Error "Invalid factorial"))) ((zero? n) 1) (else (* n (f (- n 1)))))))) 10) ;; ==> 3628800
Example of Y Combinator and inline factorial function.
(define-macro (%promisify expr) (let ((resolve (gensym "resolve"))) `(new Promise (lambda (,resolve) ,(append expr (list resolve)))))) (if (null? self.$.terminal.from_ansi) (%promisify ($.getScript "https://cdn.jsdelivr.net/npm/jquery.terminal/js/unix_formatting.js"))) (if (null? self.qrcode) (%promisify ($.getScript "https://cdn.jsdelivr.net/gh/jcubic/static/js/qrcode.js"))) (let ((code (%promisify (qrcode.generate "https://tinyurl.com/fxv87gb")))) (term.echo (code.replace #/\[47m/g "[30;47m")) #void)
Summary
LIPS Scheme is powerful lisp interpreter written in JavaScript. The aim of the project is to fully support R7RS specification of Scheme Programming Language. But add more powerful features on top.
The name LIPS is recursive acronym which stands for LIPS Is Pretty Simple. Over time the project may no longer be that simple in implementation but it always be LIPS.
Features
Most important features of LIPS:
- Great integration with JavaScript. You can use any JS library out of the box.
- Asynchronous execution (auto resolving of promises with optional promise quotation).
- Literal regular expression.
- Functional helpers (inspired by RamdaJS and Lodash).
- Possibility modify the parser at runtime (add new syntax, similar to vectors and object).
- Possibility to set representation of new data types for write and display. With parser extensions you can make new data types have different syntax and still be homoicoic.
- Small JavaScript core with Standard Library implemented in basic Scheme.
- Full support of Unicode and Emoji.
- Support for SXML, that allow to write JSX e.g. with Preact or React apps.
- I/O Ports supports in browser with BrowserFS.
- Full numerical tower and Big Integer support (still not 100% fully unit tested).
- Powerful introspection (similar to the one in Python).
- Auto formatting of lisp code (pretty print).
- Lisp/hygienic macros and macroexpand.
- Builtin help system.
Tools
Bookmarklet
When you're learning Scheme language, you can run the REPL directly on any page that have Scheme tutorial you're learning from. It even work with PDF files and new empty tab (at least in Chrome). Drag this link LIPS REPL to your bookmarks. When you click on the bookmark it will run the interpreter. You can also just click the link.
The bookmark can also be used to add REPL to your LIPS Web application.
It may not work on sites that are protected with Content Security Policy. CSP was created mostly as a security solution to prevent XSS vunerablities. You can disable this mechanism with Chrome Extension, but you do this on your own risk.
Screenshooter
This tool allow you to create nice screenshot of code. The screenshot tool is aware of any syntax added on top of Scheme. The tool is created using Codemirror and custom style build on top of Scheme syntax.
You can use this tool to generate snippets of Scheme or LIPS Code and share it on social media.
Video Presentation about LIPS Scheme
Video presentation given during FOSDEM 2023 in Declarative and Minimalistic Computing devroom. It discuss different aspect of LIPS Scheme. It first gives quick intro to Lisp and Scheme and later show different features of LIPS Scheme.
Acknowledgments
- Font used in logo is Telegrafico by ficod.
- 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
LIPS Scheme is Open Source and released on MIT license