├── .gitignore ├── LICENSE ├── README.md ├── impala ├── project.clj ├── src └── impala │ ├── core.clj │ ├── lib.clj │ └── main.clj └── test └── impala ├── test.clj └── test.imp /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 3 | Version 2, December 2004 4 | 5 | Copyright (C) 2015 Divyansh Prakash 6 | 7 | Everyone is permitted to copy and distribute verbatim or modified 8 | copies of this license document, and changing it is allowed as long 9 | as the name is changed. 10 | 11 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 12 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 13 | 14 | 0. You just DO WHAT THE FUCK YOU WANT TO. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Impala 2 | ## Simple, extensible bytecode interpreter 3 | 4 | Impala is a simple bytecode interpreter written in Clojure. 5 | Written to learn from and to teach making use of. 6 | 7 | 8 | ## Usage 9 | 10 | Load everything into namespace. 11 | 12 | ```clojure 13 | (use '[impala core lib]) 14 | ``` 15 | 16 | Write a simple program in impala bytecode and execute it, 17 | printing the whole lifecycle to `stdout`. 18 | 19 | ```clojure 20 | (let [prog [[SET :a 5] 21 | [SET :b 3] 22 | [ADD :a :b]]] 23 | (run prog true)) 24 | ``` 25 | 26 | We can even extend the instruction set by defining our own opcodes 27 | 28 | * in Clojure 29 | ```clojure 30 | (defn ADD 31 | "b := b + a" 32 | [env a b] 33 | (swap! env update-in [:vars b] + (-> @env :vars a))) 34 | ``` 35 | 36 | or 37 | 38 | * __in Impala byte code__! 39 | ```clojure 40 | ;; from impala.lib 41 | (defop ADD 42 | "b := b + a" 43 | [a b] [Z] 44 | (SUB a Z) 45 | (SUB Z b)) 46 | ``` 47 | 48 | At the heart of this capability is the [SUBLEQ](https://en.wikipedia.org/wiki/One_instruction_set_computer#Subtract_and_branch_if_less_than_or_equal_to_zero) primitive, which is Turing Equivalent. 49 | 50 | In this example, `Z` is a temporary register created (and set to 0) every time `ADD` is called, and deleted once it's done executing. 51 | An opcode may use multiple temporary registers. 52 | 53 | ### Standalone Interpreter 54 | 55 | At the terminal, run 56 | 57 | ```bash 58 | lein uberjar 59 | 60 | ./impala test/impala/test.imp 61 | ``` 62 | 63 | 64 | ## License 65 | 66 | Impala is licensed under [wtfpl](http://www.wtfpl.net/) and is effectively in the public domain. 67 | -------------------------------------------------------------------------------- /impala: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | java -jar target/impala-0.1.0-SNAPSHOT-standalone.jar $1 3 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject impala "0.1.0-SNAPSHOT" 2 | :description "Simple, extensible bytecode interpreter" 3 | :url "https://github.com/divs1210/Impala" 4 | :license {:name "WTFPL" 5 | :url "http://www.wtfpl.net/"} 6 | :dependencies [[org.clojure/clojure "1.6.0"]] 7 | :main impala.main) 8 | -------------------------------------------------------------------------------- /src/impala/core.clj: -------------------------------------------------------------------------------- 1 | (ns impala.core) 2 | 3 | ;; Primitives 4 | ;; ========== 5 | (defn GET 6 | "Get value of a" 7 | [env a] 8 | (-> @env :vars a)) 9 | 10 | (defn SET 11 | "a := val" 12 | [env a val] 13 | (swap! env update-in [:vars] assoc a val)) 14 | 15 | (defn DEL 16 | "Delete variable (free memory)" 17 | [env a] 18 | (swap! env update-in [:vars] dissoc a)) 19 | 20 | (defn GOTO 21 | "Move IP to given index" 22 | [env inst#] 23 | (swap! env assoc :IP inst#)) 24 | 25 | (defn SUB* 26 | "b := b - a" 27 | [env a b] 28 | (SET env b (- (or (GET env b) 0) 29 | (GET env a)))) 30 | 31 | 32 | ;; Compound primitive 33 | ;; ================== 34 | (defn SUBLEQ 35 | "Turing equivalent instruction that can be used to 36 | implement other 'primitives'. See impala.lib." 37 | [env a b & [c]] 38 | (SUB* env a b) 39 | (if (and (-> c nil? not) 40 | (neg? (GET env b))) 41 | (GOTO env c) 42 | env)) 43 | 44 | 45 | ;; VM Helpers 46 | ;; ========== 47 | (defn temp 48 | "Returns a randomly generated variable name." 49 | [] 50 | (keyword (gensym))) 51 | 52 | (defmacro defop 53 | "Creates a new opcode. Temporary registers, if any, will 54 | be deleted once the instruction has been executed. 55 | See impala.lib for usage." 56 | {:arglists '([name doc-string? [params*] [temps*] body])} 57 | [name & stuff] 58 | (let [has-doc? (-> stuff first string?) 59 | doc-string (if has-doc? (first stuff)) 60 | [argv tempv & body] (if has-doc? (rest stuff) stuff)] 61 | `(defn ~name ~(vec (cons 'env argv)) 62 | (let [~@(apply concat (for [t tempv] 63 | [t (temp)]))] 64 | ~@(for [t tempv] 65 | (list 'SET 'env t 0)) 66 | ~@(for [[op & args] body] 67 | (cons op (cons 'env args))) 68 | ~@(for [t tempv] 69 | (list 'DEL 'env t)))))) 70 | 71 | (defn mk-env 72 | "Returns a fresh environment with the given program ready 73 | to be executed." 74 | [prog] 75 | (atom {:instr-q (vec prog) 76 | :IP 0 77 | :vars {}})) 78 | 79 | 80 | ;; Interpreter 81 | ;; =========== 82 | (defn step 83 | "Executes the instruction pointed at by the IP. 84 | Returns the updated env." 85 | [env] 86 | (let [instr-q (:instr-q @env) 87 | IP (:IP @env) 88 | [op & args] (instr-q IP)] 89 | (apply op env args) 90 | (swap! env update-in [:IP] inc) 91 | env)) 92 | 93 | (defn run-env 94 | "Runs the given environment. 95 | Returns the updated env. 96 | Prints state at each step to stdout if supplied 97 | a truthy second argument." 98 | [env & [print?]] 99 | (let [println (if print? println (fn [& args]))] 100 | (println "\n=== start ===") 101 | (while (< (-> @env :IP) (-> @env :instr-q count)) 102 | (step env) 103 | (println (dissoc @env :instr-q))) 104 | (println "==== end ====") 105 | env)) 106 | 107 | (defn run 108 | "Runs the given program in a new environment. 109 | Returns the updated env. 110 | Prints state at each step to stdout if supplied 111 | a truthy second argument." 112 | [prog & [print?]] 113 | (run-env (mk-env prog) print?)) 114 | -------------------------------------------------------------------------------- /src/impala/lib.clj: -------------------------------------------------------------------------------- 1 | (ns impala.lib 2 | (:use impala.core)) 3 | 4 | (defop SUB 5 | "b := b - a" 6 | [a b] [] 7 | (SUBLEQ a b)) 8 | 9 | (defop ADD 10 | "b := b + a" 11 | [a b] [Z] 12 | (SUB a Z) 13 | (SUB Z b)) 14 | 15 | (defop MOV 16 | "b := a" 17 | [a b] [Z] 18 | (SUB b b) 19 | (SUB a Z) 20 | (SUB Z b)) 21 | -------------------------------------------------------------------------------- /src/impala/main.clj: -------------------------------------------------------------------------------- 1 | (ns impala.main 2 | (:gen-class) 3 | (:use [impala core lib] 4 | [clojure.string :only [split]])) 5 | 6 | (def this-ns *ns*) 7 | 8 | (defn str->instruction [s] 9 | (binding [*ns* this-ns] 10 | (eval (read-string (str "[" s "]"))))) 11 | 12 | (defn str->prog [s] 13 | (->> (split s #"\n") 14 | (map str->instruction))) 15 | 16 | (defn run-prog-string [s] 17 | (run (str->prog s) true)) 18 | 19 | (defn -main [& args] 20 | (if (empty? args) 21 | (println "Error: no file passed to impala") 22 | (run-prog-string (slurp (first args))))) 23 | -------------------------------------------------------------------------------- /test/impala/test.clj: -------------------------------------------------------------------------------- 1 | (ns impala.test 2 | (:require [clojure.test :refer :all]) 3 | (:use impala.core 4 | impala.lib)) 5 | 6 | (deftest e2e-test 7 | (testing "Environment in expected state at the end of execution." 8 | (let [prog [[SET :a 5] 9 | [SET :b 3] 10 | [ADD :a :b] 11 | [SUB :a :b] 12 | [MOV :b :a]] 13 | env (run prog)] 14 | (is (= (:IP @env) 5)) 15 | (is (= (:vars @env) {:a 3 :b 3}))))) 16 | -------------------------------------------------------------------------------- /test/impala/test.imp: -------------------------------------------------------------------------------- 1 | SET :a -4 2 | SET :b -1 3 | SUBLEQ :b :a 1 4 | --------------------------------------------------------------------------------