mirror of https://github.com/logseq/logseq
* Calculator: bug fixes and feature additions - Fix order of operations for negation and exponentiation; - Support non-integer powers; - Improve number parsing; - Support comments; - Add maths functions; - More permissive variable naming; - Store last result in 'last' variable. * Fix lint warning * Preserve last value across comments and empty lines * Fix lint warning * Use BigNumber operations to maintain precision * Add conditional around pow call * Split up long test * Remove duplicate testspull/5966/head
parent
f39f26c8d3
commit
8e2aa8415c
|
@ -40,11 +40,18 @@
|
|||
:sub (fn sub [a b] (-> a (.minus b)))
|
||||
:mul (fn mul [a b] (-> a (.multipliedBy b)))
|
||||
:div (fn div [a b] (-> a (.dividedBy b)))
|
||||
:pow (fn pow [a b] (-> a (.exponentiatedBy b)))
|
||||
:pow (fn pow [a b] (if (.isInteger b)
|
||||
(.exponentiatedBy a b)
|
||||
#?(:clj (java.lang.Math/pow a b)
|
||||
:cljs (bn/BigNumber (js/Math.pow a b)))))
|
||||
:abs (fn abs [a] (.abs a))
|
||||
:sqrt (fn abs [a] (.sqrt a))
|
||||
:log (fn log [a]
|
||||
#?(:clj (java.lang.Math/log10 a) :cljs (bn/BigNumber (js/Math.log10 a))))
|
||||
:ln (fn ln [a]
|
||||
#?(:clj (java.lang.Math/log a) :cljs (bn/BigNumber (js/Math.log a))))
|
||||
:exp (fn ln [a]
|
||||
#?(:clj (java.lang.Math/exp a) :cljs (bn/BigNumber (js/Math.exp a))))
|
||||
:sin (fn sin [a]
|
||||
#?(:clj (java.lang.Math/sin a) :cljs (bn/BigNumber(js/Math.sin a))))
|
||||
:cos (fn cos [a]
|
||||
|
@ -61,6 +68,7 @@
|
|||
(swap! env assoc var val)
|
||||
val)
|
||||
:toassign str/trim
|
||||
:comment (constantly nil)
|
||||
:variable (fn resolve [var]
|
||||
(let [var (str/trim var)]
|
||||
(or (get @env var)
|
||||
|
@ -79,12 +87,17 @@
|
|||
(catch #?(:clj Exception :cljs js/Error) e
|
||||
e))))
|
||||
|
||||
(defn assign-last-value [env val]
|
||||
(when-not (nil? val)
|
||||
(swap! env assoc "last" val))
|
||||
val)
|
||||
|
||||
(defn eval-lines [s]
|
||||
{:pre [(string? s)]}
|
||||
(let [env (new-env)]
|
||||
(mapv (fn [line]
|
||||
(when-not (str/blank? line)
|
||||
(eval env (parse line))))
|
||||
(assign-last-value env (eval env (parse line)))))
|
||||
(str/split-lines s))))
|
||||
|
||||
;; ======================================================================
|
||||
|
|
|
@ -1,28 +1,32 @@
|
|||
<start> = assignment | expr
|
||||
expr = add-sub
|
||||
<add-sub> = pow-term | mul-div | add | sub | variable
|
||||
<start> = assignment | expr | comment
|
||||
expr = add-sub comment
|
||||
comment = <#'\s*(#.*$)?'>
|
||||
<add-sub> = pow-term | mul-div | add | sub | variable
|
||||
add = add-sub <'+'> mul-div
|
||||
sub = add-sub <'-'> mul-div
|
||||
<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 = posterm <'^'> pow-term
|
||||
<function> = log | ln | exp | sqrt | abs | sin | cos | tan | acos | asin | atan
|
||||
log = <#'\s*'> <'log('> expr <')'> <#'\s*'>
|
||||
ln = <#'\s*'> <'ln('> expr <')'> <#'\s*'>
|
||||
exp = <#'\s*'> <'exp('> expr <')'> <#'\s*'>
|
||||
sqrt = <#'\s*'> <'sqrt('> expr <')'> <#'\s*'>
|
||||
abs = <#'\s*'> <'abs('> expr <')'> <#'\s*'>
|
||||
sin = <#'\s*'> <'sin('> expr <')'> <#'\s*'>
|
||||
cos = <#'\s*'> <'cos('> expr <')'> <#'\s*'>
|
||||
tan = <#'\s*'> <'tan('> expr <')'> <#'\s*'>
|
||||
atan = <#'\s*'> <'atan('> expr <')'> <#'\s*'>
|
||||
acos = <#'\s*'> <'acos('> expr <')'> <#'\s*'>
|
||||
asin = <#'\s*'> <'asin('> expr <')'> <#'\s*'>
|
||||
<posterm> = log | ln | trig | percent | scientific | number | variable | <#'\s*'> <'('> expr <')'> <#'\s*'>
|
||||
negterm = <#'\s*'> <'-'> posterm
|
||||
<posterm> = function | percent | scientific | number | variable | <#'\s*'> <'('> expr <')'> <#'\s*'>
|
||||
negterm = <#'\s*'> <'-'> posterm | <#'\s*'> <'-'> pow
|
||||
<term> = negterm | posterm
|
||||
scientific = #'\s*[0-9]+\.?[0-9]*(e|E)-?[0-9]+()\s*'
|
||||
number = #'\s*\d+(,\d+)*(\.\d*)?\s*'
|
||||
scientific = #'\s*[0-9]*\.?[0-9]+(e|E)[\-\+]?[0-9]+()\s*'
|
||||
number = #'\s*(\d+(,\d+)*(\.\d*)?|\d*\.\d+)\s*'
|
||||
percent = number <'%'> <#'\s*'>
|
||||
variable = #'\s*[a-zA-Z]+(\_+[a-zA-Z]+)*\s*'
|
||||
toassign = #'\s*[a-zA-Z]+(\_+[a-zA-Z]+)*\s*'
|
||||
variable = #'\s*_*[a-zA-Z]+[_a-zA-Z0-9]*\s*'
|
||||
toassign = #'\s*_*[a-zA-Z]+[_a-zA-Z0-9]*\s*'
|
||||
assignment = toassign <#'\s*'> <'='> <#'\s*'> expr
|
|
@ -19,7 +19,11 @@
|
|||
98123 "98123"
|
||||
1.0 " 1.0 "
|
||||
22.1124131 "22.1124131"
|
||||
100.01231 " 100.01231 ")
|
||||
100.01231 " 100.01231 "
|
||||
0.01231 " .01231 "
|
||||
0.015 ".015 "
|
||||
-0.2 "-.2"
|
||||
-0.3 "- .3")
|
||||
(testing "even when they have the commas in the wrong place"
|
||||
(are [value expr] (= value (run expr))
|
||||
98123 "9812,3"
|
||||
|
@ -62,15 +66,6 @@
|
|||
2.0 "2*100%"
|
||||
0.01 "2%/2"
|
||||
500e3 "50% * 1e6"))
|
||||
(testing "power"
|
||||
(are [value expr] (= value (run expr))
|
||||
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"
|
||||
4096.0 "200% ^ 12"))
|
||||
(testing "operator precedence"
|
||||
(are [value expr] (= value (run expr))
|
||||
1 "1 + 0 * 2"
|
||||
|
@ -90,9 +85,39 @@
|
|||
12.3 "123.0e-1"
|
||||
-12.3 "-123.0e-1"
|
||||
12.3 "123.0E-1"
|
||||
2.0 "1e0 + 1e0"))
|
||||
(testing "scientific functions"
|
||||
12300 "123.0E+2"
|
||||
2.0 "1e0 + 1e0"
|
||||
10 ".1e2"
|
||||
0.001 ".1e-2"
|
||||
-0.045 "-.45e-1"
|
||||
-210 "-.21e3"))
|
||||
(testing "avoiding rounding errors"
|
||||
(are [value expr] (= value (run expr))
|
||||
3.3 "1.1 + 2.2"
|
||||
2.2 "3.3 - 1.1"
|
||||
0.0001 "1/10000"
|
||||
1e-7 "1/10000000")))
|
||||
|
||||
(deftest scientific-functions
|
||||
(testing "power"
|
||||
(are [value expr] (= value (run expr))
|
||||
1.0 "1 ^ 0"
|
||||
4.0 "2^2 "
|
||||
-9.0 "-3^2 "
|
||||
9.0 "(-3)^2 "
|
||||
27.0 " 3^ 3"
|
||||
0.125 " 2^ -3"
|
||||
512.0 "2 ^ 3 ^ 2"
|
||||
256.0 "4.000 ^ 4.0"
|
||||
2.0 "4^0.5"
|
||||
0.1 "100^(-0.5)"
|
||||
125.0 "25^(3/2)"
|
||||
4096.0 "200% ^ 12"))
|
||||
(testing "functions"
|
||||
(are [value expr] (= value (run expr))
|
||||
2.0 "sqrt( 4 )"
|
||||
3.0 "abs( 3 )"
|
||||
3.0 "abs( -3 )"
|
||||
1.0 "cos( 0 * 1 )"
|
||||
0.0 "sin( 1 -1 )"
|
||||
0.0 "atan(tan(0))"
|
||||
|
@ -101,14 +126,9 @@
|
|||
0.0 "acos(cos(0))"
|
||||
5.0 "2 * log(10) + 3"
|
||||
1.0 "-2 * log(10) + 3"
|
||||
10.0 "ln(1) + 10"))
|
||||
(testing "avoiding rounding errors"
|
||||
(are [value expr] (= value (run expr))
|
||||
3.3 "1.1 + 2.2"
|
||||
2.2 "3.3 - 1.1"
|
||||
0.0001 "1/10000"
|
||||
1e-7 "1/10000000"
|
||||
)))
|
||||
10.0 "ln(1) + 10"
|
||||
1.0 "exp(0)"
|
||||
2.0 "ln(exp(2))")))
|
||||
|
||||
(deftest variables
|
||||
(testing "variables can be remembered"
|
||||
|
@ -116,7 +136,8 @@
|
|||
(calc/eval env (calc/parse expr))
|
||||
(= final-env (into {} (for [[k v] @env] [k (convert-bigNum v)]))))
|
||||
{"a" 1} "a = 1"
|
||||
{"a" -1} "a = -1"
|
||||
{"a" -1} "a = -1"
|
||||
{"k9" 27} "k9 = 27"
|
||||
{"variable" 1} "variable = 1 + 0 * 2"
|
||||
{"x" 1} "x= 2 * 1 - 1 "
|
||||
{"y" 4} "y =8 / 4 + 2 * 1 - 25 * 0 / 1"
|
||||
|
@ -128,6 +149,7 @@
|
|||
(calc/eval env (calc/parse expr))
|
||||
(= final-env (into {} (for [[k v] @env] [k (convert-bigNum v)]))))
|
||||
{"a_a" 1} "a_a = 1"
|
||||
{"_foo" 1} "_foo = 1"
|
||||
{"x_yy_zzz" 1} "x_yy_zzz= 1"
|
||||
{"foo_bar_baz" 1} "foo_bar_baz = 1 + -0 * 2"))
|
||||
(testing "variables can be reused"
|
||||
|
@ -150,15 +172,33 @@
|
|||
{"a" 2 "b" 2} ["a = 1" "b = a + 1" "a = b"]
|
||||
{"variable" 1 "x" 0} ["variable = 1 + 0 * 2" "x = log(variable)" "x = variable - 1"])))
|
||||
|
||||
(deftest last-value
|
||||
(testing "last value is set"
|
||||
(are [values exprs] (let [env (calc/new-env)]
|
||||
(mapv (fn [expr]
|
||||
(calc/eval env (calc/parse expr)))
|
||||
exprs))
|
||||
[42 126] ["6*7" "last*3"]
|
||||
[25 5] ["3^2+4^2" "sqrt(last)"]
|
||||
[6 12] ["2*3" "# a comment" "" " " "last*2"])))
|
||||
|
||||
(deftest comments
|
||||
(testing "comments are ignored"
|
||||
(are [value expr] (= value (run expr))
|
||||
nil "# this comment is ignored"
|
||||
nil " # this comment is ignored "
|
||||
8.0 "2*4# double 4"
|
||||
10.0 "2*5 # double 5"
|
||||
12.0 "2*6 # double 6"
|
||||
14.0 "2*7 # 99")))
|
||||
|
||||
(deftest failure
|
||||
(testing "expressions that don't match the spec fail"
|
||||
(are [expr] (calc/failure? (calc/eval (calc/new-env) (calc/parse expr)))
|
||||
"foo_ ="
|
||||
"foo__ ="
|
||||
"oo___ ="
|
||||
" "
|
||||
"bar_2 = 2 + 4"
|
||||
"bar_2a = 3 + 4"
|
||||
"foo_ = "
|
||||
"foo__ ="
|
||||
"foo_3 = a")))
|
||||
" . "
|
||||
"_ = 2"
|
||||
"__ = 4"
|
||||
"foo_3 = _")))
|
||||
|
|
Loading…
Reference in New Issue