├── .travis.yml ├── dev-resources ├── test-project │ ├── test │ │ └── app-test.cljs │ ├── src │ │ ├── common.cljs │ │ └── app.cljs │ ├── project.clj │ └── resources │ │ └── js │ │ ├── app.js │ │ └── app-test.js ├── test-sources │ ├── broken.cljs │ ├── syntax-errors │ │ ├── colons.cljs │ │ └── closing-parens.cljs │ ├── missing-deps.cljs │ ├── sub │ │ ├── two.cljs │ │ └── one.cljs │ ├── app.cljs │ └── common.cljs ├── test-project1.clj └── test-output │ └── app.js ├── .gitignore ├── .pair.io ├── config.yaml └── post-provisioning.csh.clj ├── test └── cljs │ └── test │ ├── compile.clj │ ├── watch.clj │ ├── opts.clj │ ├── deps.clj │ └── core.clj ├── src └── cljs │ ├── opts.clj │ ├── rhino.clj │ ├── testing.clj │ ├── compile.clj │ ├── deps.clj │ ├── watch.clj │ └── core.clj ├── project.clj ├── README.md └── resources └── underscore.min.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | branches: 3 | only: 4 | - master 5 | -------------------------------------------------------------------------------- /dev-resources/test-project/test/app-test.cljs: -------------------------------------------------------------------------------- 1 | (ns app-test 2 | (:use app)) 3 | -------------------------------------------------------------------------------- /dev-resources/test-sources/broken.cljs: -------------------------------------------------------------------------------- 1 | (ns broken 2 | (:use sub.one foo)) 3 | -------------------------------------------------------------------------------- /dev-resources/test-project/src/common.cljs: -------------------------------------------------------------------------------- 1 | (ns common) 2 | 3 | (def yo "Hello World!!!") 4 | -------------------------------------------------------------------------------- /dev-resources/test-sources/syntax-errors/colons.cljs: -------------------------------------------------------------------------------- 1 | (ns syntax-errors.colons) 2 | 3 | : : : 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | lib 4 | classes 5 | .DS_Store 6 | .lein-deps-sum 7 | .lein-failures 8 | -------------------------------------------------------------------------------- /dev-resources/test-sources/missing-deps.cljs: -------------------------------------------------------------------------------- 1 | (ns missing-deps 2 | (:use no.such.dep)) 3 | 4 | 5 | -------------------------------------------------------------------------------- /dev-resources/test-project/src/app.cljs: -------------------------------------------------------------------------------- 1 | (ns app 2 | (:use lib.one) 3 | (:require [lib.two :as two])) 4 | -------------------------------------------------------------------------------- /dev-resources/test-sources/sub/two.cljs: -------------------------------------------------------------------------------- 1 | (ns sub.two 2 | (:require common)) 3 | 4 | (def foo "bar") 5 | -------------------------------------------------------------------------------- /.pair.io/config.yaml: -------------------------------------------------------------------------------- 1 | quickstart: clojure 2 | inbound-ports: [22, 80, 443, 8080, 8081] 3 | provision: none 4 | -------------------------------------------------------------------------------- /dev-resources/test-sources/app.cljs: -------------------------------------------------------------------------------- 1 | (ns app 2 | (:use sub.one sub.two) 3 | (:require [common :as com])) 4 | -------------------------------------------------------------------------------- /dev-resources/test-sources/syntax-errors/closing-parens.cljs: -------------------------------------------------------------------------------- 1 | (ns syntax-errors.closing-parens) 2 | 3 | ((( 4 | -------------------------------------------------------------------------------- /dev-resources/test-sources/common.cljs: -------------------------------------------------------------------------------- 1 | (ns common) 2 | 3 | (defn some-common-fn [] 4 | (println "COMMON!")) 5 | -------------------------------------------------------------------------------- /dev-resources/test-sources/sub/one.cljs: -------------------------------------------------------------------------------- 1 | (ns sub.one 2 | (:use common)) 3 | 4 | (defn one-fn [] 5 | (println "one")) 6 | -------------------------------------------------------------------------------- /test/cljs/test/compile.clj: -------------------------------------------------------------------------------- 1 | (ns cljs.test.compile 2 | (:use [cljs.compile] :reload) 3 | (:use [clojure.test])) 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/cljs/test/watch.clj: -------------------------------------------------------------------------------- 1 | (ns cljs.test.watch 2 | (:use [cljs.watch] :reload) 3 | (:use [clojure.test])) 4 | 5 | (deftest test-find-cljs-files 6 | (is (empty? (filter #(re-find #"\.#" (.getName %)) 7 | (find-cljs-files "./resources"))))) 8 | 9 | -------------------------------------------------------------------------------- /.pair.io/post-provisioning.csh.clj: -------------------------------------------------------------------------------- 1 | (sudo 2 | (apt-install :openjdk-6-jdk)) 3 | 4 | (sudo 5 | (wget "https://github.com/technomancy/leiningen/raw/stable/bin/lein" 6 | "--no-check-certificate") 7 | (chmod :0755 "./lein") 8 | (mv "./lein" "/usr/bin")) 9 | 10 | ($ "lein") 11 | -------------------------------------------------------------------------------- /dev-resources/test-project/project.clj: -------------------------------------------------------------------------------- 1 | (defproject cljs-test-project "1.0.0-SNAPSHOT" 2 | :description "cljs test project" 3 | :dependencies [] 4 | :dev-dependencies [] 5 | :source-path "src/clj" 6 | :cljs {:source-path "dev-resources/test-project/src" 7 | :source-output-path "dev-resources/test-project/resources/js" 8 | :source-libs [app] 9 | :test-path "dev-resources/test-project/test" 10 | :test-output-path "dev-resources/test-project/resources/testjs" 11 | :test-libs [app-test]}) 12 | -------------------------------------------------------------------------------- /dev-resources/test-project1.clj: -------------------------------------------------------------------------------- 1 | (defproject test-project1 "1.0.0-SNAPSHOT" 2 | :description "" 3 | :dependencies [[org.clojure/clojure "1.2.0"] 4 | [org.clojure/clojure-contrib "1.2.0"]] 5 | :dev-dependencies [[swank-clojure "1.2.0"] 6 | [cljs "0.1-SNAPSHOT"] 7 | [lein-cljs "0.1-SNAPSHOT"] 8 | [cljs-contrib "0.1-SNAPSHOT"]] 9 | :source-path "src/clj" 10 | :cljs {:source-path "alt-source-path" 11 | :source-output-path "alt-source-output-path" 12 | :test-path "alt-test-path" 13 | :test-output-path "alt-test-output-path"}) 14 | -------------------------------------------------------------------------------- /test/cljs/test/opts.clj: -------------------------------------------------------------------------------- 1 | (ns cljs.test.opts 2 | (:use [cljs.opts] :reload) 3 | (:use [clojure.test]) 4 | (:refer-clojure :exclude (slurp))) 5 | 6 | (deftest test-slurp-opts 7 | (let [{:keys [source-path 8 | source-output-path 9 | source-libs 10 | test-path 11 | test-output-path 12 | test-libs]} 13 | (slurp "dev-resources/test-project1.clj")] 14 | (is (= "alt-source-path" source-path)) 15 | (is (= "alt-source-output-path" source-output-path)) 16 | (is (= [] source-libs)) 17 | (is (= "alt-test-path" test-path)) 18 | (is (= "alt-test-output-path" test-output-path)) 19 | (is (= [] test-libs)))) 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/cljs/test/deps.clj: -------------------------------------------------------------------------------- 1 | (ns cljs.test.deps 2 | (:use [cljs.deps] :reload) 3 | (:use [clojure.test])) 4 | 5 | (deftest test-extract-namespaces 6 | (is (= '(bar baz bap) 7 | (extract-namespaces :use '(ns foo 8 | (:use bar baz bap))))) 9 | (is (= '(asdf qwer zxcv) (extract-namespaces 10 | :require 11 | '(ns bar 12 | (:require asdf qwer [zxcv :as yay])))))) 13 | 14 | (deftest test-analyze 15 | (let [res (analyze ["dev-resources/test-sources"] 'app)] 16 | (is (= 3 (count (:deps res)))) 17 | (is (= "dev-resources/test-sources/app.cljs" (:file res))) 18 | (is (= 'app (:name res))) 19 | (is (= '(sub.one sub.two) (:uses res))) 20 | (is (= '(common) (:requires res))))) 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/cljs/opts.clj: -------------------------------------------------------------------------------- 1 | (ns cljs.opts 2 | "Responsible for parsing cljs options from a variety of sources, such as: 3 | 4 | * Leiningen project.clj 5 | * Cake project.clj 6 | 7 | See var `defaults` for default options. " 8 | (:use [clojure.java.io :only (file)]) 9 | (:import [java.io File FileReader] 10 | [clojure.lang LineNumberingPushbackReader]) 11 | (:refer-clojure :exclude (slurp))) 12 | 13 | (def defaults 14 | {:source-path "src/cljs" 15 | :source-output-path "resources/public/js" 16 | :source-libs [] 17 | :test-path "test/cljs" 18 | :test-output-path "resources/testjs" 19 | :test-libs []}) 20 | 21 | 22 | (defn slurp [file-or-path] 23 | (let [file (file file-or-path) 24 | rdr (LineNumberingPushbackReader. (FileReader. file))] 25 | (->> (read rdr) 26 | (drop 5) 27 | (apply hash-map) 28 | :cljs 29 | (merge defaults)))) 30 | -------------------------------------------------------------------------------- /src/cljs/rhino.clj: -------------------------------------------------------------------------------- 1 | (ns cljs.rhino 2 | "Used for testing / debugging of generated javascript." 3 | (:import (org.mozilla.javascript Context 4 | Scriptable))) 5 | 6 | (defn slurp-resource 7 | [resource-name] 8 | (-> (.getContextClassLoader (Thread/currentThread)) 9 | (.getResourceAsStream resource-name) 10 | (java.io.InputStreamReader.) 11 | (slurp))) 12 | 13 | (def underscore-js-source (slurp-resource "underscore.min.js")) 14 | 15 | (defn eval-js [snippet] 16 | (let [cx (Context/enter) 17 | sc (.initStandardObjects cx)] 18 | (try 19 | (.evaluateString cx sc "var console = {}; console.log = function(){};" "" 1 nil) 20 | (.evaluateString cx sc underscore-js-source "underscore.js" 1 nil) 21 | (.evaluateString cx sc snippet "" 1 nil) 22 | (finally (Context/exit))))) 23 | 24 | (defn objtomap [obj] 25 | (let [obj-ids (seq (.getIds obj)) 26 | vals (map #(.get obj % nil) obj-ids) 27 | keys (map keyword obj-ids)] 28 | (apply hash-map (interleave keys vals)))) 29 | -------------------------------------------------------------------------------- /src/cljs/testing.clj: -------------------------------------------------------------------------------- 1 | (ns cljs.testing 2 | (:require [clojure.string :as str]) 3 | (:import (com.google.jstestdriver JsTestDriver 4 | PluginLoader) 5 | (com.google.jstestdriver.config CmdFlags 6 | CmdLineFlagsFactory 7 | YamlParser))) 8 | 9 | 10 | 11 | 12 | (comment 13 | (use 'clojure.pprint) 14 | 15 | (defn run [args-str] 16 | (let [flags-fac (CmdLineFlagsFactory.) 17 | flags (.create flags-fac (into-array String (str/split args-str #"\s+"))) 18 | base-path (.getBasePath flags) 19 | plugins (.getPlugins flags) 20 | ploader (PluginLoader.) 21 | pmodules (.load ploader plugins) 22 | imodules pmodules 23 | config (.parse (YamlParser.) 24 | (java.io.FileReader. 25 | (let [cs (.getConfigurationSource flags) 26 | pf (.getParentFile cs) 27 | name (.getName cs)] 28 | (java.io.File. (str (.getAbsolutePath pf) 29 | "/" 30 | name))))) 31 | ] 32 | (pprint config))) 33 | 34 | 35 | (run "--port 4224 --config ./.jstestdriver") 36 | 37 | 38 | #_(JsTestDriver/main (into-array String ["--port" "4224"])) 39 | 40 | #_(JsTestDriver/main (into-array String ["--tests" "all" "--config" "./.jstestdriver"]))) 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject cljs "0.3.0" 2 | :description 3 | "An experimental Clojure(ish) to Javascript compiler similar to 4 | [clojurescript](https://github.com/clojure/clojure-contrib/tree/master/clojurescript/). The library also provides several tools to assist you with integrating cljs into your workflow. All this in about 1k lines. Viva Clojure! 5 | 6 | # Usage 7 | 8 | Use the `cljs.core/to-js` function to turn a list (representing some 9 | code) into javascript, like so: 10 | 11 | (to-js '((fn [x] (alert x)) \"hello world\")) 12 | 13 | -> function(x){alert(x);}(\"hello world\"); 14 | 15 | 16 | (to-js '(-> ($ \"
\") 17 | (.css {:backgroundColor \"blue\" . 18 | :border \"dashed white 5px\"}) 19 | 20 | -> (function(){var out = $(\"
\"); 21 | out.css({backgroundColor:\"blue\",border:\"dashed white 5px\"}); 22 | return out}()) 23 | 24 | Neat, huh? 25 | 26 | In addition to the compiler, cljs provides several tools to make working with cljs in the context of a web project easier: 27 | 28 | 1. [cljs.watch](#cljs.watch) provides a mechanism for automatic recompilation on source changes. Used by either `lein cljs-watch` or `(use 'cljs.watch) (start-watch-project \"./project.clj\")`. Cljs output is declared in your project.clj file, under the :cljs key. 29 | 30 | 2. [cljs.stitch](#cljs.stitch) takes care of stitching the collection of source files that make up a library into a coherent javascript file. 31 | 32 | 33 | For more examples, please see the [cljs.test.core](#cljs.test.core) namespace." 34 | 35 | :dependencies [[org.clojure/clojure "1.3.0"]] 36 | :dev-dependencies [[rhino/js "1.7R2"]] 37 | :dev-resources "dev-resources") 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## cljs 2 | 3 | 4 | 5 | An experimental Clojure(ish) to Javascript compiler. 6 | 7 | Docs: [http://zk.github.com/cljs](http://zkim.github.com/cljs) 8 | 9 | ## Usage 10 | 11 | 1. `lein new cljs-test` 12 | 13 | 2. Add a `:cljs` entry to your project.clj: 14 | 15 | 16 | (defproject... 17 | :cljs {:source-path "src/cljs" 18 | :source-output-path "resources/public/js" 19 | :source-libs [some-ns.app] 20 | :test-path "test/cljs" 21 | :test-output-path "resources/testjs" 22 | :test-libs [app-test]}) 23 | 24 | 25 | 26 | 3. Verify the above paths exist. 27 | 28 | 4. Add `[cljs "0.3.0"]` and `[lein-cljs "0.2.1"]` to your `:dev-dependencies`. 29 | 30 | 5. Then `lein deps` and `lein cljs watch`. This will start the watcher, 31 | which will automatically recompile your cljs libs when cljs source 32 | files change. 33 | 34 | 6. You now have the ability to use lisp full-stack (kind of), begin 35 | global domination. 36 | 37 | 38 | ## Examples 39 | 40 | See 41 | [http://github.com/zkim/cljs-contrib](http://github.com/zkim/cljs-contrib), 42 | specifically: 43 | 44 | * `project.clj` for info on the `:cljs` opts map. 45 | 46 | * Clone the repo and open `resources/border-layout.html` and 47 | `resources/panel.html` in your browser. This will give you an idea of 48 | how to use the compiled cljs output. 49 | 50 | Be sure to include underscore.js in a script tag before including any 51 | compiled cljs output. 52 | 53 | 54 | ## Todo 55 | 56 | * Integrate Google's Closure Compiler for minifying 57 | * Integrate js-test-driver for testing 58 | 59 | 60 | ## Missing Features 61 | 62 | * REPL -- Browser stuff from https://github.com/ivan4th/swank-js & 63 | comm from swank-clojure 64 | 65 | * Macros 66 | 67 | * TCO -- Possible with CPS? http://eriwen.com/javascript/cps-tail-call-elimination/ 68 | 69 | 70 | ## License 71 | 72 | Copyright (C) 2010-2011 Zachary Kim 73 | 74 | Distributed under the Eclipse Public License, the same as Clojure. 75 | -------------------------------------------------------------------------------- /src/cljs/compile.clj: -------------------------------------------------------------------------------- 1 | (ns cljs.compile 2 | (:use clojure.pprint 3 | [clojure.java.io :only (file)]) 4 | (:require [cljs.opts :as opts] 5 | [cljs.deps :as deps] 6 | [cljs.core :as core]) 7 | (:import [clojure.lang LispReader$ReaderException])) 8 | 9 | ;; There has to be a better way than inspecting the message 10 | ;; content 11 | (defn error [e] 12 | (let [message (.getMessage e)] 13 | (cond 14 | (re-find #"EOF while reading" message) 15 | (do (println "*** ERROR *** EOF while reading file, are you missing closing parens?")) 16 | :else (do 17 | (println "*** ERROR *** " message) 18 | (.printStackTrace e))))) 19 | 20 | (defn cljs-source 21 | "Returns `path` content as string. Checks both 22 | filesystem and classpath." 23 | [path] 24 | (let [file (file path)] 25 | (if (.exists file) 26 | (slurp file) 27 | (try 28 | (-> (.getContextClassLoader (Thread/currentThread)) 29 | (.getResourceAsStream (.getPath file)) 30 | (java.io.InputStreamReader.) 31 | (slurp)) 32 | (catch Exception e nil))))) 33 | 34 | (defn lib [opts analyzed-lib] 35 | (let [dep-files (map :file (:deps analyzed-lib)) 36 | lib-file (:file analyzed-lib)] 37 | (->> (concat dep-files [lib-file]) 38 | (map cljs-source) 39 | (map core/compile-cljs-string) 40 | (interpose "\n\n\n\n") 41 | (apply str) 42 | (str core/*core-lib*)))) 43 | 44 | 45 | ;; Why pass all this duplicate info?! 46 | (defn libs [opts search-paths libs] 47 | (doseq [source-lib libs] 48 | (let [analyzed (deps/analyze search-paths source-lib)] 49 | (println "*" (:name analyzed) "--" (:file analyzed)) 50 | (println) 51 | (when (:deps analyzed) 52 | (println " deps") 53 | (doseq [dep (:deps analyzed)] 54 | (println " " (:name dep) "--" (:file dep)))) 55 | (print " Compiling... ") 56 | (try 57 | (let [compiled (lib opts analyzed) 58 | out-path (str (:source-output-path opts) "/" (:name analyzed) ".js")] 59 | (println "done. ") 60 | (print " Writing to" (str out-path "... ")) 61 | (spit out-path compiled) 62 | (println "done.") 63 | (println)) 64 | (catch Exception e (error e)))))) 65 | 66 | (defn opts [opts] 67 | (time 68 | (let [source-libs (:source-libs opts) 69 | source-search-paths [(:source-path opts)]] 70 | (println) 71 | (println "-------------------------------") 72 | (println "Compiling cljs libs:") 73 | (pprint opts) 74 | (println) 75 | (println "Source Libraries:") 76 | (libs opts source-search-paths source-libs) 77 | (println) 78 | (println "Test Libraries:") 79 | (libs opts 80 | [(:source-path opts) 81 | (:test-path opts)] 82 | (:test-libs opts)) 83 | (println) 84 | (println "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 85 | (println (java.util.Date.)))) 86 | (println)) 87 | 88 | (defn project [& [project-file]] 89 | (opts (opts/slurp (or project-file "./project.clj")))) 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/cljs/deps.clj: -------------------------------------------------------------------------------- 1 | (ns cljs.deps 2 | (:use [clojure.java.io :only (file)]) 3 | (:require [clojure.string :as str]) 4 | (:import [clojure.lang LineNumberingPushbackReader] 5 | [java.io FileReader])) 6 | 7 | (defn read-ns-form [reader] 8 | (with-open [rdr (LineNumberingPushbackReader. reader)] 9 | (let [ns-decl (first (filter #(= 'ns (first %)) (repeatedly #(read rdr))))] 10 | ns-decl))) 11 | 12 | (defn retr-ns-form 13 | "Check the filesystem and classpath for `file-or-path` and 14 | returns the namespace form if found, nill if not." 15 | [file-or-path] 16 | (let [file (file file-or-path)] 17 | (if (.exists file) 18 | (read-ns-form (FileReader. file)) 19 | (try 20 | (-> (.getContextClassLoader (Thread/currentThread)) 21 | (.getResourceAsStream (.getPath file)) 22 | (java.io.InputStreamReader.) 23 | (read-ns-form)) 24 | (catch Exception e nil))))) 25 | 26 | (defn extract-namespaces 27 | "Given a ns and a tag (i.e. :use or :require) collect the dependencies 28 | as namespace symbols. 29 | 30 | Usage: (extract-namespaces :use '(ns foo (:use bar)))" 31 | [tag ns-form] 32 | (->> ns-form 33 | (filter coll?) 34 | (filter #(= tag (first %))) 35 | (map #(drop 1 %)) 36 | (reduce concat) 37 | (map #(if (coll? %) 38 | (first %) 39 | %)))) 40 | 41 | (defn parse-ns-form 42 | "Parses name, uses, and requires from a ns form." 43 | [ns-form] 44 | (when ns-form 45 | {:name (second ns-form) 46 | :uses (extract-namespaces :use ns-form) 47 | :requires (extract-namespaces :require ns-form)})) 48 | 49 | (defn find-namespace 50 | "Looks in coll search-paths and classpath for file named by `ns-sym`. 51 | Returns a map like so: 52 | 53 | {:name foo 54 | :file \"path/to/foo.cljs\" 55 | :uses (lib.bar) 56 | :requires (lib.baz)}" 57 | [search-paths ns-sym] 58 | (let [file-name (str (str/replace (str ns-sym) #"\." "/") 59 | ".cljs") 60 | guesses (concat (map #(str % "/" file-name) search-paths) 61 | [file-name])] 62 | (loop [guesses guesses] 63 | (if (= 0 (count guesses)) 64 | nil 65 | (if-let [res (parse-ns-form (retr-ns-form (first guesses)))] 66 | (assoc res :file (first guesses)) 67 | (recur (rest guesses))))))) 68 | 69 | 70 | (defn resolve-deps [search-paths ns-coll] 71 | "Given a list of search paths and a list of namespaces representing 72 | dependencies, recursively resolve namespaces to files. 73 | 74 | Results are in the format: `{:name foo.bar :file src/foo/bar.cljs}`" 75 | (when (not (empty? ns-coll)) 76 | (let [deps (->> ns-coll 77 | (map #(find-namespace search-paths %))) 78 | dep-deps (reduce #(concat %1 (:uses %2) (:requires %2)) [] deps)] 79 | (distinct 80 | (concat (resolve-deps search-paths dep-deps) 81 | (map #(select-keys % [:name :file]) deps)))))) 82 | 83 | (defn analyze [search-paths ns-name] 84 | "Analyzes a cljs lib for dependencies. Used by compile to collect 85 | the dependencies for a library given by `ns-name` and found in either 86 | `search-paths` or on the classpath." 87 | (let [ns (find-namespace search-paths ns-name) 88 | deps (resolve-deps search-paths (concat (:uses ns) (:requires ns)))] 89 | (when ns 90 | (assoc ns :deps deps)))) 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/cljs/watch.clj: -------------------------------------------------------------------------------- 1 | (ns cljs.watch 2 | "# Utilities for automatically compiing changed .cjls files." 3 | (:use [cljs.core :only (compile-cljs-file)]) 4 | (:require [clojure.string :as str] 5 | [cljs.compile :as compile] 6 | [cljs.opts :as opts]) 7 | (:import [java.io File])) 8 | 9 | (defn file [file-or-path] 10 | (if (string? file-or-path) 11 | (java.io.File. file-or-path) 12 | file-or-path)) 13 | 14 | (defn find-cljs-files 15 | "Returns a seq of java.io.File objects of all .cljs files 16 | found in `file-or-path` including subdirs." 17 | [file-or-path] 18 | (let [f (file file-or-path)] 19 | (->> (file-seq f) 20 | (filter #(.endsWith (.getName %) ".cljs")) 21 | (filter #(not (re-find #"\.#" (.getName %))))))) 22 | 23 | (defn last-mod [file] 24 | (.lastModified file)) 25 | 26 | (defn has-file-changed? [f last-change] 27 | (cond 28 | (nil? last-change) true 29 | (> (last-mod f) last-change) true 30 | :else false)) 31 | 32 | (defn last-mod-map [f-seq] 33 | (reduce #(assoc %1 %2 (last-mod %2)) 34 | {} 35 | f-seq)) 36 | 37 | (def ^:dynamic *mod-map* (atom {})) 38 | (def ^:dynamic *handlers* (atom [])) 39 | 40 | (defn hook-change [f] 41 | (swap! *handlers* (fn [hs] (conj hs f)))) 42 | 43 | (defn clear-hooks [] 44 | (reset! *handlers* [])) 45 | 46 | (defn run-hooks [changed-files] 47 | (doseq [f @*handlers*] 48 | (f changed-files))) 49 | 50 | (defn changed-files! [f-seq] 51 | (let [changed-files (doall (filter #(has-file-changed? % (@*mod-map* %)) f-seq))] 52 | (reset! *mod-map* (last-mod-map f-seq)) 53 | changed-files)) 54 | 55 | (defn check-and-run! [paths] 56 | (let [dirs (filter #(.isDirectory (java.io.File. %)) 57 | paths) 58 | files (map #(java.io.File. %) 59 | (filter #(not (.isDirectory (java.io.File. %))) 60 | paths)) 61 | cljs-files (concat 62 | (reduce concat (map find-cljs-files dirs)) 63 | files)] 64 | (-> cljs-files 65 | (changed-files!) 66 | (run-hooks)))) 67 | 68 | (defn spit-equiv-js [cljs-file js-out-dir-file] 69 | (let [cljs-path (.getAbsolutePath cljs-file) 70 | name (.getName cljs-file) 71 | base (str/replace name #"\.cljs$" "") 72 | js-path (str (.getAbsolutePath js-out-dir-file) "/" base ".js")] 73 | (spit js-path (compile-cljs-file cljs-path)))) 74 | 75 | (defn hook-compile-out [out-dir] 76 | (hook-change 77 | (fn [cljss] 78 | (when (empty? cljss) #_(println "Nothing to compile")) 79 | (doseq [cljs cljss] 80 | (try 81 | (println "Compiling" (.getName cljs) "to" out-dir) 82 | (spit-equiv-js cljs (file out-dir)) 83 | (catch Exception e (println "Problem compiling " (.getAbsolutePath cljs) ": " e))))))) 84 | 85 | (defn hook-re-stitch [cljs-opts] 86 | (hook-change 87 | (fn [cljss] 88 | (if (not (empty? cljss)) 89 | (compile/opts cljs-opts))))) 90 | 91 | (def ^:dynamic *run* (atom true)) 92 | 93 | (defn stop-watch [] 94 | (reset! *run* false)) 95 | 96 | (defn start-watch [watch-path js-out-path] 97 | (clear-hooks) 98 | (reset! *run* true) 99 | (hook-compile-out js-out-path) 100 | (.start (Thread. 101 | (fn [] 102 | (while @*run* 103 | (check-and-run! watch-path) 104 | (Thread/sleep 500)))))) 105 | 106 | ;; Stolen from marginalia: 107 | (defn ls 108 | [path] 109 | (let [file (java.io.File. path)] 110 | (if (.isDirectory file) 111 | (seq (.list file)) 112 | (when (.exists file) 113 | [path])))) 114 | 115 | (defn mkdir [path] 116 | (.mkdirs (File. path))) 117 | 118 | (defn ensure-directory! 119 | [path] 120 | (when-not (ls path) 121 | (mkdir path))) 122 | 123 | (defn start-watch-opts [opts] 124 | (let [test-output-path (:test-output-path opts) 125 | source-output-path (:source-output-path opts)] 126 | (when (not opts) 127 | (throw (Exception. (str "Couldn't find cljs options in project.")))) 128 | (clear-hooks) 129 | (reset! *run* true) 130 | (hook-re-stitch opts) 131 | (println "Watching" (:source-path opts) " for changes.") 132 | (println "Watching" (:test-path opts) " for changes.") 133 | (when (not (ls source-output-path)) 134 | (println "Source output path " source-output-path "not found, creating.") 135 | (ensure-directory! source-output-path)) 136 | (when (not (ls test-output-path)) 137 | (println "Test output path " test-output-path "not found, creating.") 138 | (ensure-directory! test-output-path)) 139 | (compile/opts opts) 140 | (doto (Thread. 141 | (fn [] 142 | (while @*run* 143 | (try 144 | (check-and-run! [(:source-path opts) 145 | (:test-path opts) 146 | (when (.exists (file "./project.clj")) 147 | "./project.clj")]) 148 | (Thread/sleep 500) 149 | (catch Exception e (println e)))))) 150 | (.start) 151 | (.join)))) 152 | 153 | (defn start-watch-project 154 | "Starts up a watcher which will re-compile cljs files to javascript 155 | when cljs files found in :source-path and :test-path change. Also re-compiles when 156 | your project.clj changes. 157 | 158 | Usage: `(start-watch-project \"./project.clj\")`" 159 | [& [project-path]] 160 | (let [opts (opts/slurp (or project-path "./project.clj"))] 161 | (start-watch-opts opts))) 162 | 163 | 164 | -------------------------------------------------------------------------------- /test/cljs/test/core.clj: -------------------------------------------------------------------------------- 1 | (ns cljs.test.core 2 | (:use [cljs.core] :reload) 3 | (:use [clojure.test]) 4 | (:require [cljs.rhino :as rhino]) 5 | (:import (org.mozilla.javascript Context 6 | Scriptable 7 | NativeArray 8 | NativeObject))) 9 | 10 | ;; ## Test helpers 11 | 12 | (defn clean-val 13 | "Clean values for comparison in tests. Some values come back from 14 | rhino munged (floats instead of integers, etc), and need to be 15 | 'cleaned' for ease of use in tests." 16 | [v] 17 | (cond 18 | (and (number? v) (= (double v) (Math/floor v))) (int v) 19 | :else v)) 20 | 21 | 22 | (defn narr-to-seq 23 | "Turn a rhino native array into a clojure seq." 24 | [narr] 25 | (->> narr 26 | (.getIds) 27 | seq 28 | (map #(.get narr % nil)) 29 | (map clean-val))) 30 | 31 | (defn obj-to-map [obj] 32 | (let [obj-ids (seq (.getIds obj)) 33 | vals (map (clean-val #(.get obj % nil)) obj-ids) 34 | keys (map keyword obj-ids)] 35 | (apply hash-map (interleave keys vals)))) 36 | 37 | (defn eval-js [& stmt-or-stmts] 38 | (let [cmd (str 39 | *core-lib* 40 | "\n\n" 41 | (apply wrap-with-ns "test" [] 42 | stmt-or-stmts)) 43 | res (rhino/eval-js cmd)] 44 | (cond 45 | (= NativeArray (class res)) (narr-to-seq res) 46 | (= NativeObject (class res)) (obj-to-map res) 47 | (and (number? res) 48 | (= res (Math/floor res))) (int res) 49 | :else res))) 50 | 51 | (deftest test-core 52 | (is (rhino/eval-js *core-lib*))) 53 | 54 | (deftest test-var-definition 55 | (is (= "hello" 56 | (eval-js '(def x "hello") 57 | 'x)))) 58 | 59 | (deftest test-function-definition 60 | (is (= "hello" 61 | (eval-js '(defn x [] "hello") 62 | '(x))))) 63 | 64 | (deftest test-hash-definition 65 | (is (= "hello" 66 | (eval-js '(#(str "he" "llo")))))) 67 | 68 | (deftest test-varargs 69 | (is (= [2 3 4]) 70 | (eval-js '(defn x [& args] 71 | (map (fn [i] (+ i 1)) args)) 72 | '(x 1 2 3)))) 73 | 74 | (deftest test-varargs-2 75 | (is (= 10 76 | (eval-js '(defn x [a b & args] 77 | (+ a b 78 | (reduce (fn [col i] (+ col i)) args))) 79 | '(x 1 2 3 4))))) 80 | 81 | (deftest test-let 82 | (is (= 8 (eval-js '(let [x 5 83 | y 3] 84 | (+ x y)))))) 85 | 86 | (deftest test-keyword-access 87 | (is (= 10 (eval-js '(:foo {:foo 10}))))) 88 | 89 | ;; Basic Elements 90 | (deftest test-string 91 | (is (= "foo" (eval-js '"foo")))) 92 | 93 | (deftest test-number 94 | (is (= 10 (eval-js '10))) 95 | (is (= 10 (eval-js '10.0)))) 96 | 97 | (deftest test-identifier-cleaning 98 | (is (eval-js '(def one-two 12))) 99 | (is (eval-js '(def one? 1))) 100 | (is (eval-js '(def one! 1))) 101 | (is (eval-js '(def *one* 1)))) 102 | 103 | (deftest test-hash-map-objs 104 | (is (= {:foo "bar"} (eval-js '(let [asdf "foo"] 105 | {asdf "bar"}))))) 106 | 107 | ;; Special Forms 108 | (deftest test-def 109 | (is (= 10 (eval-js '(def x 10) 'x)))) 110 | 111 | (deftest test-fn 112 | (is (= 10 (eval-js '((fn [] 10)))))) 113 | 114 | (deftest test-set! 115 | (is (= 10 (eval-js '(def x 5) 116 | '(set! x 10) 117 | 'x)))) 118 | 119 | (deftest test-- 120 | (is (= 0 (eval-js '(- 3 2 1))))) 121 | 122 | (deftest test--> 123 | (is (= "foobarbaz" (eval-js '(-> "foo" 124 | (str "bar") 125 | (str "baz")))))) 126 | 127 | (deftest test-->> 128 | (is (= "bazbarfoo" (eval-js '(->> "foo" 129 | (str "bar") 130 | (str "baz")))))) 131 | 132 | (deftest test-doto 133 | (is (= [1 2 3] (eval-js '(doto [] 134 | (.push 1) 135 | (.push 2) 136 | (.push 3)))))) 137 | 138 | (deftest test-when 139 | (is (eval-js '(when (> 2 1) 140 | true)))) 141 | 142 | ;; Scoping 143 | 144 | ;; Core Lib 145 | (deftest test-core-count 146 | (is (= 3 (eval-js '(count [1 2 3]))))) 147 | 148 | (deftest test-core-first 149 | (is (= 1 (eval-js '(first [1 2 3]))))) 150 | 151 | (deftest test-core-rest 152 | (is (= [2 3] (eval-js '(rest [1 2 3]))))) 153 | 154 | (deftest test-core-reduce 155 | (is (= 6 (eval-js '(reduce #(+ %1 %2) [1 2 3]))))) 156 | 157 | (deftest test-core-concat 158 | (is (= [1 2 3 4] (eval-js '(concat [1 2] [3 4]))))) 159 | 160 | (deftest test-core-take 161 | (is (= [1 2] (eval-js '(take 2 [1 2 3 4])))) 162 | (is (= [1] (eval-js '(take 1 [1 2 3])))) 163 | (is (= 1 (count (eval-js '(take 1 [{:foo "bar"} {:baz "bap"}])))))) 164 | 165 | (deftest test-core-drop 166 | (is (= [3 4] (eval-js '(drop 2 [1 2 3 4]))))) 167 | 168 | (deftest test-core-partition 169 | (is (= [3 4] (eval-js '(nth (partition 2 [1 2 3 4]) 1))))) 170 | 171 | (deftest test-core-assoc 172 | (is (= "foo" (:bar (eval-js '(assoc {} :bar "foo")))))) 173 | 174 | (deftest test-interpose 175 | (is (= [1 0 2 0 3] (eval-js '(interpose 0 [1 2 3]))))) 176 | 177 | (deftest test-distinct 178 | (is (= [1 2 3] (eval-js '(distinct [1 2 1 2 3 3 2 1]))))) 179 | 180 | (deftest test-empty? 181 | (is (eval-js '(empty? []))) 182 | (is (not (eval-js '(empty? [1 2 3])))) 183 | (is (eval-js '(empty? {}))) 184 | (is (not (eval-js '(empty? {:foo "bar"}))))) 185 | 186 | (deftest test-hash-map 187 | (is (= {} (eval-js '(hash-map)))) 188 | (is (= {:foo "bar"} (eval-js '(hash-map :foo "bar")))) 189 | (is (= {:foo "bar" :baz "bap"} 190 | (eval-js '(apply hash-map [:foo "bar" :baz "bap"]))))) 191 | 192 | (deftest test-apply 193 | (is (= 6 (eval-js '(apply + 1 [2 3])))) 194 | (is (= 10 (eval-js '(apply + 1 2 [3 4]))))) 195 | 196 | (deftest test-interleave 197 | (is (= [1 "a" 2 "b"] (eval-js '(interleave [1 2] [:a :b])))) 198 | (is (= [1 10 2 20 3 30] (eval-js '(interleave [1 2 3] [10 20 30 40]))))) 199 | 200 | (deftest test-keys 201 | (is (= ["foo-bar" "baz-bap"] (eval-js '(keys {:foo-bar 1 :baz-bap 2}))))) 202 | 203 | (deftest test-select-keys 204 | (is (= {:foo "bar"} (eval-js '(select-keys {:foo "bar" :baz "bap"} [:foo])))) 205 | (is (= {:foo "bar" :hello "world"} 206 | (eval-js '(select-keys {:foo "bar" 207 | :baz "bap" 208 | :hello "world"} 209 | [:foo :hello]))))) 210 | 211 | (deftest test-dissoc 212 | (is (= {:foo "bar"} (eval-js '(dissoc {:foo "bar" 213 | :baz "bap" 214 | :hello "world"} 215 | :baz :hello))))) 216 | 217 | 218 | -------------------------------------------------------------------------------- /resources/underscore.min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.1.3 2 | // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){var p=this,C=p._,m={},j=Array.prototype,n=Object.prototype,i=j.slice,D=j.unshift,E=n.toString,q=n.hasOwnProperty,s=j.forEach,t=j.map,u=j.reduce,v=j.reduceRight,w=j.filter,x=j.every,y=j.some,o=j.indexOf,z=j.lastIndexOf;n=Array.isArray;var F=Object.keys,c=function(a){return new l(a)};if(typeof module!=="undefined"&&module.exports){module.exports=c;c._=c}else p._=c;c.VERSION="1.1.3";var k=c.each=c.forEach=function(a,b,d){if(s&&a.forEach===s)a.forEach(b,d);else if(c.isNumber(a.length))for(var e= 9 | 0,f=a.length;e=e.computed&&(e={value:f,computed:g})});return e.value};c.min=function(a,b,d){if(!b&&c.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};k(a,function(f,g,h){g=b?b.call(d,f,g,h):f;gh?1:0}),"value")};c.sortedIndex=function(a,b,d){d=d||c.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};c.zip=function(){for(var a=i.call(arguments),b=c.max(c.pluck(a,"length")),d=Array(b),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};c.keys=F||function(a){if(c.isArray(a))return c.range(0,a.length);var b=[],d;for(d in a)if(q.call(a,d))b[b.length]=d;return b}; 19 | c.values=function(a){return c.map(a,c.identity)};c.functions=c.methods=function(a){return c.filter(c.keys(a),function(b){return c.isFunction(a[b])}).sort()};c.extend=function(a){k(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};c.clone=function(a){return c.isArray(a)?a.slice():c.extend({},a)};c.tap=function(a,b){b(a);return a};c.isEqual=function(a,b){if(a===b)return true;var d=typeof a;if(d!=typeof b)return false;if(a==b)return true;if(!a&&b||a&&!b)return false;if(a.isEqual)return a.isEqual(b); 20 | if(c.isDate(a)&&c.isDate(b))return a.getTime()===b.getTime();if(c.isNaN(a)&&c.isNaN(b))return false;if(c.isRegExp(a)&&c.isRegExp(b))return a.source===b.source&&a.global===b.global&&a.ignoreCase===b.ignoreCase&&a.multiline===b.multiline;if(d!=="object")return false;if(a.length&&a.length!==b.length)return false;d=c.keys(a);var e=c.keys(b);if(d.length!=e.length)return false;for(var f in a)if(!(f in b)||!c.isEqual(a[f],b[f]))return false;return true};c.isEmpty=function(a){if(c.isArray(a)||c.isString(a))return a.length=== 21 | 0;for(var b in a)if(q.call(a,b))return false;return true};c.isElement=function(a){return!!(a&&a.nodeType==1)};c.isArray=n||function(a){return!!(a&&a.concat&&a.unshift&&!a.callee)};c.isArguments=function(a){return!!(a&&a.callee)};c.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)};c.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};c.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)};c.isNaN=function(a){return E.call(a)==="[object Number]"&&isNaN(a)}; 22 | c.isBoolean=function(a){return a===true||a===false};c.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};c.isRegExp=function(a){return!!(a&&a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};c.isNull=function(a){return a===null};c.isUndefined=function(a){return a===void 0};c.noConflict=function(){p._=C;return this};c.identity=function(a){return a};c.times=function(a,b,d){for(var e=0;e/g,interpolate:/<%=([\s\S]+?)%>/g};c.template=function(a,b){var d=c.templateSettings;d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.interpolate,function(e,f){return"',"+f.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||null,function(e,f){return"');"+f.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g, 24 | "\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');";d=new Function("obj",d);return b?d(b):d};var l=function(a){this._wrapped=a};c.prototype=l.prototype;var r=function(a,b){return b?c(a).chain():a},H=function(a,b){l.prototype[a]=function(){var d=i.call(arguments);D.call(d,this._wrapped);return r(b.apply(c,d),this._chain)}};c.mixin(c);k(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=j[a];l.prototype[a]=function(){b.apply(this._wrapped,arguments); 25 | return r(this._wrapped,this._chain)}});k(["concat","join","slice"],function(a){var b=j[a];l.prototype[a]=function(){return r(b.apply(this._wrapped,arguments),this._chain)}});l.prototype.chain=function(){this._chain=true;return this};l.prototype.value=function(){return this._wrapped}})(); 26 | -------------------------------------------------------------------------------- /dev-resources/test-output/app.js: -------------------------------------------------------------------------------- 1 | if(!Function.prototype.bind){Function.prototype.bind = function(scope) {var _function = this;return function() { return _function.apply(scope, arguments); } }}var cljs = cljs || {}; 2 | cljs.core = cljs.core || {}; 3 | (function() { 4 | 5 | this.Array = Array; 6 | 7 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 8 | 9 | this._ = _; 10 | 11 | this.count = (function(col){ 12 | return (function(){ 13 | if(col){ 14 | return col.length; 15 | } else { 16 | return 0; 17 | } 18 | }.bind(this))(); 19 | }.bind(this)); 20 | 21 | this.first = (function(col){ 22 | return (function(){ 23 | 24 | if(!col) return null; 25 | 26 | return (col[0]); 27 | 28 | }.bind(this))(); 29 | }.bind(this)); 30 | 31 | this.second = (function(col){ 32 | return this.nth(col, 1); 33 | }.bind(this)); 34 | 35 | this.rest = (function(col){ 36 | return (function(){ 37 | 38 | if(!col) return null; 39 | 40 | return this.Array.prototype.slice["call"](col,1); 41 | 42 | }.bind(this))(); 43 | }.bind(this)); 44 | 45 | this.inc = (function(n){ 46 | return (function() { 47 | var _out = arguments[0]; 48 | for(var _i=1; _i n))) return null; 69 | 70 | return (col[n]); 71 | 72 | }.bind(this))(); 73 | }.bind(this)); 74 | 75 | this.last = (function(col){ 76 | return (col[this.dec(col.length)]); 77 | }.bind(this)); 78 | 79 | this.reduce = (function(f, initial, col){ 80 | return (function(){ 81 | var i = (function(){ 82 | if(col){ 83 | return initial; 84 | } else { 85 | return null; 86 | } 87 | }.bind(this))(), 88 | c = (function(){ 89 | if(col){ 90 | return col; 91 | } else { 92 | return initial; 93 | } 94 | }.bind(this))(); 95 | 96 | return (function(){ 97 | if(i){ 98 | return this._["reduce"](c,f,i); 99 | } else { 100 | return this._["reduce"](c,f); 101 | } 102 | }.bind(this))(); 103 | 104 | }.bind(this))(); 105 | }.bind(this)); 106 | 107 | this.map = (function(f, initial, col){ 108 | return (function(){ 109 | var i = (function(){ 110 | if(col){ 111 | return initial; 112 | } else { 113 | return null; 114 | } 115 | }.bind(this))(), 116 | c = (function(){ 117 | if(col){ 118 | return col; 119 | } else { 120 | return initial; 121 | } 122 | }.bind(this))(); 123 | 124 | return (function(){ 125 | 126 | if(!c) return null; 127 | 128 | return (function(){ 129 | if(i){ 130 | return this._["map"](c,f,i); 131 | } else { 132 | return this._["map"](c,f); 133 | } 134 | }.bind(this))(); 135 | 136 | }.bind(this))(); 137 | 138 | }.bind(this))(); 139 | }.bind(this)); 140 | 141 | this.str = (function(){ 142 | var args = Array.prototype.slice.call(arguments, 0); 143 | return this.reduce((function(col, el){ 144 | return (function() { 145 | var _out = arguments[0]; 146 | for(var _i=1; _i this.count(colb))){ 331 | return this.count(cola); 332 | } else { 333 | return this.count(colb); 334 | } 335 | }.bind(this))(); 336 | 337 | return this.concat([ 338 | this.first(cola), 339 | this.first(colb) 340 | ], this.interleave(this.rest(cola), this.rest(colb))); 341 | 342 | }.bind(this))(); 343 | } 344 | }.bind(this))(); 345 | }.bind(this)); 346 | 347 | this.distinct = (function(col){ 348 | return _["uniq"](col); 349 | }.bind(this)); 350 | 351 | this.identity = (function(arg){ 352 | return (function(){ 353 | if(arg){ 354 | return _["identity"](arg); 355 | } 356 | }.bind(this))(); 357 | }.bind(this)); 358 | 359 | this.empty_QM_ = (function(col){ 360 | return (function(){ 361 | if(this.array_QM_(col)){ 362 | return (0 == col.length); 363 | } else if(this.object_QM_(col)){ 364 | return _["isEqual"]((function(){ 365 | var _out = {}; 366 | return _out; 367 | }.bind(this))(),col); 368 | } else { 369 | throw this.str("Can't call empty? on ", col);; 370 | }}.bind(this))(); 371 | }.bind(this)); 372 | 373 | return this.hash_map = (function(){ 374 | var col = Array.prototype.slice.call(arguments, 0); 375 | return (function(){ 376 | var pairs = this.partition(2, col); 377 | 378 | return (function(){ 379 | if(this.empty_QM_(col)){ 380 | return (function(){ 381 | var _out = {}; 382 | return _out; 383 | }.bind(this))(); 384 | } else { 385 | return this.reduce((function(m, pair){ 386 | (m[this.first(pair)] = this.second(pair)); 387 | return m; 388 | }.bind(this)), (function(){ 389 | var _out = {}; 390 | return _out; 391 | }.bind(this))(), pairs); 392 | } 393 | }.bind(this))(); 394 | 395 | }.bind(this))(); 396 | }.bind(this)) 397 | 398 | }).call(cljs.core); 399 | 400 | 401 | 402 | var sub = sub || {}; 403 | sub.one = sub.one || {}; 404 | (function() { 405 | 406 | this.Array = Array; 407 | 408 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 409 | 410 | return this.one_fn = (function(){ 411 | return this.println("one"); 412 | }.bind(this)) 413 | 414 | }).call(sub.one); 415 | 416 | 417 | 418 | var sub = sub || {}; 419 | sub.two = sub.two || {}; 420 | (function() { 421 | 422 | this.Array = Array; 423 | 424 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 425 | 426 | return this.foo = "bar"; 427 | 428 | }).call(sub.two); 429 | 430 | 431 | 432 | var app = app || {}; 433 | (function() { 434 | 435 | this.Array = Array; 436 | 437 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 438 | 439 | for(var prop in sub.one){ this[prop] = sub.one[prop] }; 440 | 441 | for(var prop in sub.two){ this[prop] = sub.two[prop] }; 442 | 443 | return 444 | 445 | }).call(app); -------------------------------------------------------------------------------- /dev-resources/test-project/resources/js/app.js: -------------------------------------------------------------------------------- 1 | if(!Function.prototype.bind){Function.prototype.bind = function(scope) {var _function = this;return function() { return _function.apply(scope, arguments); } }}var cljs = cljs || {}; 2 | cljs.core = cljs.core || {}; 3 | (function() { 4 | 5 | this.Array = Array; 6 | 7 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 8 | 9 | this._ = _; 10 | 11 | this.count = (function(col){ 12 | return (function(){ 13 | if(col){ 14 | return col.length; 15 | } else { 16 | return 0; 17 | } 18 | }.bind(this))(); 19 | }.bind(this)); 20 | 21 | this.first = (function(col){ 22 | return (function(){ 23 | 24 | if(!col) return null; 25 | 26 | return (col[0]); 27 | 28 | }.bind(this))(); 29 | }.bind(this)); 30 | 31 | this.second = (function(col){ 32 | return this.nth(col, 1); 33 | }.bind(this)); 34 | 35 | this.rest = (function(col){ 36 | return (function(){ 37 | 38 | if(!col) return null; 39 | 40 | return this.Array.prototype.slice["call"](col,1); 41 | 42 | }.bind(this))(); 43 | }.bind(this)); 44 | 45 | this.inc = (function(n){ 46 | return (function() { 47 | var _out = arguments[0]; 48 | for(var _i=1; _i n))) return null; 69 | 70 | return (col[n]); 71 | 72 | }.bind(this))(); 73 | }.bind(this)); 74 | 75 | this.last = (function(col){ 76 | return (col[this.dec(col.length)]); 77 | }.bind(this)); 78 | 79 | this.reduce = (function(f, initial, col){ 80 | return (function(){ 81 | var i = (function(){ 82 | if(col){ 83 | return initial; 84 | } else { 85 | return null; 86 | } 87 | }.bind(this))(), 88 | c = (function(){ 89 | if(col){ 90 | return col; 91 | } else { 92 | return initial; 93 | } 94 | }.bind(this))(); 95 | 96 | return (function(){ 97 | if(i){ 98 | return this._["reduce"](c,f,i); 99 | } else { 100 | return this._["reduce"](c,f); 101 | } 102 | }.bind(this))(); 103 | 104 | }.bind(this))(); 105 | }.bind(this)); 106 | 107 | this.map = (function(f, initial, col){ 108 | return (function(){ 109 | var i = (function(){ 110 | if(col){ 111 | return initial; 112 | } else { 113 | return null; 114 | } 115 | }.bind(this))(), 116 | c = (function(){ 117 | if(col){ 118 | return col; 119 | } else { 120 | return initial; 121 | } 122 | }.bind(this))(); 123 | 124 | return (function(){ 125 | 126 | if(!c) return null; 127 | 128 | return (function(){ 129 | if(i){ 130 | return this._["map"](c,f,i); 131 | } else { 132 | return this._["map"](c,f); 133 | } 134 | }.bind(this))(); 135 | 136 | }.bind(this))(); 137 | 138 | }.bind(this))(); 139 | }.bind(this)); 140 | 141 | this.str = (function(){ 142 | var args = Array.prototype.slice.call(arguments, 0); 143 | return this.reduce((function(col, el){ 144 | return (function() { 145 | var _out = arguments[0]; 146 | for(var _i=1; _i this.count(colb))){ 331 | return this.count(cola); 332 | } else { 333 | return this.count(colb); 334 | } 335 | }.bind(this))(); 336 | 337 | return this.concat([ 338 | this.first(cola), 339 | this.first(colb) 340 | ], this.interleave(this.rest(cola), this.rest(colb))); 341 | 342 | }.bind(this))(); 343 | } 344 | }.bind(this))(); 345 | }.bind(this)); 346 | 347 | this.distinct = (function(col){ 348 | return _["uniq"](col); 349 | }.bind(this)); 350 | 351 | this.identity = (function(arg){ 352 | return (function(){ 353 | if(arg){ 354 | return _["identity"](arg); 355 | } 356 | }.bind(this))(); 357 | }.bind(this)); 358 | 359 | this.empty_QM_ = (function(col){ 360 | return (function(){ 361 | if(this.array_QM_(col)){ 362 | return (0 == col.length); 363 | } else if(this.object_QM_(col)){ 364 | return _["isEqual"]((function(){ 365 | var _out = {}; 366 | return _out; 367 | }.bind(this))(),col); 368 | } else { 369 | throw this.str("Can't call empty? on ", col);; 370 | }}.bind(this))(); 371 | }.bind(this)); 372 | 373 | return this.hash_map = (function(){ 374 | var col = Array.prototype.slice.call(arguments, 0); 375 | return (function(){ 376 | var pairs = this.partition(2, col); 377 | 378 | return (function(){ 379 | if(this.empty_QM_(col)){ 380 | return (function(){ 381 | var _out = {}; 382 | return _out; 383 | }.bind(this))(); 384 | } else { 385 | return this.reduce((function(m, pair){ 386 | (m[this.first(pair)] = this.second(pair)); 387 | return m; 388 | }.bind(this)), (function(){ 389 | var _out = {}; 390 | return _out; 391 | }.bind(this))(), pairs); 392 | } 393 | }.bind(this))(); 394 | 395 | }.bind(this))(); 396 | }.bind(this)) 397 | 398 | }).call(cljs.core);var common = common || {}; 399 | (function() { 400 | 401 | this.Array = Array; 402 | 403 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 404 | 405 | return this.yo = "Hello World!!!"; 406 | 407 | }).call(common); 408 | 409 | 410 | 411 | var lib = lib || {}; 412 | lib.one = lib.one || {}; 413 | (function() { 414 | 415 | this.Array = Array; 416 | 417 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 418 | 419 | for(var prop in common){ this[prop] = common[prop] }; 420 | 421 | return 422 | 423 | }).call(lib.one); 424 | 425 | 426 | 427 | var lib = lib || {}; 428 | lib.two = lib.two || {}; 429 | (function() { 430 | 431 | this.Array = Array; 432 | 433 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 434 | 435 | this.com = common; 436 | 437 | return 438 | 439 | }).call(lib.two); 440 | 441 | 442 | 443 | var app = app || {}; 444 | (function() { 445 | 446 | this.Array = Array; 447 | 448 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 449 | 450 | for(var prop in lib.one){ this[prop] = lib.one[prop] }; 451 | 452 | this.two = lib.two; 453 | 454 | return 455 | 456 | }).call(app); -------------------------------------------------------------------------------- /dev-resources/test-project/resources/js/app-test.js: -------------------------------------------------------------------------------- 1 | if(!Function.prototype.bind){Function.prototype.bind = function(scope) {var _function = this;return function() { return _function.apply(scope, arguments); } }}var cljs = cljs || {}; 2 | cljs.core = cljs.core || {}; 3 | (function() { 4 | 5 | this.Array = Array; 6 | 7 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 8 | 9 | this._ = _; 10 | 11 | this.count = (function(col){ 12 | return (function(){ 13 | if(col){ 14 | return col.length; 15 | } else { 16 | return 0; 17 | } 18 | }.bind(this))(); 19 | }.bind(this)); 20 | 21 | this.first = (function(col){ 22 | return (function(){ 23 | 24 | if(!col) return null; 25 | 26 | return (col[0]); 27 | 28 | }.bind(this))(); 29 | }.bind(this)); 30 | 31 | this.second = (function(col){ 32 | return this.nth(col, 1); 33 | }.bind(this)); 34 | 35 | this.rest = (function(col){ 36 | return (function(){ 37 | 38 | if(!col) return null; 39 | 40 | return this.Array.prototype.slice["call"](col,1); 41 | 42 | }.bind(this))(); 43 | }.bind(this)); 44 | 45 | this.inc = (function(n){ 46 | return (function() { 47 | var _out = arguments[0]; 48 | for(var _i=1; _i n))) return null; 69 | 70 | return (col[n]); 71 | 72 | }.bind(this))(); 73 | }.bind(this)); 74 | 75 | this.last = (function(col){ 76 | return (col[this.dec(col.length)]); 77 | }.bind(this)); 78 | 79 | this.reduce = (function(f, initial, col){ 80 | return (function(){ 81 | var i = (function(){ 82 | if(col){ 83 | return initial; 84 | } else { 85 | return null; 86 | } 87 | }.bind(this))(), 88 | c = (function(){ 89 | if(col){ 90 | return col; 91 | } else { 92 | return initial; 93 | } 94 | }.bind(this))(); 95 | 96 | return (function(){ 97 | if(i){ 98 | return this._["reduce"](c,f,i); 99 | } else { 100 | return this._["reduce"](c,f); 101 | } 102 | }.bind(this))(); 103 | 104 | }.bind(this))(); 105 | }.bind(this)); 106 | 107 | this.map = (function(f, initial, col){ 108 | return (function(){ 109 | var i = (function(){ 110 | if(col){ 111 | return initial; 112 | } else { 113 | return null; 114 | } 115 | }.bind(this))(), 116 | c = (function(){ 117 | if(col){ 118 | return col; 119 | } else { 120 | return initial; 121 | } 122 | }.bind(this))(); 123 | 124 | return (function(){ 125 | 126 | if(!c) return null; 127 | 128 | return (function(){ 129 | if(i){ 130 | return this._["map"](c,f,i); 131 | } else { 132 | return this._["map"](c,f); 133 | } 134 | }.bind(this))(); 135 | 136 | }.bind(this))(); 137 | 138 | }.bind(this))(); 139 | }.bind(this)); 140 | 141 | this.str = (function(){ 142 | var args = Array.prototype.slice.call(arguments, 0); 143 | return this.reduce((function(col, el){ 144 | return (function() { 145 | var _out = arguments[0]; 146 | for(var _i=1; _i this.count(colb))){ 331 | return this.count(cola); 332 | } else { 333 | return this.count(colb); 334 | } 335 | }.bind(this))(); 336 | 337 | return this.concat([ 338 | this.first(cola), 339 | this.first(colb) 340 | ], this.interleave(this.rest(cola), this.rest(colb))); 341 | 342 | }.bind(this))(); 343 | } 344 | }.bind(this))(); 345 | }.bind(this)); 346 | 347 | this.distinct = (function(col){ 348 | return _["uniq"](col); 349 | }.bind(this)); 350 | 351 | this.identity = (function(arg){ 352 | return (function(){ 353 | if(arg){ 354 | return _["identity"](arg); 355 | } 356 | }.bind(this))(); 357 | }.bind(this)); 358 | 359 | this.empty_QM_ = (function(col){ 360 | return (function(){ 361 | if(this.array_QM_(col)){ 362 | return (0 == col.length); 363 | } else if(this.object_QM_(col)){ 364 | return _["isEqual"]((function(){ 365 | var _out = {}; 366 | return _out; 367 | }.bind(this))(),col); 368 | } else { 369 | throw this.str("Can't call empty? on ", col);; 370 | }}.bind(this))(); 371 | }.bind(this)); 372 | 373 | return this.hash_map = (function(){ 374 | var col = Array.prototype.slice.call(arguments, 0); 375 | return (function(){ 376 | var pairs = this.partition(2, col); 377 | 378 | return (function(){ 379 | if(this.empty_QM_(col)){ 380 | return (function(){ 381 | var _out = {}; 382 | return _out; 383 | }.bind(this))(); 384 | } else { 385 | return this.reduce((function(m, pair){ 386 | (m[this.first(pair)] = this.second(pair)); 387 | return m; 388 | }.bind(this)), (function(){ 389 | var _out = {}; 390 | return _out; 391 | }.bind(this))(), pairs); 392 | } 393 | }.bind(this))(); 394 | 395 | }.bind(this))(); 396 | }.bind(this)) 397 | 398 | }).call(cljs.core);var common = common || {}; 399 | (function() { 400 | 401 | this.Array = Array; 402 | 403 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 404 | 405 | return this.yo = "Hello World!!!"; 406 | 407 | }).call(common); 408 | 409 | 410 | 411 | var lib = lib || {}; 412 | lib.one = lib.one || {}; 413 | (function() { 414 | 415 | this.Array = Array; 416 | 417 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 418 | 419 | for(var prop in common){ this[prop] = common[prop] }; 420 | 421 | return 422 | 423 | }).call(lib.one); 424 | 425 | 426 | 427 | var lib = lib || {}; 428 | lib.two = lib.two || {}; 429 | (function() { 430 | 431 | this.Array = Array; 432 | 433 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 434 | 435 | this.com = common; 436 | 437 | return 438 | 439 | }).call(lib.two); 440 | 441 | 442 | 443 | var app = app || {}; 444 | (function() { 445 | 446 | this.Array = Array; 447 | 448 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 449 | 450 | for(var prop in lib.one){ this[prop] = lib.one[prop] }; 451 | 452 | this.two = lib.two; 453 | 454 | return 455 | 456 | }).call(app); 457 | 458 | 459 | 460 | var app_test = app_test || {}; 461 | (function() { 462 | 463 | this.Array = Array; 464 | 465 | for(var prop in cljs.core){ this[prop] = cljs.core[prop] }; 466 | 467 | for(var prop in app){ this[prop] = app[prop] }; 468 | 469 | return 470 | 471 | }).call(app_test); -------------------------------------------------------------------------------- /src/cljs/core.clj: -------------------------------------------------------------------------------- 1 | (ns cljs.core 2 | "**cljs**" 3 | (:require [clojure.string :as str])) 4 | 5 | ;; Overall, cljs tries to mimic Clojure's rules where possible. 6 | 7 | ;; ## Identifiers 8 | ;; Cljs allows you to include several characters which are not allowed 9 | ;; in javascript identifiers. 10 | 11 | (defn prep-symbol 12 | "Prep-symbol will replace invalid characters in the passed symbol with 13 | javascript-compatable replacements. 14 | 15 | foo-bar -> foo_bar 16 | foo? -> foo_QM_ 17 | foo# -> foo_HASH_ 18 | foo! -> foo_BANG_ 19 | foo/bar -> foo.bar 20 | foo* -> foo_SPLAT_" 21 | [s] 22 | (-> (str s) 23 | (str/replace #"-" "_") 24 | (str/replace #"\?" "_QM_") 25 | (str/replace #"#" "_HASH_") 26 | (str/replace #"!" "_BANG_") 27 | (str/replace #"/" ".") 28 | (str/replace #"\*" "_SPLAT_") 29 | (str/replace #"=" "_EQ_") 30 | (symbol))) 31 | 32 | ;; ### Indenting / Pretty-Printing 33 | ;; As of now, pretty printing js is the only option. If you need to 34 | ;; minify the compiled output, use a tool like [Google's 35 | ;; Closure Compiler](http://code.google.com/closure/compiler/) (which 36 | ;; may be included in cljs in the future. 37 | (def ^:dynamic *indent* 0) 38 | 39 | (def nl "\n") 40 | 41 | (declare to-js) 42 | (declare special-forms) 43 | (declare apply-able-special-forms) 44 | 45 | (defn ind [] 46 | (apply str (take *indent* (repeat " ")))) 47 | 48 | (defn ind-str [& args] 49 | (let [lines (-> (apply str args) 50 | (str/split #"\n")) 51 | with-indent (interpose nl (map #(str (ind) %) lines))] 52 | (apply str with-indent))) 53 | 54 | (defmacro with-inc-indent [& body] 55 | `(binding [*indent* (+ *indent* 2)] 56 | ~@body)) 57 | 58 | (defn inc-ind-str [& body] 59 | (with-inc-indent 60 | (apply ind-str body))) 61 | 62 | (defn returnable? [el] 63 | (cond 64 | (coll? el) (let [f (first el)] 65 | (not (or (= 'throw f)))) 66 | (string? el) (not (re-find #"^throw\s+" el)) 67 | :else true)) 68 | 69 | (defn add-return [statements] 70 | (let [count (dec (count statements)) 71 | before-ret (take count statements) 72 | after-ret (first (drop count statements)) 73 | with-return (concat before-ret [(str "return " after-ret)])] 74 | (if (returnable? after-ret) 75 | with-return 76 | statements))) 77 | 78 | (defn interpose-semi-colon [col] 79 | (interpose ";" col)) 80 | 81 | ;; Several types of functions follow the pattern ` 82 | ;; `. We use the *-op-fn functions to provide a uniform 83 | ;; mechanism for defining these types of operations. 84 | ;; 85 | ;; See section 1.2 of Okasaki's [Purely Functional Data 86 | ;; Structures](http://www.cs.cmu.edu/~rwh/theses/okasaki.pdf) for more 87 | ;; info. In fact, just go read the whole thing, it's amazingly 88 | ;; well-written. 89 | 90 | 91 | (defn strict-eval-op-fn 92 | "`strict-eval-op-fn` is used to define functions of the above pattern for fuctions such as `+`, `*`, etc. Cljs special forms defined this way are applyable, such as `(apply + [1 2 3])`. 93 | 94 | Resulting expressions are wrapped in an anonymous function and, down the line, `call`ed, like so: 95 | 96 | (+ 1 2 3) -> (function(){...}.call(this, 1 2 3)" 97 | [op] 98 | (ind-str 99 | "(function() {" nl 100 | (inc-ind-str 101 | "var _out = arguments[0];" nl 102 | "for(var _i=1; _i (foo || bar || baz)" 115 | [op stmts] 116 | (str 117 | "(" 118 | (apply str (interpose (str " " op " ") (map to-js stmts))) 119 | ")")) 120 | 121 | 122 | (def ^:dynamic *fn-params* #{}) 123 | 124 | (defn to-identifier [sym] 125 | (when sym 126 | (prep-symbol sym))) 127 | 128 | 129 | ;; # Calling Functions 130 | 131 | (defn call-fn [[f & args]] 132 | (ind-str 133 | (to-js f) 134 | "(" 135 | (->> args 136 | (map to-js) 137 | (interpose ", ") 138 | (apply str)) 139 | ")")) 140 | 141 | 142 | (defn call-special-form [sexp] 143 | (let [f (first sexp) 144 | args (rest sexp) 145 | jsf (((apply-able-special-forms) f) sexp)] 146 | (ind-str 147 | jsf 148 | ".call(this" 149 | (->> args 150 | (map to-js) 151 | (interleave (repeat ", ")) 152 | #_(interpose ", ") 153 | (apply str)) 154 | ")"))) 155 | 156 | 157 | (defn to-fn [[_ arglist & body-seq]] 158 | (let [before-amp (take-while #(not= '& %) arglist) 159 | after-amp (first (drop 1 (drop-while #(not= '& %) arglist))) 160 | params (concat before-amp [after-amp]) 161 | before-amp (map to-identifier before-amp) 162 | after-amp (to-identifier after-amp)] 163 | (binding [*fn-params* (set (concat params *fn-params*))] 164 | (let [body-seq (map to-js body-seq) 165 | body-len (dec (count body-seq)) 166 | before-ret (take body-len body-seq) 167 | after-ret (drop body-len body-seq) 168 | with-return (concat before-ret [(apply str "return " after-ret)])] 169 | (str "(function(" 170 | (apply str (interpose ", " before-amp)) 171 | "){\n" 172 | (inc-ind-str 173 | (when after-amp 174 | (str "var " after-amp 175 | " = Array.prototype.slice.call(arguments, " (count before-amp) ");" nl)) 176 | (apply str (interpose (str ";" nl) with-return))) 177 | ";\n}.bind(this))"))))) 178 | 179 | (defn handle-def [[_ name body]] 180 | (ind-str 181 | "this." (to-identifier name) " = " (to-js body) ";")) 182 | 183 | (defn handle-fn [sexp] 184 | (to-fn sexp)) 185 | 186 | (defn handle-set [[_ name val]] 187 | (ind-str 188 | "(" 189 | (to-js name) 190 | " = " 191 | (to-js val) 192 | ")")) 193 | 194 | (defn handle-binding [[v binding]] 195 | (str "" (to-identifier v) " = " (to-js binding))) 196 | 197 | (defn handle-bindings [col] 198 | (str 199 | "var " 200 | (->> (partition 2 col) 201 | (map handle-binding) 202 | (interpose (str "," nl)) 203 | (apply str)) 204 | ";")) 205 | 206 | (defn binding-vars [bindings] 207 | (->> (partition 2 bindings) 208 | (map first))) 209 | 210 | (defn handle-let [[_ bindings & body]] 211 | (binding [*fn-params* (concat *fn-params* (binding-vars bindings))] 212 | (ind-str 213 | "(function(){" nl 214 | (inc-ind-str 215 | (handle-bindings bindings) nl nl 216 | (apply str (interpose (str ";" nl nl) (add-return (map to-js body))))) 217 | ";" 218 | nl nl 219 | "}.bind(this))()"))) 220 | 221 | (defn handle-defn [[_ & rest]] 222 | (let [name (first rest) 223 | fn (drop 1 rest)] 224 | (ind-str 225 | "this." (to-identifier name) " = " 226 | (to-fn rest) 227 | ))) 228 | 229 | (defn handle-aget [[_ col idx]] 230 | (ind-str 231 | "(" 232 | (to-js col) 233 | "[" (to-js idx) "]" 234 | ")")) 235 | 236 | (defn handle-aset [[_ col idx val]] 237 | (ind-str 238 | "(" 239 | (to-js col) 240 | "[" (to-js idx) "]" 241 | " = " 242 | (to-js val) 243 | ")")) 244 | 245 | (defn handle-if [[_ pred t f]] 246 | (let [pred (to-js pred) 247 | t (to-js t) 248 | f (to-js f)] 249 | (str 250 | "(function(){" nl 251 | (inc-ind-str 252 | "if(" pred "){\n return " t ";\n}" 253 | (when f 254 | (str " else {\n return " f ";\n}"))) 255 | nl 256 | "}.bind(this))()"))) 257 | 258 | (defn handle-while [[_ pred & body]] 259 | (ind-str 260 | "while(" 261 | (to-js pred) 262 | ") {" 263 | (inc-ind-str 264 | (apply str (interpose (str ";" nl) (map to-js body)))) 265 | nl 266 | "}")) 267 | 268 | (defn handle-when [[_ pred & rest]] 269 | (let [pred (to-js pred) 270 | rest (add-return (map to-js rest))] 271 | (ind-str 272 | "(function(){" nl 273 | (inc-ind-str 274 | nl 275 | "if(!" pred ") return null;" nl nl 276 | (apply str (interpose (str ";" nl) rest)) 277 | ";") 278 | nl nl 279 | "}.bind(this))()"))) 280 | 281 | (defn handle-doto [[_ & body]] 282 | (let [pivot (first body) 283 | forms (rest body)] 284 | (binding [*fn-params* (concat *fn-params* ['_out])] 285 | (str 286 | "(function(){" 287 | (apply 288 | str 289 | (interpose 290 | ";\n" 291 | (add-return 292 | (concat 293 | [(str "var _out = " (to-js pivot))] 294 | (map to-js (map #(concat (vector (first %) '_out) (rest %)) forms)) 295 | ['_out])))) 296 | "}.bind(this))()")))) 297 | 298 | (defn handle-->> [[_ pivot & forms]] 299 | (let [pivot (to-js pivot) 300 | forms (map #(concat % ['_out]) 301 | forms)] 302 | (binding [*fn-params* (concat *fn-params* ['_out])] 303 | (str 304 | "(function(){" 305 | "var _out = " 306 | pivot 307 | ";\n" 308 | (apply str (map #(str "_out = " % ";" nl) (map to-js forms))) 309 | "return _out;" 310 | "}.bind(this))()")))) 311 | 312 | (defn handle--> [[_ pivot & forms]] 313 | (let [pivot (to-js pivot) 314 | forms (map #(concat [(first %)] [''_out] (rest %)) 315 | forms)] 316 | (binding [*fn-params* (concat *fn-params* ['_out])] 317 | (ind-str 318 | "(function(){" 319 | (inc-ind-str 320 | "var _out = " 321 | pivot 322 | ";\n" 323 | (apply str (map #(str "_out = " % ";" nl) (map to-js forms))) 324 | "return _out;") 325 | "}.bind(this))()")))) 326 | 327 | (defn handle-not [[_ stmt]] 328 | (str "(!" (to-js stmt) ")")) 329 | 330 | (defn handle-do [[_ & statements]] 331 | (str 332 | "(function(){" 333 | (apply str 334 | (interpose (str ";" nl) (add-return (map to-js statements)))) 335 | "}.bind(this))()")) 336 | 337 | (defn handle-cond [[_ & conds]] 338 | (let [pairs (partition 2 conds)] 339 | (ind-str 340 | "(function(){" nl 341 | (inc-ind-str 342 | (->> pairs 343 | (map #(str 344 | (when (not (keyword? (first %))) 345 | (str "if(" 346 | (to-js (first %)) 347 | ")")) 348 | "{" nl 349 | (inc-ind-str 350 | (if (returnable? (second %)) 351 | (str "return " 352 | (to-js (second %))) 353 | (to-js (second %))) 354 | ";") nl 355 | "}")) 356 | (interpose " else ") 357 | (apply str))) 358 | "}.bind(this))()"))) 359 | 360 | (defn make-strict-op [op] 361 | (fn [& _] 362 | (strict-eval-op-fn op))) 363 | 364 | (defn make-lazy-op [op] 365 | (fn [[_ & args]] 366 | (lazy-eval-op-fn op args))) 367 | 368 | (defn handle-doseq [[_ bdg & body]] 369 | (let [colsym (gensym)] 370 | (ind-str 371 | "(function() {" nl 372 | (inc-ind-str 373 | "var " colsym " = " (to-js (second bdg)) ";" nl 374 | "for(var i=0; i < " colsym ".length; i++) {" nl 375 | (inc-ind-str 376 | "(function(" (to-identifier (first bdg)) "){" 377 | (binding [*fn-params* (concat *fn-params* [(first bdg)])] 378 | (->> body 379 | (map to-js) 380 | (interpose (str ";" nl)) 381 | (apply str)))) 382 | "}.bind(this))(" colsym "[i]);" 383 | nl 384 | "}") nl 385 | "}.bind(this))()"))) 386 | 387 | (defn handle-instanceof [[_ obj type]] 388 | (ind-str 389 | "(" 390 | (to-js obj) 391 | " instanceof " 392 | (to-js type) 393 | ")")) 394 | 395 | (defn handle-gensym [_] 396 | (to-identifier (gensym))) 397 | 398 | (defn handle-gensym-str [_] 399 | (to-js (str (gensym)))) 400 | 401 | (defn handle-throw [[_ msg]] 402 | (ind-str 403 | "throw " (to-js msg) ";")) 404 | 405 | (defn handle-dissoc [[_ m & keys]] 406 | (ind-str 407 | "(function() {" nl 408 | (inc-ind-str 409 | "var __map = " (to-js m) ";" nl nl 410 | (->> keys 411 | (map #(str "delete __map[" (to-js %) "];" nl)) 412 | (apply str)) 413 | "return __map;") 414 | nl 415 | "}.bind(this))()")) 416 | 417 | (defn special-forms [] 418 | {'def handle-def 419 | 'fn handle-fn 420 | 'fn* handle-fn 421 | 'set! handle-set 422 | 'let handle-let 423 | 'defn handle-defn 424 | 'aget handle-aget 425 | 'aset handle-aset 426 | 'if handle-if 427 | 'while handle-while 428 | 'when handle-when 429 | 'doto handle-doto 430 | '-> handle--> 431 | '->> handle-->> 432 | 'not handle-not 433 | 'do handle-do 434 | 'cond handle-cond 435 | '= (make-lazy-op '==) 436 | '> (make-lazy-op '>) 437 | '< (make-lazy-op '<) 438 | '>= (make-lazy-op '>=) 439 | '<= (make-lazy-op '<=) 440 | 'or (make-lazy-op '||) 441 | 'and (make-lazy-op '&&) 442 | 'doseq handle-doseq 443 | 'instanceof handle-instanceof 444 | 'gensym handle-gensym 445 | 'gensym-str handle-gensym-str 446 | 'throw handle-throw 447 | 'dissoc handle-dissoc}) 448 | 449 | (defn apply-able-special-forms [] 450 | {'+ (make-strict-op '+) 451 | '- (make-strict-op '-) 452 | '* (make-strict-op '*) 453 | '/ (make-strict-op '/)}) 454 | 455 | (defn map-accessor? [sexp] 456 | (and (= 2 (count sexp)) 457 | (or (seq? sexp) 458 | (list? sexp)) 459 | (keyword? (first sexp)))) 460 | 461 | (defn map-accessor-to-js [sexp] 462 | (let [kw (name (first sexp)) 463 | obj (to-js (second sexp))] 464 | (str "(" obj "['" kw "'])"))) 465 | 466 | (defn object-member? [[f & _]] 467 | (= \. (first (str f)))) 468 | 469 | (defn object-member-call-to-js [[member obj & args]] 470 | (ind-str 471 | (to-js obj) 472 | "[\"" 473 | (str/replace member #"\." "") 474 | "\"]" 475 | "(" 476 | (->> args 477 | (map to-js) 478 | (interpose ",") 479 | (apply str)) 480 | ")")) 481 | 482 | (defn chop-trailing-period [sym] 483 | (let [sym-str (str sym) 484 | len (count sym-str)] 485 | (->> sym-str 486 | (take (dec len)) 487 | (apply str) 488 | (symbol)))) 489 | 490 | (defn new-object? [[obj & _]] 491 | (re-find #"\.$" (str obj))) 492 | 493 | (defn new-object [[obj & args]] 494 | (let [clazz (chop-trailing-period obj)] 495 | (str "(new " 496 | "this." 497 | clazz 498 | "(" 499 | (apply str (interpose "," (map to-js args))) 500 | "))"))) 501 | 502 | (defn sexp-to-js 503 | "Sexps are (most of the time) translated into javascript function calls. 504 | The baseline translation works as you would expect, the first form in 505 | the sexp is moved outside the opening paren, and the remaining forms 506 | are passed as arguments: 507 | 508 | (dostuff \"foo\" \"bar\" 1 -> alert(\"foo\",\"bar\",1) 509 | 510 | However, cljs supports special case sexps to support features like 511 | sugared map access and javascript object interop. 512 | 513 | ; object member function 514 | (.foo bar \"baz\") -> foo.bar(\"baz\") 515 | 516 | ; object construction 517 | (Foo. \"bar\" \"baz\") -> new Foo(\"bar\",\"baz\") 518 | 519 | ; map access 520 | (:foo bar) -> bar[\"foo\"] 521 | 522 | It is also at this point that calls to special forms are handled by 523 | their respective handlers. `(fn [] ('alert \"hi\"))` is 524 | handled by `handle-fn`, not passed along to `call-fn` like user 525 | defined functions. 526 | 527 | See `cljs.core/special-forms` and `cljs.core/apply-able-special-forms` 528 | for symbol to handler mappings." 529 | [sexp] 530 | (cond 531 | (= 'quote (first sexp)) (str (second sexp)) 532 | (object-member? sexp) (object-member-call-to-js sexp) 533 | (new-object? sexp) (new-object sexp) 534 | ((special-forms) (first sexp)) (((special-forms) (first sexp)) sexp) 535 | ((apply-able-special-forms) (first sexp)) (call-special-form sexp) 536 | (map-accessor? sexp) (map-accessor-to-js sexp) 537 | :else (call-fn sexp))) 538 | 539 | ;; hashmap creation is handled in a seemingly convoluted way. A 540 | ;; temporary object is created to which entries are inserted, instead 541 | ;; of a direct translation to javascript's map syntax. This is to 542 | ;; support using var values as keys. For example: 543 | ;; 544 | ;; var x = "foo" 545 | ;; var m = {x: "bar"} 546 | ;; 547 | ;; would produce an object with a member "x" of value "bar", where the 548 | ;; desired behavoir is a map with member "foo" of value 549 | ;; "bar". Therefore we handle map creation as such: 550 | ;; 551 | ;; (let [x "foo"] 552 | ;; {x "bar"}) 553 | ;; 554 | ;; compiles to 555 | ;; 556 | ;; var x = "foo" 557 | ;; return (function() { 558 | ;; var _out = {} 559 | ;; _out[x] = "bar" 560 | ;; })() 561 | ;; 562 | ;; instead of 563 | ;; 564 | ;; var x = "foo" 565 | ;; {x: "bar"} 566 | ;; 567 | ;; which more closely mimics clojure's map creation behavior. 568 | ;; 569 | 570 | (defn map-to-js [m] 571 | (ind-str 572 | "(function(){" nl 573 | (inc-ind-str 574 | "var _out = {};" nl 575 | (->> m 576 | (map #(str 577 | "_out[" (to-js (key %)) "] = " (to-js (val %)) ";" nl)) 578 | (apply str)) 579 | "return _out;") 580 | nl 581 | "}.bind(this))()")) 582 | 583 | (defn vec-to-js [elements] 584 | (if (empty? elements) "[]" 585 | (ind-str 586 | "[" nl 587 | (inc-ind-str 588 | (->> elements 589 | (map to-js) 590 | (interpose ",\n") 591 | (apply str))) 592 | nl "]"))) 593 | 594 | (defn scope-symbol [sym] 595 | (if (seq? sym) 596 | sym 597 | (let [before-dot (symbol (second (re-find #"^([^.]*)" (str sym)))) 598 | sym (prep-symbol sym)] 599 | (if (some #(= before-dot %) *fn-params*) 600 | sym 601 | (str "this." sym))))) 602 | 603 | (defn symbol-to-js [sym] 604 | (cond 605 | ((apply-able-special-forms) sym) (((apply-able-special-forms) sym)) 606 | :else (scope-symbol sym))) 607 | 608 | (defn boolean? [o] 609 | (= java.lang.Boolean (type true))) 610 | 611 | (defn str-to-js [s] 612 | (-> s 613 | (str/replace #"\n" "\\\\n") 614 | (str/replace #"\"" "\\\\\""))) 615 | 616 | (defn to-js 617 | "Top-level conversion routine. The form passed to `to-js` is converted to javascript based on it's type. Valid input types are lists, vectors, symbols, keywords, strings, numbers, or booleans. Throws an exeption if the passed form is not a valid input type." 618 | [form] 619 | (cond 620 | (or (seq? form) 621 | (list? form)) (sexp-to-js form) 622 | (map? form) (map-to-js form) 623 | (vector? form) (vec-to-js form) 624 | (symbol? form) (symbol-to-js form) 625 | (keyword? form) (to-js (name form)) 626 | (string? form) (str \" (str-to-js form) \") 627 | (number? form) form 628 | (boolean? form) form 629 | (nil? form) "" 630 | :else (throw 631 | (Exception. 632 | (str 633 | "Don't know how to handle " 634 | form 635 | " of type " 636 | (:type form)))))) 637 | 638 | 639 | ;; ## Namespace Handling 640 | 641 | (defn use-to-js [u] 642 | (->> u 643 | (drop 1) 644 | (map #(str 645 | "for(" 646 | "var prop in " (to-identifier %) 647 | ")" 648 | "{ this[prop] = " (to-identifier %) "[prop] };" nl nl)) 649 | (apply str))) 650 | 651 | (defn seq-require-to-js 652 | "Handles `:require`s in the form of `[foo :as bar]`." 653 | [[name _ as]] 654 | (str "this." (to-identifier as) " = " (to-identifier name) ";" nl nl)) 655 | 656 | (defn sym-require-to-js [sym] 657 | (str "this." (to-identifier sym) " = " (to-identifier sym) ";" nl nl)) 658 | 659 | (defn require-to-js [r] 660 | (->> r 661 | (drop 1) 662 | (map #(cond 663 | (or (vector? %) (seq? %)) (seq-require-to-js %) 664 | :else (sym-require-to-js %))) 665 | (apply str))) 666 | 667 | (defn import-to-js [r] 668 | (require-to-js r)) 669 | 670 | (defn init-ns-object [name] 671 | (when name 672 | (let [parts (str/split (str name) #"\.") 673 | num (count parts)] 674 | (->> (map #(->> parts 675 | (take (inc %)) 676 | (interpose ".") 677 | (apply str)) 678 | (range num)) 679 | (map #(str 680 | (when (not (re-find #"\." %)) 681 | "var ") 682 | (to-identifier %) 683 | " = " 684 | (to-identifier %) 685 | " || {};" 686 | nl)))))) 687 | 688 | 689 | (def default-includes ['Array]) 690 | 691 | (defn wrap-with-ns [name imports & body] 692 | (ind-str 693 | (apply str (init-ns-object name)) 694 | "(function() {" nl nl 695 | (inc-ind-str 696 | (apply str (interpose ";\n" (map #(str "this." % " = " %) default-includes))) 697 | ";\n\n" 698 | (use-to-js '(:use cljs.core)) 699 | (->> imports 700 | (filter #(= :use (first %))) 701 | (map use-to-js) 702 | (interpose (str ";" nl nl)) 703 | (apply str)) 704 | (->> imports 705 | (filter #(= :require (first %))) 706 | (map require-to-js) 707 | (interpose (str ";" nl nl)) 708 | (apply str)) 709 | (->> imports 710 | (filter #(= :import (first %))) 711 | (map import-to-js) 712 | (interpose (str ";" nl nl)) 713 | (apply str)) 714 | (->> body 715 | (map to-js) 716 | (add-return) 717 | (interpose (str ";" nl nl)) 718 | (apply str))) 719 | nl nl 720 | "}).call(" (to-identifier name) ");")) 721 | 722 | (def ^:dynamic *core-lib* 723 | (str 724 | "if(!Function.prototype.bind){" 725 | "Function.prototype.bind = function(scope) {var _function = this;return function() { return _function.apply(scope, arguments); } }" 726 | "}" 727 | (wrap-with-ns "cljs.core" '[(:require _)] 728 | 729 | '(defn count [col] 730 | (if col 731 | 'col.length 732 | 0)) 733 | 734 | '(defn first [col] 735 | (when col 736 | (aget col 0))) 737 | 738 | '(defn second [col] 739 | (nth col 1)) 740 | 741 | '(defn rest [col] 742 | (when col 743 | (.call Array.prototype.slice col 1))) 744 | 745 | '(defn inc [n] 746 | (+ n 1)) 747 | 748 | '(defn dec [n] 749 | (- n 1)) 750 | 751 | '(defn nth [col n] 752 | (when (and col (> col.length n)) 753 | (aget col n))) 754 | 755 | '(defn last [col] 756 | (aget col (dec col.length))) 757 | 758 | '(defn reduce [f initial col] 759 | (let [i (if col initial 'null) 760 | c (if col col initial)] 761 | (if i 762 | (.reduce _ c f i) 763 | (.reduce _ c f)))) 764 | 765 | '(defn map [f initial col] 766 | (let [i (if col initial 'null) 767 | c (if col col initial)] 768 | (when c 769 | (if i 770 | (.map _ c f i) 771 | (.map _ c f))))) 772 | 773 | '(defn str [& args] 774 | (reduce (fn [col el] (+ col el)) "" (filter #(.identity _ %) args))) 775 | 776 | '(defn println [& args] 777 | (.log 'console args)) 778 | 779 | '(defn apply [f & args] 780 | (let [l (last args) 781 | fs (take (dec (count args)) args) 782 | flattened (concat fs l)] 783 | (.apply f 'this flattened))) 784 | 785 | '(defn filter [f col] 786 | (if col 787 | (.filter _ col f))) 788 | 789 | '(defn concat [cola colb] 790 | (let [out []] 791 | (out.push.apply out cola) 792 | (out.push.apply out colb) 793 | out)) 794 | 795 | '(defn take [n col] 796 | (when col 797 | (.slice col 0 n))) 798 | 799 | '(defn drop [n col] 800 | (when col 801 | (.slice col n))) 802 | 803 | '(defn partition [n col] 804 | (let [f (fn [out col] 805 | (if (= 0 (count col)) 806 | out 807 | (f (concat out [(take n col)]) (drop n col))))] 808 | (f [] col))) 809 | 810 | '(defn assoc [obj & rest] 811 | (let [pairs (partition 2 rest)] 812 | (doseq [p pairs] 813 | (aset obj (first p) (nth p 1))) 814 | obj)) 815 | 816 | '(defn conj [col & rest] 817 | (doseq [r rest] 818 | (.push col r)) 819 | col) 820 | 821 | '(defn array? [o] 822 | (and o 823 | (.isArray _ o))) 824 | 825 | '(defn object? [o] 826 | (and o 827 | (not (array? o)) 828 | (not (string? o)))) 829 | 830 | '(defn string? [o] 831 | (.isString _ o)) 832 | 833 | '(defn element? [o] 834 | (and o 835 | (or (.isElement _ o) 836 | (.isElement _ (first o))))) 837 | 838 | '(defn merge [& objs] 839 | (let [o {}] 840 | (map #(.extend _ o %) objs) 841 | o)) 842 | 843 | '(defn interpose [o col] 844 | (when col 845 | (let [out [] 846 | idx 0 847 | len (count col) 848 | declen (dec len)] 849 | (while (< idx len) 850 | (if (= idx declen) 851 | (.push out (aget col idx)) 852 | (do 853 | (.push out (aget col idx)) 854 | (.push out o))) 855 | (set! idx (inc idx))) 856 | out))) 857 | 858 | '(defn interleave [cola colb] 859 | (if (or (= 0 (count cola)) 860 | (= 0 (count colb))) 861 | [] 862 | (let [len (if (> (count cola) (count colb)) 863 | (count cola) 864 | (count colb))] 865 | (concat [(first cola) (first colb)] 866 | (interleave (rest cola) (rest colb)))))) 867 | 868 | '(defn distinct [col] 869 | (.uniq '_ col)) 870 | 871 | '(defn identity [arg] 872 | (if arg 873 | (.identity '_ arg))) 874 | 875 | '(defn empty? [col] 876 | (cond 877 | (array? col) (= 0 col.length) 878 | (object? col) (.isEqual '_ {} col) 879 | :else (throw (str "Can't call empty? on " col)))) 880 | 881 | '(defn hash-map [& col] 882 | (let [pairs (partition 2 col)] 883 | (if (empty? col) 884 | {} 885 | (reduce (fn [m pair] 886 | (aset m (first pair) (second pair)) 887 | m) 888 | {} 889 | pairs)))) 890 | 891 | '(defn keys [m] 892 | (when m 893 | (.keys '_ m))) 894 | 895 | '(defn vals [m] 896 | (when m 897 | (.values '_ m))) 898 | 899 | '(defn select-keys [m keys] 900 | (reduce #(assoc %1 %2 (aget m %2)) {} keys)) 901 | ))) 902 | 903 | (defn spit-cljs-core [path] 904 | (spit path *core-lib*)) 905 | 906 | (defn compile-cljs-reader [reader] 907 | (let [rdr (clojure.lang.LineNumberingPushbackReader. reader) 908 | forms (take-while #(not (nil? %)) (repeatedly (fn [] (read rdr false nil)))) 909 | ns-decl (when (= 'ns (first (first forms))) 910 | (first forms)) 911 | forms (if ns-decl (rest forms) forms) 912 | imports (drop 2 ns-decl)] 913 | (apply wrap-with-ns 914 | (second ns-decl) 915 | imports 916 | forms))) 917 | 918 | (defn compile-cljs-string [str] 919 | (compile-cljs-reader (java.io.StringReader. str))) 920 | 921 | (defn compile-cljs-file [path] 922 | (compile-cljs-reader (java.io.FileReader. path))) 923 | 924 | 925 | --------------------------------------------------------------------------------