Merge remote-tracking branch 'upstream/master' into whiteboards

This commit is contained in:
Peng Xiao
2022-07-20 11:12:54 +08:00
6 changed files with 251 additions and 34 deletions

View File

@@ -110,12 +110,10 @@ For help on more options, run `node static/tests.js -h`.
#### Autorun Tests
To run tests automatically on file save, run `yarn
shadow-cljs watch test --config-merge '{:autorun true}'`. The test output may
appear where shadow-cljs was first invoked e.g. where `yarn watch` is running.
Specific namespace(s) can be auto run with the `:ns-regexp` option e.g. `npx
shadow-cljs watch test --config-merge '{:autorun true :ns-regexp
"frontend.util.page-property-test"}'`.
To run tests automatically on file save, run `clojure -M:test watch test
--config-merge '{:autorun true}'`. Specific namespace(s) can be auto run with
the `:ns-regexp` option e.g. `clojure -M:test watch test --config-merge
'{:autorun true :ns-regexp "frontend.util.page-property-test"}'`.
## Logging

View File

@@ -3,7 +3,7 @@ import { test } from './fixtures'
import { createRandomPage } from './utils'
test('flashcard demo', async ({ page, block }) => {
test.skip('flashcard demo', async ({ page, block }) => {
await createRandomPage(page)
await block.mustFill('Why do you add cards? #card #logseq')

View File

@@ -1,7 +1,6 @@
(ns frontend.extensions.calc
(:refer-clojure :exclude [eval])
(:require [clojure.edn :as edn]
[clojure.string :as str]
(:require [clojure.string :as str]
[frontend.util :as util]
[bignumber.js :as bn]
@@ -18,6 +17,10 @@
#?(:clj (def parse (insta/parser (io/resource "grammar/calc.bnf")))
:cljs (defparser parse (rc/inline "grammar/calc.bnf")))
(def constants {
"PI" (bn/BigNumber "3.14159265358979323846")
"E" (bn/BigNumber "2.71828182845904523536")})
(defn exception? [e]
#?(:clj (instance? Exception e)
:cljs (instance? js/Error e)))
@@ -29,28 +32,40 @@
;; TODO: Set DECIMAL_PLACES https://mikemcl.github.io/bignumber.js/#decimal-places
(defn factorial [n]
(reduce
(fn [a b] (.multipliedBy a b))
(bn/BigNumber 1)
(range 2 (inc n))))
(defn eval* [env ast]
(insta/transform
{:number (comp bn/BigNumber #(str/replace % "," ""))
:percent (fn percent [a] (-> a (.dividedBy 100.00)))
:scientific (comp bn/BigNumber edn/read-string)
:scientific bn/BigNumber
:mixed-number (fn [whole numerator denominator]
(.plus (.dividedBy (bn/BigNumber numerator) denominator) whole))
:negterm (fn neg [a] (-> a (.negated)))
:expr identity
:add (fn add [a b] (-> a (.plus b)))
:sub (fn sub [a b] (-> a (.minus b)))
:mul (fn mul [a b] (-> a (.multipliedBy b)))
:div (fn div [a b] (-> a (.dividedBy b)))
:mod (fn mod [a b] (-> a (.modulo 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)))))
:factorial (fn fact [a] (if (and (.isInteger a) (.isPositive a) (.isLessThan a 254))
(factorial (.toNumber a))
(bn/BigNumber 'NaN')))
:abs (fn abs [a] (.abs a))
:sqrt (fn abs [a] (.sqrt a))
:sqrt (fn sqrt [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]
:exp (fn exp [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))))
@@ -65,13 +80,38 @@
:acos (fn acos [a]
#?(:clj (java.lang.Math/acos a) :cljs (bn/BigNumber(js/Math.acos a))))
:assignment (fn assign! [var val]
(swap! env assoc var val)
(if (contains? constants var)
(throw
(ex-info (util/format "Can't redefine constant %s" var) {:var var}))
(swap! env assoc var val))
val)
:toassign str/trim
:comment (constantly nil)
:digits int
:format-fix (fn format [places]
(swap! env assoc :mode "fix" :places places)
(get @env "last"))
:format-sci (fn format [places]
(swap! env assoc :mode "sci" :places places)
(get @env "last"))
:format-frac (fn format [max-denominator]
(swap! env dissoc :mode :improper)
(swap! env assoc :mode "frac" :max-denominator max-denominator)
(get @env "last"))
:format-impf (fn format [max-denominator]
(swap! env assoc :mode "frac" :max-denominator max-denominator :improper true)
(get @env "last"))
:format-norm (fn format [precision]
(swap! env dissoc :mode :places)
(swap! env assoc :precision precision)
(get @env "last"))
:base (fn base [b]
(swap! env assoc :base (str/lower-case b))
(get @env "last"))
:variable (fn resolve [var]
(let [var (str/trim var)]
(or (get @env var)
(or (get constants var)
(get @env var)
(throw
(ex-info (util/format "Can't find variable %s" var)
{:var var})))))}
@@ -92,12 +132,86 @@
(swap! env assoc "last" val))
val)
(defn can-fix?
"Check that number can render without loss of all significant digits,
and that the absolute value is less than 1e21."
[num places]
(or (.isZero num )
(let [mag (.abs num)
lower-bound (-> (bn/BigNumber 0.5) (.shiftedBy (- places)))
upper-bound (bn/BigNumber 1e21)]
(and (-> mag (.isGreaterThanOrEqualTo lower-bound))
(-> mag (.isLessThan upper-bound))))))
(defn can-fit?
"Check that number can render normally within the given number of digits.
Tolerance allows for leading zeros in a decimal fraction."
[num digits tolerance]
(and (< (.-e num) digits)
(.isInteger (.shiftedBy num (+ tolerance digits)))))
(defn format-base [val base]
(let [sign (.-s val)
display-val (if (neg-int? sign) (.abs val) val)]
(str
(when (neg-int? sign) "-")
(case base 2 "0b" 8 "0o" 16 "0x")
(.toString display-val base))))
(defn format-fraction [numerator denominator improper]
(let [whole (.dividedToIntegerBy numerator denominator)]
(if (or improper (.isZero whole))
(str numerator "/" denominator )
(str whole " "
(.abs (.modulo numerator denominator)) "/" denominator))))
(defn format-normal [env val]
(let [precision (or (get @env :precision) 21)
display-val (.precision val precision)]
(if (can-fit? display-val precision 1)
(.toFixed display-val)
(.toExponential display-val))))
(defn format-val [env val]
(if (instance? bn/BigNumber val)
(let [mode (get @env :mode)
base (get @env :base)
places (get @env :places)]
(cond
(= base "hex")
(format-base val 16)
(= base "oct")
(format-base val 8)
(= base "bin")
(format-base val 2)
(= mode "fix")
(if (can-fix? val places)
(.toFixed val places)
(.toExponential val places))
(= mode "sci")
(.toExponential val places)
(= mode "frac")
(let [max-denominator (or (get @env :max-denominator) 4095)
improper (get @env :improper)
[numerator denominator] (.toFraction val max-denominator)
delta (.minus (.dividedBy numerator denominator) val)]
(if (or (.isZero delta) (< (.-e delta) -16))
(if (> denominator 1)
(format-fraction numerator denominator improper)
(format-normal env numerator))
(format-normal env val)))
:else
(format-normal env val)))
val))
(defn eval-lines [s]
{:pre [(string? s)]}
(let [env (new-env)]
(mapv (fn [line]
(when-not (str/blank? line)
(assign-last-value env (eval env (parse line)))))
(format-val env (assign-last-value env (eval env (parse line))))))
(str/split-lines s))))
;; ======================================================================

View File

@@ -1252,7 +1252,8 @@
;; if different direction, keep clear until one left
(state/selection?)
(clear-last-selected-block!)))
(clear-last-selected-block!))
nil)
(defn on-select-block
[direction]
@@ -3053,7 +3054,8 @@
(select-up-down direction)
:else
(select-first-last direction)))))
(select-first-last direction)))
nil))
(defn shortcut-select-up-down [direction]
(fn [e]

View File

@@ -1,14 +1,16 @@
<start> = assignment | expr | comment
expr = add-sub comment
<start> = assignment | expr | comment | directive
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-div> = pow-term | mul | div | mod
mul = mul-div <'*'> pow-term
div = mul-div <'/'> pow-term
<pow-term> = pow | term
mod = mul-div <'mod'> pow-term
<pow-term> = pow | factorial | term
pow = posterm <'^'> pow-term
factorial = posterm <'!'> <#'\s*'>
<function> = log | ln | exp | sqrt | abs | sin | cos | tan | acos | asin | atan
log = <#'\s*'> <'log('> expr <')'> <#'\s*'>
ln = <#'\s*'> <'ln('> expr <')'> <#'\s*'>
@@ -21,12 +23,30 @@ tan = <#'\s*'> <'tan('> expr <')'> <#'\s*'>
atan = <#'\s*'> <'atan('> expr <')'> <#'\s*'>
acos = <#'\s*'> <'acos('> expr <')'> <#'\s*'>
asin = <#'\s*'> <'asin('> expr <')'> <#'\s*'>
<posterm> = function | percent | scientific | number | variable | <#'\s*'> <'('> expr <')'> <#'\s*'>
negterm = <#'\s*'> <'-'> posterm | <#'\s*'> <'-'> pow
<posterm> = function | percent | scientific | number | mixed-number | variable | <#'\s*'> <'('> expr <')'> <#'\s*'>
negterm = <#'\s*'> <'-'> ( posterm | pow | factorial )
<term> = negterm | posterm
scientific = #'\s*[0-9]*\.?[0-9]+(e|E)[\-\+]?[0-9]+()\s*'
number = #'\s*(\d+(,\d+)*(\.\d*)?|\d*\.\d+)\s*'
number = decimal-number | hexadecimal-number | octal-number | binary-number
<decimal-number> = #'\s*(\d+(,\d+)*(\.\d*)?|\d*\.\d+)\s*'
<hexadecimal-number> = #'\s*0x([0-9a-fA-F]+(,[0-9a-fA-F]+)*(\.[0-9a-fA-F]*)?|[0-9a-fA-F]*\.[0-9a-fA-F]+)\s*'
<octal-number> = #'\s*0o([0-7]+(,[0-7]+)*(\.[0-7]*)?|[0-7]*\.[0-7]+)\s*'
<binary-number> = #'\s*0b([01]+(,[01]+)*(\.[01]*)?|[01]*\.[01]+)\s*'
mixed-number = <#'\s*'> digits <#'\s+'> digits <'/'> digits <#'\s*'>
percent = number <'%'> <#'\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
assignment = toassign <#'\s*'> <'='> <#'\s*'> expr
<directive> = <#'\s*\:'> (format | base) <#'\s*'> [comment]
<format> = <#'(format|fmt)\s+'> ( format-fix | format-sci | format-norm | format-frac | format-impf )
format-fix = <#'(?i)fix(ed)?\s*'> digits
format-sci = <#'(?i)sci(entific)?\s*'> [digits]
format-norm = <#'(?i)norm(al)?\s*'> [digits]
format-frac = <#'(?i)frac(tions?)?\s*'> [digits]
format-impf = <#'(?i)imp(roper)?\s*'> [digits]
base = base-hex | base-dec | base-oct | base-bin
<base-hex> = #'(?i)hex' <#'(?i)(adecimal)?'>
<base-dec> = #'(?i)dec' <#'(?i)(imal)?'>
<base-oct> = #'(?i)oct' <#'(?i)(al)?'>
<base-bin> = #'(?i)bin' <#'(?i)(ary)?'>
digits = #'\d+'

View File

@@ -1,11 +1,11 @@
(ns frontend.extensions.calc-test
(:require [clojure.test :as test :refer [are deftest testing]]
[clojure.string :as str]
[clojure.edn :as edn]
[frontend.extensions.calc :as calc]))
(defn convert-bigNum [b]
(edn/read-string (str b))
)
(edn/read-string (str b)))
(defn run [expr]
{:pre [(string? expr)]}
@@ -130,6 +130,23 @@
1.0 "exp(0)"
2.0 "ln(exp(2))")))
(deftest additional-operators
(testing "mod"
(are [value expr] (= value (run expr))
0.0 "1 mod 1"
1.0 "7 mod 3"
3.0 "7 mod 4"
0.5 "4.5 mod 2"
-3.0 "-7 mod 4"))
(testing "factorial"
(are [value expr] (= value (run expr))
1.0 "0!"
1.0 "1!"
6.0 "3.0!"
-120.0 "-5!"
124.0 "(2+3)!+4"
240.0 "10 * 4!")))
(deftest variables
(testing "variables can be remembered"
(are [final-env expr] (let [env (calc/new-env)]
@@ -174,13 +191,78 @@
(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"])))
(are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs)))
["42" "126"] ["6*7" "last*3"]
["25" "5"] ["3^2+4^2" "sqrt(last)"]
["6" nil nil nil "12"] ["2*3" "# a comment" "" " " "last*2"])))
(deftest formatting
(testing "display normal"
(are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs)))
[nil "1000000"] [":format norm" "1e6" ]
[nil "1000000"] [":format norm 7" "1e6"]
[nil "1e+6"] [":format norm 6" "1e6"]
[nil "3.14"] [":format norm 3" "PI"]
[nil "3"] [":format norm 1" "E"]
[nil "0.000123"] [":format norm 5" "0.000123"]
[nil "1.23e-4"] [":format norm 4" "0.000123"]
[nil "123400000"] [":format normal 9" "1.234e8"]
[nil "1.234e+8"] [":format normal 8" "1.234e8"]))
(testing "display fixed"
(are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs)))
[nil "0.123450"] [":format fix 6" "0.12345"]
[nil "0.1235"] [":format fix 4" "0.12345"]
[nil "2.7183"] [":format fixed 4" "E"]
[nil "0.001"] [":format fix 3" "0.0005"]
[nil "4.000e-4"] [":format fix 3" "0.0004"]
[nil "1.00e+21"] [":format fixed 2" "1e21+0.1"]))
(testing "display scientific"
(are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs)))
[nil "1e+6"] [":format sci" "1e6"]
[nil "3.142e+0"] [":format sci 3" "PI"]
[nil "3.14e+2"] [":format scientific" "3.14*10^2"])))
(deftest fractions
(testing "mixed numbers"
(are [value expr] (= value (run expr))
0 "0 0/1"
1 "0 1/1"
1 "1 0/1"
2.5 "2 1/2"
2.5 "2 1/2"
-4.28 "-4 7/25"
2.00101 "2 101/100000"
-99.2 "-99 8/40"))
(testing "display fractions"
(are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs)))
[nil "4 3/8"] [":format frac" "4.375"]
[nil "-7 1/4"] [":format fraction" "-7.25"]
[nil "2"] [":format fractions" "19/20 + 1 1/20"]
[nil "-2"] [":format frac" "19/17 - 3 2/17"]
[nil "3.14157"] [":format frac" "3.14157"]
[nil "3 14157/100000"] [":format frac 100000" "3.14157"]))
(testing "display improper fractions"
(are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs)))
[nil "35/8"] [":format improper" "4.375"]
[nil "-29/4"] [":format imp" "-7.25"]
[nil "3.14157"] [":format improper" "3.14157" ]
[nil "314157/100000"] [":format imp 100000" "3.14157"])))
(deftest base-conversion
(testing "mixed base input"
(are [value expr] (= value (run expr))
255.0 "0xff"
511.0 "0x0A + 0xF5 + 0x100"
83.0 "0o123"
324.0 "0x100 + 0o100 + 0b100"
32.0 "0b100 * 0b1000"))
(testing "mixed base output"
(are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs)))
["12345" "0x3039"] ["12345" ":hex"]
["12345" "0o30071"] ["12345" ":oct"]
["12345" "0b11000000111001"]["12345" ":bin"]
[nil "0b100000000"] [":bin" "0b10000 * 0b10000"]
[nil "-0xff"] [":hex" "-255"])))
(deftest comments
(testing "comments are ignored"
@@ -201,4 +283,5 @@
" . "
"_ = 2"
"__ = 4"
"PI = 3.14"
"foo_3 = _")))