Skip to content

Commit

Permalink
Refactor to namspaced keywords (#5)
Browse files Browse the repository at this point in the history
* Refactor UI to replicant without state transactions

* Add dispatch function and watcher. This isn't working currently

* Fix issue causing display not to update

* Update package json with relese job

* Refactor to use namespaced keywrods

* Create deploy.yml

---------

Co-authored-by: Andrew Leverette <andrew.leverette@antage.net>
  • Loading branch information
andrewleverette and Andrew Leverette authored Oct 30, 2024
1 parent a51046e commit 4b33529
Showing 7 changed files with 136 additions and 138 deletions.
18 changes: 9 additions & 9 deletions src/clojulator/calculator/evaluator.cljc
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
"Polymorphic evaluator for AST nodes"
(fn [node _history] (first node)))

(defmethod evaluate :Env [node history]
(defmethod evaluate :node/Env [node history]
(let [var-name (second node)]
(case var-name
"p1" (repl1 history)
@@ -17,25 +17,25 @@
(throw #?(:clj (Exception. error-msg)
:cljs (js/Error. error-msg)))))))

(defmethod evaluate :Number [node _history] (second node))
(defmethod evaluate :node/Number [node _history] (second node))

(defmethod evaluate :Group
(defmethod evaluate :node/Group
[node history]
(-> node
second
(evaluate history)))

(defmethod evaluate :Caret [node history] (map-eval-reduce history Math/pow (rest node)))
(defmethod evaluate :node/Caret [node history] (map-eval-reduce history Math/pow (rest node)))

(defmethod evaluate :Star [node history] (map-eval-reduce history * (rest node)))
(defmethod evaluate :node/Star [node history] (map-eval-reduce history * (rest node)))

(defmethod evaluate :Slash [node history] (map-eval-reduce history / (rest node)))
(defmethod evaluate :node/Slash [node history] (map-eval-reduce history / (rest node)))

(defmethod evaluate :Modulo [node history] (map-eval-reduce history mod (rest node)))
(defmethod evaluate :node/Modulo [node history] (map-eval-reduce history mod (rest node)))

(defmethod evaluate :Plus [node history] (map-eval-reduce history + (rest node)))
(defmethod evaluate :node/Plus [node history] (map-eval-reduce history + (rest node)))

(defmethod evaluate :Minus [node history]
(defmethod evaluate :node/Minus [node history]
(let [remaining (rest node)]
(if (= 1 (count remaining))
(- (evaluate (first remaining) history))
30 changes: 15 additions & 15 deletions src/clojulator/calculator/parser.cljc
Original file line number Diff line number Diff line change
@@ -29,47 +29,47 @@
inner expression is enclosed in a :CloseParen. Otherwise, throws a syntax
error."
[tokens]
(when (match tokens #{:OpenParen})
(when (match tokens #{:token/OpenParen})
(let [expr (expression (rest tokens))
remaining-tokens (:remaining expr)]
(if (match remaining-tokens #{:CloseParen})
{:node [:Group (:node expr)] :remaining (rest remaining-tokens)}
(if (match remaining-tokens #{:token/CloseParen})
{:node [:node/Group (:node expr)] :remaining (rest remaining-tokens)}
(parser-error (:remaining expr))))))

(defn- env
"Environment variable rule: p1 | p2 | p3
Adds an environment variable node to the AST if the next token matches
:Repl/*1, :Repl/*2, or :Repl/*3."
[tokens]
(if (match tokens #{:Repl/*1 :Repl/*2 :Repl/*3})
{:node [:Env (tok/lexeme (first tokens))] :remaining (rest tokens)}
(if (match tokens #{:token/*1 :token/*2 :token/*3})
{:node [:node/Env (tok/lexeme (first tokens))] :remaining (rest tokens)}
(parser-error tokens)))

(defn- number
"Number rule: <number>
Adds a number literal to the AST if the next token matches :Number"
[tokens]
(if (match tokens #{:Number})
{:node [:Number (tok/literal (first tokens))] :remaining (rest tokens)}
(if (match tokens #{:token/Number})
{:node [:node/Number (tok/literal (first tokens))] :remaining (rest tokens)}
(parser-error tokens)))

(defn- primary
"Primary rule: <group> | <env> | <number>
Matches a group node, an environment variable, or a number literal."
[tokens]
(cond
(match tokens #{:OpenParen}) (group tokens)
(match tokens #{:Repl/*1 :Repl/*2 :Repl/*3}) (env tokens)
(match tokens #{:token/OpenParen}) (group tokens)
(match tokens #{:token/*1 :token/*2 :token/*3}) (env tokens)
:else (number tokens)))

(defn- unary
"Unary rule: - <unary> | <primary>
Adds a unary node to the AST if the next token matches :Minus.
Otherwise, matches a primary node."
[tokens]
(if-let [minus (match tokens #{:Minus})]
(if (match tokens #{:token/Minus})
(let [p (unary (rest tokens))]
{:node [minus (:node p)] :remaining (:remaining p)})
{:node [:node/Minus (:node p)] :remaining (:remaining p)})
(primary tokens)))

(defn- binary-expression
@@ -81,27 +81,27 @@
(if-let [operator (match remaining matchers)]
(let [right (rule (rest remaining))]
(recur
[operator expr (:node right)]
[(keyword "node" (name operator)) expr (:node right)]
(:remaining right)))
{:node expr :remaining remaining}))))

(defn- exponent
"Exponent rule: <unary> ( ^ <unary> )*
Adds an exponent node to the AST if the next token matches :Caret."
[tokens]
(binary-expression tokens unary #{:Caret}))
(binary-expression tokens unary #{:token/Caret}))

(defn- factor
"Factor rule: <exponent> ( [* / %] <exponent> )*
Adds a factor node to the AST if the next token matches :Star, :Slash, or :Modulo."
[tokens]
(binary-expression tokens exponent #{:Star :Slash :Modulo}))
(binary-expression tokens exponent #{:token/Star :token/Slash :token/Modulo}))

(defn- term
"Term rule: <factor> ( [+ -] <factor> )*
Adds a term node to the AST if the next token matches :Plus or :Minus."
[tokens]
(binary-expression tokens factor #{:Plus :Minus}))
(binary-expression tokens factor #{:token/Plus :token/Minus}))

(defn- expression
"Expression rule: <term>
44 changes: 22 additions & 22 deletions src/clojulator/calculator/token.cljc
Original file line number Diff line number Diff line change
@@ -3,34 +3,34 @@
;; Symbols that can be turned into tokens
;; Most symbols are represented as a single
;; character, but the history symbols are strings
(def symbol-tokens {\( :OpenParen
\) :CloseParen
\+ :Plus
\- :Minus
\* :Star
\/ :Slash
\^ :Caret
\% :Modulo
"p1" :Repl/*1
"p2" :Repl/*2
"p3" :Repl/*3})
(def symbol-tokens {\( :token/OpenParen
\) :token/CloseParen
\+ :token/Plus
\- :token/Minus
\* :token/Star
\/ :token/Slash
\^ :token/Caret
\% :token/Modulo
"p1" :token/*1
"p2" :token/*2
"p3" :token/*3})

(defn token
"Token constructor"
([type lexeme pos len] (token type lexeme pos len nil))
([type lexeme pos len literal]
{:type type
:lexeme lexeme
:literal literal
:pos pos
:len len}))
{:token/type type
:token/lexeme lexeme
:token/literal literal
:token/pos pos
:token/len len}))

(defn number->token
"Given a number and a position, return a token
that represents a numeric value in the expression."
[n pos]
(let [lexeme (str n)]
(token :Number lexeme pos (count lexeme) (parse-double n))))
(token :token/Number lexeme pos (count lexeme) (parse-double n))))

(defn symbol->token
"Given a symbol and a position, return a token
@@ -42,20 +42,20 @@

(defn token-type
[token]
(:type token))
(:token/type token))

(defn lexeme
[token]
(:lexeme token))
(:token/lexeme token))

(defn literal
[token]
(:literal token))
(:token/literal token))

(defn pos
[token]
(:pos token))
(:token/pos token))

(defn length
[token]
(:len token))
(:token/len token))
2 changes: 0 additions & 2 deletions src/clojulator/web/core.cljs
Original file line number Diff line number Diff line change
@@ -48,7 +48,6 @@

(defalias display
[{:keys [last-result display]}]
(js/console.log "Display" display)
[:div
{:class ["w-full" "h-16" "sm:h-28" "bg-white" "border-2" "border-blue-400" "rounded-lg" "mt-5" "md:mt-0" "flex" "flex-col" "justify-evenly" "items-end" "pr-2" "border-blue-500" "text-right"]}
[:div
@@ -112,7 +111,6 @@
(defn render [root]
(add-watch state :display
(fn [_ _ _ new-state]
(js/console.log "Rendering wiht new state" new-state)
(r/render root (index new-state))))
(r/render root (index @state)))

70 changes: 35 additions & 35 deletions test/calculator_test/evaluator_test.clj
Original file line number Diff line number Diff line change
@@ -6,80 +6,80 @@

(deftest numeric-expression-tests
(testing "evaluating a number should return the number"
(is (= 1.0 (evaluate [:Number 1.0] history)))
(is (= 10.0 (evaluate [:Number 10.0] history)))
(is (= 1234567890.0 (evaluate [:Number 1234567890.0] history))))
(is (= 1.0 (evaluate [:node/Number 1.0] history)))
(is (= 10.0 (evaluate [:node/Number 10.0] history)))
(is (= 1234567890.0 (evaluate [:node/Number 1234567890.0] history))))
(testing "evaluating a number that is nil should return nil"
(is (nil? (evaluate [:Number nil] history)))))
(is (nil? (evaluate [:node/Number nil] history)))))

(deftest environment-variable-tests
(testing "evaluating an environment variable should return the value"
(is (nil? (evaluate [:Env "p1"] history)))
(is (= 1.0 (evaluate [:Env "p1"] (atom [1.0 nil nil]))))
(is (= 2.0 (evaluate [:Env "p2"] (atom [1.0 2.0 nil]))))
(is (= 3.0 (evaluate [:Env "p3"] (atom [1.0 2.0 3.0]))))))
(is (nil? (evaluate [:node/Env "p1"] history)))
(is (= 1.0 (evaluate [:node/Env "p1"] (atom [1.0 nil nil]))))
(is (= 2.0 (evaluate [:node/Env "p2"] (atom [1.0 2.0 nil]))))
(is (= 3.0 (evaluate [:node/Env "p3"] (atom [1.0 2.0 3.0]))))))

(deftest unary-minus-tests
(testing "evaluating a unary minus should return the negated number"
(is (= -1.0 (evaluate [:Minus [:Number 1.0]] history))))
(is (= -1.0 (evaluate [:node/Minus [:node/Number 1.0]] history))))
(testing "evaluating a minus minus should return a positive number"
(is (= 1.0 (evaluate [:Minus [:Minus [:Number 1.0]]] history))))
(is (= 1.0 (evaluate [:node/Minus [:node/Minus [:node/Number 1.0]]] history))))
(testing "evaluating a minus minus minus should return a negated number"
(is (= -1.0 (evaluate [:Minus [:Minus [:Minus [:Number 1.0]]]] history)))))
(is (= -1.0 (evaluate [:node/Minus [:node/Minus [:node/Minus [:node/Number 1.0]]]] history)))))

(deftest simple-arithetic-expression-tests
(testing "adding two numbers should return the sum"
(is (= 3.0 (evaluate [:Plus [:Number 1.0] [:Number 2.0]] history))))
(is (= 3.0 (evaluate [:node/Plus [:node/Number 1.0] [:node/Number 2.0]] history))))
(testing "subtracting two numbers should return the difference"
(is (= -1.0 (evaluate [:Minus [:Number 1.0] [:Number 2.0]] history))))
(is (= -1.0 (evaluate [:node/Minus [:node/Number 1.0] [:node/Number 2.0]] history))))
(testing "multiplying two numbers should return the product"
(is (= 2.0 (evaluate [:Star [:Number 1.0] [:Number 2.0]] history))))
(is (= 2.0 (evaluate [:node/Star [:node/Number 1.0] [:node/Number 2.0]] history))))
(testing "dividing two numbers should return the quotient"
(is (= 0.5 (evaluate [:Slash [:Number 1.0] [:Number 2.0]] history)))
(is (= 0.5 (evaluate [:node/Slash [:node/Number 1.0] [:node/Number 2.0]] history)))
(testing "dividing by zero should throw an exception"
(is (thrown? ArithmeticException (evaluate [:Slash [:Number 1.0] [:Number 0.0]] history)))))
(is (thrown? ArithmeticException (evaluate [:node/Slash [:node/Number 1.0] [:node/Number 0.0]] history)))))
(testing "raising a number to a power should return the result"
(is (= 8.0 (evaluate [:Caret [:Number 2.0] [:Number 3.0]] history))))
(is (= 8.0 (evaluate [:node/Caret [:node/Number 2.0] [:node/Number 3.0]] history))))
(testing "taking the remainder of two numbers should return the remainder"
(is (= 1.0 (evaluate [:Modulo [:Number 5.0] [:Number 2.0]] history)))
(is (= 0.0 (evaluate [:Modulo [:Number 3.0] [:Number 3.0]] history)))))
(is (= 1.0 (evaluate [:node/Modulo [:node/Number 5.0] [:node/Number 2.0]] history)))
(is (= 0.0 (evaluate [:node/Modulo [:node/Number 3.0] [:node/Number 3.0]] history)))))

(deftest precedence-tests
(testing "multiplicative expressions have higher precedence than additive expressions"
;; 1 + 2 * 3
(is (= 7.0 (evaluate [:Plus [:Number 1.0] [:Star [:Number 2.0] [:Number 3.0]]] history)))
(is (= 7.0 (evaluate [:node/Plus [:node/Number 1.0] [:node/Star [:node/Number 2.0] [:node/Number 3.0]]] history)))
;; 2 * 3 - 5
(is (= 1.0 (evaluate [:Minus [:Star [:Number 2.0] [:Number 3.0]] [:Number 5.0]] history)))
(is (= 1.0 (evaluate [:node/Minus [:node/Star [:node/Number 2.0] [:node/Number 3.0]] [:node/Number 5.0]] history)))
;; 1 + 4 % 2
(is (= 1.0 (evaluate [:Plus [:Number 1.0] [:Modulo [:Number 4.0] [:Number 2.0]]] history))))
(is (= 1.0 (evaluate [:node/Plus [:node/Number 1.0] [:node/Modulo [:node/Number 4.0] [:node/Number 2.0]]] history))))
(testing "exponentiation has higher precedence than multiplicative expressions"
;; 2 * 2 ^ 3
(is (= 16.0 (evaluate [:Star [:Number 2.0] [:Caret [:Number 2.0] [:Number 3.0]]] history))))
(is (= 16.0 (evaluate [:node/Star [:node/Number 2.0] [:node/Caret [:node/Number 2.0] [:node/Number 3.0]]] history))))
(testing "unary minus has higher precedence than multiplicative expressions"
;; -2 * 1
(is (= -2.0 (evaluate [:Star [:Minus [:Number 2.0]] [:Number 1.0]] history)))
(is (= -2.0 (evaluate [:node/Star [:node/Minus [:node/Number 2.0]] [:node/Number 1.0]] history)))
;; 4 / -2
(is (= -2.0 (evaluate [:Slash [:Number 4] [:Minus [:Number 2.0]]] history))))
(is (= -2.0 (evaluate [:node/Slash [:node/Number 4] [:node/Minus [:node/Number 2.0]]] history))))
(testing "parentheses have the same precedence as numbers"
;; (2 * 3) + (6 - 2)
(is (= 10.0 (evaluate [:Plus
[:Group [:Star [:Number 2.0] [:Number 3.0]]]
[:Group [:Minus [:Number 6.0] [:Number 2.0]]]] history)))))
(is (= 10.0 (evaluate [:node/Plus
[:node/Group [:node/Star [:node/Number 2.0] [:node/Number 3.0]]]
[:node/Group [:node/Minus [:node/Number 6.0] [:node/Number 2.0]]]] history)))))

(deftest associativity-tests
(testing "additive exressions are left associative"
;; 2 - 1 + 3
(is (= 4.0 (evaluate [:Plus [:Minus [:Number 2.0] [:Number 1.0]] [:Number 3.0]] history)))
(is (= 4.0 (evaluate [:node/Plus [:node/Minus [:node/Number 2.0] [:node/Number 1.0]] [:node/Number 3.0]] history)))
;; 2 + 1 - 3
(is (= 0.0 (evaluate [:Minus [:Plus [:Number 2.0] [:Number 1.0]] [:Number 3.0]] history))))
(is (= 0.0 (evaluate [:node/Minus [:node/Plus [:node/Number 2.0] [:node/Number 1.0]] [:node/Number 3.0]] history))))
(testing "multiplicative expressions are left associative"
;; 1 / 2 * 2
(is (= 1.0 (evaluate [:Star [:Slash [:Number 1.0] [:Number 2.0]] [:Number 2.0]] history)))
(is (= 1.0 (evaluate [:node/Star [:node/Slash [:node/Number 1.0] [:node/Number 2.0]] [:node/Number 2.0]] history)))
;; 4 * 3 / 2
(is (= 6.0 (evaluate [:Slash [:Star [:Number 4] [:Number 3]] [:Number 2.0]] history)))
(is (= 6.0 (evaluate [:node/Slash [:node/Star [:node/Number 4] [:node/Number 3]] [:node/Number 2.0]] history)))
;; 5 % 2 * 2
(is (= 2.0 (evaluate [:Star [:Modulo [:Number 5.0] [:Number 2.0]] [:Number 2.0]] history))))
(is (= 2.0 (evaluate [:node/Star [:node/Modulo [:node/Number 5.0] [:node/Number 2.0]] [:node/Number 2.0]] history))))

(testing "unary minus is right associative"
(is (= -1.0 (evaluate [:Minus [:Number 1.0]] history)))
(is (= -6.0 (evaluate [:Minus [:Group [:Star [:Number 2.0] [:Number 3.0]]]] history)))))
(is (= -1.0 (evaluate [:node/Minus [:node/Number 1.0]] history)))
(is (= -6.0 (evaluate [:node/Minus [:node/Group [:node/Star [:node/Number 2.0] [:node/Number 3.0]]]] history)))))
Loading

0 comments on commit 4b33529

Please sign in to comment.