LIPS Logo: Scheme (lisp dialect) in JavaScript

Powerful Scheme lisp in JavaScript

Variables and functions


(define x 10)
(define square (lambda (x) (* x x)))
(define (square x) (* x x))
    

Lisp Macros


(define-macro (foo x) `(1 2 ,@(car x) 3 4))
    

BigInt

lips have no problems in evaluating factorial of thousand, and it's fast.


(define (! n) (reduce * (map 1+ (range n))))

(print (! 1000))
    

List operations


(cons 1 2)
(cons 1 (cons 2 nil))
(list 1 2 3 4)
'(1 2 3 4)

(let ((lst '(1 2 (3 4 5 6))))
   (print (car lst))
   (print (cadaddr lst)))
    

all functions that match this regex `c[ad]{1,5}r` are defined.

ALists


(let ((l '((foo . "lorem") (bar "ipsum"))))
   (set-cdr! (assoc l 'foo) "hello")
   (set-cdr! (assoc l 'bar) "world")
   (print l))
    

Flow constructs


(let ((x 5))
    (while (> (-- x) 0) (print x)))
    

Operator == work as == in javascript but it should only be used on number or values that can be converted to numbers like string "10".

To compare strings or any values use eq?


(if (== "10" 10)
    (print "newer printed"))

(if (eq? "foo" "bar")
    (print "equal")
    (print "not equal"))

(let ((x 10))
  (if (and (> x 1) (< x 20))
      (begin
         (print "this is x > 1")
         (print "and x < 20"))))
    

Eval


(eval (read "(print \"hello\")"))
    

Apply

Apply function the same as the one from javascript invoke a function with arguments taken from list


(let ((strings '("foo" "bar" "baz")))
   (print (apply concat strings)))
    

Async code


(eval (read))
    

then type S-Expression like (print 10). If function return Promise the execution is paused and restored when Promise is resolved

Access JavaScript functions and objects


((. window "alert") "hello")
((. console "log") "hello")
    

If object is not found in environment, then window object is tested for presense of the element.

You can execute jQuery functions


(let* ((term ($ ".terminal")))
  ((.  term "css") "background" "red"))
    

function $ is available because it's in window object.

or operate on strings


((. "foo bar baz" "replace") /^[a-z]+/g "baz")

(let ((match (. "foo bar baz" "match")))
    (array->list (match /([a-z]+)/g)))
    

String operations


;; todo
    

Mapping, filtering and reducing


(map car (list
            (cons "a" 2)
            (cons "b" 3)))

(filter odd (list 1 2 3 4 5))

(filter (lambda (x)
          (== (% x 2) 0))
    (list 1 2 3 4 5))

(define (reverse list)
    (reduce (lambda (list x) (cons x list)) list nil))

(reverse '(1 2 3 4))

(define (sum . list) (reduce (lambda (a b) (+ a b)) list))
;; or
(define (sum . list) (reduce + list))

(print (sum 1 2 3 4 5))
    

Auto resolving promises


(let* ((res (fetch "https://terminal.jcubic.pl"))
       (text ((. res "text"))))
   (. ((. text "match") "<title>([^<]+)") 1))
    

Currying


(define (add a b) (+ a b))
(define add10 (curry add 10))
(print (add10 20))
    

Working with arrays

You can modify array with set! function and to get the value of the array you can use . dot function.


(let ((arr (list->array '(1 2 3 4))))
   (set! (. x 1) 10)
   (print (array->list arr)))

(let* ((div ((. document "querySelectorAll") ".terminal .cmd-prompt"))
       (len (. div "length"))
       (i 0))
    (while (< i len)
       (print (. (. div i) "innerHTML"))
       (++ i)))
    

this equivalent of JavaScript code:


var div = document.querySelectorAll(".terminal .cmd-prompt");
var len = div.length;
var i = 0;
while (i < len) {
   console.log(div[i].innerHTML);
   ++i;
}
    

Stdin and stdout

If you're using print function to display values or using read to get values from users, you can provide your own handlers in environment, jQuery Terminal based implementation look like this:

By default stdout prints to console and stdin use browser prompt function.


var env = lips.env.inherit('name', {
    stdout: {
        write: function(...args) {
            args.forEach((arg) => {
                term.echo(arg, {keepWords: true});
            });
        }
    },
    stdin: {
        read: function() {
            return new Promise(function(resolve) {
                term.read('', function(command) {
                    resolve(command);
                });
            });
        }
    }
});
    

Math and boolean operators (functions and macros)

< > => <= 1+ 1- ++ -- + - * / % and or not

Bit operation

| & ~ << >>

Extending LIPS

to create new function from JavaScript you can use:


env.set('greeter', function(user) {
   return "hello " + user;
});
    

then you can use it in LIPS:


(greeter "john")
    

To define a macro in javascript you can use Macro constructor that accept single function argument, that should return lisp code (instance of Pair).


var {Macro, Pair, Symbol, nil} = lips;

env.set('quote-car', new Macro('quote-car', function(code) {
    return Pair.fromArray([new Symbol('quote'), code.car.car]);
}));
    

and you can execute this macro in LIPS:


(quote-car (foo bar baz))
    

it will return first symbol and not execute it as function foo.

you can also use eval here to execute code


env.set('async', new Macro('async', function(code, {dynamic_scope, error}) {
    var args = {error, env: this};
    if (dynamic_scope) {
        args.dynamic_scope = this;
    }
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(evaluate(code.car, args));
        }, 0);
    });
}));
    

This function will invoke code in next iteration of event loop

if you want to create macro like quasiquote, the returned code need to be wrapped with function quote

When creating macros in JavaScript you can use helper Pair.fromArray() and code.toArray().