├── .gitignore ├── README.md ├── bin ├── cljp └── cljpc ├── examples ├── basic.cljp ├── bindings.cljp ├── double.cljp ├── includes.cljp ├── map.cljp ├── namespaces.cljp ├── objects.cljp ├── php.cljp ├── php │ └── Namer.php └── require.cljp ├── project.clj ├── src ├── clj_php │ ├── core.clj │ ├── exprs.clj │ ├── fs.clj │ ├── funcs.clj │ ├── ns.clj │ └── vars.clj ├── clojure │ └── core.cljp └── php │ ├── bootstrap.php │ └── clojure │ ├── lang.php │ └── lang │ ├── ASeq.php │ ├── Base.php │ ├── CList.php │ ├── Cons.php │ ├── DefMap.php │ ├── ISeq.php │ ├── LazySeq.php │ ├── Php.php │ ├── Seq.php │ └── Vector.php └── test ├── clj_php └── test │ ├── core.clj │ ├── exprs.clj │ ├── funcs.clj │ └── ns.clj ├── example.cljp └── php └── clojure ├── lang ├── CListTest.php ├── ConsTest.php ├── DefMapTest.php ├── LazySeqTest.php ├── PhpTest.php ├── SeqTest.php └── VectorTest.php └── langTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /pom.xml 2 | *jar 3 | /lib 4 | /classes 5 | /native 6 | /.lein-failures 7 | /checkouts 8 | /.lein-deps-sum 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clj-php 2 | 3 | A very naive experiment on compiling Clojure to PHP. Hmmm... 4 | 5 | ## Using 6 | 7 | Simple example of defining and cailling a function: 8 | 9 | ```clojure 10 | (ns examples.basic) 11 | 12 | (defn double [x] 13 | (* 2 x)) 14 | 15 | (println (str "Double 2 is... " (double 2))) 16 | ``` 17 | 18 | You can compile and run this with: 19 | 20 | ```bash 21 | ./bin/cljp examples/basic.cljp 22 | ``` 23 | 24 | Which should output: 25 | 26 | ``` 27 | Double 2 is... 4 28 | ``` 29 | 30 | ## Features 31 | 32 | At the moment only the basic of Clojure have been implemented, but hopefully this will be a growing list. 33 | 34 | * defn 35 | * namespaces (with :use and :require) 36 | * PHP integration 37 | 38 | ## PHP Integration 39 | 40 | Integration with PHP is handled (like in ClojureScript) through the *php* import to each namespace. You 41 | can then reference any functions from the standard PHP distributions. 42 | 43 | ```clojure 44 | (println "Date is: " (php/date "dS F Y, H:i:s")) 45 | ``` 46 | 47 | You can also include other PHP libraries and use objects, much like the Java interop provided by Clojure. 48 | Like this contrived database example: 49 | 50 | ```clojure 51 | (ns examples.objects) 52 | 53 | (def cnn (DBConnection. "localhost" "root" "")) 54 | 55 | (.query cnn "select * from table") 56 | ``` 57 | 58 | ### Including PHP Files 59 | 60 | You can also include PHP files using *:include*. This is executed at runtime though, so should be used 61 | for any application bootstrap (like pulling in required PHP libraries). 62 | 63 | ```clojure 64 | (ns examples 65 | (:include "path/to/bootstrap.php")) 66 | ``` 67 | 68 | ## Tests 69 | 70 | Tests written with Midje and PHPUnit, run them with... 71 | 72 | ```bash 73 | lein midje 74 | phpunit test/php 75 | ``` 76 | 77 | ### Disclaimer 78 | 79 | This is not meant to ever be an actually useful thing, compiling Clojure to PHP is insane. It's just 80 | a learning tool for me to get more familiar with Clojure. 81 | 82 | ## TODO 83 | 84 | I don't plan to implement *all* Clojure features, some of them don't make sense (like Futures, as PHP 85 | has no thread support). These are some things I am looking to implement though: 86 | 87 | * variable arity 88 | * destructuring 89 | * tail recursion 90 | * lazy sequences 91 | * macros 92 | 93 | -------------------------------------------------------------------------------- /bin/cljp: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DIR="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | $DIR/cljpc $@ | php 6 | 7 | 8 | -------------------------------------------------------------------------------- /bin/cljpc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DIR="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | BIN="clj-php.jar" 5 | 6 | cd $DIR/../ 7 | 8 | if [ ! -e $BIN ]; then 9 | lein uberjar > /dev/null 10 | fi 11 | 12 | java -jar clj-php.jar $@ 13 | 14 | -------------------------------------------------------------------------------- /examples/basic.cljp: -------------------------------------------------------------------------------- 1 | 2 | (ns examples.basic) 3 | 4 | (defn double [x] 5 | (* 2 x)) 6 | 7 | (println (str "Double 2 is... " (double 2))) 8 | 9 | -------------------------------------------------------------------------------- /examples/bindings.cljp: -------------------------------------------------------------------------------- 1 | 2 | (ns examples.bindings) 3 | 4 | (let [x 10 5 | y 10] 6 | (println x " * " y " = " (* x y))) 7 | 8 | -------------------------------------------------------------------------------- /examples/double.cljp: -------------------------------------------------------------------------------- 1 | 2 | (ns examples.double) 3 | 4 | (defn double [x] 5 | (* 2 x)) 6 | 7 | -------------------------------------------------------------------------------- /examples/includes.cljp: -------------------------------------------------------------------------------- 1 | 2 | (ns examples.includes 3 | (:include "php/Namer.php")) 4 | 5 | (def namer (Namer. "Rich")) 6 | 7 | (println "Full name: " (.getFullName namer "Hickey")) 8 | 9 | -------------------------------------------------------------------------------- /examples/map.cljp: -------------------------------------------------------------------------------- 1 | 2 | (ns examples.map) 3 | 4 | (def x [1 2 3 4 5]) 5 | 6 | (defn double [x] (* x 2)) 7 | 8 | (def doubled (map double [1 2 3])) 9 | 10 | (println "Doubled: " 11 | (first doubled)) 12 | 13 | -------------------------------------------------------------------------------- /examples/namespaces.cljp: -------------------------------------------------------------------------------- 1 | 2 | (ns examples.namespaces 3 | (:use examples.double)) 4 | 5 | (println "Double 2 should still be... " (double 2)) 6 | 7 | -------------------------------------------------------------------------------- /examples/objects.cljp: -------------------------------------------------------------------------------- 1 | 2 | (ns examples.objects) 3 | 4 | (let [foo (Namer. "Rich")] 5 | (println (.getFullName foo "Hickey"))) 6 | 7 | -------------------------------------------------------------------------------- /examples/php.cljp: -------------------------------------------------------------------------------- 1 | 2 | (ns examples.php) 3 | 4 | (println "Date is: " (php/date "dS F Y H:i:s")) 5 | 6 | -------------------------------------------------------------------------------- /examples/php/Namer.php: -------------------------------------------------------------------------------- 1 | firstName = $firstName; 7 | } 8 | 9 | public function getFullName( $lastName ) { 10 | return $this->firstName . ' ' . $lastName; 11 | } 12 | 13 | } 14 | 15 | -------------------------------------------------------------------------------- /examples/require.cljp: -------------------------------------------------------------------------------- 1 | 2 | (ns examples.req 3 | (:require [examples.double :as dbl])) 4 | 5 | (println "Double 2 is " (dbl/double 2)) 6 | 7 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | 2 | (defproject clj-php "0.0.7" 3 | :description "Clojure to PHP compiler" 4 | :dependencies [[org.clojure/clojure "1.3.0"]] 5 | :dev-dependencies [[com.stuartsierra/lazytest "1.2.3"] 6 | [lein-midje "1.0.8"] 7 | [midje "1.3.1" :exclusions [org.clojure/clojure]]] 8 | :repositories {"stuart" "http://stuartsierra.com/maven2"} 9 | :main clj-php.core 10 | :uberjar-name "clj-php.jar") 11 | 12 | -------------------------------------------------------------------------------- /src/clj_php/core.clj: -------------------------------------------------------------------------------- 1 | 2 | (ns clj-php.core 3 | (:gen-class) 4 | (:use clj-php.exprs 5 | clj-php.fs)) 6 | 7 | (def include-paths [ 8 | "lang/Base" 9 | "lang/Php" 10 | "lang/DefMap" 11 | "lang" 12 | "lang/ISeq" 13 | "lang/ASeq" 14 | "lang/Cons" 15 | "lang/Seq" 16 | "lang/LazySeq" 17 | "lang/Vector" 18 | "lang/CList" 19 | ]) 20 | 21 | (defn php-includes [] 22 | (->> include-paths 23 | (map #(str "src/php/clojure/" % ".php")) 24 | (map slurp-resource) 25 | (map #(.substring % 5)) 26 | (reduce str ""))) 27 | 28 | (defn compile-cljp 29 | [path] 30 | (str "%s = %s;") 9 | (def format-defn "ns::$def->%s = function(%s) {return %s};") 10 | (def format-func "%s(%s)") 11 | (def format-constructor "new \\%s(%s)") 12 | (def format-method "ns::$def->%s->%s(%s)") 13 | (def format-vector "new \\clojure\\lang\\Vector(%s)") 14 | 15 | (def ^:dynamic *is-statement* true) 16 | 17 | (declare parse-file parse-body parse-expr) 18 | 19 | ; Arguments 20 | 21 | (defn parse-args 22 | "Parse args into argument string" 23 | [args] 24 | (apply str (interpose ", " args))) 25 | 26 | (defn parse-defn-args 27 | "Parse an argument list" 28 | [args] 29 | (parse-args 30 | (map (partial str "$") args))) 31 | 32 | (defn parse-func-names 33 | "Parse arguments to a function call" 34 | [args] 35 | (parse-args 36 | (map parse-expr args))) 37 | 38 | ; Definitions 39 | 40 | (defn parse-def 41 | "Parse a definition" 42 | [[_ def-name value]] 43 | (format format-def 44 | def-name 45 | (parse-expr value))) 46 | 47 | (defn parse-defn 48 | "Parse a function definition" 49 | [[_ func-name args & body]] 50 | (with-local-args args 51 | (let [body-str (apply parse-body body)] 52 | (format format-defn 53 | func-name 54 | (parse-defn-args args) 55 | (if (> (count body-str) 0) 56 | body-str 57 | "null;"))))) 58 | 59 | ; Bindings 60 | 61 | (defn parse-let 62 | "Parse a let binding" 63 | [[_ args & body]] 64 | (let [def-str (apply str 65 | (map #(parse-def (cons nil %1)) 66 | (partition 2 args))) 67 | body-str (apply str 68 | (map parse-expr body))] 69 | (str def-str body-str))) 70 | 71 | ; Interop 72 | 73 | (defn constructor? 74 | [func-name] 75 | (not (nil? 76 | (re-matches #".*\.$" (str func-name))))) 77 | 78 | (defn method? 79 | [method-name] 80 | (not (nil? 81 | (re-matches #"^\..*" (str method-name))))) 82 | 83 | (defn- parse-constructor 84 | [func-name args] 85 | (let [str-name (str func-name)] 86 | (format format-constructor 87 | (.substring str-name 0 (dec (count str-name))) 88 | (parse-func-names args)))) 89 | 90 | (defn- parse-method 91 | [[method-name obj-name & args]] 92 | (format format-method 93 | obj-name 94 | (.substring (str method-name) 1) 95 | (parse-func-names args))) 96 | 97 | ; Functions 98 | 99 | (defn parse-func 100 | "Parse a function call" 101 | [[func-name & args]] 102 | (binding [*is-statement* false] 103 | (cond (constructor? func-name) (parse-constructor func-name args) 104 | (method? func-name) (parse-method (cons func-name args)) 105 | :else (format format-func 106 | (parse-func-name func-name) 107 | (parse-func-names args))))) 108 | 109 | ; Namespaces 110 | 111 | (defn parse-ns 112 | "Parse a namespace declaration" 113 | [[_ ns-decl & body]] 114 | (str (parse-ns-includes parse-file body) 115 | (parse-ns-decl ns-decl) 116 | (parse-ns-body body))) 117 | 118 | ; Data structures 119 | 120 | (defn parse-list 121 | "Parse a list" 122 | [expr] 123 | (condp = (str (first expr)) 124 | "def" (parse-def expr) 125 | "defn" (parse-defn expr) 126 | "ns" (parse-ns expr) 127 | "let" (parse-let expr) 128 | (str (parse-func expr) 129 | (if *is-statement* ";")))) 130 | 131 | (defn parse-vector 132 | "Parses a vector" 133 | [expr] 134 | (binding [*is-statement* false] 135 | (format format-vector 136 | (parse-args (map parse-expr expr))))) 137 | 138 | ; Expressions 139 | 140 | (defn parse-expr 141 | "Parses an expression" 142 | [expr] 143 | (cond (list? expr) (parse-list expr) 144 | (vector? expr) (parse-vector expr) 145 | (string? expr) (str "\"" expr "\"") 146 | (re-matches #"\d+" (str expr)) expr 147 | :else (parse-func-name expr))) ; check for ref required? ie. \clojure\lang::$add, not $add 148 | 149 | (defn parse-body 150 | "Parse a function body" 151 | [& exprs] 152 | (reduce str 153 | (map parse-expr exprs))) 154 | 155 | (defn parse-file 156 | "Parse a cljp file, if it hasn't already been" 157 | [path] 158 | (binding [*cljp-file* path] 159 | (let [exprs (format "'(%s)" (slurp-resource path))] 160 | (apply parse-body (load-string exprs))))) 161 | 162 | -------------------------------------------------------------------------------- /src/clj_php/fs.clj: -------------------------------------------------------------------------------- 1 | 2 | (ns clj-php.fs 3 | (:use clojure.java.io)) 4 | 5 | ; Public 6 | 7 | (defn slurp-resource 8 | "Try to read a resource from current Jar file, and fall back 9 | to the filesystem for development" 10 | [resource-name] 11 | (let [jar-resource-name (resource (.substring resource-name 4))] 12 | (slurp (if (nil? jar-resource-name) 13 | resource-name 14 | jar-resource-name)))) 15 | 16 | -------------------------------------------------------------------------------- /src/clj_php/funcs.clj: -------------------------------------------------------------------------------- 1 | 2 | (ns clj-php.funcs 3 | (:require [clojure.string :as str])) 4 | 5 | (def ^:dynamic *local-args* []) 6 | 7 | (def format-local "$%s") 8 | (def format-ns-def "ns::$def->%s") 9 | 10 | (def func-map { 11 | "*" "multiply" 12 | "/" "divide" 13 | "+" "add" 14 | "-" "subtract" 15 | }) 16 | 17 | (defn- resolve-name 18 | "Switches certain lang function names" 19 | [func-name] 20 | (let [new-name (get func-map (str func-name))] 21 | (if (nil? new-name) 22 | (str (str/replace func-name #"/" "->")) 23 | new-name))) 24 | 25 | (defn- in? 26 | "Indicates of the needle is in the haystack" 27 | [needle haystack] 28 | (some #{needle} haystack)) 29 | 30 | (defn- parse-constructor 31 | [expr] 32 | "CONST") 33 | 34 | ; Public 35 | 36 | (defn parse-func-name 37 | "Parse a name to either a mapped function, local var, of namespace def" 38 | [func-name] 39 | (let [str-name (resolve-name func-name)] 40 | (format 41 | (if (in? str-name *local-args*) 42 | format-local 43 | format-ns-def) str-name))) 44 | 45 | (defmacro with-local-args [args & body] 46 | `(binding [*local-args* (map str ~args)] 47 | (do ~@body))) 48 | 49 | -------------------------------------------------------------------------------- /src/clj_php/ns.clj: -------------------------------------------------------------------------------- 1 | 2 | (ns clj-php.ns 3 | (:use clj-php.vars) 4 | (:require [clojure.string :as str])) 5 | 6 | (def decl-format (str "namespace %s;" 7 | "class ns extends \\clojure\\lang {" 8 | "public static $def;" 9 | "}" 10 | "ns::$def = new \\clojure\\lang\\DefMap();" 11 | "ns::$def->__use(\\clojure\\core\\ns::$def);")) 12 | (def format-use "ns::$def->__use(\\%s\\ns::$def);") 13 | (def format-require "ns::$def->__require('%s',\\%s\\ns::$def);") 14 | (def format-include "include_once '%s/%s';") 15 | 16 | (def ^:dynamic *includes* (ref [])) 17 | 18 | (defn- to-php-ns 19 | [ns-decl] 20 | (.replace (str ns-decl) "." "\\")) 21 | 22 | (defn- parse-ns-use 23 | [ns-names] 24 | (map #(format format-use 25 | (to-php-ns (first %))) 26 | ns-names)) 27 | 28 | (defn- parse-ns-require 29 | [ns-names] 30 | (map (fn [[ns-name _ req-name]] 31 | (format format-require 32 | req-name 33 | (to-php-ns ns-name))) ns-names)) 34 | 35 | (defn- to-namespaces 36 | [acc [type & body]] 37 | (concat acc 38 | (condp = type 39 | :use body 40 | :include () 41 | :require (map first body)))) 42 | 43 | (defn- parse-include-path 44 | "Parse an include path from current file" 45 | [inc-path] 46 | (let [file (java.io.File. *cljp-file*)] 47 | (format format-include 48 | (.getParent file) 49 | inc-path))) 50 | 51 | (defn- parse-ns-include 52 | "Allows including arbitrary files" 53 | [inc-names] 54 | (map parse-include-path inc-names)) 55 | 56 | ; Public 57 | 58 | (defn to-ns-incl 59 | "Parse the includes body for the namespace" 60 | [[type & body]] 61 | (reduce str "" 62 | (condp = type 63 | :use (parse-ns-use body) 64 | :require (parse-ns-require body) 65 | :include (parse-ns-include body)))) 66 | 67 | (defn parse-ns-body 68 | "Parse the body of a namespace decl" 69 | [body] 70 | (reduce str "" 71 | (map to-ns-incl body))) 72 | 73 | (defn parse-ns-decl 74 | "Parse the namespace declaration" 75 | [ns-decl] 76 | (format decl-format 77 | (to-php-ns ns-decl))) 78 | 79 | (defn path-not-included? 80 | "Indicates of the path has been included yet" 81 | [path] 82 | (not (some (partial = path) 83 | (deref *includes*)))) 84 | 85 | (defn ns-to-fs 86 | "Convert a namespace to its path on the file system" 87 | [ns-name] 88 | (str (str/replace (str/replace ns-name #"\." "/") 89 | #"-" "_") 90 | ".cljp")) 91 | 92 | (defn parse-ns-includes 93 | "Parse any namespaces includes that need to be compiled" 94 | [parse-file body] 95 | (let [nss (->> (reduce to-namespaces [] body) 96 | (filter path-not-included?) 97 | (map ns-to-fs))] 98 | (reduce str 99 | (map #(parse-file %) nss)))) 100 | 101 | -------------------------------------------------------------------------------- /src/clj_php/vars.clj: -------------------------------------------------------------------------------- 1 | 2 | (ns clj-php.vars) 3 | 4 | (def ^:dynamic *cljp-file* nil) 5 | 6 | -------------------------------------------------------------------------------- /src/clojure/core.cljp: -------------------------------------------------------------------------------- 1 | 2 | (ns clojure.core) 3 | 4 | -------------------------------------------------------------------------------- /src/php/bootstrap.php: -------------------------------------------------------------------------------- 1 | items = func_get_args(); 11 | } 12 | 13 | public function first() { 14 | return isset( $this->items[0] ) 15 | ? $this->items[ 0 ] 16 | : null; 17 | } 18 | 19 | public function nxt() { 20 | $more = $this->more(); 21 | return $more->count() == 0 ? null : $more; 22 | } 23 | 24 | protected function newInstance( $items ) { 25 | $class = new \ReflectionClass( get_called_class() ); 26 | return $class->newInstanceArgs( $items ); 27 | } 28 | 29 | public function more() { 30 | return $this->newInstance( 31 | array_slice( $this->items, 1 ) 32 | ); 33 | } 34 | 35 | public function cons( $item ) { 36 | return $this->newInstance( 37 | array_merge( array($item), $this->items ) 38 | ); 39 | } 40 | 41 | public function count() { 42 | return count( $this->items ); 43 | } 44 | 45 | public function toArray() { 46 | return $this->items; 47 | } 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/php/clojure/lang/Base.php: -------------------------------------------------------------------------------- 1 | $name, 12 | $args 13 | ); 14 | } 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/php/clojure/lang/CList.php: -------------------------------------------------------------------------------- 1 | first = $first; 9 | $this->more = $more; 10 | } 11 | 12 | /** 13 | * Returns the first item in this sequence, or null 14 | * 15 | * @return mixed 16 | */ 17 | public function first() { 18 | return $this->first; 19 | } 20 | 21 | /** 22 | * Returns all the items in this sequence apart from the first as 23 | * a sequence. This new sequence could be empty. 24 | * 25 | * @return Cons 26 | */ 27 | public function rest() { 28 | return $this->more 29 | ? $this->more 30 | : new Cons(); 31 | } 32 | 33 | /** 34 | * Returns a new sequence which has the specified item first, and then 35 | * this sequence as the rest. 36 | * 37 | * @param mixed $item 38 | * 39 | * @return Cons 40 | */ 41 | public function cons( $item ) { 42 | return new Cons( $item, $this ); 43 | } 44 | 45 | /** 46 | * Returns the size of this sequence, this requires realising 47 | * all items in the sequence. 48 | * 49 | * @return integer 50 | */ 51 | public function count() { 52 | return $this->first != null 53 | ? 1 + $this->rest()->count() 54 | : 0; 55 | } 56 | 57 | public function nxt() {} 58 | 59 | public function more() {} 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /src/php/clojure/lang/DefMap.php: -------------------------------------------------------------------------------- 1 | defs['php'] = new Php(); 14 | 15 | $this->defs['add'] = function() { 16 | return array_reduce( 17 | func_get_args(), 18 | function( $x, $y ) { return $x + $y; }, 19 | 0 20 | ); 21 | }; 22 | 23 | $this->defs['multiply'] = function() { 24 | return array_reduce( 25 | func_get_args(), 26 | function( $x, $y ) { return $x * $y; }, 27 | 1 28 | ); 29 | }; 30 | 31 | $this->defs['divide'] = function() { 32 | $rest = func_get_args(); 33 | $first = \array_shift( $rest ); 34 | return array_reduce( 35 | $rest, 36 | function( $x, $y ) { return $x / $y; }, 37 | $first 38 | ); 39 | }; 40 | 41 | $this->defs['subtract'] = function() { 42 | $rest = func_get_args(); 43 | $first = \array_shift( $rest ); 44 | return array_reduce( 45 | $rest, 46 | function( $x, $y ) { return $x - $y; }, 47 | $first 48 | ); 49 | }; 50 | 51 | $this->defs['str'] = function() { 52 | return array_reduce( 53 | func_get_args(), 54 | function( $acc, $e ) { return $acc . $e; }, 55 | "" 56 | ); 57 | }; 58 | 59 | $this->defs['seq'] = function( $obj ) { 60 | if ( is_subclass_of($obj,'\clojure\lang\ASeq') ) { return $obj; } 61 | if ( is_subclass_of($obj,'\clojure\lang\LazySeq') ) { return $obj->seq(); } 62 | return seqFrom( $obj ); 63 | }; 64 | 65 | $this->defs['apply'] = function( $func, $args ) { 66 | return call_user_func_array( $func, $args ); 67 | }; 68 | 69 | $this->defs['first'] = function( ISeq $seq ) { 70 | return $seq->first(); 71 | }; 72 | 73 | $this->defs['println'] = function() use ( $self ) { 74 | echo $self->defs['apply']( $self->defs['str'], func_get_args() ) . "\n"; 75 | }; 76 | 77 | $this->defs['cons'] = function( $item, ISeq $seq ) { 78 | return $seq->cons( $item ); 79 | }; 80 | 81 | $this->defs['map'] = function( $func, ISeq $seq ) { 82 | $first = $seq->first(); 83 | return ( $first != null ) 84 | ? new lang\Cons( 85 | $func( $first ), 86 | lang::$def->map( $func, $seq->more() ) 87 | ) 88 | : null; 89 | }; 90 | 91 | } 92 | 93 | protected function _seqFrom( $obj ) { 94 | throw new Exception( 'Cant create sequence from object: ' . $obj ); 95 | } 96 | 97 | protected function _seqFor( $items, ISeq $orig ) { 98 | $class = new \ReflectionClass( get_class($orig) ); 99 | return $class->newInstanceArgs( $items ); 100 | } 101 | 102 | /** 103 | * Set a property magic method 104 | * 105 | * @param string $name 106 | * @param mixed $value 107 | */ 108 | public function __set( $name, $value ) { 109 | $this->defs[ $name ] = $value; 110 | } 111 | 112 | /** 113 | * Get a value magic method 114 | * 115 | * @param string $name 116 | * 117 | * @return mixed 118 | */ 119 | public function __get( $name ) { 120 | return $this->defs[ $name ]; 121 | } 122 | 123 | /** 124 | * Allows calling functions magically 125 | * 126 | * @param string $name 127 | * @param array $args 128 | * 129 | * @return mixed 130 | */ 131 | public function __call( $name, $args ) { 132 | return call_user_func_array( $this->defs[$name], $args ); 133 | } 134 | 135 | /** 136 | * "use" another DefMap, which imports all properties from that 137 | * into this DefMap 138 | * 139 | * @param DefMap $def 140 | */ 141 | public function __use( DefMap $def ) { 142 | $this->defs = array_merge( 143 | $this->defs, 144 | $def->defs() 145 | ); 146 | } 147 | 148 | /** 149 | * "requires" another DefMap on this one by name 150 | * 151 | * @param string $name 152 | * @param DefMap $map 153 | */ 154 | public function __require( $name, DefMap $map ) { 155 | $this->defs[ $name ] = $map; 156 | } 157 | 158 | /** 159 | * Returns all the properties defined in this DefMap 160 | * 161 | * @return array 162 | */ 163 | public function defs() { 164 | return $this->defs; 165 | } 166 | 167 | } 168 | 169 | 170 | -------------------------------------------------------------------------------- /src/php/clojure/lang/ISeq.php: -------------------------------------------------------------------------------- 1 | fn = $fn; 15 | } 16 | 17 | protected function sequenceValue() { 18 | if ( $this->fn ) { 19 | $fn = $this->fn; 20 | $this->sv = $fn(); 21 | $this->fn = null; 22 | } 23 | if ( $this->sv ) { 24 | return $this->sv; 25 | } 26 | return $this->sequence; 27 | } 28 | 29 | /** 30 | * Realises this lazy sequence 31 | * 32 | */ 33 | protected function seq() { 34 | $this->sequenceValue(); 35 | if ( $this->sv ) { 36 | $ls = $this->sv; 37 | while ( is_subclass_of($ls,'\clojure\lang\LazySeq') ) { 38 | $ls = $ls->sequenceValue(); 39 | } 40 | //$this->sequence = \clojure\lang::seq( $ls ); 41 | } 42 | return $this->sequence; 43 | } 44 | 45 | /** 46 | * Returns the first item in the sequence, as ISeq 47 | * 48 | * @return mixed 49 | */ 50 | public function first() { 51 | $this->seq(); 52 | return $this->sequence 53 | ? $this->sequence->first() 54 | : null; 55 | } 56 | 57 | public function nxt() { 58 | } 59 | 60 | public function more() {} 61 | 62 | public function cons( $item ) {} 63 | 64 | public function count() { 65 | $this->seq(); 66 | return $this->sequence 67 | ? $this->sequence->count() 68 | : 0; 69 | } 70 | 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/php/clojure/lang/Php.php: -------------------------------------------------------------------------------- 1 | items = func_get_args(); 18 | } 19 | 20 | /** 21 | * Returns the number of items in the sequence 22 | * 23 | * @return integer 24 | */ 25 | public function count() { 26 | return count( $this->items ); 27 | } 28 | 29 | /** 30 | * Return the first item from the sequence 31 | * 32 | * @return mixes 33 | */ 34 | public function first() { 35 | return $this->nth( 0 ); 36 | } 37 | 38 | /** 39 | * Returns a new sequence containing all but the first 40 | * item in the sequence 41 | * 42 | * @return LazySeq 43 | */ 44 | public function rest() { 45 | return new LazySeq( 46 | function( $x ) { return $x; }, 47 | array_slice( $this->items, 1 ) 48 | ); 49 | } 50 | 51 | protected function nth( $n ) { 52 | return isset( $this->items[$n] ) 53 | ? $this->items[ $n ] 54 | : null; 55 | } 56 | 57 | public function offsetExists( $offset ) { 58 | return isset( $this->items[$offset] ); 59 | } 60 | 61 | public function offsetGet( $offset ) { 62 | return $this->nth( $offset ); 63 | } 64 | 65 | public function offsetSet( $offset, $value ) { 66 | $this->items[ $offset ] = $value; 67 | } 68 | 69 | public function offsetUnset( $offset ) { 70 | unset( $this->items[$offset] ); 71 | } 72 | 73 | public function cons( $item ) {} 74 | public function nxt() {} 75 | public function more() {} 76 | 77 | 78 | } 79 | 80 | -------------------------------------------------------------------------------- /src/php/clojure/lang/Vector.php: -------------------------------------------------------------------------------- 1 | "ns::$def->foo = function() {return null;};" 8 | (parse-defn '(defn foo [x] (+ x x))) => "ns::$def->foo = function($x) {return ns::$def->add($x, $x);};" 9 | (parse-defn '(defn foo [a b])) => "ns::$def->foo = function($a, $b) {return null;};") 10 | 11 | (facts "about defn arguments" 12 | (parse-defn-args '[a]) => "$a" 13 | (parse-defn-args '[foo bar]) => "$foo, $bar") 14 | 15 | (facts "about def'ing vars" 16 | (parse-def '(def x [1 2 3])) => "ns::$def->x = new \\clojure\\lang\\Vector(1, 2, 3);" 17 | (parse-def '(def x 123)) => "ns::$def->x = 123;") 18 | 19 | (facts "about vectors" 20 | (parse-vector '[[1 2] [3 4]]) => "new \\clojure\\lang\\Vector(new \\clojure\\lang\\Vector(1, 2), new \\clojure\\lang\\Vector(3, 4))" 21 | (parse-vector '[1 2 3]) => "new \\clojure\\lang\\Vector(1, 2, 3)") 22 | 23 | (facts "about parsing expression bodies" 24 | (parse-body '(def x 1) '(defn foo [x])) => "ns::$def->x = 1;ns::$def->foo = function($x) {return null;};" 25 | (parse-body '(def x 1) '(def y 2)) => "ns::$def->x = 1;ns::$def->y = 2;") 26 | 27 | (facts "about let bindings" 28 | (parse-let '(let [x 1 y 2] (* x y))) => "ns::$def->x = 1;ns::$def->y = 2;ns::$def->multiply(ns::$def->x, ns::$def->y);" 29 | (parse-let '(let [x 1])) => "ns::$def->x = 1;") 30 | 31 | (facts "about string literals" 32 | (parse-expr '(foo "bar")) => "ns::$def->foo(\"bar\");" 33 | (parse-expr "foo bar") => "\"foo bar\"") 34 | 35 | (facts "about creating objects" 36 | (parse-expr '(Foo. "baz")) => "new \\Foo(\"baz\");" 37 | (parse-expr '(Foo. 123)) => "new \\Foo(123);") 38 | 39 | (facts "about calling methods on objects" 40 | (parse-expr '(.bar foo 123)) => "ns::$def->foo->bar(123);") 41 | 42 | (facts "about constructors" 43 | (constructor? 'Foo.) => true 44 | (constructor? "Foo.") => true) 45 | 46 | (facts "about method calls" 47 | (method? ".bar") => true 48 | (method? '.bar) => true) 49 | 50 | -------------------------------------------------------------------------------- /test/clj_php/test/funcs.clj: -------------------------------------------------------------------------------- 1 | 2 | (ns clj-php.test.funcs 3 | (:use clj-php.funcs 4 | midje.sweet)) 5 | 6 | (facts "about function names" 7 | (parse-func-name "foo") => "ns::$def->foo" 8 | (parse-func-name 'foo) => "ns::$def->foo" 9 | (parse-func-name "*") => "ns::$def->multiply") 10 | 11 | (facts "about functions as arguments" 12 | (parse-func-name "foo") => "ns::$def->foo" 13 | (parse-func-name "+") => "ns::$def->add") 14 | 15 | (facts "about local function vars" 16 | (with-local-args ["foo" 'bar] 17 | (parse-func-name 'bar) => "$bar" 18 | (parse-func-name 'foo) => "$foo" 19 | (parse-func-name "foo") => "$foo") 20 | (parse-func-name "foo") => "ns::$def->foo") 21 | 22 | (facts "about functions with require prefixes" 23 | (parse-func-name "foo/bar") => "ns::$def->foo->bar") 24 | 25 | -------------------------------------------------------------------------------- /test/clj_php/test/ns.clj: -------------------------------------------------------------------------------- 1 | 2 | (ns clj-php.test.ns 3 | (:use clj-php.ns 4 | midje.sweet)) 5 | 6 | (facts "about namespaces" 7 | (parse-ns-decl 'foo.bar) 8 | => (str "namespace foo\\bar;" 9 | "class ns extends \\clojure\\lang {" 10 | "public static $def;}" 11 | "ns::$def = new \\clojure\\lang\\DefMap();" 12 | "ns::$def->__use(\\clojure\\core\\ns::$def);")) 13 | 14 | (facts "about namespace file system paths" 15 | (path-not-included? "foo/wumba.cljp") => true 16 | (ns-to-fs 'foo.bar) => "foo/bar.cljp") 17 | 18 | ;(facts "about namespace includes" 19 | ; (to-ns-incl '(:use foo.bar baz.boo)) => 20 | ; (str "ns::$def->__use(\\foo\\bar\\ns::$def);" 21 | ; "ns::$def->__use(\\baz\\boo\\ns::$def);")) 22 | 23 | -------------------------------------------------------------------------------- /test/example.cljp: -------------------------------------------------------------------------------- 1 | 2 | (ns cljphp.example) 3 | 4 | (def x 123) 5 | 6 | (defn double [x] (* x 2)) 7 | 8 | (println (double x)) 9 | 10 | -------------------------------------------------------------------------------- /test/php/clojure/lang/CListTest.php: -------------------------------------------------------------------------------- 1 | markTestIncomplete(); 11 | } 12 | 13 | } 14 | 15 | -------------------------------------------------------------------------------- /test/php/clojure/lang/ConsTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 0, $cons->count() ); 12 | } 13 | 14 | public function testConsCanBeCreatedWithAFirstItem() { 15 | $first = 123; 16 | $cons = new Cons( $first ); 17 | $this->assertEquals( $first, $cons->first() ); 18 | } 19 | 20 | public function testRestOfConsCreatedWithOnlyOneItemIsEmpty() { 21 | $cons = new Cons( 1 ); 22 | $this->assertEquals( 0, $cons->rest()->count() ); 23 | } 24 | 25 | public function testConsReturnsNewSequenceWithItemAsFirst() { 26 | $cons1 = new Cons( "foo" ); 27 | $cons2 = $cons1->cons( "bar" ); 28 | $this->assertEquals( 2, $cons2->count() ); 29 | $this->assertEquals( "bar", $cons2->first() ); 30 | } 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /test/php/clojure/lang/DefMapTest.php: -------------------------------------------------------------------------------- 1 | map = new DefMap(); 13 | $this->map->foo = function() { return 2; }; 14 | $this->other = new DefMap(); 15 | $this->other->foo = function() { return 3; }; 16 | } 17 | 18 | public function testFunctionsCanBeAddedToDefMap() { 19 | $this->assertEquals( 2, $this->map->foo() ); 20 | } 21 | 22 | public function testDefMapCanUseDefsFromOtherMap() { 23 | $this->map->__use( $this->other ); 24 | $this->assertEquals( 3, $this->map->foo() ); 25 | } 26 | 27 | public function testFunctionsCanBeAccessedAsProperties() { 28 | $this->assertNotNull( $this->map->foo ); 29 | } 30 | 31 | public function testOtherNamespacesCanBeRequiredInByName() { 32 | $this->map->__require( 'baz', $this->other ); 33 | $this->assertEquals( 3, $this->map->baz->foo() ); 34 | } 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /test/php/clojure/lang/LazySeqTest.php: -------------------------------------------------------------------------------- 1 | x = 1; 16 | // lazy sequence returns 2..5 17 | $fn = null; 18 | $fn = function() use ( $self, $fn ) { 19 | return $self->x <= 5 20 | ? new Cons( ++$self->x, new LazySeq($fn) ) 21 | : null; 22 | }; 23 | $this->seq = new LazySeq( $fn ); 24 | } 25 | 26 | public function testLazySequenceDoesntEvaluateImmediately() { 27 | $this->assertEquals( 1, $this->x ); 28 | } 29 | 30 | public function testFirstItemCanBeFetchedFromSequence() { 31 | return $this->markTestIncomplete(); 32 | $this->assertEquals( 2, $this->seq->first() ); 33 | } 34 | 35 | public function testSizeOfSequenceCanBeRetreived() { 36 | return $this->markTestIncomplete(); 37 | $this->assertEquals( 4, $this->seq->count() ); 38 | } 39 | 40 | } 41 | 42 | -------------------------------------------------------------------------------- /test/php/clojure/lang/PhpTest.php: -------------------------------------------------------------------------------- 1 | php = new Php(); 13 | } 14 | 15 | public function testPhpFunctionsCanBeCalledAsProperties() { 16 | $this->assertEquals( 17 | '24th May 1981', 18 | $this->php->date('dS F Y',$this->php->strtotime('1981-05-24 00:00:00')) 19 | ); 20 | } 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /test/php/clojure/lang/SeqTest.php: -------------------------------------------------------------------------------- 1 | seq = new MySeq( 1, 2, 3 ); 15 | $this->emp = new MySeq(); 16 | } 17 | 18 | public function testFirstItemFromSequenceCanVeFetched() { 19 | $this->assertEquals( 1, $this->seq->first() ); 20 | } 21 | 22 | public function testNullReturnedWhenNoFirstItemInSequence() { 23 | $this->assertNull( $this->emp->first() ); 24 | } 25 | 26 | public function testCountReturnsSizeOfSequence() { 27 | $this->assertEquals( 3, $this->seq->count() ); 28 | } 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /test/php/clojure/lang/VectorTest.php: -------------------------------------------------------------------------------- 1 | vector = new Vector( 1, 2, 3 ); 13 | } 14 | 15 | public function testFirstItemCanBeFetched() { 16 | $this->assertEquals( 1, $this->vector->first() ); 17 | } 18 | 19 | public function testCountOfVectorCanBeFetched() { 20 | $this->assertEquals( 3, $this->vector->count() ); 21 | } 22 | 23 | public function testConsReturnsNewVectorWithItemPrepended() { 24 | $vector = $this->vector->cons( 0 ); 25 | $this->assertEquals( 4, $vector->count() ); 26 | $this->assertEquals( 0, $vector->first() ); 27 | } 28 | 29 | public function testNxtReturnsANewSequenceWithAllButFirstItem() { 30 | $vector = $this->vector->nxt(); 31 | $this->assertInstanceOf( '\clojure\lang\Vector', $vector ); 32 | $this->assertEquals( 2, $vector->count() ); 33 | } 34 | 35 | public function testNxtReturnsNullWhenNoMoreItems() { 36 | $vector = new Vector(); 37 | $this->assertNull( $vector->nxt() ); 38 | } 39 | 40 | public function testMoreReturnsNewVectorWithAllButFirstItem() { 41 | $vector = $this->vector->more(); 42 | $this->assertInstanceOf( '\clojure\lang\Vector', $vector ); 43 | $this->assertEquals( 2, $vector->count() ); 44 | } 45 | 46 | public function testMoreReturnsEmptyVectorWhenNoItems() { 47 | $vector = new Vector(); 48 | $more = $vector->more(); 49 | $this->assertInstanceOf( '\clojure\lang\Vector', $more ); 50 | $this->assertEquals( 0, $more->count() ); 51 | } 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /test/php/clojure/langTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 10, lang::add(1,4,3,2) ); 11 | } 12 | 13 | public function testMultiplyingSomeNumbers() { 14 | $this->assertEquals( 24, lang::multiply(2,2,3,2) ); 15 | } 16 | 17 | public function testDividingNumbers() { 18 | $this->assertEquals( 10, lang::divide(100,2,5) ); 19 | } 20 | 21 | public function testSubtractingSomeNumbers() { 22 | $this->assertEquals( 3, lang::subtract(20,9,5,3) ); 23 | } 24 | 25 | public function testStrConcatenatesAllArgumentsToString() { 26 | $this->assertEquals( "foo 1 bar", lang::str("foo ", 1, " bar") ); 27 | } 28 | 29 | public function testASeqReturnedAsIsFromSeq() { 30 | $seq = new lang\Cons(); 31 | $this->assertSame( $seq, lang::seq($seq) ); 32 | } 33 | 34 | public function testConsPrependsAnItemToASequence() { 35 | $seq = lang::cons( 1, new lang\Vector(2,3) ); 36 | $this->assertEquals( 3, $seq->count() ); 37 | $this->assertEquals( 1, $seq->first() ); 38 | } 39 | 40 | public function testFirstReturnsFirstItemOfASequence() { 41 | $seq = new lang\Vector( 23, 45 ); 42 | $this->assertEquals( 23, lang::first($seq) ); 43 | } 44 | 45 | } 46 | 47 | --------------------------------------------------------------------------------