Skip to content

Instantly share code, notes, and snippets.

@MysteryMachine
Created September 8, 2015 07:58
Show Gist options
  • Save MysteryMachine/87c8f3e8b4361507d2be to your computer and use it in GitHub Desktop.
Save MysteryMachine/87c8f3e8b4361507d2be to your computer and use it in GitHub Desktop.
;; 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