Created
September 8, 2015 07:58
-
-
Save MysteryMachine/87c8f3e8b4361507d2be to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; Please excuse any lack of neatness, I finished this at 4 am! | |
(ns fizzbuzz.lisp | |
(require [clojure.test :as t] | |
[clojure.string :as s])) | |
(defn trim | |
"Trims parens and whitepsace from a combination" | |
[expr] (-> expr (s/trim) (subs 1 (dec (count expr))))) | |
(defn combination? | |
"Returns true if the string is a combination" | |
[expr] (= \( (first expr))) | |
(defn literal-split [expr] | |
"Splits a string with a leading literal into that literal | |
and the remaining expression." | |
(s/split expr #"\s" 2)) | |
(defn combo-split | |
"Splits a string with a leading combination into that combination | |
and the remaining expression by counting parens." | |
([expr] (combo-split [\(] (rest expr) 1)) | |
([in-combo out-combo count] | |
(let [peek (first out-combo) | |
next-count (cond | |
(= \) peek) (dec count) | |
(= \( peek) (inc count) | |
:else count)] | |
(if (zero? count) | |
[(apply str in-combo) (s/trim (apply str out-combo))] | |
(combo-split (conj in-combo peek) | |
(rest out-combo) | |
next-count))))) | |
(defn read [expr] | |
"Reads a string with a lisp expression. If it is empty or nil it returns | |
an empty list. Otherwise, it breaks out the leading expression and | |
recurses on itself until the string is exhausted." | |
(cond | |
(or (empty? expr) (nil? expr) (= "" expr) (= 0 (count expr))) () | |
(combination? expr) (let [[in-combo out-combo] (combo-split expr)] | |
(cons in-combo (read out-combo))) | |
:else (let [[in-literal out-literal] (literal-split expr)] | |
(cons in-literal (read out-literal))))) | |
(defn parse-inner | |
[expr] | |
(if (combination? expr) | |
(map parse-inner (read (trim expr))) | |
expr)) | |
(defn parse | |
"Converts lisp code into an AST" | |
[expr] (-> expr (s/trim) (parse-inner))) | |
(t/deftest combo-split-test | |
(t/is (= ["(+ 1 2)" "a b c"] (combo-split "(+ 1 2) a b c")))) | |
(t/deftest literal-split-test | |
(t/is (= ["42" "(+ 1 2) A B"] (literal-split "42 (+ 1 2) A B")))) | |
(t/deftest read-test | |
(t/is (= ["42"] (read "42"))) | |
(t/is (= ["42" "34" "12" "12"] (read "42 34 12 12"))) | |
(t/is (= ["42" "(+ 1 2)" "12"] (read "42 (+ 1 2) 12"))) | |
(t/is (= ["(+ 1 2)" "(+ 3 2)"] (read "(+ 1 2) (+ 3 2)"))) | |
(t/is (= ["(+ 1 2 (+ 2 3))"] (read "(+ 1 2 (+ 2 3))"))) | |
(t/is (= ["+" "1" "2"] (read "+ 1 2")))) | |
(t/deftest trim-test | |
(t/is (= "42" (trim "(42)"))) | |
(t/is (= "+ 42 1" (trim "(+ 42 1)")))) | |
(t/deftest parse-test | |
(t/is (= "42" (parse "42"))) | |
(t/is (= "42" (parse "42 \n\t\t"))) | |
(t/is (= ["42"] (parse "(42)"))) | |
(t/is (= ["+" "1" "2"] (parse "(+ 1 2)"))) | |
(t/is (= ["+" ["+" "1" "2"] ["inc" "1"]] (parse "(+ (+ 1 2) (inc 1))"))) | |
(t/is (= ["first" ["list" "1" ["+" "2" "3"] "9"]] | |
(parse "(first (list 1 (+ 2 3) 9))")))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment