Browse Source

fix: simplify calc parsing

Sebastian Bensusan 4 years ago
parent
commit
5c426ec603

+ 21 - 22
src/main/frontend/extensions/calc.cljc

@@ -27,39 +27,38 @@
 
 (defn eval* [env ast]
   (insta/transform
-   {:number        (comp edn/read-string #(str/replace % "," ""))
-    :negnumber     (comp edn/read-string #(str/replace % "," ""))
-    :scientific    edn/read-string
-    :negscientific edn/read-string
-    :expr          identity
-    :add           +
-    :sub           -
-    :mul           *
-    :div           /
-    :pow           (fn [a b]
+   {:number     (comp edn/read-string #(str/replace % "," ""))
+    :scientific edn/read-string
+    :negterm    (fn neg [a] (- a))
+    :expr       identity
+    :add        +
+    :sub        -
+    :mul        *
+    :div        /
+    :pow        (fn pow [a b]
                   #?(:clj (java.lang.Math/pow a b) :cljs (js/Math.pow a b)))
-    :log           (fn [a]
+    :log        (fn log [a]
                   #?(:clj (java.lang.Math/log10 a) :cljs (js/Math.log10 a)))
-    :ln            (fn [a]
+    :ln         (fn ln [a]
                   #?(:clj (java.lang.Math/log a) :cljs (js/Math.log a)))
-    :sin           (fn [a]
+    :sin        (fn sin [a]
                   #?(:clj (java.lang.Math/sin a) :cljs (js/Math.sin a)))
-    :cos           (fn [a]
+    :cos        (fn cos [a]
                   #?(:clj (java.lang.Math/cos a) :cljs (js/Math.cos a)))
-    :tan           (fn [a]
+    :tan        (fn tan [a]
                   #?(:clj (java.lang.Math/tan a) :cljs (js/Math.tan a)))
-    :atan          (fn [a]
+    :atan       (fn atan [a]
                   #?(:clj (java.lang.Math/atan a) :cljs (js/Math.atan a)))
-    :asin          (fn [a]
+    :asin       (fn asin [a]
                   #?(:clj (java.lang.Math/asin a) :cljs (js/Math.asin a)))
-    :acos          (fn [a]
+    :acos       (fn acos [a]
                   #?(:clj (java.lang.Math/acos a) :cljs (js/Math.acos a)))
-    :assignment    (fn [var val]
+    :assignment (fn assign! [var val]
                   (swap! env assoc var val)
                   val)
-    :toassign      str/trim
-    :variable      (fn [var]
-                     (let [var (str/trim var)]
+    :toassign   str/trim
+    :variable   (fn resolve [var]
+                  (let [var (str/trim var)]
                     (or (get @env var)
                         (throw
                          (ex-info (util/format "Can't find variable %s" var)

+ 9 - 9
src/main/grammar/calc.bnf

@@ -1,14 +1,14 @@
 <start> = assignment | expr
 expr = add-sub
-<add-sub> = pow-log | mul-div | add | sub |  variable
+<add-sub> = pow-term | mul-div | add | sub |  variable
 add = add-sub <'+'> mul-div
 sub = add-sub <'-'> mul-div
-<mul-div> = pow-log | mul | div
-mul = mul-div <'*'> pow-log
-div = mul-div <'/'> pow-log
+<mul-div> = pow-term | mul | div
+mul = mul-div <'*'> pow-term
+div = mul-div <'/'> pow-term
+<pow-term> = pow | term
+pow = pow-term <'^'> term
 <trig> = sin | cos | tan | acos | asin | atan
-<pow-log> = term | pow | log | ln | trig
-pow = pow-log <'^'> term
 log = <#'\s*'> <'log('> expr <')'> <#'\s*'>
 ln = <#'\s*'> <'ln('> expr <')'> <#'\s*'>
 sin = <#'\s*'> <'sin('> expr <')'> <#'\s*'>
@@ -17,10 +17,10 @@ tan = <#'\s*'> <'tan('> expr <')'> <#'\s*'>
 atan = <#'\s*'> <'atan('> expr <')'> <#'\s*'>
 acos = <#'\s*'> <'acos('> expr <')'> <#'\s*'>
 asin = <#'\s*'> <'asin('> expr <')'> <#'\s*'>
-<term> = negscientific | scientific | negnumber | number | variable | <#'\s*'> <'('> expr <')'> <#'\s*'>
-negscientific = #'\s*-[0-9]+\.?[0-9]*(e|E)-?[0-9]+()\s*'
+<posterm> = log | ln | trig | scientific | number | variable | <#'\s*'> <'('> expr <')'> <#'\s*'>
+negterm = <#'\s*'> <'-'> posterm
+<term> = negterm | posterm
 scientific = #'\s*[0-9]+\.?[0-9]*(e|E)-?[0-9]+()\s*'
-negnumber = #'\s*-\d+(,\d+)*(\.\d*)?\s*'
 number = #'\s*\d+(,\d+)*(\.\d*)?\s*'
 variable = #'\s*[a-zA-Z]+(\_+[a-zA-Z]+)*\s*'
 toassign = #'\s*[a-zA-Z]+(\_+[a-zA-Z]+)*\s*'

+ 7 - 1
src/test/frontend/extensions/calc_test.cljc

@@ -54,6 +54,7 @@
       1.0   "1 ^ 0"
       4.0   "2^2 "
       27.0  " 3^ 3"
+      0.125 " 2^ -3"
       16.0  "2 ^ 2 ^ 2"
       256.0 "4.000 ^ 4.0"))
   (testing "operator precedence"
@@ -63,6 +64,8 @@
       4     "8 / 4 + 2 * 1 - 25 * 0 / 1"
       14.0  "3 *2 ^ 2 + 1 * 2"
       74.0  "((3*2) ^ 2 + 1) * 2"
+      -74.0 "((3*2) ^ 2 + 1) * -2"
+      74.0  "-((3*2) ^ 2 + 1) * -2"
       432.0 "(3*2) ^ (2 + 1) * 2"
       97.0  "(2 * 3) * 2 ^ (2 * 2) + 1"
       4.0   "2 * 3 / 2 ^ 2 * 2 + 1"))
@@ -80,8 +83,10 @@
       0.0  "sin( 1 -1 )"
       0.0  "atan(tan(0))"
       1.0  "sin(asin(0)) + 1"
+      1.0  "-sin(asin(0)) + 1"
       0.0  "acos(cos(0))"
       5.0  "2 * log(10) + 3"
+      1.0  "-2 * log(10) + 3"
       10.0 "ln(1) + 10")))
 
 (deftest variables
@@ -103,13 +108,14 @@
                             (= final-env @env))
       {"a_a" 1}         "a_a = 1"
       {"x_yy_zzz" 1}    "x_yy_zzz= 1"
-      {"foo_bar_baz" 1} "foo_bar_baz = 1 + 0 * 2"))
+      {"foo_bar_baz" 1} "foo_bar_baz = 1 + -0 * 2"))
   (testing "variables can be reused"
     (are [final-env exprs] (let [env (calc/new-env)]
                              (doseq [expr exprs]
                                (calc/eval env (calc/parse expr)))
                              (= final-env @env))
       {"a" 1 "b" 2}          ["a = 1" "b = a + 1"]
+      {"a" 1 "b" 0}          ["a = 1" "b = -a + 1"]
       {"a" 1 "b" 3}          ["a = 1" "b=a*2+1"]
       {"a_a" 1 "b_b" 2}      ["a_a = 1" "b_b = a_a + 1"]
       {"variable" 1 "x" 0.0} ["variable = 1 + 0 * 2" "x = log(variable)"]