├── .gitignore ├── .travis.yml ├── README.md ├── pom.xml └── src ├── main ├── clojure │ └── kiss │ │ ├── compiler.clj │ │ └── core.clj ├── java │ └── kiss │ │ └── lang │ │ ├── Analyser.java │ │ ├── Compiler.java │ │ ├── Environment.java │ │ ├── Expression.java │ │ ├── KFn.java │ │ ├── Keywords.java │ │ ├── RT.java │ │ ├── Result.java │ │ ├── Symbols.java │ │ ├── Type.java │ │ ├── Types.java │ │ ├── expression │ │ ├── Application.java │ │ ├── Cast.java │ │ ├── ClojureLookup.java │ │ ├── Constant.java │ │ ├── Def.java │ │ ├── Do.java │ │ ├── FieldLookup.java │ │ ├── If.java │ │ ├── InstanceOf.java │ │ ├── Lambda.java │ │ ├── Let.java │ │ ├── Lookup.java │ │ ├── Loop.java │ │ ├── Map.java │ │ ├── MethodCall.java │ │ ├── Recur.java │ │ ├── Return.java │ │ └── Vector.java │ │ ├── impl │ │ ├── ATypedFn.java │ │ ├── ClojureFn.java │ │ ├── EvalResult.java │ │ ├── ExitResult.java │ │ ├── IExitResult.java │ │ ├── KissException.java │ │ ├── KissUtils.java │ │ ├── LambdaFn.java │ │ ├── MapEntry.java │ │ ├── Mapping.java │ │ ├── RecurResult.java │ │ ├── ReturnResult.java │ │ └── WrappedFn.java │ │ └── type │ │ ├── ACompoundType.java │ │ ├── AFunctionType.java │ │ ├── Anything.java │ │ ├── FunctionType.java │ │ ├── Intersection.java │ │ ├── JavaType.java │ │ ├── Maybe.java │ │ ├── Not.java │ │ ├── Nothing.java │ │ ├── Null.java │ │ ├── Predicate.java │ │ ├── Reference.java │ │ ├── Something.java │ │ ├── Union.java │ │ ├── Value.java │ │ └── ValueSet.java └── resources │ └── kiss.png └── test ├── clojure └── kiss │ ├── demo │ ├── blank.clj │ └── example.clj │ └── test │ ├── test_clojure.clj │ ├── test_core.clj │ ├── test_examples.clj │ └── test_lambda.clj └── java └── kiss └── test ├── EnvironmentTests.java ├── ExpressionTests.java ├── KissClojureTest.java ├── TestUtils.java └── TypeTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | /classes/ 2 | /.project 3 | /target 4 | /.settings 5 | /.classpath 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk7 4 | - openjdk7 5 | - openjdk6 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## IMPORTANT NOTE: Merging with Magic 2 | 3 | Ongoing Kiss development has been merged into Magic (https://github.com/mikera/magic). Magic incorporates all of the key ideas developed in Kiss, but starts afresh with a much more solid implementation. If you are interested in Kiss, please join the Magic community going forwards, thanks! 4 | 5 | Mike. 6 | 7 | Kiss 8 | ==== 9 | 10 | 11 | 12 | 13 | Kiss is Immutable, Statically compiled and Symbiotic (with Clojure). 14 | 15 | This is an **EXPERIMENT** in programming language design, combining several big ideas from different programming languages: 16 | 17 | - A smart static type system that makes it easy to write correct code without boilerplate 18 | - Lisp concepts of homoiconicity and macro-driven metaprogramming 19 | - Functional programming concepts of programming with pure functions and immutable values 20 | - The ability to run on the excellent JVM platform and take advantage of the huge library ecosystem this gives you 21 | - The (I believe novel?) concept of programming with a succession of immutable environments 22 | 23 | Kiss is designed to be identical to Clojure except where necessary to incorporate the above ideas. 24 | 25 | ![Kiss!](https://raw.github.com/mikera/kiss/master/src/main/resources/kiss.png) 26 | 27 | ## Objectives 28 | 29 | - Productivity like **Clojure** 30 | - Performance like **Java** 31 | - Type safety like **Haskell** 32 | 33 | 34 | ## Examples 35 | 36 | ```clojure 37 | ;; NOTE: This is Clojure code, being used to bootstrap Kiss.... 38 | (ns my.clojure.project 39 | (:use kiss.core)) 40 | 41 | (defn call-kiss-from-clojure [] 42 | (kiss 43 | (str "Hello from Kiss!"))) 44 | ``` 45 | 46 | For more working code examples see the [examples.clj](https://github.com/mikera/kiss/blob/master/src/test/clojure/kiss/demo/example.clj) namespace. 47 | 48 | ## Solution 49 | 50 | Kiss takes the following approach to language design: 51 | 52 | - **Immutable environments** - all Kiss code is compiled against a specific immutable environment, potentially creating a new immutable environment (with any definitions updated). Environments are *first class* and represent the complete state of the code base, making them highly amenable to static analysis. 53 | - **Statically compiled** - Kiss objects all have a static type, and the compiler will use these to generate decent bytecode. Types are *first class* and can be used both at compile time and runtime. Exact features of the type system are still to be determined, but at a minimum will take full advantage of all JVM types. 54 | - **Symbiotic with Clojure** - Kiss is bootstrapped on top of Clojure, and designed to be used within a Clojure environment. You can call Clojure functions / macros transparently, and Kiss functions will be IFn instances that are equally usable from Clojure. Kiss will just use the Clojure reader and syntax directly. 55 | 56 | ## See the Wiki for more details 57 | 58 | - [Detailed Rationale](https://github.com/mikera/kiss/wiki/Rationale) 59 | - [Differences with Typed Clojure](https://github.com/mikera/kiss/wiki/Differences-with-Typed-Clojure) 60 | - [Implementation Ideas](https://github.com/mikera/kiss/wiki/Implementation-Ideas) 61 | 62 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | kiss 5 | net.mikera 6 | 0.0.1-SNAPSHOT 7 | 8 | net.mikera 9 | clojure-pom 10 | 0.0.4 11 | 12 | 13 | 14 | 15 | clojars.org 16 | Clojars repository 17 | https://clojars.org/repo 18 | 19 | 20 | 21 | 22 | org.clojure 23 | clojure 24 | 1.7.0-alpha4 25 | 26 | 27 | net.mikera 28 | cljunit 29 | 0.3.1 30 | test 31 | 32 | 33 | net.mikera 34 | clojure-utils 35 | 0.6.1 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/main/clojure/kiss/compiler.clj: -------------------------------------------------------------------------------- 1 | (ns kiss.compiler) 2 | 3 | (set! *warn-on-reflection* true) 4 | (set! *unchecked-math* true) 5 | 6 | (defn clojure-compile 7 | "Converts kiss code to an executable Clojure form" 8 | [& body] 9 | `(do ~@body)) -------------------------------------------------------------------------------- /src/main/clojure/kiss/core.clj: -------------------------------------------------------------------------------- 1 | (ns kiss.core 2 | (:require [kiss.compiler :as compiler]) 3 | (:refer-clojure :exclude [compile]) 4 | (:import [kiss.lang RT Environment Analyser Expression KFn Result]) 5 | (:use [mikera.cljutils error])) 6 | 7 | (set! *warn-on-reflection* true) 8 | (set! *unchecked-math* true) 9 | 10 | ;; EXPERIMENTAL - API subject to change!!! 11 | ;; 12 | ;; Don't rely on any of this stuff being here in the future 13 | 14 | (defmacro environment 15 | "Creates an Environment with the given Symbol -> Expression mappings." 16 | ([] 17 | `RT/ENVIRONMENT) 18 | ([mappings] 19 | `(environment RT/ENVIRONMENT ~mappings)) 20 | ([env mappings] 21 | `(reduce 22 | (fn [^Environment e# [^Symbol k# v#]] (.define e# k# (analyse v#))) 23 | ~env 24 | (quote ~mappings)))) 25 | 26 | (defn empty-environment 27 | "Returns an empty Kiss Environment" 28 | ([] 29 | Environment/EMPTY)) 30 | 31 | (defn new-environment 32 | "Returns an empty Kiss Environment" 33 | ([] 34 | (environment))) 35 | 36 | (defn analyse 37 | "Analyse a form, resulting a Kiss Expression AST" 38 | (^Expression [form] 39 | (analyse (environment) form)) 40 | (^Expression [^Environment env form] 41 | (Analyser/analyse env form))) 42 | 43 | (defn compile 44 | (^KFn [form] 45 | (compile (environment) form)) 46 | (^KFn [^Environment env form] 47 | (let [ex (analyse env form)] 48 | (kiss.lang.Compiler/compile env ex)))) 49 | 50 | 51 | 52 | 53 | (defn kmerge 54 | "Merge Kiss Environments, returning a new Environment" 55 | (^Environment [^Environment a] a) 56 | (^Environment [^Environment a ^Environment b] (.merge a b)) 57 | (^Environment [^Environment a ^Environment b & more ] 58 | (reduce kmerge (kmerge a b) more))) 59 | 60 | (defn result 61 | "Returns the latest evaluation result from a given Kiss Environment" 62 | ([^Result e] 63 | (.getResult e))) 64 | 65 | (defmacro kiss 66 | "Compiles and executes Kiss code in the given Environment, returning the result" 67 | ([body] 68 | `(let [env# (environment)] 69 | (kiss env# ~body))) 70 | ([env body] 71 | `(let [env# ~env 72 | ex# (compile env# (quote ~body))] 73 | (ex#)))) 74 | 75 | (defmacro kisst 76 | "Returns the type of a given expression, without executing it." 77 | ([body] 78 | `(kisst (environment) ~body)) 79 | ([env body] 80 | `(let [env# ~env 81 | ex# (compile env# (quote ~body))] 82 | (.getReturnType ex#)))) 83 | 84 | (defn kisse* 85 | ([^Environment env form] 86 | (let [body (analyse env form)] 87 | (.getEnvironment (.interpret body env))))) 88 | 89 | (defmacro kisse 90 | "Compiles and executes Kiss code in the given Environment, returning the new Environment." 91 | ([body] 92 | `(kisse (environment) ~body)) 93 | ([env body] 94 | `(let [env# ~env 95 | body# (quote ~body)] 96 | (kisse* env# body#)))) 97 | 98 | (def kiss-repl-env (atom (environment))) 99 | 100 | (defn kissify 101 | "TODO: figure out how to hack the REPL" 102 | ([] 103 | (alter-var-root #'clojure.core/eval 104 | (fn [form] 105 | (swap! kiss-repl-env (fn [env] (kisse* env form))) 106 | (.getResult ^Result @kiss-repl-env))))) 107 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/Analyser.java: -------------------------------------------------------------------------------- 1 | package kiss.lang; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.Iterator; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import kiss.lang.expression.Application; 10 | import kiss.lang.expression.ClojureLookup; 11 | import kiss.lang.expression.Constant; 12 | import kiss.lang.expression.Def; 13 | import kiss.lang.expression.Do; 14 | import kiss.lang.expression.If; 15 | import kiss.lang.expression.InstanceOf; 16 | import kiss.lang.expression.Lambda; 17 | import kiss.lang.expression.Let; 18 | import kiss.lang.expression.Lookup; 19 | import kiss.lang.expression.Loop; 20 | import kiss.lang.expression.Recur; 21 | import kiss.lang.expression.Return; 22 | import kiss.lang.expression.Vector; 23 | import kiss.lang.impl.KissException; 24 | import kiss.lang.impl.KissUtils; 25 | import kiss.lang.type.Intersection; 26 | import kiss.lang.type.JavaType; 27 | import kiss.lang.type.Union; 28 | import clojure.lang.IFn; 29 | import clojure.lang.IPersistentMap; 30 | import clojure.lang.IPersistentVector; 31 | import clojure.lang.ISeq; 32 | import clojure.lang.PersistentHashMap; 33 | import clojure.lang.RT; 34 | import clojure.lang.Symbol; 35 | 36 | /** 37 | * Kiss language analyser 38 | * 39 | * Design intent: 40 | * - Converts Clojure forms to Kiss ASTs 41 | * 42 | * @author Mike 43 | */ 44 | public class Analyser { 45 | 46 | /** 47 | * Converts a Kiss form into an Expression 48 | * 49 | * @param form 50 | * @return 51 | */ 52 | public static Expression analyse(Environment env, Object form) { 53 | if (form instanceof Symbol) return analyseSymbol(env,(Symbol)form); 54 | if (form instanceof ISeq) return analyseSeq(env,(ISeq)form); 55 | if (form instanceof IPersistentVector) return analyseVector(env,(IPersistentVector)form); 56 | if (form instanceof IPersistentMap) return analyseMap(env,(IPersistentMap)form); 57 | return Constant.create(form); 58 | } 59 | 60 | public static Expression analyse(Object form) { 61 | return analyse(Environment.EMPTY,form); 62 | } 63 | 64 | @SuppressWarnings("unchecked") 65 | public static Type analyseType(Object form) { 66 | if (form instanceof Symbol) { 67 | return analyseTypeSymbol((Symbol)form); 68 | } 69 | if (form instanceof Class) { 70 | return JavaType.create((Class) form); 71 | } 72 | if (form instanceof ISeq) { 73 | return analyseTypeSeq((ISeq)form); 74 | } 75 | throw new KissException("Unrecognised type form: "+form); 76 | } 77 | 78 | private static Type analyseTypeSymbol(Symbol sym) { 79 | if (sym.equals(Symbols.ANY)) return Types.ANYTHING; 80 | if (sym.equals(Symbols.NOTHING)) return Types.NOTHING; 81 | if (sym.equals(Symbols.NIL)) return Types.NULL; 82 | if (sym.equals(Symbols.TYPE)) return Types.TYPE; 83 | if (sym.equals(Symbols.SYMBOL_TYPE)) return Types.SYMBOL; 84 | if (sym.equals(Symbols.KEYWORD_TYPE)) return Types.KEYWORD; 85 | 86 | if (sym.getNamespace()==null) { 87 | String name=sym.getName(); 88 | if (!name.contains(".")) name="java.lang."+name; 89 | Class c=RT.classForName(name); 90 | if (c!=null) return JavaType.create(c); 91 | } 92 | throw new KissException("Unrecognised type symbol: "+sym); 93 | } 94 | 95 | private static Type analyseTypeSeq(ISeq s) { 96 | List al=KissUtils.asList(s); 97 | Symbol sym=(Symbol) al.get(0); 98 | if (sym.equals(Symbols.U)) { 99 | Type[] types=analyseSequenceOfTypes(al,1,al.size()-1); 100 | return Union.create(types); 101 | } else if (sym.equals(Symbols.I)) { 102 | Type[] types=analyseSequenceOfTypes(al,1,al.size()-1); 103 | return Intersection.create(types); 104 | } 105 | throw new KissException("Unrecognised type form: "+s); 106 | } 107 | 108 | private static Type[] analyseSequenceOfTypes(List al, int start, int length) { 109 | Type[] types=new Type[length]; 110 | for (int i=0; i al=new ArrayList(); 126 | int n=form.count(); 127 | for (int i=0; i hm=new HashMap(); 136 | Iterator it=form.iterator(); 137 | while (it.hasNext()) { 138 | Map.Entry e=it.next(); 139 | hm.put(analyse(env, e.getKey()), analyse(env, e.getValue())); 140 | } 141 | return kiss.lang.expression.Map.create(hm); 142 | } 143 | 144 | 145 | private static Expression analyseSeq(Environment env, ISeq form) { 146 | int n=form.count(); 147 | if (n==0) return Constant.create(form); 148 | Object first=form.first(); 149 | 150 | if (first instanceof Symbol) { 151 | Symbol s=(Symbol)first; 152 | if (s.equals(Symbols.LET)) { 153 | IPersistentVector v=KissUtils.expectVector(RT.second(form)); 154 | int vc=v.count(); 155 | if ((vc&1)!=0) throw new KissException("let requires an even number of binding forms"); 156 | 157 | // start with expression body 158 | Expression e = analyse(env, RT.nth(form, 2)); 159 | 160 | for (int i=vc-2; i>=0; i-=2) { 161 | Symbol sym=KissUtils.expectSymbol(v.nth(i)); 162 | Expression exp=analyse(env, v.nth(i+1)); 163 | e= Let.create(sym, exp, e); 164 | } 165 | return e; 166 | } 167 | 168 | if (s.equals(Symbols.LOOP)) { 169 | IPersistentVector v=KissUtils.expectVector(RT.second(form)); 170 | int vc=v.count(); 171 | if ((vc&1)!=0) throw new KissException("let requires an even number of binding forms"); 172 | 173 | Symbol[] syms=new Symbol[vc/2]; 174 | Expression[] initials=new Expression[vc/2]; 175 | 176 | for (int i=0; i Values 25 | * - Maintains a dependency graph for recompilation 26 | * - Maintains a "result" value, so it can include an expression return value 27 | * 28 | * It is a first class object, but probably shouldn't be messed with outside of the kiss.core functions. 29 | * 30 | * @author Mike 31 | * 32 | */ 33 | public final class Environment extends APersistentMap { 34 | private static final long serialVersionUID = -2048617052932290067L; 35 | 36 | public static final Environment EMPTY = new Environment(); 37 | 38 | public final IPersistentMap map; // Symbol -> Mapping 39 | public final IPersistentMap dependencies; // Symbol -> set of Symbols 40 | public final IPersistentMap dependents; // Symbol -> set of Symbols 41 | 42 | private Environment() { 43 | this(PersistentHashMap.EMPTY,PersistentHashMap.EMPTY,PersistentHashMap.EMPTY); 44 | } 45 | 46 | private Environment(IPersistentMap map, IPersistentMap deps, IPersistentMap backDeps) { 47 | this.map=map; 48 | this.dependencies=deps; 49 | this.dependents=backDeps; 50 | } 51 | 52 | public EvalResult withResult(Object value) { 53 | return new EvalResult(this,value); 54 | } 55 | 56 | /** 57 | * Redefine a symbol in this environment to a new Expression. 58 | * 59 | * @param key 60 | * @param body 61 | * @return 62 | */ 63 | public Environment define(Symbol key, Expression body) { 64 | return define(key,body,PersistentHashMap.EMPTY); 65 | } 66 | 67 | public Environment define(Symbol key, Expression body, IPersistentMap bindings) { 68 | // handle define with local bindings 69 | if (bindings.count()>0) { 70 | Expression newBody=body.substitute(bindings); 71 | if (newBody!=body) newBody=newBody.optimise(); // re-optimise if needed 72 | body=newBody; 73 | } 74 | 75 | // manage dependency updates 76 | IPersistentMap tempDependencies=this.dependencies; 77 | IPersistentMap tempDependents=this.dependents; 78 | 79 | IPersistentSet free=body.accumulateFreeSymbols(PersistentHashSet.EMPTY); 80 | 81 | IPersistentSet oldDeps=(IPersistentSet) tempDependencies.valAt(key); 82 | if ((oldDeps==null)) oldDeps=PersistentHashSet.EMPTY; 83 | 84 | // update dependencies to match the free variables in the expression 85 | tempDependencies=tempDependencies.assoc(key, free); 86 | tempDependents=updateBackDeps(key,tempDependents,oldDeps,free); 87 | 88 | // Compute which symbols cannot yet be bound from the current environment 89 | IPersistentSet unbound=free; 90 | for (ISeq s=RT.seq(unbound);s!=null; s=s.next()) { 91 | Symbol sym=(Symbol) s.first(); 92 | if (isBound(sym)) { 93 | unbound=unbound.disjoin(sym); 94 | } 95 | } 96 | 97 | if (unbound.count()==0) { 98 | Result res=body.interpret(this, bindings); 99 | Object value=res.getResult(); 100 | Environment newEnv= new Environment(map.assoc(key, Mapping.createExpression(body, value, null)),tempDependencies,tempDependents); 101 | 102 | newEnv=updateDependents(newEnv,key); 103 | 104 | return newEnv; 105 | } else { 106 | return new Environment(map.assoc(key, Mapping.createExpression(body, null, unbound)),tempDependencies,tempDependents); 107 | } 108 | } 109 | 110 | @SuppressWarnings("unchecked") 111 | private static Environment updateDependents(Environment e, Symbol key) { 112 | // get the set of symbols that depend directly or indirectly on the given key 113 | IPersistentSet ss = e.accumulateDependents(PersistentHashSet.EMPTY,key); 114 | 115 | // check if there are any dependents 116 | if (ss==PersistentHashSet.EMPTY) return e; 117 | 118 | for (Symbol s:((java.util.Collection)ss)) { 119 | Mapping m=(Mapping) e.map.valAt(s); 120 | IPersistentSet sunbound=m.getUnbound(); 121 | if (sunbound.count()==0) { 122 | // TODO: update needed! 123 | } 124 | } 125 | return e; 126 | 127 | } 128 | 129 | @SuppressWarnings("unchecked") 130 | private IPersistentSet accumulateDependents(IPersistentSet set, Symbol key) { 131 | IPersistentSet ss=(IPersistentSet)(dependents.valAt(key)); 132 | if ((ss==null)||(ss==PersistentHashSet.EMPTY)) return set; 133 | for (Symbol s: ((java.util.Collection)ss)) { 134 | if (!set.contains(s)) { 135 | set=(IPersistentSet) set.cons(s); 136 | accumulateDependents(set,s); 137 | } 138 | } 139 | return set; 140 | } 141 | 142 | private boolean isBound(Symbol sym) { 143 | Object m= map.valAt(sym); 144 | if (m==null) return false; 145 | return ((Mapping)m).isBound(); 146 | } 147 | 148 | private IPersistentMap updateBackDeps(Symbol key,IPersistentMap backDeps, 149 | IPersistentSet oldDeps, IPersistentSet newDeps) { 150 | if (oldDeps==newDeps) return backDeps; 151 | 152 | // add new back dependencies 153 | for (ISeq s=newDeps.seq(); s!=null; s=s.next()) { 154 | Symbol sym=(Symbol)s.first(); 155 | if (oldDeps.contains(sym)) continue; 156 | IPersistentSet bs=(IPersistentSet) backDeps.valAt(sym); 157 | if (bs==null) bs=PersistentHashSet.EMPTY; 158 | backDeps=backDeps.assoc(sym, bs.cons(key)); 159 | } 160 | 161 | // remove old back dependencies 162 | for (ISeq s=oldDeps.seq(); s!=null; s=s.next()) { 163 | Symbol sym=(Symbol)s.first(); 164 | if (newDeps.contains(sym)) continue; 165 | IPersistentSet bs=(IPersistentSet) backDeps.valAt(sym); 166 | bs=bs.disjoin(key); 167 | if (bs.count()==0) { 168 | backDeps=backDeps.without(sym); 169 | } else { 170 | backDeps=backDeps.assoc(sym, bs); 171 | } 172 | } 173 | 174 | return backDeps; 175 | } 176 | 177 | @Override 178 | public IPersistentMap without(Object key) { 179 | Mapping m=getMapping(key); 180 | if (m==null) return this; 181 | return new Environment(map.without(key),dependencies,dependents); 182 | } 183 | 184 | @Override 185 | public Environment assoc(Object key, Object val) { 186 | return define((Symbol) key,Constant.create(val)); 187 | } 188 | 189 | 190 | @Override 191 | public Environment assocEx(Object key, Object val) { 192 | return define((Symbol) key,Constant.create(val)); 193 | } 194 | 195 | 196 | public Mapping getMapping(Object key) { 197 | return (Mapping)map.valAt(key); 198 | } 199 | 200 | @SuppressWarnings("unchecked") 201 | @Override 202 | public Iterator iterator() { 203 | return new EnvioronmentIterator(map.iterator()); 204 | } 205 | 206 | private static final class EnvioronmentIterator implements Iterator> { 207 | final Iterator> source; 208 | 209 | private EnvioronmentIterator(Iterator> vs) { 210 | this.source=vs; 211 | } 212 | 213 | @Override 214 | public boolean hasNext() { 215 | return source.hasNext(); 216 | } 217 | 218 | @SuppressWarnings("unchecked") 219 | @Override 220 | public Entry next() { 221 | Entry entry=source.next(); 222 | Mapping m=entry.getValue(); 223 | return m.toMapEntry(entry.getKey()); 224 | } 225 | 226 | @Override 227 | public void remove() { 228 | throw new UnsupportedOperationException("Immutable!"); 229 | } 230 | } 231 | 232 | /** 233 | * Merges a second environment into this one 234 | * @param e 235 | * @return 236 | */ 237 | @SuppressWarnings("unchecked") 238 | public Environment merge(Environment e) { 239 | Environment result=this; 240 | for (Object o : e.map) { 241 | Map.Entry ent=(Entry) o; 242 | result=result.define(ent.getKey(), ent.getValue().getExpression()); 243 | } 244 | return result; 245 | } 246 | 247 | @Override 248 | public boolean containsKey(Object key) { 249 | return map.containsKey(key); 250 | } 251 | 252 | @Override 253 | public IMapEntry entryAt(Object key) { 254 | Mapping m=getMapping(key); 255 | if (m==null) return null; 256 | return m.toMapEntry(key); 257 | } 258 | 259 | @Override 260 | public int count() { 261 | return map.count(); 262 | } 263 | 264 | @Override 265 | public Environment empty() { 266 | return EMPTY; 267 | } 268 | 269 | @Override 270 | public ISeq seq() { 271 | return clojure.lang.IteratorSeq.create(iterator()); 272 | } 273 | 274 | @Override 275 | public Object valAt(Object key) { 276 | Mapping m=getMapping(key); 277 | if (m==null) return null; 278 | return m.getValue(); 279 | } 280 | 281 | @Override 282 | public Object valAt(Object key, Object notFound) { 283 | Mapping m=getMapping(key); 284 | if (m==null) return notFound; 285 | return m.getValue(); 286 | } 287 | 288 | @SuppressWarnings("unchecked") 289 | public void validate() { 290 | for (Object o : map) { 291 | Map.Entry ent=(Entry) o; 292 | Symbol key=ent.getKey(); 293 | Mapping m=ent.getValue(); 294 | if (m==null) throw new KissException("Unexcpected null mapping for symbol: "+key); 295 | 296 | // check free symbols equals dependencies 297 | IPersistentSet free=m.getExpression().accumulateFreeSymbols(PersistentHashSet.EMPTY); 298 | IPersistentSet ds=(IPersistentSet) dependencies.valAt(key); 299 | if (!free.equiv(ds)) { 300 | throw new KissException("Mismatched dependencies for symbol: "+key+" free="+free+" deps="+ds); 301 | } 302 | 303 | // check unbound dependencies are consistent 304 | IPersistentSet unbound=m.getUnbound(); 305 | for (ISeq s=unbound.seq(); s!=null; s=s.next()) { 306 | Symbol sym=(Symbol)s.first(); 307 | if (isBound(sym)) throw new KissException("Expected symbol to be unbound: "+sym); 308 | } 309 | 310 | // check reverse dependencies 311 | for (ISeq s=ds.seq(); s!=null; s=s.next()) { 312 | Symbol sym=(Symbol)s.first(); 313 | IPersistentSet bs=(IPersistentSet) dependents.valAt(sym); 314 | if (!bs.contains(key)) throw new KissException("Missing back dependency from "+sym+"=>"+key); 315 | } 316 | } 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/Expression.java: -------------------------------------------------------------------------------- 1 | package kiss.lang; 2 | 3 | import clojure.lang.IPersistentMap; 4 | import clojure.lang.IPersistentSet; 5 | import clojure.lang.PersistentHashMap; 6 | import clojure.lang.PersistentHashSet; 7 | import kiss.lang.impl.KissUtils; 8 | 9 | /** 10 | * Abstract base class for immutable Kiss Expression nodes 11 | * 12 | * Design intent: 13 | * - Represent Kiss AST 14 | * - Can be optimised 15 | * - Can be evaluated / interpreted given an execution environment 16 | * - Can be compiled, given satisfaction of all external dependencies 17 | * - Immutable 18 | * 19 | * @author Mike 20 | * 21 | */ 22 | public abstract class Expression { 23 | 24 | /** 25 | * Gets the result type of the expression. Evaluation of an expression is guaranteed to return 26 | * a result of this type. 27 | */ 28 | public abstract Type getType(); 29 | 30 | /** 31 | * Specialises an expression to guarantee returning the given type, or a strict subset 32 | * 33 | * Returns null if this specialisation is impossible 34 | * Must return the same expression if no additional specialisation can be performed. 35 | */ 36 | public abstract Expression specialise(Type type); 37 | 38 | /** 39 | * Specialises an expression using the given Symbol -> Value substitution map 40 | * 41 | * @param bindings A map of symbols to values 42 | * @return 43 | */ 44 | public abstract Expression substitute(IPersistentMap bindings); 45 | 46 | /** 47 | * Optimises this expression. Performs constant folding, etc. 48 | * @return An optimised Expression 49 | */ 50 | public Expression optimise() { 51 | return this; 52 | } 53 | 54 | /** 55 | * Evaluate an expression within an environment, interpreter style. 56 | * 57 | * Any changes to the Environment are discarded. 58 | * 59 | * @param e Any Environment in which to evaluate the expression 60 | * @return The result of the expression. 61 | */ 62 | public Object eval(Environment e) { 63 | return interpret(KissUtils.ret1(e,e=null), PersistentHashMap.EMPTY).getResult(); 64 | } 65 | 66 | /** 67 | * Evaluates this expression in an empty environment. 68 | * 69 | * Any changes to the Environment are discarded. 70 | * 71 | * @return 72 | */ 73 | public Object eval() { 74 | return interpret(Environment.EMPTY, PersistentHashMap.EMPTY).getResult(); 75 | } 76 | 77 | /** 78 | * Compute the effect of this expression, returning a new Environment 79 | * @param bindings TODO 80 | */ 81 | public abstract Result interpret(Environment d, IPersistentMap bindings); 82 | 83 | /** 84 | * Computes the result of this expression in a given Environment. 85 | * 86 | * Returns a new Environment, use Environment.getResult() to see the result of the expression. 87 | * 88 | * @param e 89 | * @return 90 | */ 91 | public final Result interpret(Environment e) { 92 | return interpret(KissUtils.ret1(e,e=null),PersistentHashMap.EMPTY); 93 | } 94 | 95 | /** 96 | * Computes the result of the expression in the environmnet contained by a previous EvalResult 97 | * 98 | * The old evaluation result is discarded 99 | */ 100 | public final Result interpret(Result e) { 101 | return interpret(e.getEnvironment()); 102 | } 103 | 104 | /** 105 | * Returns true if this expression is a constant value 106 | * @return 107 | */ 108 | public boolean isConstant() { 109 | return false; 110 | } 111 | 112 | /** 113 | * Returns true if this expression is a macro 114 | * @return 115 | */ 116 | public boolean isMacro() { 117 | return false; 118 | } 119 | 120 | /** 121 | * Returns true if the expression is pure (no side effects) with respect to all free symbols. 122 | * 123 | * A pure expression can be safely replaced with its evaluation result 124 | * @return 125 | */ 126 | public boolean isPure() { 127 | return false; 128 | } 129 | 130 | /** 131 | * Gets the free symbols in an Expression, conj'ing them onto a given persistent set 132 | * @param s 133 | * @return 134 | */ 135 | public abstract IPersistentSet accumulateFreeSymbols(IPersistentSet s); 136 | 137 | public IPersistentSet getFreeSymbols() { 138 | return accumulateFreeSymbols(PersistentHashSet.EMPTY); 139 | } 140 | 141 | /** 142 | * Validates the structure of the expression. Checks that all invariants are satisfied 143 | */ 144 | public abstract void validate(); 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/KFn.java: -------------------------------------------------------------------------------- 1 | package kiss.lang; 2 | 3 | import kiss.lang.type.Something; 4 | import clojure.lang.AFn; 5 | 6 | /** 7 | * Base class for reified Kiss functions 8 | * 9 | * Design intent: 10 | * - Compatible with Clojure IFn 11 | * - Can be extended to create optimised, compiled functions 12 | * - Stores internal Kiss-relevant metadata (e.g. types) 13 | * 14 | * @author Mike 15 | */ 16 | public class KFn extends AFn { 17 | 18 | /** 19 | * Return type of the function 20 | * @return 21 | */ 22 | public Type getReturnType() { 23 | return Something.INSTANCE; 24 | } 25 | 26 | public Type getParamType(int n) { 27 | // TODO: arity check? 28 | return Something.INSTANCE; 29 | } 30 | 31 | /** 32 | * Return true if this is a pure function. Being so is a GOOD THING. 33 | * 34 | * @return 35 | */ 36 | public boolean isPure() { 37 | return true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/Keywords.java: -------------------------------------------------------------------------------- 1 | package kiss.lang; 2 | 3 | import clojure.lang.Keyword; 4 | 5 | public class Keywords { 6 | 7 | public static final Keyword MACRO=Keyword.intern("macro"); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/RT.java: -------------------------------------------------------------------------------- 1 | package kiss.lang; 2 | 3 | import clojure.lang.IFn; 4 | import clojure.lang.Symbol; 5 | import clojure.lang.Var; 6 | import mikera.cljutils.Clojure; 7 | import kiss.lang.expression.Constant; 8 | import kiss.lang.impl.WrappedFn; 9 | import kiss.lang.type.AFunctionType; 10 | import kiss.lang.type.FunctionType; 11 | import kiss.lang.type.JavaType; 12 | 13 | public class RT { 14 | public static Environment ENVIRONMENT=Environment.EMPTY; 15 | 16 | 17 | public static JavaType type(Class klass) { 18 | return JavaType.create(klass); 19 | } 20 | 21 | public static void importClojure(String name, Type t) { 22 | Var cv=Clojure.var(name); 23 | if (cv==null) throw new IllegalArgumentException("Can't import Clojure var: "+name); 24 | Object value=cv.deref(); 25 | if (t instanceof AFunctionType) { 26 | value=new WrappedFn((IFn)value,(AFunctionType)t); 27 | } 28 | ENVIRONMENT=ENVIRONMENT.define(Symbol.create(name), Constant.create(t, value)); 29 | } 30 | 31 | static { 32 | importClojure ("+", FunctionType.createVariadic(Types.NUMBER, Types.NUMBER)); 33 | importClojure ("-", FunctionType.createVariadic(Types.NUMBER, Types.NUMBER, Types.NUMBER)); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/Result.java: -------------------------------------------------------------------------------- 1 | package kiss.lang; 2 | 3 | import kiss.lang.impl.EvalResult; 4 | 5 | /** 6 | * Abstract base class for evaluation results. 7 | * 8 | * Encapsulates the resulting environment, plus any result information 9 | * @author Mike 10 | * 11 | */ 12 | public abstract class Result { 13 | protected final Environment env; 14 | 15 | public Result(Environment env) { 16 | this.env=env; 17 | } 18 | 19 | public Environment getEnvironment() { 20 | return env; 21 | } 22 | 23 | public void validate() { 24 | env.validate(); 25 | } 26 | 27 | public abstract Object getResult(); 28 | 29 | public abstract boolean isExiting(); 30 | 31 | public final EvalResult withResult(Object a) { 32 | return new EvalResult(env,a); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/Symbols.java: -------------------------------------------------------------------------------- 1 | package kiss.lang; 2 | 3 | import clojure.lang.Symbol; 4 | 5 | /** 6 | * Static class containing Kiss symbols for reserved words 7 | * 8 | * @author Mike 9 | * 10 | */ 11 | public class Symbols { 12 | public static final Symbol[] EMPTY_SYMBOL_ARRAY=new Symbol[0]; 13 | 14 | // primitive expressions 15 | public static final Symbol LET= Symbol.intern("let"); 16 | public static final Symbol FN= Symbol.intern("fn"); 17 | public static final Symbol IF = Symbol.intern("if"); 18 | public static final Symbol NIL = Symbol.intern("nil"); 19 | public static final Symbol TRUE = Symbol.intern("true"); 20 | public static final Symbol FALSE = Symbol.intern("false"); 21 | public static final Symbol DEF = Symbol.intern("def"); 22 | public static final Symbol DO = Symbol.intern("do"); 23 | public static final Symbol CAST = Symbol.intern("cast"); 24 | public static final Symbol INSTANCE = Symbol.intern("instance?"); 25 | public static final Symbol VAR = Symbol.intern("var"); 26 | public static final Symbol LOOP = Symbol.intern("loop"); 27 | public static final Symbol RECUR = Symbol.intern("recur"); 28 | public static final Symbol RETURN = Symbol.intern("return"); 29 | public static final Symbol QUOTE = Symbol.intern("quote"); 30 | public static final Symbol TRY = Symbol.intern("try"); 31 | public static final Symbol THROW = Symbol.intern("throw"); 32 | public static final Symbol NEW = Symbol.intern("new"); 33 | public static final Symbol SET_BANG = Symbol.intern("set!"); 34 | public static final Symbol DOT = Symbol.intern("."); 35 | public static final Symbol DOTDOT = Symbol.intern(".."); 36 | 37 | 38 | // type symbols 39 | public static final Symbol U = Symbol.intern("U"); 40 | public static final Symbol I = Symbol.intern("I"); 41 | public static final Symbol ANY = Symbol.intern("Any"); 42 | public static final Symbol NOTHING = Symbol.intern("Nothing"); 43 | public static final Symbol VALUE = Symbol.intern("Value"); 44 | public static final Symbol SYMBOL_TYPE = Symbol.intern("Symbol"); 45 | public static final Symbol KEYWORD_TYPE = Symbol.intern("Keyword"); 46 | public static final Symbol FN_TYPE = Symbol.intern("Fn"); 47 | public static final Symbol TYPE = Symbol.intern("Type"); 48 | 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/Type.java: -------------------------------------------------------------------------------- 1 | package kiss.lang; 2 | 3 | import clojure.lang.Symbol; 4 | import kiss.lang.impl.KissUtils; 5 | import kiss.lang.type.Anything; 6 | import kiss.lang.type.Intersection; 7 | import kiss.lang.type.JavaType; 8 | import kiss.lang.type.Union; 9 | 10 | /** 11 | * Abstract base class for Kiss types 12 | * 13 | * @author Mike 14 | * 15 | */ 16 | public abstract class Type extends KFn { 17 | public static final Type[] EMPTY_TYPE_ARRAY = new Type[0]; 18 | 19 | /** 20 | * Creates a type by parsing a given String 21 | */ 22 | public static Type parse(String s) { 23 | return Analyser.analyseType(KissUtils.read(s)); 24 | } 25 | 26 | /** 27 | * Performs a runtime check if an object is an instance of this type 28 | * 29 | * @param o 30 | * @return 31 | */ 32 | public abstract boolean checkInstance(Object o); 33 | 34 | /** 35 | * Returns the most specific Java class or interface that can represent all instances of this type 36 | * @return 37 | */ 38 | public abstract Class getJavaClass(); 39 | 40 | /** 41 | * Returns true if this is a JVM primitive type 42 | * @return 43 | */ 44 | public boolean isPrimitive() { 45 | return false; 46 | } 47 | 48 | /** 49 | * Return true if this type provably contains the null value 50 | * @return 51 | */ 52 | public abstract boolean canBeNull(); 53 | 54 | /** 55 | * Returns true if this type provably contains at least one truthy value 56 | * @return 57 | */ 58 | public abstract boolean canBeTruthy(); 59 | 60 | /** 61 | * Returns true if this type provably contains at least one falsey value 62 | * @return 63 | */ 64 | public abstract boolean canBeFalsey(); 65 | 66 | /** 67 | * Returns true if another type t is provably contained within this type. 68 | * 69 | * Equivalently this means: 70 | * - t is a subtype of this type 71 | * - every instance of t is an instance of this type 72 | * 73 | * @param t 74 | * @return 75 | */ 76 | public abstract boolean contains(Type t); 77 | 78 | /** 79 | * Returns the intersection of this type with another type 80 | * @param t 81 | * @return 82 | */ 83 | public Type intersection(Type t) { 84 | return Intersection.create(this,t); 85 | } 86 | 87 | /** 88 | * Returns the union of this type with another type 89 | * @param t 90 | * @return 91 | */ 92 | public Type union(Type t) { 93 | return Union.create(this,t); 94 | } 95 | 96 | /** 97 | * Resolves a Clojure tag symbol into a type 98 | * @param s 99 | * @return 100 | */ 101 | public static Type resolveTag(Symbol s) { 102 | // TODO: extract type hints from Clojure symbols 103 | return Anything.INSTANCE; 104 | } 105 | 106 | /** 107 | * Returns true if this type can be proven to equal another type. 108 | * 109 | */ 110 | @Override 111 | public boolean equals(Object o) { 112 | if (o==this) return true; 113 | if (!(o instanceof Type)) return false; 114 | Type t=(Type)o; 115 | return t.contains(this)&&this.contains(t); 116 | } 117 | 118 | @Override 119 | public int hashCode() { 120 | return super.hashCode(); 121 | } 122 | 123 | @Override 124 | public Type getReturnType() { 125 | return Anything.INSTANCE; 126 | } 127 | 128 | public Object cast(Object a) { 129 | if (!checkInstance(a)) throw new ClassCastException("Can't cast value to type "+this.toString()); 130 | return a; 131 | } 132 | 133 | @Override 134 | public final Object invoke(Object a) { 135 | return cast(a); 136 | } 137 | 138 | public abstract void validate(); 139 | 140 | public abstract Type inverse(); 141 | 142 | public boolean isWellBehaved() { 143 | return true; 144 | } 145 | 146 | public boolean cannotBeNull() { 147 | return false; 148 | } 149 | 150 | public boolean cannotBeFalsey() { 151 | return false; 152 | } 153 | 154 | public boolean cannotBeTruthy() { 155 | return false; 156 | } 157 | 158 | public JavaType toJavaType() { 159 | return JavaType.create(this.getJavaClass()); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/Types.java: -------------------------------------------------------------------------------- 1 | package kiss.lang; 2 | 3 | import kiss.lang.type.Anything; 4 | import kiss.lang.type.JavaType; 5 | import kiss.lang.type.Nothing; 6 | import kiss.lang.type.Null; 7 | 8 | /** 9 | * Static constant types 10 | * 11 | * @author Mike 12 | */ 13 | public class Types { 14 | public static final JavaType NUMBER = JavaType.NUMBER; 15 | public static final JavaType STRING = JavaType.STRING; 16 | public static final JavaType BOOLEAN = JavaType.BOOLEAN; 17 | public static final JavaType SYMBOL = JavaType.SYMBOL; 18 | public static final JavaType KEYWORD = JavaType.KEYWORD; 19 | 20 | public static final Anything ANYTHING = Anything.INSTANCE; 21 | public static final Nothing NOTHING = Nothing.INSTANCE; 22 | public static final Null NULL = Null.INSTANCE; 23 | public static final Type TYPE = JavaType.KISS_TYPE; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/expression/Application.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.expression; 2 | 3 | import kiss.lang.Environment; 4 | import kiss.lang.Expression; 5 | import kiss.lang.Result; 6 | import kiss.lang.Type; 7 | import kiss.lang.impl.EvalResult; 8 | import kiss.lang.impl.KissException; 9 | import kiss.lang.impl.KissUtils; 10 | import kiss.lang.type.AFunctionType; 11 | import kiss.lang.type.FunctionType; 12 | import kiss.lang.type.Reference; 13 | import clojure.lang.ArraySeq; 14 | import clojure.lang.IFn; 15 | import clojure.lang.IPersistentMap; 16 | import clojure.lang.IPersistentSet; 17 | import clojure.lang.RT; 18 | 19 | /** 20 | * Expression representing a function application 21 | * 22 | * @author Mike 23 | * 24 | */ 25 | @SuppressWarnings("unused") 26 | public class Application extends Expression { 27 | private final Expression func; 28 | private final Expression[] params; 29 | private final int arity; 30 | 31 | private Application(Expression func, Expression[] params) { 32 | this.func=func; 33 | this.params=params; 34 | this.arity=params.length; 35 | } 36 | 37 | public static Expression create(Expression func, Expression... params) { 38 | return new Application(func,params.clone()); 39 | } 40 | 41 | private Expression update(Expression nFunc, Expression[] nParams) { 42 | if (nFunc!=func) return Application.create(nFunc, nParams); 43 | for (int i=0; i)nFunc).getValue(); 72 | if (KissUtils.isPureFn(fn)) { 73 | // compute result 74 | Object[] ps=new Object[arity]; 75 | for (int i=0; i)nParams[i]).getValue(); 77 | } 78 | return Constant.create(fn.applyTo(RT.seq(ps))); 79 | } 80 | } 81 | return update(nFunc,nParams); 82 | } 83 | 84 | @Override 85 | public Type getType() { 86 | Type ft=func.getType(); 87 | if (ft instanceof AFunctionType) { 88 | return ((AFunctionType)ft).getReturnType(); 89 | } 90 | return Reference.INSTANCE; 91 | } 92 | 93 | @Override 94 | public Result interpret(Environment d, IPersistentMap bindings) { 95 | Result r=func.interpret(d, bindings); 96 | if (r.isExiting()) return r; 97 | Object o=r.getResult(); 98 | if (!(o instanceof IFn)) throw new KissException("Not a function: "+o); 99 | IFn fn=(IFn)o; 100 | 101 | int n=params.length; 102 | Object[] args=new Object[n]; 103 | for (int i=0; i klass, Expression body) { 41 | return create(JavaType.create(klass),body); 42 | } 43 | 44 | @Override 45 | public Type getType() { 46 | return type; 47 | } 48 | 49 | @Override 50 | public Result interpret(Environment d, IPersistentMap bindings) { 51 | Result ev= body.interpret(d, bindings); 52 | if (ev.isExiting()) return ev; 53 | Object result=ev.getResult(); 54 | if (type.checkInstance(result)) { 55 | throw new KissException("Can't cast value of class "+KissUtils.typeName(result)+" to "+type); 56 | 57 | } 58 | return ev; 59 | } 60 | 61 | @Override 62 | public Expression optimise() { 63 | Expression b=body.optimise(); 64 | Type bt=body.getType(); 65 | if (b.isConstant()) { 66 | Object val=b.eval(); 67 | if (type.checkInstance(val)) throw new KissException("Impossible to cast value "+val+" to type: "+type); 68 | // TODO: is this logic sound? what about interface casts? 69 | return b; 70 | } 71 | Type t=type; 72 | if (t.contains(bt)) t=bt; 73 | if ((b==body)&&(t==type)) return this; 74 | return create(t,b); 75 | } 76 | 77 | @Override 78 | public boolean isPure() { 79 | return body.isPure(); 80 | } 81 | 82 | @Override 83 | public Expression specialise(Type type) { 84 | if (type==this.type) return this; 85 | if (type.contains(this.type)) return this; 86 | Type it = type.intersection(this.type); 87 | if (it==Nothing.INSTANCE) return null; 88 | 89 | return create(it,body.specialise(it)); 90 | } 91 | 92 | @Override 93 | public IPersistentSet accumulateFreeSymbols(IPersistentSet s) { 94 | s=body.accumulateFreeSymbols(s); 95 | return s; 96 | } 97 | 98 | @Override 99 | public Expression substitute(IPersistentMap bindings) { 100 | Expression nBody=body.substitute(bindings); 101 | if (nBody==body) return this; 102 | if (nBody==null) return null; 103 | return create(type,nBody); 104 | } 105 | 106 | @Override 107 | public void validate() { 108 | // OK? 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/expression/ClojureLookup.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.expression; 2 | 3 | import kiss.lang.Environment; 4 | import kiss.lang.Expression; 5 | import kiss.lang.Keywords; 6 | import kiss.lang.Type; 7 | import kiss.lang.impl.EvalResult; 8 | import kiss.lang.impl.KissException; 9 | import kiss.lang.impl.KissUtils; 10 | import kiss.lang.type.Anything; 11 | import clojure.lang.IPersistentMap; 12 | import clojure.lang.IPersistentSet; 13 | import clojure.lang.RT; 14 | import clojure.lang.Symbol; 15 | import clojure.lang.Var; 16 | 17 | /** 18 | * An expression representing a lookup in the Clojure global environment 19 | * @author Mike 20 | * 21 | */ 22 | public class ClojureLookup extends Expression { 23 | private final Symbol sym; 24 | 25 | private ClojureLookup(Symbol sym) { 26 | this.sym=sym; 27 | } 28 | 29 | public static Expression create(Symbol symbol) { 30 | return new ClojureLookup(symbol); 31 | } 32 | 33 | public static Expression create(String symName) { 34 | return create(Symbol.intern(symName)); 35 | } 36 | 37 | @Override 38 | public Type getType() { 39 | return Anything.INSTANCE; 40 | } 41 | 42 | @Override 43 | public boolean isMacro() { 44 | Var v=RT.var(sym.getNamespace(),sym.getName()); 45 | return KissUtils.isTruthy(v.meta().valAt(Keywords.MACRO)); 46 | } 47 | 48 | @Override 49 | public EvalResult interpret(Environment e, IPersistentMap bindings) { 50 | try { 51 | Var v=RT.var(sym.getNamespace(),sym.getName()); 52 | if (v!=null) return e.withResult(v.deref()); 53 | } catch (Throwable t) { 54 | String err="Error trying to lookp var "+sym+" "; 55 | err+=" with Environment "+e.toString(); 56 | throw new KissException(err,t); 57 | } 58 | 59 | throw new KissException("Cannot find Clojure symbol "+sym+" in environment"); 60 | } 61 | 62 | @Override 63 | public Expression specialise(Type type) { 64 | return Cast.create(type, this); 65 | } 66 | 67 | @Override 68 | public Expression substitute(IPersistentMap bindings) { 69 | return this; 70 | } 71 | 72 | @Override 73 | public IPersistentSet accumulateFreeSymbols(IPersistentSet s) { 74 | return s; 75 | } 76 | 77 | @Override 78 | public void validate() { 79 | // OK? 80 | } 81 | 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/expression/Constant.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.expression; 2 | 3 | import kiss.lang.Environment; 4 | import kiss.lang.Expression; 5 | import kiss.lang.Type; 6 | import kiss.lang.impl.EvalResult; 7 | import kiss.lang.impl.KissException; 8 | import kiss.lang.type.Null; 9 | import kiss.lang.type.Value; 10 | import clojure.lang.IPersistentMap; 11 | import clojure.lang.IPersistentSet; 12 | 13 | /** 14 | * A typed constant value in an Expression 15 | * 16 | * @author Mike 17 | */ 18 | public class Constant extends Expression { 19 | private final T value; 20 | private final Type type; 21 | 22 | public static final Constant NULL=new Constant(Null.INSTANCE,null); 23 | public static final Constant FALSE =new Constant(Boolean.FALSE); 24 | public static final Constant TRUE =new Constant(Boolean.TRUE); 25 | 26 | private Constant(Type type, T value) { 27 | this.value=value; 28 | this.type=type; 29 | } 30 | 31 | private Constant(T value) { 32 | this ((value==null)?Null.INSTANCE:Value.create(value),value); 33 | } 34 | 35 | @SuppressWarnings("unchecked") 36 | public static Constant create(T value) { 37 | if (value==null) return (Constant) NULL; 38 | return new Constant(value); 39 | } 40 | 41 | @SuppressWarnings("unchecked") 42 | public static Constant create(Type type, T value) { 43 | if ((value==null)&&(type instanceof Null)) return (Constant) NULL; 44 | return new Constant(type,value); 45 | } 46 | 47 | 48 | public T getValue() { 49 | return value; 50 | } 51 | 52 | @Override 53 | public Type getType() { 54 | return type; 55 | } 56 | 57 | @Override 58 | public boolean isConstant() { 59 | return true; 60 | } 61 | 62 | @Override 63 | public boolean isPure() { 64 | return true; 65 | } 66 | 67 | @Override 68 | public Object eval(Environment e) { 69 | return value; 70 | } 71 | 72 | @Override 73 | public Object eval() { 74 | return value; 75 | } 76 | 77 | @Override 78 | public EvalResult interpret(Environment d, IPersistentMap bindings) { 79 | return d.withResult(value); 80 | } 81 | 82 | @Override 83 | public Expression specialise(Type type) { 84 | if (type==this.type) return this; 85 | if (type.checkInstance(value)) return Constant.create(type.intersection(this.type),value); 86 | return null; 87 | } 88 | 89 | @Override 90 | public IPersistentSet accumulateFreeSymbols(IPersistentSet s) { 91 | return s; 92 | } 93 | 94 | @Override 95 | public Expression substitute(IPersistentMap bindings) { 96 | return this; 97 | } 98 | 99 | @Override 100 | public void validate() { 101 | if (!type.checkInstance(value)) throw new KissException("Mismatched type!"); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/expression/Def.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.expression; 2 | 3 | import clojure.lang.IPersistentMap; 4 | import clojure.lang.IPersistentSet; 5 | import clojure.lang.Symbol; 6 | import kiss.lang.Environment; 7 | import kiss.lang.Expression; 8 | import kiss.lang.Type; 9 | import kiss.lang.impl.EvalResult; 10 | 11 | /** 12 | * A kiss "def" expression. 13 | * 14 | * Alters the current immutable environment, binding the specified symbol to a new Expression 15 | * 16 | * The result is the value of the new Expression 17 | * 18 | * @author Mike 19 | * 20 | */ 21 | public class Def extends Expression { 22 | 23 | private final Symbol sym; 24 | private final Expression body; 25 | 26 | private Def(Symbol sym, Expression body) { 27 | this.sym=sym; 28 | this.body=body; 29 | } 30 | 31 | public static Def create(Symbol sym, Expression body) { 32 | return new Def(sym,body); 33 | } 34 | 35 | public Def update(Symbol sym, Expression body) { 36 | if ((sym==this.sym)&&(body==this.body)) return this; 37 | return new Def(sym,body); 38 | } 39 | 40 | @Override 41 | public Type getType() { 42 | return body.getType(); 43 | } 44 | 45 | @Override 46 | public Expression specialise(Type type) { 47 | Expression b=body.specialise(type); 48 | if ((b==body)||(b==null)) return this; 49 | return update(sym,b); 50 | } 51 | 52 | @Override 53 | public EvalResult interpret(Environment d, IPersistentMap bindings) { 54 | return new EvalResult(d.define(sym,body,bindings)); 55 | } 56 | 57 | @Override 58 | public IPersistentSet accumulateFreeSymbols(IPersistentSet s) { 59 | s=body.accumulateFreeSymbols(s); 60 | return s; 61 | } 62 | 63 | @Override 64 | public Expression substitute(IPersistentMap bindings) { 65 | Expression nBody=body.substitute(bindings); 66 | if (nBody==null) return null; 67 | return update(sym,nBody); 68 | } 69 | 70 | @Override 71 | public void validate() { 72 | // OK? 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/expression/Do.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.expression; 2 | 3 | import java.util.Arrays; 4 | 5 | import kiss.lang.Environment; 6 | import kiss.lang.Expression; 7 | import kiss.lang.Result; 8 | import kiss.lang.Type; 9 | import kiss.lang.impl.EvalResult; 10 | import kiss.lang.impl.KissException; 11 | import kiss.lang.type.Nothing; 12 | import clojure.lang.IPersistentMap; 13 | import clojure.lang.IPersistentSet; 14 | 15 | /** 16 | * A Kiss "do" expression. 17 | * 18 | * All subexpressions are evaluated in sequence, and the result is the result of the final 19 | * subexpression. 20 | * 21 | * @author Mike 22 | * 23 | */ 24 | public class Do extends kiss.lang.Expression { 25 | private final Expression[] exps; 26 | private final int length; 27 | 28 | private Do(Expression[] exps) { 29 | this.exps=exps; 30 | length=exps.length; 31 | } 32 | 33 | public static Do create(Expression... exps) { 34 | return new Do(exps); 35 | } 36 | 37 | @Override 38 | public Type getType() { 39 | if (length==0) return Nothing.INSTANCE; 40 | return exps[length-1].getType(); 41 | } 42 | 43 | @Override 44 | public Expression optimise() { 45 | Expression[] es=new Expression[length]; 46 | int j=0; 47 | boolean found=false; 48 | for (int i=0; i type; 21 | private final Expression body; 22 | 23 | private InstanceOf(JavaType type, Expression body) { 24 | this.type=type; 25 | this.body=body; 26 | } 27 | 28 | public static Expression create(Type t, Expression body) { 29 | JavaType type=t.toJavaType(); 30 | return new InstanceOf(type,body).optimise(); 31 | } 32 | 33 | public InstanceOf update(Type t, Expression body) { 34 | JavaType type=t.toJavaType(); 35 | if ((type==this.type)&&(body==this.body)) return this; 36 | return new InstanceOf(type,body); 37 | } 38 | 39 | @Override 40 | public Type getType() { 41 | // TODO: primitive boolean? Or Kiss Bool type? 42 | return JavaType.BOOLEAN; 43 | } 44 | 45 | @Override 46 | public Expression optimise() { 47 | Expression body=this.body.optimise(); 48 | Type bt=body.getType(); 49 | if (type.contains(bt)) return Constant.TRUE; 50 | if (type.intersection(bt)==Nothing.INSTANCE) return Constant.FALSE; 51 | return update(type,body); 52 | } 53 | 54 | @Override 55 | public Expression specialise(Type type) { 56 | return this; 57 | } 58 | 59 | @Override 60 | public Expression substitute(IPersistentMap bindings) { 61 | return update(type,body.substitute(bindings)); 62 | } 63 | 64 | @Override 65 | public Result interpret(Environment d, IPersistentMap bindings) { 66 | Result r=body.interpret(d, bindings); 67 | if (r.isExiting()) return r; 68 | return r.withResult(type.checkInstance(r.getResult())); 69 | } 70 | 71 | @Override 72 | public IPersistentSet accumulateFreeSymbols(IPersistentSet s) { 73 | return body.accumulateFreeSymbols(s); 74 | } 75 | 76 | @Override 77 | public void validate() { 78 | // TODO Auto-generated method stub 79 | 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/expression/Lambda.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.expression; 2 | 3 | import java.util.Arrays; 4 | import java.util.Map.Entry; 5 | 6 | import kiss.lang.Environment; 7 | import kiss.lang.Expression; 8 | import kiss.lang.KFn; 9 | import kiss.lang.Type; 10 | import kiss.lang.impl.EvalResult; 11 | import kiss.lang.impl.LambdaFn; 12 | import kiss.lang.type.Anything; 13 | import kiss.lang.type.FunctionType; 14 | import clojure.lang.IPersistentMap; 15 | import clojure.lang.IPersistentSet; 16 | import clojure.lang.ISeq; 17 | import clojure.lang.Symbol; 18 | 19 | /** 20 | * A lambda expression, equivalent to Clojure "fn" 21 | * 22 | * Notes: 23 | * - only supports fixed arity at present 24 | * 25 | * @author Mike 26 | */ 27 | public class Lambda extends Expression { 28 | 29 | public static final Lambda IDENTITY=create(Lookup.create("x"),new Symbol[] {Symbol.intern("x")},new Type[] {Anything.INSTANCE}); 30 | 31 | private final FunctionType type; 32 | private final Expression body; 33 | private final Type[] types; 34 | private final Symbol[] syms; 35 | private KFn compiled=null; 36 | 37 | private Lambda(Expression body, Symbol[] syms, Type[] types) { 38 | this.body=body; 39 | this.types=types; 40 | this.type=FunctionType.create(body.getType(), types); 41 | this.syms=syms; 42 | } 43 | 44 | public static Lambda create(Expression body, Symbol[] syms, Type[] types) { 45 | return new Lambda(body,syms,types); 46 | } 47 | 48 | public Lambda update(Expression body, Symbol[] syms, Type[] types) { 49 | if ((body==this.body)&&(Arrays.equals(syms, this.syms))&&(Arrays.equals(types, this.types))) return this; 50 | return new Lambda(body,syms,types); 51 | } 52 | 53 | @Override 54 | public Type getType() { 55 | return type; 56 | } 57 | 58 | @Override 59 | public EvalResult interpret(Environment d, IPersistentMap bindings) { 60 | if (compiled!=null) return d.withResult(compiled); 61 | 62 | // TODO is this sensible? capture the dynamic environment at exact point of lambda creation? 63 | Environment e=d; 64 | for (ISeq s= bindings.seq(); s!=null; s=s.next()) { 65 | Entry me=(Entry)s.first(); 66 | e=e.assoc(me.getKey(),me.getValue()); 67 | } 68 | 69 | KFn fn=LambdaFn.create(e,body,syms); 70 | return d.withResult(fn); 71 | } 72 | 73 | @Override 74 | public boolean isPure() { 75 | return body.isPure(); 76 | } 77 | 78 | @Override 79 | public Expression specialise(Type type) { 80 | if (this.type==type) return this; 81 | if (type.contains(this.type)) return this; 82 | return update(body.specialise(type),syms,types); 83 | } 84 | 85 | @Override 86 | public Expression substitute(IPersistentMap bindings) { 87 | for (Symbol s:syms) { 88 | bindings=bindings.without(s); 89 | } 90 | Expression nbody=body.substitute(bindings); 91 | if (nbody==null) return null; 92 | 93 | return update(nbody,syms,types); 94 | } 95 | 96 | @Override 97 | public Lambda optimise() { 98 | return update(body.optimise(),syms,types); 99 | } 100 | 101 | @Override 102 | public IPersistentSet accumulateFreeSymbols(IPersistentSet s) { 103 | s=body.accumulateFreeSymbols(s); 104 | for (Symbol sym:syms) { 105 | s=s.disjoin(sym); 106 | } 107 | return s; 108 | } 109 | 110 | @Override 111 | public void validate() { 112 | // OK? 113 | 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/expression/Let.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.expression; 2 | 3 | import clojure.lang.IPersistentMap; 4 | import clojure.lang.IPersistentSet; 5 | import clojure.lang.PersistentHashMap; 6 | import clojure.lang.PersistentHashSet; 7 | import clojure.lang.Symbol; 8 | import kiss.lang.Environment; 9 | import kiss.lang.Expression; 10 | import kiss.lang.Result; 11 | import kiss.lang.Type; 12 | 13 | /** 14 | * A let expression, creates a local lexical binding 15 | * 16 | * @author Mike 17 | */ 18 | public class Let extends Expression { 19 | 20 | private final Symbol sym; 21 | private final Expression value; 22 | private final Expression body; 23 | 24 | public Let(Symbol sym, Expression value, Expression body) { 25 | this.sym=sym; 26 | this.value=value; 27 | this.body=body; 28 | } 29 | 30 | public static Let create(Symbol sym, Expression value, Expression body) { 31 | return new Let(sym,value,body); 32 | } 33 | 34 | public Let update(Symbol sym, Expression value, Expression body) { 35 | if ((this.sym==sym)&&(this.body==body)&&(this.value==value)) return this; 36 | return create(sym, value,body); 37 | } 38 | 39 | @Override 40 | public Expression optimise() { 41 | Expression b=body.optimise(); 42 | Expression v=value.optimise(); 43 | if (value.isPure()) { 44 | IPersistentSet bfree= b.accumulateFreeSymbols(PersistentHashSet.EMPTY); 45 | if (!(bfree.contains(sym))) { 46 | return b; 47 | } 48 | if (value.isConstant()) { 49 | return b.substitute(PersistentHashMap.EMPTY.assoc(sym,value.eval())).optimise(); 50 | } 51 | } 52 | return update(sym,v,b); 53 | } 54 | 55 | @Override 56 | public Type getType() { 57 | return body.getType(); 58 | } 59 | 60 | @Override 61 | public boolean isPure() { 62 | if (!body.isPure()) return false; 63 | if (!value.isPure()) return false; 64 | return true; 65 | } 66 | 67 | @Override 68 | public Result interpret(Environment d, IPersistentMap bindings) { 69 | Result r=value.interpret(d, bindings); 70 | if (r.isExiting()) return r; 71 | 72 | Object result=r.getResult(); 73 | bindings=bindings.assoc(sym, result); 74 | return body.interpret(d, bindings); 75 | } 76 | 77 | @Override 78 | public Expression specialise(Type type) { 79 | Expression newBody=body.specialise(type); 80 | return update(sym,value,newBody); 81 | } 82 | 83 | @Override 84 | public Expression substitute(IPersistentMap bindings) { 85 | Expression nv=value.substitute(bindings); 86 | if (nv==null) return null; 87 | bindings=bindings.without(sym); 88 | Expression nbody=body.substitute(bindings); 89 | if (nbody==null) return null; 90 | 91 | return update(sym,nv,nbody); 92 | } 93 | 94 | @Override 95 | public IPersistentSet accumulateFreeSymbols(IPersistentSet s) { 96 | s=body.accumulateFreeSymbols(s); 97 | s=s.disjoin(sym); 98 | s=value.accumulateFreeSymbols(s); 99 | return s; 100 | } 101 | 102 | @Override 103 | public void validate() { 104 | // OK? 105 | } 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/expression/Lookup.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.expression; 2 | 3 | import java.util.Map.Entry; 4 | 5 | import kiss.lang.Environment; 6 | import kiss.lang.Expression; 7 | import kiss.lang.Type; 8 | import kiss.lang.impl.EvalResult; 9 | import kiss.lang.impl.KissException; 10 | import kiss.lang.type.Anything; 11 | import clojure.lang.IPersistentCollection; 12 | import clojure.lang.IPersistentMap; 13 | import clojure.lang.IPersistentSet; 14 | import clojure.lang.Symbol; 15 | 16 | /** 17 | * An expression representing a lookup in the current Kiss Environment 18 | * @author Mike 19 | * 20 | */ 21 | public class Lookup extends Expression { 22 | private final Symbol sym; 23 | 24 | private Lookup(Symbol sym) { 25 | this.sym=sym; 26 | } 27 | 28 | public static Expression create(Symbol symbol) { 29 | return new Lookup(symbol); 30 | } 31 | 32 | public static Expression create(String symName) { 33 | return create(Symbol.intern(symName)); 34 | } 35 | 36 | @Override 37 | public Type getType() { 38 | return Anything.INSTANCE; 39 | } 40 | 41 | @SuppressWarnings("unchecked") 42 | @Override 43 | public EvalResult interpret(Environment e, IPersistentMap bindings) { 44 | Entry lb=(Entry)bindings.entryAt(sym); 45 | if (lb!=null) return e.withResult(lb.getValue()); 46 | 47 | Entry o=(Entry)e.entryAt(sym); 48 | if (o!=null) return e.withResult(o.getValue()); 49 | 50 | throw new KissException("Cannot lookup symbol "+sym+" in environment"); 51 | } 52 | 53 | @Override 54 | public Expression specialise(Type type) { 55 | if (type.contains(this.getType())) return this; 56 | return Cast.create(type, this); 57 | } 58 | 59 | @Override 60 | public Expression substitute(IPersistentMap bindings) { 61 | if(bindings.containsKey(sym)) { 62 | return Constant.create(bindings.valAt(sym)); 63 | } 64 | return this; 65 | } 66 | 67 | @Override 68 | public boolean isPure() { 69 | return true; 70 | } 71 | 72 | @Override 73 | public IPersistentSet accumulateFreeSymbols(IPersistentSet s) { 74 | s=(IPersistentSet) ((IPersistentCollection)s).cons(sym); 75 | return s; 76 | } 77 | 78 | @Override 79 | public void validate() { 80 | // OK? 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/expression/Loop.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.expression; 2 | 3 | import clojure.lang.IPersistentMap; 4 | import clojure.lang.IPersistentSet; 5 | import clojure.lang.Symbol; 6 | import kiss.lang.Environment; 7 | import kiss.lang.Expression; 8 | import kiss.lang.Result; 9 | import kiss.lang.Type; 10 | import kiss.lang.impl.RecurResult; 11 | 12 | /** 13 | * A let expression, creates a local lexical binding 14 | * 15 | * @author Mike 16 | */ 17 | public class Loop extends Expression { 18 | 19 | private final Symbol[] syms; 20 | private final Expression[] initials; 21 | private final Expression body; 22 | 23 | public Loop(Symbol[] syms, Expression[] initials, Expression body) { 24 | this.syms=syms; 25 | this.initials=initials; 26 | this.body=body; 27 | } 28 | 29 | public static Loop create(Symbol[] syms, Expression[] initials, Expression body) { 30 | return new Loop(syms,initials,body); 31 | } 32 | 33 | public Loop update(Symbol[] syms, Expression[] initials, Expression body) { 34 | Expression[] nis =this.initials; 35 | for (int i=0; i keys; 28 | private List vals; 29 | private int length; 30 | 31 | private Map(List ks, List vs) { 32 | this.keys=ks; 33 | this.vals=vs; 34 | this.length=vs.size(); 35 | } 36 | 37 | public static Map create (List ks,List vs) { 38 | return new Map(ks,vs); 39 | } 40 | 41 | public static Map create (java.util.Map m) { 42 | ArrayList alv=new ArrayList(); 43 | ArrayList alk=new ArrayList(); 44 | for (Entry e:m.entrySet()) { 45 | alk.add(e.getKey()); 46 | alv.add(e.getValue()); 47 | } 48 | return new Map(alk,alv); 49 | } 50 | 51 | @Override 52 | public Type getType() { 53 | return TYPE; 54 | } 55 | 56 | @Override 57 | public Expression specialise(Type type) { 58 | return null; 59 | } 60 | 61 | @Override 62 | public Expression substitute(IPersistentMap bindings) { 63 | int i=0; 64 | Expression nxv=null; 65 | Expression nxk=null; 66 | for (;i alv=new ArrayList(); 79 | ArrayList alk=new ArrayList(); 80 | for (int j=0; j hm=new HashMap(); 103 | for (int i=0; i extends Expression { 19 | private final Expression[] values; 20 | 21 | private Recur(Expression[] values) { 22 | this.values=values; 23 | } 24 | 25 | public static Recur create(Expression[] values) { 26 | return new Recur(values); 27 | } 28 | 29 | @Override 30 | public Type getType() { 31 | return Nothing.INSTANCE; 32 | } 33 | 34 | @Override 35 | public boolean isConstant() { 36 | return true; 37 | } 38 | 39 | @Override 40 | public boolean isPure() { 41 | return false; 42 | } 43 | 44 | @Override 45 | public Object eval(Environment e) { 46 | throw new KissException("Can't evaluate recur"); 47 | } 48 | 49 | @Override 50 | public Result interpret(Environment d, IPersistentMap bindings) { 51 | int n=values.length; 52 | Object[] rs=new Object[n]; 53 | for (int i=0; i extends Expression { 21 | private final Expression value; 22 | 23 | private Return(Expression value) { 24 | this.value=value; 25 | } 26 | 27 | public static Return create(Expression value) { 28 | return new Return(value); 29 | } 30 | 31 | @Override 32 | public Type getType() { 33 | return Nothing.INSTANCE; 34 | } 35 | 36 | @Override 37 | public boolean isConstant() { 38 | return true; 39 | } 40 | 41 | @Override 42 | public boolean isPure() { 43 | return false; 44 | } 45 | 46 | @Override 47 | public Object eval(Environment e) { 48 | throw new KissException("Can't evaluate return"); 49 | } 50 | 51 | @Override 52 | public Result interpret(Environment d, IPersistentMap bindings) { 53 | Result r=value.interpret(d, bindings); 54 | if (r.isExiting()) return r; 55 | return new ReturnResult(r.getEnvironment(),r.getResult()); 56 | } 57 | 58 | @Override 59 | public Expression specialise(Type type) { 60 | return this; 61 | } 62 | 63 | @Override 64 | public IPersistentSet accumulateFreeSymbols(IPersistentSet s) { 65 | return s; 66 | } 67 | 68 | @Override 69 | public Expression substitute(IPersistentMap bindings) { 70 | return this; 71 | } 72 | 73 | @Override 74 | public void validate() { 75 | // TODO: anything to validate? 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/expression/Vector.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.expression; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import clojure.lang.APersistentVector; 7 | import clojure.lang.IPersistentMap; 8 | import clojure.lang.IPersistentSet; 9 | import clojure.lang.PersistentVector; 10 | import kiss.lang.Environment; 11 | import kiss.lang.Expression; 12 | import kiss.lang.Result; 13 | import kiss.lang.Type; 14 | import kiss.lang.impl.KissException; 15 | import kiss.lang.type.JavaType; 16 | 17 | /** 18 | * A vector producing expression 19 | * @author Mike 20 | * 21 | */ 22 | public class Vector extends Expression { 23 | public Type TYPE = JavaType.create(APersistentVector.class); 24 | 25 | private List vals; 26 | private int length; 27 | 28 | private Vector(List vs) { 29 | this.vals=vs; 30 | this.length=vs.size(); 31 | } 32 | 33 | public static Vector create (List vs) { 34 | return new Vector(vs); 35 | } 36 | 37 | @Override 38 | public Type getType() { 39 | return TYPE; 40 | } 41 | 42 | @Override 43 | public Expression specialise(Type type) { 44 | return null; 45 | } 46 | 47 | @Override 48 | public Expression substitute(IPersistentMap bindings) { 49 | int i=0; 50 | Expression nx=null; 51 | for (;i al=new ArrayList(); 59 | for (int j=0; j al=new ArrayList(length); 75 | for (int i=0; i al=new ArrayList(); 62 | for (Object o: vals) { 63 | al.add(o); 64 | } 65 | return RT.seq(al); 66 | } 67 | 68 | public static boolean isPureFn(IFn fn) { 69 | if (fn instanceof KFn) { 70 | return ((KFn)fn).isPure(); 71 | } 72 | return false; 73 | } 74 | 75 | public static boolean equalsWithNulls(Object a, Object b) { 76 | return (a==b)||((a!=null)&&a.equals(b)); 77 | } 78 | 79 | public static boolean isClojureVar(Symbol sym) { 80 | String ns=sym.getNamespace(); 81 | if (ns==null) return false; 82 | String name=sym.getName(); 83 | return (RT.var(ns, name)!=null); 84 | } 85 | 86 | /** 87 | * Trick function used to clear local values, enabling GC 88 | */ 89 | public static Environment ret1(Environment ret, Environment nil) { 90 | return ret; 91 | } 92 | 93 | /** 94 | * Trick function used to clear local values, enabling GC 95 | */ 96 | public static Object ret1(Object ret, Object nil) { 97 | return ret; 98 | } 99 | 100 | public static java.util.List asList(ISeq s) { 101 | ArrayList al=new ArrayList(); 102 | while (s!=null) { 103 | al.add(s.first()); 104 | s=s.next(); 105 | } 106 | return al; 107 | } 108 | 109 | 110 | public static boolean isMacro(Expression fn) { 111 | return fn.isMacro(); 112 | } 113 | 114 | 115 | public static boolean isTruthy(Object x) { 116 | return (x!=null)&&(x!=Boolean.FALSE); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/impl/LambdaFn.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.impl; 2 | 3 | import clojure.lang.IPersistentMap; 4 | import clojure.lang.PersistentHashMap; 5 | import clojure.lang.Symbol; 6 | import kiss.lang.Environment; 7 | import kiss.lang.Expression; 8 | import kiss.lang.KFn; 9 | import kiss.lang.Result; 10 | 11 | /** 12 | * Intermediate lambda representation 13 | * 14 | * @author Mike 15 | */ 16 | public class LambdaFn extends KFn { 17 | 18 | private final Symbol[] params; 19 | private final Expression body; 20 | private final Environment env; 21 | private final int arity; 22 | 23 | public LambdaFn(Environment env, Expression body, Symbol[] params) { 24 | this.env=env; 25 | this.body = body; 26 | this.params = params; 27 | this.arity = params.length; 28 | } 29 | 30 | public static KFn create(Environment env, Expression body, Symbol[] params) { 31 | return new LambdaFn(env, body, params); 32 | } 33 | 34 | public Object invokeArray(Object... args) { 35 | if (args.length!=arity) throwArity(args.length); 36 | IPersistentMap bindings=PersistentHashMap.EMPTY; 37 | for (int i=0; i create(Symbol sym, Object o) { 19 | return new MapEntry(sym,o); 20 | } 21 | 22 | @Override 23 | public Symbol getKey() { 24 | return key; 25 | } 26 | 27 | public void setKey(Object key) { 28 | throw new UnsupportedOperationException(); 29 | } 30 | 31 | @Override 32 | public Object getValue() { 33 | return value; 34 | } 35 | 36 | @Override 37 | public Object setValue(Object value) { 38 | throw new UnsupportedOperationException(); 39 | } 40 | 41 | @Override 42 | public Object key() { 43 | return key; 44 | } 45 | 46 | @Override 47 | public Object val() { 48 | return value; 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /src/main/java/kiss/lang/impl/Mapping.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.impl; 2 | 3 | import kiss.lang.Expression; 4 | import kiss.lang.Type; 5 | import kiss.lang.expression.Constant; 6 | import kiss.lang.type.JavaType; 7 | import clojure.lang.IMapEntry; 8 | import clojure.lang.IPersistentSet; 9 | import clojure.lang.PersistentHashSet; 10 | import clojure.lang.Symbol; 11 | 12 | /** 13 | * A Kiss Environment mapping 14 | * 15 | * @author Mike 16 | * 17 | */ 18 | public class Mapping { 19 | private final Type type; 20 | private final Expression exp; 21 | private final Object value; 22 | private final IPersistentSet unboundDeps; 23 | 24 | private Mapping(Expression exp, Object value, Type type, IPersistentSet unbound) { 25 | this.type=type; 26 | this.exp=exp; 27 | this.value=value; 28 | this.unboundDeps=unbound; 29 | } 30 | 31 | public static Object create(Object val) { 32 | return new Mapping(Constant.create(val),val,JavaType.analyse(val),null); 33 | } 34 | 35 | public static Object createExpression(Expression ex, Object val, IPersistentSet unbound) { 36 | return new Mapping(ex,val,ex.getType(),unbound); 37 | } 38 | 39 | public Object getValue() { 40 | if (unboundDeps==null) { 41 | return value; 42 | } else { 43 | throw new KissException("Free symbols cannot be resolved: "+unboundDeps.toString()); 44 | } 45 | } 46 | 47 | public boolean isBound() { 48 | return (unboundDeps==null); 49 | } 50 | 51 | public Object maybeValue() { 52 | return value; 53 | } 54 | 55 | public Expression getExpression() { 56 | return exp; 57 | } 58 | 59 | public IMapEntry toMapEntry(Object key) { 60 | if (!isBound()) throw new KissException("Free symbols cannot be resolved: "+unboundDeps.toString()); 61 | return new MapEntry((Symbol)key,value); 62 | } 63 | 64 | public Type getType() { 65 | return type; 66 | } 67 | 68 | public IPersistentSet getUnbound() { 69 | return (unboundDeps==null)?PersistentHashSet.EMPTY:unboundDeps; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/impl/RecurResult.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.impl; 2 | 3 | import java.util.Arrays; 4 | 5 | import kiss.lang.Environment; 6 | 7 | public class RecurResult extends ExitResult { 8 | public Object[] values; 9 | 10 | public RecurResult(Environment env, Object[] values) { 11 | super(env); 12 | this.values=values; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "(RecurResult "+Arrays.toString(values)+")"; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/impl/ReturnResult.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.impl; 2 | 3 | import kiss.lang.Environment; 4 | 5 | /** 6 | * Class representing a return statement result. 7 | * 8 | * @author Mike 9 | * 10 | */ 11 | public class ReturnResult extends ExitResult { 12 | public Object value; 13 | 14 | public ReturnResult(Environment env, Object value) { 15 | super(env); 16 | this.value=value; 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | return "(ReturnResult "+value.toString()+")"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/impl/WrappedFn.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.impl; 2 | 3 | import kiss.lang.Type; 4 | import kiss.lang.type.AFunctionType; 5 | import clojure.lang.IFn; 6 | import clojure.lang.RT; 7 | 8 | /** 9 | * Wrapped for Clojure functions that adds a Kiss type 10 | * @author Mike 11 | * 12 | */ 13 | public class WrappedFn extends ATypedFn { 14 | private final IFn fn; 15 | 16 | public WrappedFn(IFn fn, Type returnType, Type... paramTypes) { 17 | super(returnType,paramTypes); 18 | this.fn=fn; 19 | } 20 | 21 | public WrappedFn(IFn fn, AFunctionType t) { 22 | super(t.getReturnType(),t.getParamTypes(),t.isVariadic()); 23 | this.fn=fn; 24 | } 25 | 26 | @Override 27 | public Object invoke() { 28 | return fn.invoke(); 29 | } 30 | 31 | @Override 32 | public Object invoke(Object arg1) { 33 | return fn.invoke(arg1); 34 | } 35 | 36 | @Override 37 | public Object invoke(Object arg1, Object arg2) { 38 | return fn.invoke(arg1,arg2); 39 | } 40 | 41 | @Override 42 | public Object invoke(Object arg1, Object arg2, Object arg3) { 43 | return fn.invoke(arg1,arg2,arg3); 44 | } 45 | 46 | @Override 47 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4) { 48 | return fn.invoke(arg1,arg2,arg3,arg4); 49 | } 50 | 51 | @Override 52 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 53 | Object arg5) { 54 | return fn.invoke(arg1,arg2,arg3,arg4,arg5); 55 | } 56 | 57 | @Override 58 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 59 | Object arg5, Object arg6) { 60 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6); 61 | } 62 | 63 | @Override 64 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 65 | Object arg5, Object arg6, Object arg7) { 66 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7); 67 | } 68 | 69 | @Override 70 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 71 | Object arg5, Object arg6, Object arg7, Object arg8) { 72 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8); 73 | } 74 | 75 | @Override 76 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 77 | Object arg5, Object arg6, Object arg7, Object arg8, Object arg9) { 78 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9); 79 | } 80 | 81 | @Override 82 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 83 | Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, 84 | Object arg10) { 85 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10); 86 | } 87 | 88 | @Override 89 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 90 | Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, 91 | Object arg10, Object arg11) { 92 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11); 93 | } 94 | 95 | @Override 96 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 97 | Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, 98 | Object arg10, Object arg11, Object arg12) { 99 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12); 100 | } 101 | 102 | @Override 103 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 104 | Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, 105 | Object arg10, Object arg11, Object arg12, Object arg13) { 106 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12,arg13); 107 | } 108 | 109 | @Override 110 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 111 | Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, 112 | Object arg10, Object arg11, Object arg12, Object arg13, Object arg14) { 113 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12,arg13,arg14); 114 | } 115 | 116 | @Override 117 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 118 | Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, 119 | Object arg10, Object arg11, Object arg12, Object arg13, 120 | Object arg14, Object arg15) { 121 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12,arg13,arg14,arg15); 122 | } 123 | 124 | @Override 125 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 126 | Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, 127 | Object arg10, Object arg11, Object arg12, Object arg13, 128 | Object arg14, Object arg15, Object arg16) { 129 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12,arg13,arg14,arg15,arg16); 130 | } 131 | 132 | @Override 133 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 134 | Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, 135 | Object arg10, Object arg11, Object arg12, Object arg13, 136 | Object arg14, Object arg15, Object arg16, Object arg17) { 137 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12,arg13,arg14,arg15,arg16,arg17); 138 | } 139 | 140 | @Override 141 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 142 | Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, 143 | Object arg10, Object arg11, Object arg12, Object arg13, 144 | Object arg14, Object arg15, Object arg16, Object arg17, Object arg18) { 145 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12,arg13,arg14,arg15,arg16,arg17,arg18); 146 | } 147 | 148 | @Override 149 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 150 | Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, 151 | Object arg10, Object arg11, Object arg12, Object arg13, 152 | Object arg14, Object arg15, Object arg16, Object arg17, 153 | Object arg18, Object arg19) { 154 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12,arg13,arg14,arg15,arg16,arg17,arg18,arg19); 155 | } 156 | 157 | @Override 158 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 159 | Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, 160 | Object arg10, Object arg11, Object arg12, Object arg13, 161 | Object arg14, Object arg15, Object arg16, Object arg17, 162 | Object arg18, Object arg19, Object arg20) { 163 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12,arg13,arg14,arg15,arg16,arg17,arg18,arg19,arg20); 164 | } 165 | 166 | @Override 167 | public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, 168 | Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, 169 | Object arg10, Object arg11, Object arg12, Object arg13, 170 | Object arg14, Object arg15, Object arg16, Object arg17, 171 | Object arg18, Object arg19, Object arg20, Object... args) { 172 | return fn.invoke(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12,arg13,arg14,arg15,arg16,arg17,arg18,arg19,arg20,args); 173 | } 174 | 175 | public Object invokeArray(Object... args) { 176 | return fn.applyTo((RT.seq(KissUtils.ret1(args,args=null)))); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/type/ACompoundType.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.type; 2 | 3 | import kiss.lang.Type; 4 | 5 | 6 | public abstract class ACompoundType extends Type { 7 | protected final Type[] types; 8 | 9 | protected ACompoundType(Type[] types) { 10 | this.types=types; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/type/AFunctionType.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.type; 2 | 3 | import kiss.lang.Type; 4 | 5 | public abstract class AFunctionType extends Type { 6 | 7 | @Override 8 | public abstract Type getParamType(int i); 9 | 10 | public abstract boolean isVariadic(); 11 | 12 | public Type[] getParamTypes() { 13 | int n=getMinArity(); 14 | if (isVariadic()) n++; 15 | Type[] ts=new Type[n]; 16 | for (int i=0; i getJavaClass() { 26 | return Object.class; 27 | } 28 | 29 | @Override 30 | public boolean contains(Type t) { 31 | return true; 32 | } 33 | 34 | @Override 35 | public Type intersection(Type t) { 36 | return t; 37 | } 38 | 39 | @Override 40 | public boolean canBeNull() { 41 | return true; 42 | } 43 | 44 | @Override 45 | public boolean canBeTruthy() { 46 | return true; 47 | } 48 | 49 | @Override 50 | public boolean canBeFalsey() { 51 | return true; 52 | } 53 | 54 | @Override 55 | public Type inverse() { 56 | return Nothing.INSTANCE; 57 | } 58 | 59 | @Override 60 | public Type union(Type t) { 61 | return this; 62 | } 63 | 64 | @Override 65 | public void validate() { 66 | if (this!=Anything.INSTANCE) throw new KissException(this+ " should be a singleton!"); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/type/FunctionType.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.type; 2 | 3 | import kiss.lang.KFn; 4 | import kiss.lang.Type; 5 | import kiss.lang.impl.KissException; 6 | import kiss.lang.impl.Mapping; 7 | import clojure.lang.IFn; 8 | 9 | /** 10 | * Type representing a fixed arity function 11 | * 12 | * Covers both Kiss (KFn) and Clojure (Ifn) functions, however type checking is only 13 | * supported on Kiss functions. 14 | * 15 | * @author Mike 16 | * 17 | */ 18 | public class FunctionType extends AFunctionType { 19 | 20 | private final Type returnType; 21 | private final Type[] paramTypes; 22 | private final int minArity; 23 | private final boolean variadic; 24 | 25 | private FunctionType(Type returnType) { 26 | this(returnType,Type.EMPTY_TYPE_ARRAY,true); 27 | } 28 | 29 | private FunctionType(Type returnType, Type[] paramTypes, boolean variadic) { 30 | this.returnType=returnType; 31 | this.paramTypes=paramTypes; 32 | this.variadic=variadic; 33 | this.minArity=paramTypes.length - (variadic?1:0); 34 | } 35 | 36 | public static FunctionType create(Type returnType, Mapping[] params) { 37 | int n=params.length; 38 | Type[] ptypes=new Type[n]; 39 | for (int i=0; i=minArity; 66 | } else { 67 | return n==minArity; 68 | } 69 | } 70 | 71 | @Override 72 | public Type getReturnType() { 73 | return returnType; 74 | } 75 | 76 | @Override 77 | public boolean checkInstance(Object o) { 78 | if (o instanceof KFn) { 79 | KFn fn=(KFn)o; 80 | if (!returnType.contains(fn.getReturnType())) return false; // covariance on return type 81 | for (int i=0; i getJavaClass() { 96 | return IFn.class; 97 | } 98 | 99 | @Override 100 | public boolean contains(Type t) { 101 | if (t==this) return true; 102 | 103 | if (t instanceof FunctionType) { 104 | FunctionType ft=(FunctionType)t; 105 | 106 | // TODO: handle variable arity 107 | int n=getMinArity(); 108 | if (ft.getMinArity()!=n) return false; 109 | 110 | if (!returnType.contains(ft.returnType)) return false; 111 | 112 | for (int i=0; i jt = (JavaType) t; 146 | if (jt.klass.isAssignableFrom(KFn.class)) { 147 | return this; 148 | } else if (KFn.class.isAssignableFrom(jt.klass)) { 149 | return jt; 150 | } else { 151 | return Nothing.INSTANCE; 152 | } 153 | } 154 | 155 | return t.intersection(this); 156 | } 157 | 158 | @Override 159 | public boolean canBeNull() { 160 | return false; 161 | } 162 | 163 | @Override 164 | public boolean canBeTruthy() { 165 | return true; 166 | } 167 | 168 | @Override 169 | public boolean canBeFalsey() { 170 | return false; 171 | } 172 | 173 | @Override 174 | public boolean cannotBeFalsey() { 175 | return true; 176 | } 177 | 178 | @Override 179 | public Type inverse() { 180 | return Not.createNew(this); 181 | } 182 | 183 | @Override 184 | public Type union(Type t) { 185 | if (t==this) return t; 186 | return Union.create(this,t); 187 | } 188 | 189 | @Override 190 | public void validate() { 191 | if (variadic) { 192 | if (minArity!=paramTypes.length-1) throw new KissException("Mismatched arity count (variadic)"); 193 | } else { 194 | if (minArity!=paramTypes.length) throw new KissException("Mismatched arity count (non-variadic)"); 195 | } 196 | 197 | } 198 | 199 | @Override 200 | public Type getParamType(int i) { 201 | if (variadic&&i>=minArity) i=minArity; 202 | return paramTypes[i]; 203 | } 204 | 205 | @Override 206 | public boolean isVariadic() { 207 | return variadic; 208 | } 209 | 210 | @Override 211 | public int getMinArity() { 212 | return minArity; 213 | } 214 | 215 | } 216 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/type/Intersection.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.type; 2 | 3 | import kiss.lang.Type; 4 | import kiss.lang.impl.KissException; 5 | 6 | /** 7 | * Intersection type. 8 | * 9 | * @author Mike 10 | * 11 | */ 12 | public class Intersection extends ACompoundType { 13 | 14 | protected Intersection(Type[] types) { 15 | super(types); 16 | } 17 | 18 | private Intersection extendWith(Type t) { 19 | int n=types.length; 20 | Type[] nts=new Type[n+1]; 21 | System.arraycopy(types, 0, nts, 0, n); 22 | nts[n]=t; 23 | return new Intersection(nts); 24 | } 25 | 26 | private Intersection replaceWith(int i, Type t) { 27 | int n=types.length; 28 | Type[] nts=new Type[n]; 29 | System.arraycopy(types, 0, nts, 0, n); 30 | nts[i]=t; 31 | return new Intersection(nts); 32 | } 33 | 34 | public static Type create(Type... types) { 35 | if (types.length==0) return Anything.INSTANCE; 36 | if (types.length==1) return types[0]; 37 | return new Intersection(types); 38 | } 39 | 40 | @Override 41 | public boolean checkInstance(Object o) { 42 | for (int i=0; i getJavaClass() { 50 | Class c=types[0].getJavaClass(); 51 | for (int i=1; i ci=types[i].getJavaClass(); 53 | if (c.isAssignableFrom(ci)) { 54 | c=ci; 55 | } 56 | } 57 | return c; 58 | } 59 | 60 | @Override 61 | public Type getReturnType() { 62 | Type t=types[0].getReturnType(); 63 | for (int i=1; i=0) return replaceWith(rep,t); 120 | return extendWith(t); 121 | } 122 | 123 | @Override 124 | public Type inverse() { 125 | return Not.createNew(this); 126 | } 127 | 128 | @Override 129 | public boolean isWellBehaved() { 130 | // not a well behaved type 131 | return false; 132 | } 133 | 134 | @Override 135 | public Type union(Type t) { 136 | return super.union(t); 137 | } 138 | 139 | @Override 140 | public String toString() { 141 | StringBuilder sb=new StringBuilder(); 142 | sb.append("(I"); 143 | for (int i=0; i extends Type { 14 | final Class klass; 15 | 16 | public static final JavaType BOOLEAN=create(Boolean.class); 17 | public static final JavaType KISS_TYPE = create(Type.class); 18 | public static final JavaType SYMBOL = create(Symbol.class); 19 | public static final JavaType KEYWORD = create(Keyword.class); 20 | public static final JavaType NUMBER = create(Number.class); 21 | public static final JavaType OBJECT = create(Object.class); 22 | public static final JavaType STRING = create(String.class); 23 | 24 | public JavaType(Class c) { 25 | klass=c; 26 | } 27 | 28 | @SuppressWarnings("unchecked") 29 | public static JavaType analyse(T val) { 30 | return new JavaType((Class) val.getClass()); 31 | } 32 | 33 | public static JavaType create(Class c) { 34 | if (c==null) throw new NullPointerException("Null Class not allowed for JavaType"); 35 | return new JavaType(c); 36 | } 37 | 38 | @Override 39 | public boolean checkInstance(Object o) { 40 | return (o!=null)&&klass.isInstance(o); 41 | } 42 | 43 | @Override 44 | public T cast(Object a) { 45 | return klass.cast(a); 46 | } 47 | 48 | @Override 49 | public Class getJavaClass() { 50 | return klass; 51 | } 52 | 53 | @Override 54 | public boolean contains(Type t) { 55 | if (t==this) return true; 56 | 57 | if (t instanceof JavaType) { 58 | JavaType jt=(JavaType)t; 59 | if (klass==jt.klass) return true; 60 | return klass.isAssignableFrom(jt.klass); 61 | } else { 62 | // TODO: check logic 63 | // not a Java type, so can't contain? 64 | return false; 65 | } 66 | } 67 | 68 | @Override 69 | public Type intersection(Type t) { 70 | if ((t==this)||(t instanceof Anything)) return this; 71 | 72 | if (t instanceof Null) return Nothing.INSTANCE; 73 | if (t instanceof Maybe) { 74 | return ((Maybe)t).intersection(this); 75 | } 76 | if (t instanceof JavaType) { 77 | JavaType jt=(JavaType)t; 78 | if (this.klass==jt.klass) return this; 79 | if (this.contains(t)) return t; 80 | if (t.contains(this)) return this; 81 | return Nothing.INSTANCE; 82 | } 83 | return t.intersection(this); 84 | } 85 | 86 | @Override 87 | public boolean canBeNull() { 88 | return false; 89 | } 90 | 91 | @Override 92 | public boolean cannotBeNull() { 93 | return true; 94 | } 95 | 96 | @Override 97 | public boolean canBeTruthy() { 98 | return true; 99 | } 100 | 101 | @Override 102 | public boolean cannotBeTruthy() { 103 | return false; 104 | } 105 | 106 | @Override 107 | public boolean canBeFalsey() { 108 | return klass.isAssignableFrom(Boolean.class); 109 | } 110 | 111 | @Override 112 | public boolean cannotBeFalsey() { 113 | return !klass.isAssignableFrom(Boolean.class); 114 | } 115 | 116 | @Override 117 | public Type inverse() { 118 | return Not.createNew(this); 119 | } 120 | 121 | @Override 122 | public String toString() { 123 | return "(JavaType "+klass.toString()+")"; 124 | } 125 | 126 | @Override 127 | public JavaType toJavaType() { 128 | return this; 129 | } 130 | 131 | @Override 132 | public Type union(Type t) { 133 | if (t==this) return this; 134 | if (t instanceof JavaType) { 135 | JavaType jt=(JavaType) t; 136 | if (jt.klass==this.klass) return this; 137 | if (jt.klass.isAssignableFrom(this.klass)) return jt; 138 | if (this.klass.isAssignableFrom(jt.klass)) return this; 139 | } 140 | if (t instanceof Null) { 141 | return Maybe.create(this); 142 | } 143 | return super.union(t); 144 | } 145 | 146 | @Override 147 | public void validate() { 148 | // OK 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/type/Maybe.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.type; 2 | 3 | import kiss.lang.Type; 4 | import kiss.lang.impl.KissException; 5 | 6 | /** 7 | * Maybe type, represents the type of values that may be either null or non-null values of another type 8 | * 9 | * Specialises Reference to a specific subset of non-null types 10 | * 11 | * @author Mike 12 | * 13 | */ 14 | public class Maybe extends Type { 15 | Type type; 16 | 17 | private Maybe(Type t) { 18 | this.type=t; 19 | } 20 | 21 | public static Type create(Type t) { 22 | if ((t instanceof Null)||(t instanceof Nothing)) { 23 | return Null.INSTANCE; 24 | } 25 | if (t.checkInstance(null)) { 26 | return t; 27 | } 28 | if (t instanceof Something) { 29 | return Reference.INSTANCE; 30 | } 31 | return new Maybe(t.intersection(Reference.INSTANCE)); 32 | } 33 | 34 | @Override 35 | public boolean checkInstance(Object o) { 36 | return (o==null)||type.checkInstance(o); 37 | } 38 | 39 | @Override 40 | public Class getJavaClass() { 41 | return type.getJavaClass(); 42 | } 43 | 44 | @Override 45 | public Type getReturnType() { 46 | return type.getReturnType(); 47 | } 48 | 49 | @Override 50 | public boolean contains(Type t) { 51 | if (t==this) return true; 52 | 53 | if (t instanceof Maybe) { 54 | return type.contains(((Maybe)t).type); 55 | } 56 | return Null.INSTANCE.contains(t)||type.contains(t); 57 | } 58 | 59 | @Override 60 | public Type intersection(Type t) { 61 | if ((t==this)||(t instanceof Anything)||(t instanceof Reference)) return this; 62 | 63 | // handle possible null cases 64 | if (t instanceof Null) return t; 65 | if (t instanceof Maybe) { 66 | Type mt=((Maybe)t).type; 67 | Type it = type.intersection(mt); 68 | if (it==type) return this; 69 | if (it==mt) return t; 70 | return Maybe.create(it); 71 | } 72 | 73 | return type.intersection(t); 74 | } 75 | 76 | @Override 77 | public boolean canBeNull() { 78 | return true; 79 | } 80 | 81 | @Override 82 | public boolean canBeTruthy() { 83 | return true; 84 | } 85 | 86 | @Override 87 | public boolean canBeFalsey() { 88 | return true; 89 | } 90 | 91 | @Override 92 | public Type inverse() { 93 | return Not.createNew(this); 94 | } 95 | 96 | @Override 97 | public Type union(Type t) { 98 | // handle optimisable Null cases 99 | if (t instanceof Null) return this; 100 | if (t instanceof Maybe) { 101 | Type ot=((Maybe)t).type; 102 | if (ot==type) return this; 103 | if (ot.contains(type)) return t; 104 | if (type.contains(ot)) return this; 105 | t=ot; // fall through, just consider the non-null case 106 | } 107 | if (type.contains(t)) return this; 108 | 109 | return Union.create(Null.INSTANCE,type,t); 110 | } 111 | 112 | @Override 113 | public void validate() { 114 | if (type.checkInstance(null)) { 115 | throw new KissException("Maybe should not contain nullable type!"); 116 | } 117 | type.validate(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/type/Not.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.type; 2 | 3 | import kiss.lang.Type; 4 | 5 | /** 6 | * Represents the inverse of a given type 7 | * 8 | * @author Mike 9 | */ 10 | public class Not extends Type { 11 | 12 | private Type type; 13 | 14 | private Not(Type t) { 15 | this.type=t; 16 | } 17 | 18 | public static Type create(Type t) { 19 | if (t instanceof Nothing) return Anything.INSTANCE; 20 | if (t instanceof Anything) return Nothing.INSTANCE; 21 | return t.inverse(); 22 | } 23 | 24 | public static Not createNew(Type t) { 25 | return new Not(t); 26 | } 27 | 28 | @Override 29 | public boolean checkInstance(Object o) { 30 | return !(type.checkInstance(o)); 31 | } 32 | 33 | @Override 34 | public Class getJavaClass() { 35 | return Object.class; 36 | } 37 | 38 | @Override 39 | public boolean canBeNull() { 40 | return (type.cannotBeNull()); 41 | } 42 | 43 | @Override 44 | public boolean cannotBeNull() { 45 | return (type.canBeNull()); 46 | } 47 | 48 | @Override 49 | public boolean canBeTruthy() { 50 | return type.cannotBeTruthy(); 51 | } 52 | 53 | @Override 54 | public boolean canBeFalsey() { 55 | return type.cannotBeFalsey(); 56 | } 57 | 58 | @Override 59 | public boolean contains(Type t) { 60 | return false; 61 | } 62 | 63 | @Override 64 | public Type intersection(Type t) { 65 | if (t instanceof Not) { 66 | return Union.create(((Not)t).type,type).inverse(); 67 | } 68 | if (t instanceof Anything) return this; 69 | // TODO: better specialisation via Intersection? 70 | return t; 71 | } 72 | 73 | @Override 74 | public Type inverse() { 75 | return type; 76 | } 77 | 78 | @Override 79 | public boolean isWellBehaved() { 80 | // not a well behaved type 81 | return false; 82 | } 83 | 84 | @Override 85 | public Type union(Type t) { 86 | // TODO any applicable optimisations here? 87 | return super.union(t); 88 | } 89 | 90 | @Override 91 | public void validate() { 92 | type.validate(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/type/Nothing.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.type; 2 | 3 | import kiss.lang.Type; 4 | import kiss.lang.impl.KissException; 5 | 6 | /** 7 | * A type that has no possible instances. 8 | * 9 | * @author Mike 10 | * 11 | */ 12 | public class Nothing extends Type { 13 | 14 | public static final Nothing INSTANCE = new Nothing(); 15 | 16 | private Nothing() { 17 | // nothing to do, this is a singleton 18 | } 19 | 20 | @Override 21 | public boolean checkInstance(Object o) { 22 | // nothing is an instance of this type 23 | return false; 24 | } 25 | 26 | @Override 27 | public Class getJavaClass() { 28 | // TODO figure out if this is correct? 29 | return Void.TYPE; 30 | } 31 | 32 | @Override 33 | public Type getReturnType() { 34 | // not a function, so can't return anything 35 | return Nothing.INSTANCE; 36 | } 37 | 38 | @Override 39 | public boolean contains(Type t) { 40 | // nothing is an instance of this type 41 | return t==this; 42 | } 43 | 44 | @Override 45 | public Type intersection(Type t) { 46 | return Nothing.INSTANCE; 47 | } 48 | 49 | @Override 50 | public boolean canBeFalsey() { 51 | return false; 52 | } 53 | 54 | @Override 55 | public boolean canBeNull() { 56 | return false; 57 | } 58 | 59 | @Override 60 | public boolean canBeTruthy() { 61 | return false; 62 | } 63 | 64 | @Override 65 | public boolean cannotBeFalsey() { 66 | return true; 67 | } 68 | 69 | @Override 70 | public boolean cannotBeNull() { 71 | return true; 72 | } 73 | 74 | @Override 75 | public boolean cannotBeTruthy() { 76 | return true; 77 | } 78 | 79 | 80 | @Override 81 | public Type inverse() { 82 | return Anything.INSTANCE; 83 | } 84 | 85 | @Override 86 | public Type union(Type t) { 87 | return t; 88 | } 89 | 90 | @Override 91 | public void validate() { 92 | if (this!=Nothing.INSTANCE) throw new KissException(this+ " should be a singleton!"); 93 | } 94 | 95 | @Override 96 | public Object cast(Object a) { 97 | throw new ClassCastException("Can't cast to Nothing!"); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/type/Null.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.type; 2 | 3 | import kiss.lang.Type; 4 | import kiss.lang.impl.KissException; 5 | 6 | /** 7 | * The type of the value null 8 | * 9 | * @author Mike 10 | * 11 | */ 12 | public class Null extends Type { 13 | 14 | public static final Null INSTANCE = new Null(); 15 | 16 | private Null() { 17 | // nothing to do 18 | } 19 | 20 | @Override 21 | public boolean checkInstance(Object o) { 22 | return o==null; 23 | } 24 | 25 | @Override 26 | public Object cast(Object a) { 27 | if (a!=null) throw new ClassCastException("Can't cast non-null object to null"); 28 | return null; 29 | } 30 | 31 | @Override 32 | public Class getJavaClass() { 33 | return Void.TYPE; 34 | } 35 | 36 | @Override 37 | public Type getReturnType() { 38 | return Nothing.INSTANCE; 39 | } 40 | 41 | @Override 42 | public boolean contains(Type t) { 43 | return t==INSTANCE; 44 | } 45 | 46 | @Override 47 | public Type intersection(Type t) { 48 | if (t.canBeNull()) return this; 49 | return Nothing.INSTANCE; 50 | } 51 | 52 | @Override 53 | public boolean canBeNull() { 54 | return true; 55 | } 56 | 57 | @Override 58 | public boolean canBeTruthy() { 59 | return false; 60 | } 61 | 62 | @Override 63 | public boolean cannotBeTruthy() { 64 | return true; 65 | } 66 | 67 | @Override 68 | public boolean canBeFalsey() { 69 | return true; 70 | } 71 | 72 | @Override 73 | public Type inverse() { 74 | // TODO: what about primitives? 75 | return Something.INSTANCE; 76 | } 77 | 78 | @Override 79 | public Type union(Type t) { 80 | if (t.checkInstance(null)) return t; 81 | return Maybe.create(t); 82 | } 83 | 84 | @Override 85 | public void validate() { 86 | if (this!=Null.INSTANCE) throw new KissException(this+ " should be a singleton!"); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/type/Predicate.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.type; 2 | 3 | import kiss.lang.KFn; 4 | import kiss.lang.Type; 5 | import clojure.lang.RT; 6 | 7 | /** 8 | * Type defined by a predicate function. 9 | * 10 | * The predicate function must have arity 1 return true if the parameter is a member of the type, false otherwise. 11 | * 12 | * @author Mike 13 | * 14 | */ 15 | public class Predicate extends Type { 16 | 17 | private KFn pred; 18 | 19 | public Predicate(KFn fn) { 20 | this.pred=fn; 21 | } 22 | 23 | public Type create(KFn fn) { 24 | Type rt=fn.getReturnType(); 25 | if (rt.cannotBeFalsey()) return Anything.INSTANCE; 26 | if (rt.cannotBeTruthy()) return Nothing.INSTANCE; 27 | return new Predicate(fn); 28 | } 29 | 30 | @Override 31 | public boolean checkInstance(Object o) { 32 | return RT.booleanCast(pred.invoke(o)); 33 | } 34 | 35 | @Override 36 | public Class getJavaClass() { 37 | return Object.class; 38 | } 39 | 40 | @Override 41 | public Type getReturnType() { 42 | return pred.getReturnType(); 43 | } 44 | 45 | @Override 46 | public boolean canBeNull() { 47 | return checkInstance(null); 48 | } 49 | 50 | @Override 51 | public boolean canBeTruthy() { 52 | return true; 53 | } 54 | 55 | @Override 56 | public boolean canBeFalsey() { 57 | return checkInstance(null)||checkInstance(Boolean.FALSE); 58 | } 59 | 60 | @Override 61 | public boolean contains(Type t) { 62 | if (t instanceof Value) { 63 | return checkInstance(((Value) t).value); 64 | } 65 | return false; 66 | } 67 | 68 | @Override 69 | public Type intersection(Type t) { 70 | if (t instanceof Nothing) return Nothing.INSTANCE; 71 | if (t instanceof ValueSet) return t.intersection(this); 72 | if (t instanceof Value) return t.intersection(this); 73 | return Intersection.create(t,this); 74 | } 75 | 76 | @Override 77 | public void validate() { 78 | // TODO Auto-generated method stub 79 | 80 | } 81 | 82 | @Override 83 | public Type inverse() { 84 | return Not.create(this); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/type/Reference.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.type; 2 | 3 | import kiss.lang.Type; 4 | 5 | /** 6 | * Type that represents any reference (including null) 7 | * 8 | * @author Mike 9 | * 10 | */ 11 | public class Reference extends Type { 12 | 13 | public static final Reference INSTANCE = new Reference(); 14 | 15 | @Override 16 | public boolean checkInstance(Object o) { 17 | return true; 18 | } 19 | 20 | @Override 21 | public Object cast(Object a) { 22 | return a; 23 | } 24 | 25 | @Override 26 | public Class getJavaClass() { 27 | return Object.class; 28 | } 29 | 30 | @Override 31 | public boolean canBeNull() { 32 | return true; 33 | } 34 | 35 | @Override 36 | public boolean canBeTruthy() { 37 | return true; 38 | } 39 | 40 | @Override 41 | public boolean canBeFalsey() { 42 | // TODO Auto-generated method stub 43 | return false; 44 | } 45 | 46 | @Override 47 | public boolean contains(Type t) { 48 | // TODO filter out primitives? 49 | return true; 50 | } 51 | 52 | @Override 53 | public Type intersection(Type t) { 54 | // TODO filter out primitives? 55 | return t; 56 | } 57 | 58 | @Override 59 | public Type inverse() { 60 | // TODO what about primitives? 61 | return Nothing.INSTANCE; 62 | } 63 | 64 | @Override 65 | public Type union(Type t) { 66 | // TODO what about primitives? 67 | return this; 68 | } 69 | 70 | @Override 71 | public void validate() { 72 | // OK? 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/type/Something.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.type; 2 | 3 | import kiss.lang.Type; 4 | import kiss.lang.impl.KissException; 5 | 6 | /** 7 | * Type that represents any non-null reference 8 | * 9 | * @author Mike 10 | */ 11 | public class Something extends Type { 12 | public static final Something INSTANCE=new Something(); 13 | 14 | private Something() { 15 | // nothing to do, this is a singleton 16 | } 17 | 18 | @Override 19 | public boolean checkInstance(Object o) { 20 | return (o!=null); 21 | } 22 | 23 | @Override 24 | public Object cast(Object a) { 25 | if (a!=null) throw new ClassCastException("null cannot be cast to Something"); 26 | return a; 27 | } 28 | 29 | @Override 30 | public Class getJavaClass() { 31 | return Object.class; 32 | } 33 | 34 | @Override 35 | public boolean contains(Type t) { 36 | // we just need to eliminate null possibilities 37 | if (t instanceof Maybe) return false; 38 | if (t instanceof Null) return false; 39 | return true; 40 | } 41 | 42 | @Override 43 | public Type intersection(Type t) { 44 | if ((t==this)||(t instanceof Anything)||(t instanceof Reference)) return this; 45 | 46 | if ((t instanceof Null)||(t instanceof Nothing)) return Nothing.INSTANCE; 47 | if (t instanceof Maybe) return ((Maybe)t).type; 48 | return t; 49 | } 50 | 51 | @Override 52 | public boolean canBeNull() { 53 | return false; 54 | } 55 | 56 | @Override 57 | public boolean cannotBeNull() { 58 | return true; 59 | } 60 | 61 | @Override 62 | public boolean canBeTruthy() { 63 | return true; 64 | } 65 | 66 | @Override 67 | public boolean canBeFalsey() { 68 | return true; 69 | } 70 | 71 | @Override 72 | public Type inverse() { 73 | // TODO what about primitives? 74 | return Null.INSTANCE; 75 | } 76 | 77 | @Override 78 | public Type union(Type t) { 79 | // TODO what about primitives? 80 | if (t.cannotBeNull()) return this; 81 | return Reference.INSTANCE; 82 | } 83 | 84 | @Override 85 | public void validate() { 86 | if (this!=Something.INSTANCE) throw new KissException(this+ " should be a singleton!"); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/type/Union.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.type; 2 | 3 | import kiss.lang.Type; 4 | import kiss.lang.impl.KissException; 5 | 6 | public class Union extends ACompoundType { 7 | 8 | protected Union(Type[] types) { 9 | super(types); 10 | } 11 | 12 | /** 13 | * Compress an array of types to create a Union data structure. May destroy the passed array. 14 | * @param types 15 | * @return 16 | */ 17 | private static Type[] compress(Type... types) { 18 | int n=types.length; 19 | int found=0; 20 | for (int i=0; i 13 | */ 14 | public class Value extends Type { 15 | final T value; 16 | private final Class klass; 17 | 18 | @SuppressWarnings("unchecked") 19 | private Value(T value) { 20 | this.value=value; 21 | this.klass=(Class) value.getClass(); 22 | } 23 | 24 | public static Type create(T value) { 25 | if (value==null) return Null.INSTANCE; 26 | return new Value(value); 27 | } 28 | 29 | @Override 30 | public boolean checkInstance(Object o) { 31 | return value.equals(o); 32 | } 33 | 34 | @Override 35 | public Class getJavaClass() { 36 | return klass; 37 | } 38 | @Override 39 | public boolean canBeNull() { 40 | return false; 41 | } 42 | 43 | @Override 44 | public boolean cannotBeNull() { 45 | return true; 46 | } 47 | 48 | @Override 49 | public boolean canBeTruthy() { 50 | return value!=Boolean.FALSE; 51 | } 52 | 53 | @Override 54 | public boolean canBeFalsey() { 55 | return value==Boolean.FALSE; 56 | } 57 | 58 | @Override 59 | public boolean cannotBeTruthy() { 60 | return value==Boolean.FALSE; 61 | } 62 | 63 | @Override 64 | public boolean cannotBeFalsey() { 65 | return value!=Boolean.FALSE; 66 | } 67 | 68 | @Override 69 | public boolean contains(Type t) { 70 | if (t==this) return true; 71 | if (t instanceof Nothing) return true; 72 | if (t instanceof Value) { 73 | Value ev=(Value) t; 74 | if (ev.klass!=this.klass) return false; 75 | return ev.value.equals(this.value); 76 | } 77 | return false; 78 | } 79 | 80 | @Override 81 | public Type intersection(Type t) { 82 | if (t.checkInstance(value)) return this; 83 | return Nothing.INSTANCE; 84 | } 85 | 86 | @Override 87 | public Type inverse() { 88 | return Not.createNew(this); 89 | } 90 | 91 | @Override 92 | public Type union(Type t) { 93 | if (t==this) return t; 94 | if (t.checkInstance(value)) return t; 95 | if (t instanceof Value) { 96 | Object tv=((Value)t).value; 97 | if (tv.equals(this.value)) return this; 98 | return ValueSet.create(new Object[] {value,tv}); 99 | } 100 | if (t instanceof ValueSet) { 101 | return t.union(this); 102 | } 103 | 104 | return super.union(t); 105 | } 106 | 107 | @Override 108 | public boolean equals(Object t) { 109 | if (t instanceof Value) { 110 | Value v=(Value) t; 111 | if (KissUtils.equalsWithNulls(v.value,this.value)) return true; 112 | return false; 113 | } 114 | return super.equals(t); 115 | } 116 | 117 | @Override 118 | public String toString() { 119 | return "(Value "+value.toString()+")"; 120 | } 121 | 122 | @Override 123 | public void validate() { 124 | if (!(klass.isInstance(value))) throw new KissException(value+ " is of wrong type, should be "+klass); 125 | } 126 | 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/kiss/lang/type/ValueSet.java: -------------------------------------------------------------------------------- 1 | package kiss.lang.type; 2 | 3 | import java.util.Collection; 4 | 5 | import clojure.lang.ISeq; 6 | import clojure.lang.PersistentHashSet; 7 | import clojure.lang.RT; 8 | import kiss.lang.Type; 9 | import kiss.lang.impl.KissException; 10 | 11 | /** 12 | * The type of a set of 2 or more values. Values may include null. 13 | * 14 | * 15 | * 16 | * @author Mike 17 | * 18 | * @param 19 | */ 20 | public class ValueSet extends Type { 21 | private final PersistentHashSet values; 22 | private final Class klass; 23 | 24 | @SuppressWarnings("unchecked") 25 | private ValueSet(PersistentHashSet values) { 26 | this.values=values; 27 | this.klass=(Class) Object.class; 28 | } 29 | 30 | public static Type create(Collection values) { 31 | int n=values.size(); 32 | if (n==0) return Nothing.INSTANCE; 33 | if (n==1) return Value.create(values.iterator().next()); 34 | return new ValueSet(PersistentHashSet.create(RT.seq(values))); 35 | } 36 | 37 | public static Type create(Object[] values) { 38 | int n=values.length; 39 | if (n==0) return Nothing.INSTANCE; 40 | if (n==1) return Value.create(values[0]); 41 | return new ValueSet(PersistentHashSet.create(RT.seq(values))); 42 | } 43 | 44 | public Type update(PersistentHashSet values) { 45 | if (values==this.values) return this; 46 | int n=values.count(); 47 | if (n==1) return Value.create(values.seq().first()); 48 | if (n==0) return Nothing.INSTANCE; 49 | return new ValueSet(values); 50 | } 51 | 52 | @Override 53 | public boolean checkInstance(Object o) { 54 | return values.contains(o); 55 | } 56 | 57 | @Override 58 | public Class getJavaClass() { 59 | return klass; 60 | } 61 | @Override 62 | public boolean canBeNull() { 63 | return values.contains(null); 64 | } 65 | 66 | @Override 67 | public boolean cannotBeNull() { 68 | return !values.contains(null); 69 | } 70 | 71 | @Override 72 | public boolean canBeTruthy() { 73 | return true; 74 | } 75 | 76 | @Override 77 | public boolean isWellBehaved() { 78 | // not a well behaved type 79 | return false; 80 | } 81 | 82 | @Override 83 | public boolean canBeFalsey() { 84 | return (values.contains(Boolean.FALSE))||(values.contains(null)); 85 | } 86 | 87 | @Override 88 | public boolean cannotBeTruthy() { 89 | return false; 90 | } 91 | 92 | @Override 93 | public boolean cannotBeFalsey() { 94 | return !((values.contains(Boolean.FALSE))||(values.contains(null))); 95 | } 96 | 97 | @Override 98 | public boolean contains(Type t) { 99 | if (t==this) return true; 100 | if (t instanceof Nothing) return true; 101 | if (t instanceof Value) { 102 | Value ev=(Value) t; 103 | return values.contains(ev.value); 104 | } 105 | if (t instanceof ValueSet) { 106 | @SuppressWarnings("rawtypes") 107 | PersistentHashSet tvs=((ValueSet) t).values; 108 | return tvs.containsAll(values); 109 | } 110 | 111 | return false; 112 | } 113 | 114 | @Override 115 | public Type intersection(Type t) { 116 | PersistentHashSet values=this.values; 117 | ISeq s=values.seq(); 118 | while(s!=null) { 119 | Object o=s.first(); 120 | if (!t.checkInstance(o)) { 121 | values=(PersistentHashSet) values.disjoin(o); 122 | } 123 | s=s.next(); 124 | } 125 | return update(values); 126 | } 127 | 128 | @Override 129 | public Type inverse() { 130 | return Not.createNew(this); 131 | } 132 | 133 | @Override 134 | public Type union(Type t) { 135 | if (t==this) return t; 136 | if (t instanceof Value) { 137 | Object value=((Value)t).value; 138 | if (values.contains(value)) { 139 | return this; 140 | } else { 141 | return update((PersistentHashSet) values.cons(value)); 142 | } 143 | } 144 | return super.union(t); 145 | } 146 | 147 | @Override 148 | public boolean equals(Object t) { 149 | if (t instanceof ValueSet) { 150 | return values.equals(t); 151 | } 152 | return super.equals(t); 153 | } 154 | 155 | @Override 156 | public String toString() { 157 | return "(Values "+values.toString()+")"; 158 | } 159 | 160 | @Override 161 | public void validate() { 162 | if (values.count()<=1) throw new KissException("Insufficient values in ValueSet!"); 163 | 164 | // TODO: class tests? 165 | } 166 | 167 | 168 | } 169 | -------------------------------------------------------------------------------- /src/main/resources/kiss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikera/kiss/fb71dccc08014a40179384d906bde4fccb81c6de/src/main/resources/kiss.png -------------------------------------------------------------------------------- /src/test/clojure/kiss/demo/blank.clj: -------------------------------------------------------------------------------- 1 | (ns kiss.demo.blank 2 | (:use kiss.core)) 3 | -------------------------------------------------------------------------------- /src/test/clojure/kiss/demo/example.clj: -------------------------------------------------------------------------------- 1 | (ns kiss.demo.example 2 | (:use kiss.core)) 3 | 4 | ;; ==================================================================== 5 | ;; Kiss works with immutable Environments. Let's play with one 6 | ;; ==================================================================== 7 | 8 | ;; define an environment to work with 9 | (def env (environment {foo 1, 10 | bar 3, 11 | f clojure.core/inc 12 | baz (clojure.core/+ foo bar)})) 13 | 14 | ;; ================================================= 15 | ;; Environments are specialised Clojure maps 16 | 17 | (map? env) 18 | ; => true 19 | 20 | (env 'foo) 21 | ; => 1 22 | 23 | (keys env) 24 | ; => (baz foo bar f) 25 | 26 | (class env) 27 | ; => kiss.lang.Environment 28 | 29 | ;; ================================================= 30 | ;; Expression results are computed as values 31 | 32 | (env 'f) 33 | ;; => # 34 | ;; i.e. a Clojure function, looked up via the var #'clojure.core/inc 35 | 36 | (env 'baz) 37 | ;; => 4 38 | ;; i.e. the expression (+ foo bar) has been computed 39 | 40 | 41 | ;; ================================================= 42 | ;; Changing an environment 43 | ;; - leaves original environment unchanged 44 | ;; - causes update of dependent Expressions (!!!!!!) 45 | 46 | (let [env1 env 47 | env2 (assoc env 'foo 2)] 48 | [(env1 'foo) (env2 'foo)]) 49 | ; => [1 2] 50 | 51 | 52 | ;; ==================================================================== 53 | ;; Kiss code can be executed against any envirnment 54 | ;; ==================================================================== 55 | 56 | (kiss env (f foo)) 57 | ;; => 2 58 | 59 | ;; ================================================= 60 | ;; Clojure functions can also be used 61 | 62 | (kiss env (clojure.core/range bar)) 63 | ;; => (0 1 2) -------------------------------------------------------------------------------- /src/test/clojure/kiss/test/test_clojure.clj: -------------------------------------------------------------------------------- 1 | (ns kiss.test.test-clojure 2 | (:use clojure.test) 3 | (:use kiss.core) 4 | (:use [mikera.cljutils error]) 5 | (:import [kiss.lang Expression Environment] )) 6 | 7 | (deftest number-tests 8 | (is (= 5 (kiss (clojure.core/+ 2 3))))) 9 | 10 | ;; TODO: figure out how to make Clojure macros work 11 | ;;(deftest macro-tests 12 | ;; (is (= 2 (kiss (clojure.core/or 2 3))))) 13 | 14 | -------------------------------------------------------------------------------- /src/test/clojure/kiss/test/test_core.clj: -------------------------------------------------------------------------------- 1 | (ns kiss.test.test-core 2 | (:use clojure.test) 3 | (:use kiss.core) 4 | (:use [mikera.cljutils error]) 5 | (:import [kiss.lang Expression Environment] )) 6 | 7 | (deftest environment-tests 8 | (let [e (empty-environment)] 9 | (is (empty? (seq e))) 10 | (let [e (assoc e 'foo 1)] 11 | (is (== 1 (e 'foo))))) 12 | (let [env (environment {foo 1, 13 | bar 2, 14 | baz (clojure.core/+ foo bar)})] 15 | (is (== 3 ('baz env))))) 16 | 17 | (deftest test-analyser 18 | (let [^Environment e (environment) 19 | ^Expression ex (analyse 1)] 20 | (is (instance? Expression ex)) 21 | (is (== 1 (.eval ex e))))) 22 | 23 | (deftest test-lookup 24 | (let [^Environment e (environment) 25 | e (assoc e 'foo 10) 26 | ^Expression ex (analyse 'foo)] 27 | (is (instance? Expression ex)) 28 | (is (== 10 (.eval ex e))) 29 | (is (== 10 (kiss e foo))) 30 | (is (== 17 (kiss e (let [foo 17] foo)))))) 31 | 32 | (deftest test-errors 33 | (is (error? (kiss (1)))) 34 | (is (error? (kiss (1 2)))) 35 | (is (error? (kiss (clojure.core/+ 1 "foo"))))) 36 | 37 | (deftest test-constants 38 | (is (= nil (kiss nil))) 39 | (is (= 1 (kiss 1)))) 40 | 41 | (deftest test-let 42 | (is (== 13 (kiss (let [a 13] a)))) 43 | (is (== 13 (kiss (let [a 13 b a] b)))) 44 | (is (error? (kiss (let [a 13 b] b))))) 45 | 46 | (deftest test-if 47 | (is (== 4 (kiss (if true 4 5)))) 48 | (is (== 5 (kiss (if false 9 5)))) 49 | (is (== 5 (kiss (if nil 9 5)))) 50 | (is (= "foo" (kiss (if false 9 "foo"))))) 51 | 52 | (deftest test-def 53 | (let [e (kisse (def kiss.core/a 1))] 54 | (is (instance? Environment e)) 55 | (is (= 1 (e 'kiss.core/a)))) 56 | (let [e (kisse (do (def kiss.core/a 1) (def kiss.core/b 2)))] 57 | (is (instance? Environment e)) 58 | (is (= 1 (e 'kiss.core/a))) 59 | (is (= 2 (e 'kiss.core/b)))) 60 | (let [e (kisse (let [foo 2] (def kiss.core/a (clojure.core/+ foo 1))))] 61 | (is (instance? Environment e)) 62 | (is (= 3 (e 'kiss.core/a))))) 63 | 64 | (deftest test-merge 65 | (let [e1 (kisse (def a 1)) 66 | e2 (kisse (def b 2)) 67 | e3 (kmerge e1 e2)] 68 | (is (= 1 (e3 'a))) 69 | (is (= 2 (e3 'b))))) 70 | 71 | (deftest test-vectors 72 | (is (= [] (kiss []))) 73 | (is (= [1] (kiss [1]))) 74 | (is (= [3 5 nil] (kiss [(clojure.core/+ 1 2) (clojure.core/inc 4) nil])))) 75 | 76 | (deftest test-loop-recur 77 | ;; (is (error? (kiss (recur 2)))) ;; TODO: figure out wha this should do? 78 | ) 79 | 80 | (deftest test-return 81 | (is (= 3 (kiss ((fn [x] (do (return (clojure.core/inc x)) (clojure.core/dex x))) 2)))) 82 | ;; (is (error? (kiss (return 3)))) ; TODO figure out what this should be? 83 | ) 84 | 85 | (deftest test-maps 86 | (is (= {} (kiss {}))) 87 | (is (= {5 3} (kiss {(clojure.core/+ 2 3) (clojure.core/+ 1 2)})))) 88 | 89 | (deftest test-clojure-fn 90 | (is (== 3 (kiss (clojure.core/+ 1 2)))) 91 | (is (nil? (kiss ({} 2))))) 92 | 93 | (deftest test-unbound-error 94 | (is (== 6 (kiss (do (def b a) 6)))) 95 | (is (error? (kiss (do (def b a) b)))) 96 | ;; (is (== 1 (kiss (do (def b a) (def a 1) b)))) 97 | (is (== 1 (kiss (do (def a 1) a))))) 98 | 99 | (deftest test-lambda 100 | (is (== 3 (kiss ((fn [x] 3) 2))))) 101 | -------------------------------------------------------------------------------- /src/test/clojure/kiss/test/test_examples.clj: -------------------------------------------------------------------------------- 1 | (ns kiss.test.test-examples 2 | (:use clojure.test) 3 | (:use kiss.core) 4 | (:use [mikera.cljutils error]) 5 | (:import [kiss.lang Expression Environment] )) 6 | 7 | (deftest test-defines 8 | (is (= [1 2 3] 9 | (kiss 10 | (do 11 | (def g (fn [x] (clojure.core/inc x))) 12 | (def map (fn [f xs] (clojure.core/map f xs))) 13 | (map g [0 1 2])))))) -------------------------------------------------------------------------------- /src/test/clojure/kiss/test/test_lambda.clj: -------------------------------------------------------------------------------- 1 | (ns kiss.test.test-lambda 2 | (:use clojure.test) 3 | (:use [mikera.cljutils error]) 4 | (:use kiss.core)) 5 | 6 | (deftest test-lambda-application 7 | (testing "Apply a lambda to select a single argument" 8 | (is (== 4 (kiss ((fn [x y] x) 4 5)))))) 9 | 10 | (deftest test-higher-order 11 | (testing "Higher order functions returning a lambda" 12 | (is (== 4 (kiss (((fn [f] (fn [g] 4)) 5) 6))))) 13 | (testing "Higher order functions returning a lambda, overriding lexical params" 14 | (is (== 4 (kiss (((fn [f] (fn [f] 4)) 5) 6)))))) 15 | 16 | (deftest test-clojure-iterop 17 | (testing "Kiss lambdas produce Clojure fns" 18 | (let [kfn (kiss (fn [] 3))] 19 | (is (== 3 (kfn)))) 20 | (let [kfn2 (kiss (fn [x y] (clojure.core/+ x y)))] 21 | (is (== 3 (kfn2 1 2))))) 22 | (testing "Clojure functions work in Kiss lambdas" 23 | (is (== 3 (kiss ((fn [] (clojure.core/+ 1 2)))))))) 24 | 25 | (deftest test-arity-error 26 | (testing "Arity errors result in an exception" 27 | (is (error? (kiss ((fn [] 3) "foo")))) 28 | (is (error? (kiss ((fn [x] 3))))))) 29 | 30 | (deftest test-closure 31 | (testing "Attempt to invoke function with undefined free variable is a runtime error" 32 | (let [kfn (kiss (fn [x] a))] 33 | (is (error? (kfn 4))))) 34 | (testing "Closing over a lexically scoped value" 35 | (let [kfn (kiss (let [a 3] (fn [x] a)))] 36 | (is (== 3 (kfn 7))))) 37 | (testing "Closing over a dynamically scoped value" 38 | (let [e (environment {a 4}) 39 | kfn (kiss e (fn [] a))] 40 | (is (== 4 (kfn)))))) 41 | -------------------------------------------------------------------------------- /src/test/java/kiss/test/EnvironmentTests.java: -------------------------------------------------------------------------------- 1 | package kiss.test; 2 | 3 | import static org.junit.Assert.*; 4 | import kiss.lang.Environment; 5 | import kiss.lang.Expression; 6 | import kiss.lang.Result; 7 | import kiss.lang.expression.Constant; 8 | import kiss.lang.expression.Def; 9 | import kiss.lang.expression.Lookup; 10 | import kiss.lang.impl.EvalResult; 11 | 12 | import org.junit.Test; 13 | 14 | import clojure.lang.Symbol; 15 | 16 | public class EnvironmentTests { 17 | 18 | @Test public void testDef() { 19 | Expression x=Def.create(Symbol.intern("foo"),Constant.create(1)); 20 | Environment e=Environment.EMPTY; 21 | 22 | Result r=x.interpret(e); 23 | Environment e2=r.getEnvironment(); 24 | assertEquals(1,e2.get(Symbol.intern("foo"))); 25 | assertEquals(null,r.getResult()); 26 | } 27 | 28 | @Test public void testValidity() { 29 | Result e=new EvalResult(); 30 | e.validate(); 31 | 32 | e=Def.create(Symbol.intern("foo"),Constant.create(1)).interpret(e); 33 | e.validate(); 34 | 35 | e=Def.create(Symbol.intern("bar"),Lookup.create("foo")).interpret(e); 36 | e.validate(); 37 | 38 | e=Def.create(Symbol.intern("bar"),Lookup.create("baz")).interpret(e); 39 | e.validate(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/kiss/test/ExpressionTests.java: -------------------------------------------------------------------------------- 1 | package kiss.test; 2 | 3 | import static org.junit.Assert.*; 4 | import kiss.lang.Analyser; 5 | import kiss.lang.Environment; 6 | import kiss.lang.Expression; 7 | import kiss.lang.Type; 8 | import kiss.lang.expression.Application; 9 | import kiss.lang.expression.Constant; 10 | import kiss.lang.expression.Def; 11 | import kiss.lang.expression.Do; 12 | import kiss.lang.expression.If; 13 | import kiss.lang.expression.Lambda; 14 | import kiss.lang.expression.Let; 15 | import kiss.lang.expression.Lookup; 16 | import kiss.lang.expression.Loop; 17 | import kiss.lang.impl.KissException; 18 | import kiss.lang.impl.KissUtils; 19 | import kiss.lang.type.Anything; 20 | import kiss.lang.type.FunctionType; 21 | 22 | import org.junit.Test; 23 | 24 | import clojure.lang.IFn; 25 | import clojure.lang.IPersistentSet; 26 | import clojure.lang.ISeq; 27 | import clojure.lang.PersistentHashSet; 28 | import clojure.lang.Symbol; 29 | 30 | public class ExpressionTests { 31 | 32 | static final Expression[] testExprs={ 33 | Constant.create(null), 34 | Constant.create("friend"), 35 | Let.create(Symbol.intern("foo"), Constant.create(3), Lookup.create("foo")), 36 | Lambda.IDENTITY, 37 | Lookup.create("foo"), 38 | If.create(Constant.create(null), Constant.create(1), Constant.create(2)), 39 | Loop.create(new Symbol[] {Symbol.intern("foo")}, 40 | new Expression[] {Constant.create(3)}, 41 | Constant.create(4)), 42 | Do.create(Constant.create(1)), 43 | Do.create(Constant.create(1),Lookup.create("foo")), 44 | Application.create(Lambda.IDENTITY, Constant.create(3)) 45 | }; 46 | 47 | @Test 48 | public void testIdentity() { 49 | Lambda id=Lambda.IDENTITY; 50 | FunctionType ft=(FunctionType) id.getType(); 51 | assertEquals(Anything.INSTANCE,ft.getReturnType()); 52 | IFn fn=(IFn) id.eval(); 53 | assertEquals(1,fn.invoke(1)); 54 | assertTrue(ft.checkInstance(fn)); 55 | } 56 | 57 | @Test 58 | public void testSpecialise() { 59 | for (Expression e:testExprs) { 60 | Type ert=e.getType(); 61 | Expression se=e.specialise(e.getType()); 62 | assertTrue("Specialise has widened return type!! "+e, ert.contains(se.getType())); 63 | } 64 | } 65 | 66 | @Test 67 | public void testSubstitutions() { 68 | for (Expression e:testExprs) { 69 | try { 70 | IPersistentSet free=e.accumulateFreeSymbols(PersistentHashSet.EMPTY); 71 | if (free.count()==0) { 72 | Object result=e.eval(); // should work 73 | assertTrue(e.getType().checkInstance(result)); 74 | } else { 75 | try { 76 | e.eval(); 77 | fail(); 78 | } catch (KissException t) { 79 | // OK! 80 | } 81 | } 82 | } catch (Throwable t) { 83 | throw new KissException("Error testing expression "+e,t); 84 | } 85 | } 86 | } 87 | 88 | @Test 89 | public void testProperties() { 90 | for (Expression e:testExprs) { 91 | try { 92 | e.validate(); 93 | } catch (Throwable t) { 94 | throw new KissException("Error testing expression "+e,t); 95 | } 96 | } 97 | } 98 | 99 | @Test 100 | public void testConstants() { 101 | assertNull(Constant.create(null).eval()); 102 | assertEquals(1,Constant.create(1).eval()); 103 | assertEquals("foo",Constant.create("foo").eval()); 104 | } 105 | 106 | @Test 107 | public void testOptimisations() { 108 | checkConstant(1,Constant.create(1)); 109 | checkConstant(2,If.create(Constant.create(1),Constant.create(2),Constant.create(3))); 110 | checkConstant(3,Do.create(Constant.create(1),Constant.create(2),Constant.create(3))); 111 | checkConstant(3,Let.create(Symbol.create("foo"),Constant.create(3),Lookup.create("foo"))); 112 | } 113 | 114 | @Test 115 | public void testLet() { 116 | assertEquals(10L, KissUtils.eval("(let [a 10 b a] b)")); 117 | } 118 | 119 | @Test 120 | public void testReturn() { 121 | assertEquals(4L, KissUtils.eval("((fn [] (do (return 4) 3)))")); 122 | } 123 | 124 | @Test 125 | public void testRecur() { 126 | assertEquals(1L, KissUtils.eval("(loop [] 1)")); 127 | assertEquals(4L, KissUtils.eval("(loop [i 1] (if (clojure.core/= i 3) 4 (recur (clojure.core/inc i))))")); 128 | } 129 | 130 | @Test 131 | public void testInstanceOf() { 132 | assertTrue(Analyser.analyse(Environment.EMPTY,KissUtils.read("(instance? Integer 2)")).isConstant()); 133 | assertEquals("foo", KissUtils.eval("(if (instance? Long 3) \"foo\" \"bar\")")); 134 | } 135 | 136 | @Test 137 | public void testNotConstant() { 138 | checkNotConstant(Lookup.create("foo")); 139 | checkNotConstant(Def.create(Symbol.intern("foo"),Constant.create(1))); 140 | } 141 | 142 | private void checkNotConstant(Expression x) { 143 | Expression opt=x.optimise(); 144 | assertFalse("Expression is constant: "+x,opt.isConstant()); 145 | } 146 | 147 | private void checkConstant(Object expected,Expression x) { 148 | Expression opt=x.optimise(); 149 | assertTrue("Expression not constant: "+x,opt.isConstant()); 150 | assertEquals(expected,opt.eval()); 151 | } 152 | 153 | @Test 154 | public void testIf() { 155 | assertEquals(2,If.create(Constant.create(null), Constant.create(1), Constant.create(2)).eval()); 156 | 157 | ISeq s=KissUtils.createSeq(Symbol.intern("if"),Symbol.intern("nil"),1,2); 158 | Expression x=Analyser.analyse(Environment.EMPTY,s); 159 | assertEquals(2,x.eval()); 160 | 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/test/java/kiss/test/KissClojureTest.java: -------------------------------------------------------------------------------- 1 | package kiss.test; 2 | 3 | import mikera.cljunit.ClojureTest; 4 | 5 | public class KissClojureTest extends ClojureTest { 6 | 7 | @Override 8 | public String filter() { 9 | return "kiss.test"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/kiss/test/TestUtils.java: -------------------------------------------------------------------------------- 1 | package kiss.test; 2 | 3 | import static org.junit.Assert.*; 4 | import kiss.lang.impl.KissUtils; 5 | 6 | import org.junit.Test; 7 | 8 | import clojure.lang.ISeq; 9 | 10 | public class TestUtils { 11 | @Test public void testTruthy() { 12 | assertTrue(KissUtils.truthy(1)); 13 | assertTrue(KissUtils.truthy(true)); 14 | assertTrue(KissUtils.truthy(new Boolean(false))); // watch out for this one!!! 15 | 16 | assertFalse(KissUtils.truthy(null)); 17 | assertFalse(KissUtils.truthy(Boolean.FALSE)); 18 | } 19 | 20 | @Test public void testRead() { 21 | assertEquals(Long.valueOf(1),KissUtils.read("1")); 22 | assertTrue(KissUtils.read("(foo 1 3 4)") instanceof ISeq); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/kiss/test/TypeTests.java: -------------------------------------------------------------------------------- 1 | package kiss.test; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import java.math.BigDecimal; 6 | 7 | import kiss.lang.Type; 8 | import kiss.lang.expression.Cast; 9 | import kiss.lang.expression.Constant; 10 | import kiss.lang.impl.KissException; 11 | import kiss.lang.type.Anything; 12 | import kiss.lang.type.Value; 13 | import kiss.lang.type.FunctionType; 14 | import kiss.lang.type.Intersection; 15 | import kiss.lang.type.JavaType; 16 | import kiss.lang.type.Maybe; 17 | import kiss.lang.type.Not; 18 | import kiss.lang.type.Nothing; 19 | import kiss.lang.type.Null; 20 | import kiss.lang.type.Something; 21 | import kiss.lang.type.Union; 22 | import kiss.lang.type.ValueSet; 23 | 24 | import org.junit.Test; 25 | 26 | public class TypeTests { 27 | 28 | static final Type[] testTypes={ 29 | Nothing.INSTANCE, 30 | Something.INSTANCE, 31 | Anything.INSTANCE, 32 | JavaType.create(Integer.class), 33 | JavaType.create(String.class), 34 | JavaType.create(Number.class), 35 | Value.create("foo"), 36 | Value.create(1), 37 | Null.INSTANCE, 38 | Maybe.create(JavaType.create(Integer.class)), 39 | Maybe.create(JavaType.create(String.class)), 40 | Not.create(JavaType.create(Integer.class)), 41 | Not.create(Value.create("foo")), 42 | ValueSet.create(new Object[] {1, "foo"}), 43 | FunctionType.create(Something.INSTANCE, Something.INSTANCE), 44 | FunctionType.create(Something.INSTANCE), 45 | FunctionType.create(Something.INSTANCE, JavaType.create(Number.class)) 46 | }; 47 | 48 | static final Object[] testObjects={null,0,1,true,false,"Foo",1.0,new Object(),Anything.INSTANCE}; 49 | 50 | 51 | @Test public void testFnType() { 52 | FunctionType t=FunctionType.create(Null.INSTANCE,JavaType.create(Integer.class)); 53 | assertEquals(1,t.getMinArity()); 54 | assertFalse(t.isVariadic()); 55 | assertEquals(1,t.getParamTypes().length); 56 | 57 | assertTrue(t.contains(FunctionType.create(Null.INSTANCE,JavaType.create(Number.class)))); 58 | assertFalse(t.contains(FunctionType.create(Null.INSTANCE,JavaType.create(String.class)))); 59 | } 60 | 61 | @Test public void testVariadic() { 62 | Type nt=JavaType.create(Number.class); 63 | FunctionType t=FunctionType.createVariadic(nt,new Type[] {nt}); 64 | t.validate(); 65 | assertTrue(t.isVariadic()); 66 | assertEquals(0,t.getMinArity()); 67 | assertEquals(1,t.getParamTypes().length); 68 | } 69 | 70 | @Test public void testExactValue() { 71 | assertTrue(Null.INSTANCE==Value.create(null)); 72 | } 73 | 74 | 75 | @Test public void testIntersections() { 76 | for (Type a:testTypes) { 77 | for (Type b: testTypes) { 78 | if (!(a.isWellBehaved()&&b.isWellBehaved())) continue; 79 | Type c=a.intersection(b); 80 | if (!c.equals(b.intersection(a))) throw new KissException(a+ " x "+b); 81 | assertEquals(c,b.intersection(a)); 82 | } 83 | } 84 | 85 | for (Type a:testTypes) { 86 | assertTrue(a==a.intersection(Anything.INSTANCE)); 87 | assertTrue(a==Anything.INSTANCE.intersection(a)); 88 | assertTrue(Nothing.INSTANCE==a.intersection(Nothing.INSTANCE)); 89 | assertTrue(Nothing.INSTANCE==Nothing.INSTANCE.intersection(a)); 90 | } 91 | } 92 | 93 | @Test public void testUnions() { 94 | for (Type a:testTypes) { 95 | for (Type b: testTypes) { 96 | Type c=a.union(b); 97 | 98 | for (Object o:testObjects) { 99 | if (a.checkInstance(o) || b.checkInstance(o)) { 100 | assertTrue("Union of "+a+" and "+b+" = "+c+ " should include "+o,c.checkInstance(o)); 101 | } 102 | } 103 | } 104 | } 105 | 106 | } 107 | 108 | @Test public void testJavaClass() { 109 | assertEquals(Integer.class,Intersection.create(JavaType.create(Integer.class),JavaType.create(Number.class)).getJavaClass()); 110 | assertEquals(Integer.class,Intersection.create(JavaType.create(Number.class),JavaType.create(Integer.class)).getJavaClass()); 111 | 112 | assertEquals(Number.class,Union.create(JavaType.create(Long.class),JavaType.create(Float.class)).getJavaClass()); 113 | assertEquals(Object.class,Union.create(JavaType.create(BigDecimal.class),JavaType.create(String.class)).getJavaClass()); 114 | assertEquals(Number.class,Union.create(JavaType.create(BigDecimal.class),JavaType.create(Number.class)).getJavaClass()); 115 | } 116 | 117 | @Test public void testParse() { 118 | assertEquals(Integer.class,Type.parse("java.lang.Integer").getJavaClass()); 119 | assertEquals(Number.class,Type.parse("(U Integer java.lang.Long)").getJavaClass()); 120 | } 121 | 122 | @Test 123 | public void testProperties() { 124 | for (Type a:testTypes) { 125 | a.validate(); 126 | 127 | if (a.canBeNull()) assertTrue(a.checkInstance(null)); 128 | if (a.cannotBeNull()) assertFalse(a.checkInstance(null)); 129 | 130 | if (a.canBeFalsey()) assertTrue("Issue with canBeFalsey with: "+a, 131 | a.checkInstance(null)||a.checkInstance(Boolean.FALSE)); 132 | 133 | for (Object o: testObjects) { 134 | assertTrue(a.checkInstance(o)!=a.inverse().checkInstance(o)); 135 | } 136 | 137 | } 138 | 139 | } 140 | 141 | @Test public void testBooleans() { 142 | assertTrue(Constant.TRUE.getType().canBeTruthy()); 143 | assertTrue(Constant.FALSE.getType().canBeFalsey()); 144 | assertFalse(Constant.TRUE.getType().cannotBeTruthy()); 145 | assertFalse(Constant.FALSE.getType().cannotBeFalsey()); 146 | assertTrue(Constant.TRUE.getType().cannotBeFalsey()); 147 | assertTrue(Constant.FALSE.getType().cannotBeTruthy()); 148 | assertFalse(Constant.TRUE.getType().canBeFalsey()); 149 | assertFalse(Constant.FALSE.getType().canBeTruthy()); 150 | } 151 | 152 | @Test public void testOddIntersections() { 153 | assertTrue(Intersection.create(Null.INSTANCE).checkInstance(null)); 154 | assertFalse(Intersection.create(Null.INSTANCE,JavaType.create(Integer.class)).checkInstance(null)); 155 | } 156 | 157 | @Test public void testMiscUnions() { 158 | assertEquals(Null.INSTANCE,Union.create(Null.INSTANCE)); 159 | assertEquals(JavaType.create(Number.class),Union.create(JavaType.create(Number.class),JavaType.create(Integer.class))); 160 | assertEquals(JavaType.create(String.class),Union.create(Nothing.INSTANCE,JavaType.create(String.class))); 161 | } 162 | 163 | @SuppressWarnings("unused") 164 | @Test public void testCast() { 165 | try { 166 | Cast cast=Cast.create(String.class, Constant.create(10)); 167 | fail("Cast should throw exception if cast is not possible"); 168 | } catch (KissException ke) { 169 | // OK! 170 | } 171 | 172 | } 173 | } 174 | --------------------------------------------------------------------------------