[go: up one dir, main page]

Skip to content

Commit

Permalink
update README; add jars
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack You authored and Jack You committed Apr 9, 2018
1 parent 696290b commit 44ce002
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 146 deletions.
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# clj-whitespace
# Clojure Whitespace Compiler

FIXME: description

Expand All @@ -8,13 +8,17 @@ Download from http://example.com/FIXME.

## Usage

FIXME: explanation

$ java -jar clj-whitespace-0.1.0-standalone.jar [args]
```
$ java -jar clj-whitespace-0.1.0-standalone.jar [options] file
```

## Options

FIXME: listing of options this app accepts.
```
-i, --intermediate Execute Clojure-Whitespace source generated by the compiler
-c, --compile Compile whitespace program into intermediate code
-h, --help
```

## Examples

Expand All @@ -30,7 +34,4 @@ FIXME: listing of options this app accepts.

## License

Copyright © 2018 FIXME

Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.
Distributed under the MIT Public License
5 changes: 3 additions & 2 deletions project.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(defproject clj-whitespace "0.1.0-SNAPSHOT"
(defproject clj-whitespace "1.0"
:description "A Whitespace compiler in Clojure"
:url "https://github.com/jacksyou/clj-whitespace"
:license {:name "MIT Public License"
Expand All @@ -7,7 +7,8 @@
[org.clojure/core.match "0.3.0-alpha5"]
[org.clojure/tools.cli "0.3.5"]
]
:main ^:skip-aot clj-whitespace.core
:aot [clj-whitespace.core]
:main clj-whitespace.core
:java-source-paths ["src/" "test/"]
:target-path "target/%s"
:profiles {:uberjar {:aot :all}})
8 changes: 5 additions & 3 deletions resources/helloworld.ws
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include <stdio.h> int main() {puts("Hello World")}



Expand Down Expand Up @@ -27,9 +27,11 @@













1 change: 1 addition & 0 deletions resources/helloworld.ws.clj
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@
:printc
[:push 10]
:printc
:pop
:end)
40 changes: 21 additions & 19 deletions src/clj_whitespace/compiler.clj
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
(ns clj-whitespace.compiler
(:require [clojure.core.match :refer [match]])
(:require [clj-whitespace.parser :as parser])
(:gen-class))
(:require [clojure.core.match :refer [match]])
(:require [clj-whitespace.parser :as parser])
(:gen-class))

(defn compile-helper [cmds prgm labels pc]
(if (empty? cmds)
[prgm labels]
(let [[n-cmds n-prgm n-labels n-pc]
(match [cmds]
[([[:push a] :pop & xs] :seq)] [xs prgm labels pc]
[([[:push a] [:push b] :swap & xs] :seq)] [xs (concat prgm [[:push b] [:push a]]) labels (+ pc 2)]
[([([:label l] :as x) & xs] :seq)] (if (contains? labels l)
(throw (Exception. "label already present in global table"))
[xs prgm (conj labels {l pc}) pc])
[([x & xs] :seq)] [xs (conj prgm x) labels (inc pc)]
[([:end] :seq)] [(list) (conj prgm :end) labels (-1)])]
(recur n-cmds n-prgm n-labels n-pc))))
""
(if (empty? cmds)
[prgm labels]
(let [[n-cmds n-prgm n-labels n-pc]
(match [cmds]
[([[:push a] :pop & xs] :seq)] [xs prgm labels pc]
[([:swap :swap & xs] :seq)] [xs prgm labels pc]
[([[:push a] [:push b] :swap & xs] :seq)] [xs (concat prgm [[:push b] [:push a]]) labels (+ pc 2)]
[([([:label l] :as x) & xs] :seq)] (if (contains? labels l)
(throw (Exception. "label already present in global table"))
[xs prgm (conj labels {l pc}) pc])
[([x & xs] :seq)] [xs (conj prgm x) labels (inc pc)]
[([:end] :seq)] [(list) (conj prgm :end) labels (-1)])]
(recur n-cmds n-prgm n-labels n-pc))))

(defn compile-tokens [cmds] (compile-helper cmds [] {} 0))

(defn compile-program [s & {:keys [mode] :or {mode :string}}]
(case mode
:commands (compile-tokens s)
(let [tokens (parser/parse s :mode mode)]
(compile-tokens tokens))))
(case mode
:commands (compile-tokens s)
(let [tokens (parser/parse s :mode mode)]
(compile-tokens tokens))))
59 changes: 49 additions & 10 deletions src/clj_whitespace/core.clj
Original file line number Diff line number Diff line change
@@ -1,14 +1,53 @@
(ns clj-whitespace.core
(:require [clj-whitespace.parser :as parser])
(:require [clj-whitespace.compiler :as compiler])
(:require [clj-whitespace.programs :as programs])
(:require [clj-whitespace.runtime :as runtime])
(:require [clojure.tools.cli :refer [parse-opts]])
(:require [clj-whitespace.parser :as parser]
[clj-whitespace.compiler :as compiler]
[clj-whitespace.programs :as programs]
[clj-whitespace.runtime :as runtime]
[clojure.string :as string]
[clojure.tools.cli :refer [parse-opts]])
(:gen-class))

(defn -main
"I don't do a whole lot ... yet."
[& args]
()
)
(def cli-options
[["-i" "--intermediate" "Execute Clojure-Whitespace source generated by the compiler" :default false]
["-c" "--compile" "Compile whitespace program into intermediate code" :default false]
["-h" "--help" :default false]])

(defn usage [options-summary]
(->> ["This program compiles and executes Whitespace source, producing an intermediate representation."
""
"Usage: clj-whitespace [options] source-file"
""
"Options:"
options-summary
""]
(string/join \newline)))

(defn error-msg [errors]
(str "The following errors occurred while parsing your command:\n\n"
(string/join \newline errors)))

(defn validate-args [args]
(let [{:keys [options arguments errors summary]} (parse-opts args cli-options)]
(cond
(:help options) {:exit-message (usage summary) :ok? true}
errors {:exit-message (error-msg errors)}
(and (= 1 (count arguments)) (:compile options) (not (:intermediate options)))
{:action :compile-source :file (first arguments)}
(and (= 1 (count arguments)) (not (:compile options)) (:intermediate options))
{:action :execute-intermediate :file (first arguments)}
(and (= 1 (count arguments)) (not (:compile options)) (not (:intermediate options)))
{:action :execute-source :file (first arguments)}
:else ; failed custom validation => exit with usage summary
{:exit-message (usage summary)})))

(defn -main [& args]
(let [{:keys [action file exit-message ok?]} (validate-args args)]
(if exit-message
(do (println exit-message)
(System/exit (if ok? 0 1)))
(case action
:compile-source (parser/parse file :mode :file :intermediate? true)
:execute-intermediate (let [[prgm labels] (compiler/compile-tokens (eval (read-string (slurp file))))]
(runtime/main-routine prgm labels))
:execute-source (runtime/exec file :mode :file)))))

158 changes: 87 additions & 71 deletions src/clj_whitespace/parser.clj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(ns clj-whitespace.parser
(:require [clojure.core.match :refer [match]])
(:require [clojure.pprint])
(:gen-class))
(:require [clojure.core.match :refer [match]])
(:require [clojure.pprint])
(:gen-class))

(def parse-stack-tokens)
(def parse-flow-tokens)
Expand All @@ -13,88 +13,104 @@
(def parse-tokens)

(defn tokenize [s & {:keys [mode] :or {mode :string}}]
(case mode
:string (replace {"\t" :t, "\n" :n, " " :s} (re-seq #"[ \t\n]" s))
:file (tokenize (slurp s) :mode :string)))
(case mode
:string (replace {"\t" :t, "\n" :n, " " :s} (re-seq #"[ \t\n]" s))
:file (tokenize (slurp s) :mode :string)))

(defn parse-tokens [stream]
(match [stream]
[([:s & xs] :seq)] (parse-stack-tokens xs)
[([:t :s & xs] :seq)] (parse-arithmetic-tokens xs)
[([:t :t & xs] :seq)] (parse-heap-tokens xs)
[([:n & xs] :seq)] (parse-flow-tokens xs)
[([:t :n & xs] :seq)] (parse-io-tokens xs)
:else (throw (Exception. "unexpected token encountered while determining next op"))))
(match [stream]
[([:s & xs] :seq)] (parse-stack-tokens xs)
[([:t :s & xs] :seq)] (parse-arithmetic-tokens xs)
[([:t :t & xs] :seq)] (parse-heap-tokens xs)
[([:n & xs] :seq)] (parse-flow-tokens xs)
[([:t :n & xs] :seq)] (parse-io-tokens xs)
:else (throw (Exception. "unexpected token encountered while determining next op"))))

(defn parse-stack-tokens [stream]
(match [stream]
[([:s & xs] :seq)] (let [[val rest] (parse-parameter xs)]
(let [[sign & num] val]
(def sign' (if (= "0" sign) 1 (-1)))
(def num' (Integer/parseInt (apply str num) 2))
(def number (* sign' num'))
(cons [:push number] (parse-tokens rest))))
[([:n :s & xs] :seq)] (cons :dup (parse-tokens xs))
[([:n :t & xs] :seq)] (cons :swap (parse-tokens xs))
[([:n :n & xs] :seq)] (cons :pop (parse-tokens xs))
:else (throw (Exception. "unexpected token encountered while parsing stack op"))))
"This function parses 1 stack op at the head of `stream`.
"
(match [stream]
[([:s & xs] :seq)] (let [[val rest] (parse-parameter xs)]
(let [[sign & num] val]
(def sign' (if (= "0" sign) 1 (-1)))
(def num' (Integer/parseInt (apply str num) 2))
(def number (* sign' num'))
(cons [:push number] (parse-tokens rest))))
[([:n :s & xs] :seq)] (cons :dup (parse-tokens xs))
[([:n :t & xs] :seq)] (cons :swap (parse-tokens xs))
[([:n :n & xs] :seq)] (cons :pop (parse-tokens xs))
:else (throw (Exception. "unexpected token encountered while parsing stack op"))))

(defn parse-arithmetic-tokens [stream]
(match [stream]
[([:s :s & xs] :seq)] (cons :add (parse-tokens xs))
[([:s :t & xs] :seq)] (cons :sub (parse-tokens xs))
[([:s :n & xs] :seq)] (cons :mul (parse-tokens xs))
[([:t :s & xs] :seq)] (cons :div (parse-tokens xs))
[([:t :t & xs] :seq)] (cons :mod (parse-tokens xs))
:else (throw (Exception. "unexpected token encountered while parsing arith op"))))
"This function parses 1 arithmetik op at the head of `stream`.
"
(match [stream]
[([:s :s & xs] :seq)] (cons :add (parse-tokens xs))
[([:s :t & xs] :seq)] (cons :sub (parse-tokens xs))
[([:s :n & xs] :seq)] (cons :mul (parse-tokens xs))
[([:t :s & xs] :seq)] (cons :div (parse-tokens xs))
[([:t :t & xs] :seq)] (cons :mod (parse-tokens xs))
:else (throw (Exception. "unexpected token encountered while parsing arith op"))))

(defn parse-heap-tokens [stream]
(match [stream]
[([:s & xs] :seq)] (cons :store (parse-tokens xs))
[([:t & xs] :seq)] (cons :load (parse-tokens xs))
:else (throw (Exception. "unexpected newline encountered while parsing heap op"))))
"This function parses 1 store/load op at the head of `stream`.
"
(match [stream]
[([:s & xs] :seq)] (cons :store (parse-tokens xs))
[([:t & xs] :seq)] (cons :load (parse-tokens xs))
:else (throw (Exception. "unexpected newline encountered while parsing heap op"))))

(defn parse-flow-tokens [stream]
(match [stream]
[([:s :s & xs] :seq)] (let [[val rest] (parse-label xs)] (cons [:label val] (parse-tokens rest)))
[([:s :t & xs] :seq)] (let [[val rest] (parse-label xs)] (cons [:call val] (parse-tokens rest)))
[([:s :n & xs] :seq)] (let [[val rest] (parse-label xs)] (cons [:jmp val] (parse-tokens rest)))
[([:t :s & xs] :seq)] (let [[val rest] (parse-label xs)] (cons [:jz val] (parse-tokens rest)))
[([:t :t & xs] :seq)] (let [[val rest] (parse-label xs)] (cons [:jn val] (parse-tokens rest)))
[([:t :n & xs] :seq)] (cons :return (parse-tokens xs))
[([:n :n & xs] :seq)] (cons :end (sequence nil))
:else (throw (Exception. "unexpected token encountered with parsing flow control op"))))
"This function parses 1 flow control op at the head of `stream`.
"
(match [stream]
[([:s :s & xs] :seq)] (let [[val rest] (parse-label xs)] (cons [:label val] (parse-tokens rest)))
[([:s :t & xs] :seq)] (let [[val rest] (parse-label xs)] (cons [:call val] (parse-tokens rest)))
[([:s :n & xs] :seq)] (let [[val rest] (parse-label xs)] (cons [:jmp val] (parse-tokens rest)))
[([:t :s & xs] :seq)] (let [[val rest] (parse-label xs)] (cons [:jz val] (parse-tokens rest)))
[([:t :t & xs] :seq)] (let [[val rest] (parse-label xs)] (cons [:jn val] (parse-tokens rest)))
[([:t :n & xs] :seq)] (cons :return (parse-tokens xs))
[([:n :n & xs] :seq)] (cons :end (sequence nil))
:else (throw (Exception. "unexpected token encountered with parsing flow control op"))))

(defn parse-io-tokens [stream]
(match [stream]
[([:s :s & xs] :seq)] (cons :printc (parse-tokens xs))
[([:s :t & xs] :seq)] (cons :printi (parse-tokens xs))
[([:t :s & xs] :seq)] (cons :read-char (parse-tokens xs))
[([:t :t & xs] :seq)] (cons :read-int (parse-tokens xs))
:else (throw (Exception. "unexpected newline encountered while parsing io op"))))
(match [stream]
[([:s :s & xs] :seq)] (cons :printc (parse-tokens xs))
[([:s :t & xs] :seq)] (cons :printi (parse-tokens xs))
[([:t :s & xs] :seq)] (cons :read-char (parse-tokens xs))
[([:t :t & xs] :seq)] (cons :read-int (parse-tokens xs))
:else (throw (Exception. "unexpected newline encountered while parsing io op"))))

(defn parse-parameter [stream]
(let [[x xs] (split-with (partial not= :n) stream)]
(if (< (count x) 2) (throw (Exception. "malformed parameter before linefeed")))
(def values (map (fn [chr] (if (= chr :s) "0" "1")) x))
(def xs' (drop 1 xs))
[values xs']))
(let [[x xs] (split-with (partial not= :n) stream)]
(if (< (count x) 2) (throw (Exception. "malformed parameter before linefeed")))
(def values (map (fn [chr] (if (= chr :s) "0" "1")) x))
(def xs' (drop 1 xs))
[values xs']))

(defn parse-label [stream]
(let [[x xs] (split-with (partial not= :n) stream)]
(if (< (count x) 1) (do
(println x)
(println xs)
(throw (Exception. "malformed label before linefeed"))))
(def values (map (fn [chr] (if (= chr :s) "0" "1")) x))
(def xs' (drop 1 xs))
[(Long/parseLong (apply str values) 2) xs']))
"This function parses a binary label of length (>=1).
"
(let [[x xs] (split-with (partial not= :n) stream)]
(if (< (count x) 1)
(do
(println x)
(println xs)
(throw (Exception. "malformed label before linefeed"))))
(def values (map (fn [chr] (if (= chr :s) "0" "1")) x))
(def xs' (drop 1 xs))
[(Long/parseLong (apply str values) 2) xs']))

(defn pretty-intermediate-out [s prgm]
(spit (str s ".clj") (str "'" (with-out-str (clojure.pprint/pprint prgm)))))

(defn parse [s & {:keys [mode intermediate?] :or {mode :string intermediate? false}}]
(case mode
:string (let [tokens (tokenize s :mode :string)] (apply list (parse-tokens tokens)))
:file
(let [tokens (tokenize s :mode :file)]
(def output (apply list (parse-tokens tokens)))
(if intermediate? (pretty-intermediate-out s output))
output)))


(defn parse [s & {:keys [mode] :or {mode :string}}]
(case mode
:string (let [tokens (tokenize s :mode :string)] (apply list (parse-tokens tokens)))
:file (let [tokens (tokenize s :mode :file)]
(def output (apply list (parse-tokens tokens)))
(spit (str s ".clj") (str "'" (with-out-str (clojure.pprint/pprint output))))
output)))

2 changes: 1 addition & 1 deletion src/clj_whitespace/programs.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

(def hello-world " \t \t \n \t\t\t \t\t \t\t \t \n \n\t\t \t \t \n\t\n \t\t \t \t\n\t\n \t\t \t\t \n \n \t\n \t\n \t\t \t\t\t\t\n\t\n \t \n\t\n \t \t \t\t\t\n\t\n \t\t \t\t\t\t\n\t\n \t\t\t \t \n\t\n \t\t \t\t \n\t\n \t\t \t \n\t\n \t \t\n\t\n \t \t \n\t\n \n\n\n\n ")

(def count '([:push 1] [:label 0] :dup :printi [:push 10] :printc [:push 1] :add :dup [:push 11] :sub [:jz 1] [:jmp 0] [:label 1] :pop :end))
(def count-to-ten '([:push 1] [:label 0] :dup :printi [:push 10] :printc [:push 1] :add :dup [:push 11] :sub [:jz 1] [:jmp 0] [:label 1] :pop :end))
Loading

0 comments on commit 44ce002

Please sign in to comment.