node package manager
Stop writing boring code. Discover, share, and reuse within your team. Create a free org »

lisp2js

lisp2js beta (clojure like syntax)

By Yiyi Wang (shd101wyy)

Simple Lisp that compiles to JavaScript (targeting ECMAScript 6)
So ECMAScript 5 might not work.
As es6 is not fully supported, the language will compile to es5.

npm / github

npm
github #### Installation

    npm -g install lisp2js  

How to run

    lisp2js                               # repl 
    lisp2js   [file1.lisp]                # run file1.lisp 
    lisp2js   [file1.lisp]  [file2.js]    # compile [file1.lisp] to js file [file2.js] 

Use lisp2js in Browser

<script src="lisp2js.js"></script>
<script>
    var output = lisp.compile("(def x 12)"); // compile/eval expression, then return compiled result.
</script> 

Click Me to Try it Online

Online REPL


all comma, tab, space will be ignored.


Examples

Basics

  • comment
    
    ; semicolon is used as comment
    ;; this is comment
  • define variable value
    
    (def x 12)
    (def ->this*name$invalid@in*js 13)   ;; a invalid js variable name, which will be replaced with another name.
    (def ** Math.pow)
    var x = 12;
    var _$45__$62_this_$42_name$invalid_$64_in_$42_js = 13;  // all invalid characters are replaced with its own charcode. 
    var _$42__$42_ = Math.pow;
  • change variable value
    (set! x 15)  
    x = 15;
  • define function
    (defn add [a b]
        (+ a b))
    function add(a, b){
        return a + b;
    }
  • call function
    (add 3 4)
    add(3, 4);
  • define function with default parameters
    (defn add [:a 12 :b 3]
        (+ a b))
    function add(a, b) {
        a = (=== void 0 ? 12 : a);
        b = (=== void 0 ? 3 : b);
        return (+ b);
    };
  • define function with keyword parameters
    (defn add [x {:y 1 :z 2}]
        (+ x y z))
    (add 0)        ;;  3
    (add 1 :y 3)   ;;  6
    function add(x, __lisp_args__) {
        var __lisp_args_v__;
        __lisp_args__ = (__lisp_args__ === void 0 ? {} : __lisp_args__);
        var y = ((__lisp_args_v__ = __lisp_args__.y) === void 0 ? 1 : __lisp_args_v__);
        var z = ((__lisp_args_v__ = __lisp_args__.z) === void 0 ? 2 : __lisp_args_v__);
        return (+ y + z);
    };
    add(0);
    add(1, {
        y: 3
    });
  • call function with named(keyword) parameters
    (defn add [{:a 1 :b 2}] (+ a b))
    (add)                  ;; => 3
    (add :a 3 :b 4)        ;; => 7
    (add :b 3)             ;; => 4
    function add(__lisp_args__) {
        var __lisp_args_v__;
        __lisp_args__ = (__lisp_args__ === void 0 ? {} : __lisp_args__);
        var a = ((__lisp_args_v__ = __lisp_args__.a) === void 0 ? 1 : __lisp_args_v__);
        var b = ((__lisp_args_v__ = __lisp_args__.b) === void 0 ? 2 : __lisp_args_v__);
        return (+ b);
    };
    add();
    add({
        a: 3,
        b: 4
    });
    add({
        b: 3
    });
  • define function with rest parameters
    (defn add [a & b]   ;; b here is Array
        (+ a b[0]))
    (defn add [a . b]   ;; b here is List
        (+ a (car b)))
    function add(a) {
        for (var b = [], $__0 = 1; $__0 < arguments.length; $__0++) b[$__0 - 1] = arguments[$__0];
        return (+ b[0]);
    };  
    function add(a) {
        for (var b = [], $__0 = 1; $__0 < arguments.length; $__0++) b[$__0 - 1] = arguments[$__0];
        b = list.apply(null, b);
        return (+ car(b));
    };
  • anonymous function
    (fn [a :b 13 & c]
        (+ a b c[0]))
    function (a, b){
        for (var c = [], $__0 = 2; $__0 < arguments.length; $__0++) c[$__0 - 2] = arguments[$__0];
        b = (=== void 0 ? 13 : b);
        return (+ b + c[0]);
    };
  • do. run a series of exps.
    (do  (+ 1 2)
         (- 3 4)
         (* 5 6))
 
    (fn []
        (do (+ 1 2)
            (- 3 4)
            (* 5 6)))
    (if 1
        (do (def x 1) (def y 2))
        (do (def x 2) (def y 1)))
    (1 + 2);
    (3 - 4);
    (5 * 6);;
 
    function() {
        (1 + 2);
        (3 - 4);
        return (5 * 6);;
    };
    if (1) {
        var x = 1;
        var y = 2;
    } else {
        var x = 2;
        var y = 1;
    };
  • if
    (if 1 2 3)
    (def x (if 1 2 3))
    if (1) {
        2
    } else {
        3
    };
    var x = (1 ? 2 : 3);
  • cond
    (cond test1 (do stm1 stm2)
          test2 (do stm3 stm4)
          test3 stm5
          else stm6)
    if (test1) {
      stm1;
      stm2;
    } else if (test2) {
      stm3;
      stm4;
    } else if (test3) {
      stm5;
    } else {
      stm6;
    };
  • case
    (defn test [x]
        (case x
            "apple" "This is apple"
            "orange" "This is orange"
            else "This is nothing"))
    function test(x) {
        switch (x) {
            case "apple":
                return "This is apple";
            case "orange":
                return "This is orange";
            default:
                return "This is nothing";
        };
    };
  • let (es5) // I might change this to es6 let in the future
    (let [x 1
          y 2
          x (+ x y)
          z 4]
        (+ x y z))
    (+ (let [x 1 y 2] (- x y))
        3)
    (defn test []
        (let x 1 y 2 (+ x y)))
    ((function() {
        var x = 1;
        var y = 2;
        x = (+ y);
        var z = 4;
        return (+ y + z)
    })());
    (((function() {
        var x = 1;
        var y = 2;
        return (- y)
    })()) + 3);
    function test() {
        return ((function() {
            var x = 1;
            var y = 2;
            return (+ y)
        })());
    };
  • throw
    (throw "Too Big")
    throw "Too Big";
  • yield
    (defn test []
        (yield 1)
        (yield 2))
    (def x (test))
    (x.next)   ;; 1
    (x.next)   ;; 2
    (x.next)   ;; stop
    function test() {
        yield 1;
        yield 2;
        return;
    };
    var x = test();
    x.next();
    x.next();
    x.next();
  • try/catch/finally
    (try (console.log "This is try")
    catch e (console.log "This is catch")
    finally (console.log "This is finally"))
    try {
      console.log("This is try");
    } catch (e) {
      console.log("This is catch");
    } finally {
      console.log("This is finally");
    };
  • some operators
    (= 1 1)
    (+ 1 2 3)
    (- 1 2 3)
    (* 1 2 3)
    (/ 1 2 3)
    (* (+ 1 2) (- 3 4))
    (> 1 2 3 4)
    (<= 1 2 3 4)
    (&& true false)
    (|| 1 2)
    (| 1 0x12)
    (and true false)
    (or true false)
    (not true)
    (1 === 1);
    (1 + 2 + 3);
    (1 - 2 - 3);
    (1 * 2 * 3);
    (1 / 2 / 3);
    ((1 + 2) * (3 - 4));
    (1 > 2 && 2 > 3 && 3 > 4);
    (1 <= 2 && 2 <= 3 && 3 <= 4);
    (true && false);
    (1 || 2);
    (1 | 0x12);
    (true && false);
    (true || false);
    (!true);
  • get
    (get "abcd" 'length)
    (get console .log)
    "abcd"["length"];
    console.log;
  • ->
    (-> console (.log "Hello World"))
    (-> $ (.post "test.php")
          (.done (fn () "done"))
          (.fail (fn () "fail")))
    (-> "i am cool"
        .length)
    console.log("Hello World");
    $.post("test.php").done(function() {
        return "done";
    }).fail(function() {
        return "fail";
    });
    "i am cool".length;
  • class (this might be buggy, I will implement class in es6 in the future)
    (class Animal
        :constructor (fn [age]                ;; define constructor
                        (set! this.age age))
        :showAge (fn []                       ;; define method
                    (console.log "Called from Animal")
                    (console.log this.age)))
    (class Dog extends Animal
        :constructor (fn [age]                ;; define constructor
                        (super age))          ;; call superclass constructor
        :showAge (fn []                       ;; define method
                    (console.log "Called from Dog")
                    (super.showAge))          ;; call superclass method
        :bark (fn []                          ;; define method
                (console.log "Bark!")))
 
    (def dog (new Dog 5))
    (dog.showAge)  ;;  Called from Dog
                   ;;  Called from Animal
                   ;;  5
 
    (dog.bark)     ;;  Bark!
  • loop
    ;; calculate factorial 10
    (loop [i 10
           acc 1]
        (if (= i 0)
            acc
            (recur (- i 1)
                   (* i acc))))
    (function __lisp__recur__$0(i, acc) {
        if ((=== 0)) {
            return acc
        } else {
            return __lisp__recur__$0((- 1), (* acc))
        };
    })(10, 1);
  • new
    (def x (new Array 1 2 3 4))
    var x = (new Array(1, 2, 3, 4));
  • in
    (in 'a {'a 12})
    ("a" in {"a": 12});
  • instanceof
    (instanceof [1 2 3] Array)
    ([1, 2, 3] instanceof Array)

  • List functions
    • To enable List datatype, include lisp.js from https://github.com/shd101wyy/List_for_FP
    • after you compile your .lisp file to javascript file.
    • This file will give you 4 functions: car, cdr, cons, list.
    •                  and 1 datatype: $List  
      
    • See the link above for more information.
  • define a list.
    (def x '(1 2 3))
    var x = cons(1, cons(2, cons(3, null)));
  • quasiquote
    (def x 12)
    `(~x x)     ;; => (12 x)
    var x = 12;
    cons(x, cons("x", null));
  • car, cdr, cons, list
    (def a 1)
    (def b 2)
    (def c (cons a (cons b '())))   ;; => (1 2)
    (car c)                         ;; => 1
    (cdr c)                         ;; => (2)
    (def l (list a b))           ;; => (1 2)
    var a = 1;
    var b = 2;
    var c = cons(a, cons(b, null));
    car(c);
    cdr(c);
    var l = list(a, b);

  • Use JavaScript Object/Array

  • define Array

    (def x [1 2 3])
    var x = [1, 2, 3];
  • define Object
    (def x {:a 12 b 13 "c" 14})
    // es6 
    var x = {a: 12, [b]: 13, "c": 14};
  • es6 define value
    (def [x y z] [1 2 3])
    (def {:m :n} {:m 12 :n 20})
    // es6 
    var [x, y, z] = [1, 2, 3];
    var {
        m, n
    } = {
        m: 12,
        n: 20
    };
  • change value
    (def x [1 2 3])
    (set! x[0] 12)
    (def y {:a 12 :b 13 :c (fn (a b) (+ a b))})
    (set! y.a 13)
    (set! y["a"] 13)
    var x = [1, 2, 3];
    x[0] = 12;
    var y = {
        a: 12,
        b: 13,
        c: function(a, b) {
            return (+ b);
        }
    };
    y.a = 13;
    y["a"] = 13;
  • get value
    (def y {:a 12 :b 13 :c (fn (a b) (+ a b))})
    (y.add y.a y.b)
    var y = {
        a: 12,
        b: 13,
        c: function(a, b) {
            return (+ b);
        }
    };
    y.add(y.a, y.b);

recur

similar to recur in clojure
  • recur
    (defn test [n]
      (cond (= n 0) 0
            1 (recur (- n 2))                ;; recur here means test
            else (recur (- n 1))))
 
    ;; anonymous function recur
    ((fn [n acc]
      (if (= n 0)
        acc
        (recur (- n 1) (* n acc)))) 10 1)  ;; recur <=> that anonymous function
    var test = function(n) {
      if ((=== 0)) {
        return 0;
      } else if (1) {
        return test((- 2));
      } else {
        return test((- 1));
      };
    };
    (function __lisp__recur__$0(n, acc) {
      if ((=== 0)) {
        return acc;
      } else {
        return __lisp__recur__$0((- 1), (* acc));
      };
    })(10, 1)

Macro

  • define a macro (unhygienic right now)
    (defmacro square [x] `(* ~x ~x))
    (square 12)
    (defmacro square-with-different-params
        [x] `(* ~x ~x)
        [x y] `(+ (* ~x ~x) (* ~y ~y)))
    (square-with-different-params 12)
    (square-with-different-params 15 16)
    (12 * 12);
    (12 * 12);
    ((15 * 15) + (16 * 16));
  • macro-expand: expand a macro form
    (defmacro square [x] `(* ~x ~x))
    ;; the macro-expand function will expand the macro until it no longer represents a macro form.
    (macro-expand '(square 12))  ; => '(* 12 12)
 
    (defmacro test [x] `(test (+ 1 ~x)))
    ;; macro-expand can also expand macro forms for n times.
    (macro-expand '(test 1) 2)   ; => '(test (+ 1 (+ 1 1)))   this will expand macro twice.
    (macro-expand '(test 1) 3)   ; => '(test (+ 1 (+ 1 (+ 1 1))))   this will expand macro for 3 times.
However, the macro implementation still has errors.

Change Log

  • Version 0.0.36

  • 2015/6/14

    • begin to use clojure like syntax.
  • 2015/3/15

  • Version 0.0.33

    • fix one macro bug
    • add =>
    • eg:
        (=> (x y) (+ x y))   ;; es6 (x, y) => {return x + y};
  • Version 0.0.31 ~ 0.0.32

  • 2015/3/8

    • change code generation for class statement.
    • add macro-expand function.
    • eg:
        (defmacro square (x) `(* ~x ~x))
        ;; the macro-expand function will expand the macro until it no longer represents a macro form.
        (macro-expand '(square 12))  ; => '(* 12 12)
     
        (defmacro test (x) `(test (+ 1 ~x)))
        ;; macro-expand can also expand macro forms for n times.
        (macro-expand '(test 1) 2)   ; => '(test (+ 1 (+ 1 1)))   this will expand macro twice.
        (macro-expand '(test 1) 3)   ; => '(test (+ 1 (+ 1 (+ 1 1))))   this will expand macro for 3 times.
  • 2015/3/4

    • fix one macro bug.
  • Version 0.0.30

    • add class support (this might be buggy though)
    • eg:
        (class Animal
            :constructor (fn (age)                ;; define constructor
                            (= this.age age))
            :showAge (fn ()
                        (console.log "Called from Animal")
                        (console.log this.age)))
        (class Dog extends Animal
            :constructor (fn (age)                ;; define constructor
                            (super age))          ;; call superclass constructor
            :showAge (fn ()
                        (console.log "Called from Dog")
                        (super.showAge))          ;; call superclass method
            :bark (fn ()
                    (console.log "Bark!")))
     
        (def dog (new Dog 5))
        (dog.showAge)  ;;  Called from Dog
                       ;;  Called from Animal
                       ;;  5
     
        (dog.bark)     ;;  Bark!
  • Version 0.0.28

  • 2015/3/1

    • add case statement.
    • eg:
        (def test (x)
            (case x
                "apple" "This is apple"
                "orange" "This is orange"
                else "This is nothing"))
        (test "pear")    ;; => This is nothing
        (test "apple")   ;; => This is apple
        (test "orange")  ;; => This is orange
    • fix one if and cond bug.
  • 2015/2/25

    • fix demo link error.
  • 2015/2/24

    • add loop macro
    • eg:
        ;; calculate factorial 10
        (loop i 10
              acc 1
            (if (== i 0)
                acc
                (recur (- i 1)
                       (* i acc))))
  • 2015/2/23

    • add REPL demo
    • fix <= >= < > == != comparison operator bug, they now support multiple arguments.
    • eg:
        (== 1 1 1)
        (<= 1 2 3 4 5 2)
  • Version 0.0.24

  • 2015/2/22

    • add -> macro
    • eg:
        (-> console (.log "Hello World"))
        (-> $ (.post "test.php")
              (.done (fn () "done"))
              (.fail (fn () "fail")))
        (-> "i am cool"
            .length)
        console.log("Hello World");
        $.post("test.php").done(function() {
            return "done";
        }).fail(function() {
            return "fail";
        });
        "i am cool".length;
  • Version 0.0.20 - 0.0.22

  • 2015/2/17

    • Happy New Year []~( ̄▽ ̄)~*
    • Add and, or, not macros that behave the same as && || !
    • Change default parameters and keyword parameters.
    • For example:
    • Default Parameters
        (def add (:a 1 :b 2)
            (+ a b))
        (add)     ;; => 3
        (add 2)   ;; => 4
        (add 3 4) ;; => 7
    • Keyword Parameters
        (def add ({:x 1 :y 2})
            (+ x y))
        (add)            ;; => 3
        (add :x 3)       ;; => 5
        (add :y 6)       ;; => 7
        (add :x 4 :y 5)  ;; => 9
        (add :y 1 :x 5)  ;; => 6
  • Version 0.0.18

  • 2015/2/16

    • Add yield and throw support.
  • 2015/2/9

    • Improve compatibility with es5
  • 2015/2/7

    • Change the way of defining the default parameters and calling function with named parameters
  • 2015/1/31

    • Fix one macro bug.
  • 2015/1/26

    • Change do function.
      (do ...) will be wrapped as function when it is argument or assignment value.
  • Version 0.0.13

    • add in support.
    (in 'a {'a 12})
    ("a" in {"a": 12});
  • Version 0.0.12

    • fix one macro bug.
  • 2015/1/23

    • change . & for rest parameters
    • . => list
    • & => array
    (def add (a & b)   ;; b here is Array
        (+ a b[0]))
    (def add (a . b)   ;; b here is List
        (+ a (car b)))
    // es6 
    var add = function(a, ...b){
        return a + b[0];
    }
    var add = function(a, ...b) {
        b = list.apply(null, b);
        return (+ car(b));
    };
  • 2015/1/19

    • add get fn
    • fix "abc".length like exp bug.
  • 2015/1/14

    • add cond
    • add recur support
    • add try/catch/finally support
    • change if statement
      Went snowboarding and fell down too many times. I twisted my wrist unfortunately. (T_T)
  • 2015/1/7

    • add support for fn with name .
    (fn add (x) (+ x y))
    function add(x) {
        return (+ y);
    };
* fix one macro bug
  • 2015/1/5
    • add support for const
    • change let . see doc above.
    • fix several bugs.
  • 2015/1/5 First Release
    • There are still lots of bugs.
    • ...

MIT License ;)