Converts Clojure code to JavaScript


Keywords
clojure, convert, converter, javascript
License
EPL-1.0

Documentation

cloJS Clojars Project

A library for Clojure to convert Clojure code to JavaScript. It combines the simplicity of Clojure syntax with the power of JavaScript libraries. So all the function calls are of JavaScript.

Artifacts

clojs artifacts are released to Clojars.

If you are using Maven, add the following repository definition to your pom.xml:

<repository>
  <id>clojars.org</id>
  <url>http://clojars.org/repo</url>
</repository>

The Most Recent Release

With Leiningen

With Maven:

<dependency>
  <groupId>clojs</groupId>
  <artifactId>clojs</artifactId>
  <version>0.1.4</version>
</dependency>

Dependencies

node.js

escodegen npm package

npm(node.js package manager) is installed with node.js. escodegen can be installed by typing:

npm install escodegen

in your terminal/command line.

Bugs and Enhancements

Please open issues against the cloJS repo on Github.

Mailing List

Please ask questions on the cloJS mailing list.

Who is it for?

This is for:

  • JavaScript programmers who want to use simple LISP like syntax of clojure. All the JavaScript functions can be called.
  • Clojure programmers who want to use JavaScript functions and libraries without learning JavaScript syntax.

Introduction

You write Clojure syntax code using JavaScript functions and it is converted into JavaScript code.

Usage

clojs.clojs

The namespace for code conversion in the clojs library is clojs.clojs.

(require '[clojs.clojs :refer [convert convert-string] :as cj])

Convert a code snippet as string with convert-string. It takes a string(Clojure code) and returns a string(JavaScript code):

(convert-string "(def a 1)")
=> const a = 1;

Convert one or more files containing code with convert. It takes the path of each input file as a string and creates the equivalent JavaScript file(.js) in the same folder as the input file:

(convert "a.clj" "resources/b.clj")
=> nil

This will create a.jsin the folder where a.clj is present and b.js in the resources folder where b.clj is present. Note: If a.js or b.js are already present in the respective folders, they will be overwritten.

Here's a sample Clojure app that uses clojs

Syntax

Here is all the syntax that clojs supports: Legend: a -> b means a in Clojure is converted to b in JavaScript

Literals

Input literal type Input Output Output literal type
String "abc" 'abc' String
Number 123, 123.456 123, 123.456 Number
Nil nil null Null
Boolean true, false true, false Boolean

Note

  • null in JavaScript is nil in Clojure.
  • Strings in Clojure use only "" as delimiters.

Identifiers

Identifiers like variable names, function names are converted as it is. Note to Clojure programmers: Do not use - in variable/function names like string-length because running the resulting JavaScript code will error out as JavaScript use infix notation and - is not allowed in identifiers. Use _ instead like string_length.

Vectors -> Arrays

["abc" 123 nil true false [1 2 3]]

->

['abc', 123, null, true, false, [1, 2, 3]]

No commas in Clojure. Nesting is supported.

Maps -> Objects

{"a" 1 "b" {10 "t" 20 "f"} c 3 "d" [1 2 3]}

->

{'a': 1, 'b': { 10: 't', 20: 'f'}, c: 3, 'd': [1, 2, 3]}

No semicolons or commas in Clojure. Nesting is supported.

Note: Literals, identifiers, vectors, objects are not supported at the top-level. They have to be inside parentheses.

def -> const

(def a 1 b "xyz" c nil d true e [1 2 3] f a g {a 1})

->

const a = 1, b = 'xyz', c = null, d = true, e = [1, 2, 3], f = a, g = {a: 1}

Array element access and Object property access

(def h e[0] i g.a j g["a"])

->

const h = e[0], i = g.a, j = g['a'];

Note: Currently only 1D arrays are supported i.e. a[1][2] wont work.

Operators

Prefix Operators -> Binary Operators

As Clojure uses prefix notation you can give any number of arguments to the operators. clojs requires them to be greater than or equal to two.

Input Output
(+ a b 1 1.2 c) a + b + 1 + 1.2 + c
(- a b 1 1.2 c) a - b - 1 - 1.2 - c
(* a b 1 1.2 c) a * b * 1 * 1.2 * c
(/ a b 1 1.2 c) a / b / 1 / 1.2 / c
(mod a b 1 1.2 c) a % b % 1 % 1.2 % c
(< a b 1 1.2 c) a < b < 1 < 1.2 < c
(<= a b 1 1.2 c) a <= b <= 1 <= 1.2 <= c
(> a b 1 1.2 c) a > b > 1 > 1.2 > c
(>= a b 1 1.2 c) a >= b >= 1 >= 1.2 >= c
(= a b 1 1.2 c) a === b === 1 === 1.2 === c
(== a b 1 1.2 c) a == b == 1 == 1.2 == c
(!== a b 1 1.2 c) a !== b !== 1 !== 1.2 !== c
(!= a b 1 1.2 c) a != b != 1 != 1.2 != c
(in a b 1 1.2 c) a in b in 1 in 1.2 in c
(instanceof a b 1 1.2 c) a instanceof b instanceof 1 instanceof 1.2 instanceof c
(and a b 1 1.2 c) a && b && 1 && 1.2 && c
(or a b 1 1.2 c) a || b || 1 || 1.2 || c
(assign a b) a = b

Unary Operators

Input Output
(not a) !a
(typeof a) typeof a

if statement

(if (= n 0)
  true
  false)
if (n === 0)
  true;
else
  false;

Function Call

(console.log 1 2 "abc" a b)

->

console.log(1, 2, "abc", a, b);

No commas in Clojure.

Block Statement - do

  (if (= 1 1)
    (do (console.log "x :" x)
        (console.log "y :" y)
        (console.log "z :" z))
    (do (console.log "a :" a)
        (console.log "b :" b)
        (console.log "c :" c))

->

if (1 === 1) {
        console.log('x :', x);
        console.log('y :', y);
        console.log('z :', z);;
    } else {
        console.log('a :', a);
        console.log('b :', b);
        console.log('c :', c);
    }

Function Definition - defn

(defn factorial [n]
  (if (= n 0)
    1
    (* n (factorial (- n 1)))))

->

const factorial = n => {
    if (n === 0)
        return 1;
    else
        return n * factorial(n - 1);
};

Return is implicit. The last statement is the return statement.

Lambda -> Anonymous functions

(fn [x]
  (console.log x)
  (console.log (+ 5 x)))

->

x => {
    console.log(x);
    return console.log(5 + x);
};

let

(fn [x]
  (console.log x)
  (let a 2 b 5)
  (console.log (+ 5 x a b)))

->

x => {
    console.log(x);
    let a = 2, b = 5;
    return console.log(5 + x + a + b);
};

return statement

(return a)

->

return a;

chaining

(.attr (.parent ($ this)) "id")

->

$(this).parent().attr('id');

cond -> if-else chain

(fn [x]
  (cond
    (is_array_member form) (do (get_array_member form) (+ 1 2))
    (is_defn form)     (get_defn form)
    (is_def form)      (get_const form)
    (is_if form)       (get_if form)
    (is_do form)       (get_do form)
    (is_vec form)      (get_vec form)
    (is_lambda form)   (get_lambda form)
    (is_map_ds form)   (get_map_ds form)
    (is_literal form)  (get_literal form)
    (is_operator form) (get_operator form)
    (is_fn_call form)  (get_fn_call form)
    true             nil))

->

x => {
    if (is_array_member(form)) {
        get_array_member(form);
        return 1 + 2;
    } else if (is_defn(form))
        return get_defn(form);
    else if (is_def(form))
        return get_const(form);
    else if (is_if(form))
        return get_if(form);
    else if (is_do(form))
        return get_do(form);
    else if (is_vec(form))
        return get_vec(form);
    else if (is_lambda(form))
        return get_lambda(form);
    else if (is_map_ds(form))
        return get_map_ds(form);
    else if (is_literal(form))
        return get_literal(form);
    else if (is_operator(form))
        return get_operator(form);
    else if (is_fn_call(form))
        return get_fn_call(form);
    else if (true)
        return null;
};

Macros

(defmacro m-bind [mv mf]
  (conj (list ~mv test) ~mf))

(m-bind mvv mff)

->

mff(mvv, test);

Samples

You can see a converted sample containing all the syntax: all.clj -> all.js

Examples

A todo app written using cloJS.

Components

  1. Ramshreyas's seqingclojure - it makes the AST for the input code.
  2. clojs uses this AST to convert it into an equivalent JavaScript AST in JSON format.
  3. estool's npm package escodegen - it converts the JavaScript AST into JavaScript code.

License

Released under the Eclipse Public License: https://github.com/puneetpahuja/cloJS/blob/master/LICENSE