From 70c04e34bf1288d12acfcdd94df825b3688e08a7 Mon Sep 17 00:00:00 2001 From: Martin Scott Date: Fri, 15 Jul 2022 19:43:34 +0100 Subject: [PATCH 1/7] Add operations, display formats, constants and bases New operations: - factorial operator `!`; - modulo operator `mod`. Constants added: - `PI` - `E` New display format controls: - `:fix ` display format; - `:sci ` display format; - `:norm ` display format; - `:hex`, `:oct`, `:bin` and`:dec` display formats. New number formats: - Enter hexadecimal, octal and binary numbers using `0x`, `0o` and `0b` prefixes respectively. Bugs fixed: - Loss of precision when supplying numbers in scientific notation. --- src/main/frontend/extensions/calc.cljc | 93 +++++++++++++++++++-- src/main/grammar/calc.bnf | 30 +++++-- src/test/frontend/extensions/calc_test.cljc | 71 ++++++++++++++++ 3 files changed, 179 insertions(+), 15 deletions(-) diff --git a/src/main/frontend/extensions/calc.cljc b/src/main/frontend/extensions/calc.cljc index d20f0d301c..552c53d13c 100644 --- a/src/main/frontend/extensions/calc.cljc +++ b/src/main/frontend/extensions/calc.cljc @@ -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,38 @@ ;; 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 :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 +78,31 @@ :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 + :mode-fix (fn format [places] + (swap! env assoc :mode "fix" :places places) + (get @env "last")) + :mode-sci (fn format [places] + (swap! env assoc :mode "sci" :places places) + (get @env "last")) + :mode-norm (fn format [precision] + (swap! env dissoc :mode :places) + (swap! env assoc :precision precision) + (get @env "last")) + :mode-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 +123,58 @@ (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-val [env val] + (if (instance? bn/BigNumber val) + (let [mode (get @env :mode) + base (get @env :base) + places (get @env :places)] + (cond + (= base "hex") + (.toString val 16) + (= base "oct") + (.toString val 8) + (= base "bin") + (.toString val 2) + + (= mode "fix") + (if (can-fix? val places) + (.toFixed val places) + (.toExponential val places)) + (= mode "sci") + (.toExponential val places) + + :else + (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))))) + 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)))) ;; ====================================================================== diff --git a/src/main/grammar/calc.bnf b/src/main/grammar/calc.bnf index 12466ef6b6..a6f1addc2c 100644 --- a/src/main/grammar/calc.bnf +++ b/src/main/grammar/calc.bnf @@ -1,14 +1,16 @@ - = assignment | expr | comment -expr = add-sub comment + = assignment | expr | comment | mode +expr = add-sub [comment] comment = <#'\s*(#.*$)?'> = pow-term | mul-div | add | sub | variable add = add-sub <'+'> mul-div sub = add-sub <'-'> mul-div - = pow-term | mul | div + = pow-term | mul | div | mod mul = mul-div <'*'> pow-term div = mul-div <'/'> pow-term - = pow | term +mod = mul-div <'mod'> pow-term + = pow | factorial | term pow = posterm <'^'> pow-term +factorial = posterm <'!'> <#'\s*'> = log | ln | exp | sqrt | abs | sin | cos | tan | acos | asin | atan log = <#'\s*'> <'log('> expr <')'> <#'\s*'> ln = <#'\s*'> <'ln('> expr <')'> <#'\s*'> @@ -22,11 +24,25 @@ atan = <#'\s*'> <'atan('> expr <')'> <#'\s*'> acos = <#'\s*'> <'acos('> expr <')'> <#'\s*'> asin = <#'\s*'> <'asin('> expr <')'> <#'\s*'> = function | percent | scientific | number | variable | <#'\s*'> <'('> expr <')'> <#'\s*'> -negterm = <#'\s*'> <'-'> posterm | <#'\s*'> <'-'> pow +negterm = <#'\s*'> <'-'> posterm | <#'\s*'> <'-'> pow | <#'\s*'> <'-'> factorial = 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 + = #'\s*(\d+(,\d+)*(\.\d*)?|\d*\.\d+)\s*' + = #'\s*0x([0-9a-fA-F]+(,[0-9a-fA-F]+)*(\.[0-9a-fA-F]*)?|[0-9a-fA-F]*\.[0-9a-fA-F]+)\s*' + = #'\s*0o([0-7]+(,[0-7]+)*(\.[0-7]*)?|[0-7]*\.[0-7]+)\s*' + = #'\s*0b([01]+(,[01]+)*(\.[01]*)?|[01]*\.[01]+)\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 \ No newline at end of file +assignment = toassign <#'\s*'> <'='> <#'\s*'> expr + = <#'\s*\:'> ( mode-fix | mode-sci | mode-norm | mode-base ) <#'\s*'> [comment] +mode-fix = <#'(?i)fix(ed)?\s*'> digits +mode-sci = <#'(?i)sci(entific)?\s*'> [digits] +mode-norm = <#'(?i)norm(al)?\s*'> [digits] +mode-base = mode-hex | mode-dec | mode-oct | mode-bin + = #'(?i)hex' <#'(?i)(adecimal)?'> + = #'(?i)dec' <#'(?i)(imal)?'> + = #'(?i)oct' <#'(?i)(al)?'> + = #'(?i)bin' <#'(?i)(ary)?'> +digits = #'\d+' \ No newline at end of file diff --git a/src/test/frontend/extensions/calc_test.cljc b/src/test/frontend/extensions/calc_test.cljc index 79ee24e4df..c94b3e212d 100644 --- a/src/test/frontend/extensions/calc_test.cljc +++ b/src/test/frontend/extensions/calc_test.cljc @@ -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)] @@ -182,6 +199,59 @@ [25 5] ["3^2+4^2" "sqrt(last)"] [6 12] ["2*3" "# a comment" "" " " "last*2"]))) +(deftest formatting + (testing "display normal" + (are [values exprs] (let [env (calc/new-env)] + (mapv (fn [expr] + (calc/eval env (calc/parse expr))) + exprs)) + [1e6 "1000000"] ["1e6" ":norm"] + [1e6 "1000000"] ["1e6" ":norm 7"] + [1e6 "1e+6"] ["1e6" ":norm 6"] + [0 "0" "3.14"] ["0" ":norm 3" "PI"] + [0 "0" "2"] ["0" ":norm 1" "E"] + [0.000123 "0.000123"] ["0.000123" ":norm 5"] + [0.000123 "1.23e-4"] ["0.000123" ":norm 4"] + [123400000 "123400000"] ["1.234e8" ":norm 9"] + [123400000 "1.234e+8"] ["1.234e8" ":norm 8"])) + (testing "display fixed" + (are [values exprs] (let [env (calc/new-env)] + (mapv (fn [expr] + (calc/eval env (calc/parse expr))) + exprs)) + [0.12345 "0.123450"] ["0.12345" ":fix 6"] + [0.12345 "0.1235"] ["0.12345" ":fix 4"] + ["" "2.7183"] [":fixed 4" "E"] + [0.0005 "0.001"] ["0.0005" ":fix 3"] + [0.0005 "4.000e-4"] ["0.0004" ":fix 3"] + [1e21 "1.00e+21"] ["1e21+0.1" ":fix 2"])) + (testing "display scientific" + (are [values exprs] (let [env (calc/new-env)] + (mapv (fn [expr] + (calc/eval env (calc/parse expr))) + exprs)) + [1e6 "1e+6"] ["1e6" ":sci"] + [0 "0.000e0" "3.142e+3"]["0" ":sci 3" "PI"] + ["" "3.14e+2"] [":sci" "3.14*10^2"]))) + +(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] (let [env (calc/new-env)] + (mapv (fn [expr] + (calc/eval env (calc/parse expr))) + exprs)) + ["12345" "3039"] ["12345" ":hex"] + ["12345" "30071"] ["12345" ":oct"] + ["12345" "11000000111001"]["12345" ":bin"] + ["" "100000000"] [":bin" "0b10000 * 0b10000"]))) + (deftest comments (testing "comments are ignored" (are [value expr] (= value (run expr)) @@ -201,4 +271,5 @@ " . " "_ = 2" "__ = 4" + "PI = 3.14" "foo_3 = _"))) From a4b79ffe4fbfc3d8b87292bfe6938a34cf1ce120 Mon Sep 17 00:00:00 2001 From: Martin Scott Date: Mon, 18 Jul 2022 00:49:59 +0100 Subject: [PATCH 2/7] Fix tests Compare output strings based on input lines. --- src/test/frontend/extensions/calc_test.cljc | 73 ++++++++------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/src/test/frontend/extensions/calc_test.cljc b/src/test/frontend/extensions/calc_test.cljc index c94b3e212d..d735a89126 100644 --- a/src/test/frontend/extensions/calc_test.cljc +++ b/src/test/frontend/extensions/calc_test.cljc @@ -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)]} @@ -191,48 +191,36 @@ (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] (let [env (calc/new-env)] - (mapv (fn [expr] - (calc/eval env (calc/parse expr))) - exprs)) - [1e6 "1000000"] ["1e6" ":norm"] - [1e6 "1000000"] ["1e6" ":norm 7"] - [1e6 "1e+6"] ["1e6" ":norm 6"] - [0 "0" "3.14"] ["0" ":norm 3" "PI"] - [0 "0" "2"] ["0" ":norm 1" "E"] - [0.000123 "0.000123"] ["0.000123" ":norm 5"] - [0.000123 "1.23e-4"] ["0.000123" ":norm 4"] - [123400000 "123400000"] ["1.234e8" ":norm 9"] - [123400000 "1.234e+8"] ["1.234e8" ":norm 8"])) + (are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs))) + [nil "1000000"] [":norm" "1e6" ] + [nil "1000000"] [":norm 7" "1e6"] + [nil "1e+6"] [":norm 6" "1e6"] + [nil "3.14"] [":norm 3" "PI"] + [nil "3"] [":norm 1" "E"] + [nil "0.000123"] [":norm 5" "0.000123"] + [nil "1.23e-4"] [":norm 4" "0.000123"] + [nil "123400000"] [":norm 9" "1.234e8"] + [nil "1.234e+8"] [":norm 8" "1.234e8"])) (testing "display fixed" - (are [values exprs] (let [env (calc/new-env)] - (mapv (fn [expr] - (calc/eval env (calc/parse expr))) - exprs)) - [0.12345 "0.123450"] ["0.12345" ":fix 6"] - [0.12345 "0.1235"] ["0.12345" ":fix 4"] - ["" "2.7183"] [":fixed 4" "E"] - [0.0005 "0.001"] ["0.0005" ":fix 3"] - [0.0005 "4.000e-4"] ["0.0004" ":fix 3"] - [1e21 "1.00e+21"] ["1e21+0.1" ":fix 2"])) + (are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs))) + [nil "0.123450"] [":fix 6" "0.12345"] + [nil "0.1235"] [":fix 4" "0.12345"] + [nil "2.7183"] [":fixed 4" "E"] + [nil "0.001"] [":fix 3" "0.0005"] + [nil "4.000e-4"] [":fix 3" "0.0004"] + [nil "1.00e+21"] [":fix 2" "1e21+0.1"])) (testing "display scientific" - (are [values exprs] (let [env (calc/new-env)] - (mapv (fn [expr] - (calc/eval env (calc/parse expr))) - exprs)) - [1e6 "1e+6"] ["1e6" ":sci"] - [0 "0.000e0" "3.142e+3"]["0" ":sci 3" "PI"] - ["" "3.14e+2"] [":sci" "3.14*10^2"]))) + (are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs))) + [nil "1e+6"] [":sci" "1e6"] + [nil "3.142e+0"] [":sci 3" "PI"] + [nil "3.14e+2"] [":sci" "3.14*10^2"]))) (deftest base-conversion (testing "mixed base input" @@ -243,14 +231,11 @@ 324.0 "0x100 + 0o100 + 0b100" 32.0 "0b100 * 0b1000")) (testing "mixed base output" - (are [values exprs] (let [env (calc/new-env)] - (mapv (fn [expr] - (calc/eval env (calc/parse expr))) - exprs)) + (are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs))) ["12345" "3039"] ["12345" ":hex"] ["12345" "30071"] ["12345" ":oct"] ["12345" "11000000111001"]["12345" ":bin"] - ["" "100000000"] [":bin" "0b10000 * 0b10000"]))) + [nil "100000000"] [":bin" "0b10000 * 0b10000"]))) (deftest comments (testing "comments are ignored" From 7c11de885bdd83185931efbdc1a34e73e6ee0615 Mon Sep 17 00:00:00 2001 From: Martin Scott Date: Mon, 18 Jul 2022 01:04:48 +0100 Subject: [PATCH 3/7] Add fraction display mode Support for showing results as fractions (mixed numbers or improper) and entering mixed numbers. --- src/main/frontend/extensions/calc.cljc | 39 ++++++++++++++++++--- src/main/grammar/calc.bnf | 9 +++-- src/test/frontend/extensions/calc_test.cljc | 34 +++++++++++++++--- 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/src/main/frontend/extensions/calc.cljc b/src/main/frontend/extensions/calc.cljc index 552c53d13c..44e1a88659 100644 --- a/src/main/frontend/extensions/calc.cljc +++ b/src/main/frontend/extensions/calc.cljc @@ -43,6 +43,8 @@ {:number (comp bn/BigNumber #(str/replace % "," "")) :percent (fn percent [a] (-> a (.dividedBy 100.00))) :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))) @@ -92,6 +94,13 @@ :mode-sci (fn format [places] (swap! env assoc :mode "sci" :places places) (get @env "last")) + :mode-frac (fn format [max-denominator] + (swap! env dissoc :mode :improper) + (swap! env assoc :mode "frac" :max-denominator max-denominator) + (get @env "last")) + :mode-frac-i (fn format [max-denominator] + (swap! env assoc :mode "frac" :max-denominator max-denominator :improper true) + (get @env "last")) :mode-norm (fn format [precision] (swap! env dissoc :mode :places) (swap! env assoc :precision precision) @@ -141,6 +150,20 @@ (and (< (.-e num) digits) (.isInteger (.shiftedBy num (+ tolerance digits))))) +(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) @@ -160,13 +183,19 @@ (.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 - (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))))) + (format-normal env val))) val)) (defn eval-lines [s] diff --git a/src/main/grammar/calc.bnf b/src/main/grammar/calc.bnf index a6f1addc2c..f6d4e05e20 100644 --- a/src/main/grammar/calc.bnf +++ b/src/main/grammar/calc.bnf @@ -23,8 +23,8 @@ tan = <#'\s*'> <'tan('> expr <')'> <#'\s*'> atan = <#'\s*'> <'atan('> expr <')'> <#'\s*'> acos = <#'\s*'> <'acos('> expr <')'> <#'\s*'> asin = <#'\s*'> <'asin('> expr <')'> <#'\s*'> - = function | percent | scientific | number | variable | <#'\s*'> <'('> expr <')'> <#'\s*'> -negterm = <#'\s*'> <'-'> posterm | <#'\s*'> <'-'> pow | <#'\s*'> <'-'> factorial + = function | percent | scientific | number | mixed-number | variable | <#'\s*'> <'('> expr <')'> <#'\s*'> +negterm = <#'\s*'> <'-'> ( posterm | pow | factorial ) = negterm | posterm scientific = #'\s*[0-9]*\.?[0-9]+(e|E)[\-\+]?[0-9]+()\s*' number = decimal-number | hexadecimal-number | octal-number | binary-number @@ -32,14 +32,17 @@ number = decimal-number | hexadecimal-number | octal-number | binary-number = #'\s*0x([0-9a-fA-F]+(,[0-9a-fA-F]+)*(\.[0-9a-fA-F]*)?|[0-9a-fA-F]*\.[0-9a-fA-F]+)\s*' = #'\s*0o([0-7]+(,[0-7]+)*(\.[0-7]*)?|[0-7]*\.[0-7]+)\s*' = #'\s*0b([01]+(,[01]+)*(\.[01]*)?|[01]*\.[01]+)\s*' +mixed-number = <#'\s*'> digits <'_'> 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 - = <#'\s*\:'> ( mode-fix | mode-sci | mode-norm | mode-base ) <#'\s*'> [comment] + = <#'\s*\:'> ( mode-fix | mode-sci | mode-norm | mode-frac | mode-frac-i | mode-base ) <#'\s*'> [comment] mode-fix = <#'(?i)fix(ed)?\s*'> digits mode-sci = <#'(?i)sci(entific)?\s*'> [digits] mode-norm = <#'(?i)norm(al)?\s*'> [digits] +mode-frac = <#'(?i)frac(tions?)?\s*'> [digits] +mode-frac-i = <#'(?i)frac(tions?)?-i(mp(roper)?)?\s*'> [digits] mode-base = mode-hex | mode-dec | mode-oct | mode-bin = #'(?i)hex' <#'(?i)(adecimal)?'> = #'(?i)dec' <#'(?i)(imal)?'> diff --git a/src/test/frontend/extensions/calc_test.cljc b/src/test/frontend/extensions/calc_test.cljc index d735a89126..1b0a8335b5 100644 --- a/src/test/frontend/extensions/calc_test.cljc +++ b/src/test/frontend/extensions/calc_test.cljc @@ -206,8 +206,8 @@ [nil "3"] [":norm 1" "E"] [nil "0.000123"] [":norm 5" "0.000123"] [nil "1.23e-4"] [":norm 4" "0.000123"] - [nil "123400000"] [":norm 9" "1.234e8"] - [nil "1.234e+8"] [":norm 8" "1.234e8"])) + [nil "123400000"] [":normal 9" "1.234e8"] + [nil "1.234e+8"] [":normal 8" "1.234e8"])) (testing "display fixed" (are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs))) [nil "0.123450"] [":fix 6" "0.12345"] @@ -215,12 +215,38 @@ [nil "2.7183"] [":fixed 4" "E"] [nil "0.001"] [":fix 3" "0.0005"] [nil "4.000e-4"] [":fix 3" "0.0004"] - [nil "1.00e+21"] [":fix 2" "1e21+0.1"])) + [nil "1.00e+21"] [":fixed 2" "1e21+0.1"])) (testing "display scientific" (are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs))) [nil "1e+6"] [":sci" "1e6"] [nil "3.142e+0"] [":sci 3" "PI"] - [nil "3.14e+2"] [":sci" "3.14*10^2"]))) + [nil "3.14e+2"] [":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"] [":frac" "4.375"] + [nil "-7_1/4"] [":fraction" "-7.25"] + [nil "2"] [":fractions" "19/20 + 1_1/20"] + [nil "-2"] [":frac" "19/17 - 3_2/17"] + [nil "3.14157"] [":frac" "3.14157"] + [nil "3_14157/100000"] [":frac 100000" "3.14157"])) + (testing "display improper fractions" + (are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs))) + [nil "35/8"] [":frac-i" "4.375"] + [nil "-29/4"] [":frac-imp" "-7.25"] + [nil "3.14157"] [":fractions-improper" "3.14157" ] + [nil "314157/100000"] [":frac-i 100000" "3.14157"]))) (deftest base-conversion (testing "mixed base input" From 48306d37aaef59f0b52240fe3305da9160fc1cec Mon Sep 17 00:00:00 2001 From: Gabriel Horner Date: Mon, 18 Jul 2022 10:54:55 -0400 Subject: [PATCH 4/7] test: temporarily skip intermittently failing test --- e2e-tests/flashcards.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/flashcards.spec.ts b/e2e-tests/flashcards.spec.ts index e07a301683..fb8cb33fd3 100644 --- a/e2e-tests/flashcards.spec.ts +++ b/e2e-tests/flashcards.spec.ts @@ -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') From 1d0fa763a5e06ff669e120ff6e7a3f102801d7d9 Mon Sep 17 00:00:00 2001 From: Martin Scott Date: Tue, 19 Jul 2022 10:25:26 +0100 Subject: [PATCH 5/7] Revised format directives and mixed number format Also changed output of non-decimal numbers to use base prefixes. --- src/main/frontend/extensions/calc.cljc | 28 ++++--- src/main/grammar/calc.bnf | 27 +++---- src/test/frontend/extensions/calc_test.cljc | 81 +++++++++++---------- 3 files changed, 73 insertions(+), 63 deletions(-) diff --git a/src/main/frontend/extensions/calc.cljc b/src/main/frontend/extensions/calc.cljc index 44e1a88659..8f1efad5f0 100644 --- a/src/main/frontend/extensions/calc.cljc +++ b/src/main/frontend/extensions/calc.cljc @@ -88,24 +88,24 @@ :toassign str/trim :comment (constantly nil) :digits int - :mode-fix (fn format [places] + :format-fix (fn format [places] (swap! env assoc :mode "fix" :places places) (get @env "last")) - :mode-sci (fn format [places] + :format-sci (fn format [places] (swap! env assoc :mode "sci" :places places) (get @env "last")) - :mode-frac (fn format [max-denominator] + :format-frac (fn format [max-denominator] (swap! env dissoc :mode :improper) (swap! env assoc :mode "frac" :max-denominator max-denominator) (get @env "last")) - :mode-frac-i (fn format [max-denominator] + :format-impf (fn format [max-denominator] (swap! env assoc :mode "frac" :max-denominator max-denominator :improper true) (get @env "last")) - :mode-norm (fn format [precision] + :format-norm (fn format [precision] (swap! env dissoc :mode :places) (swap! env assoc :precision precision) (get @env "last")) - :mode-base (fn base [b] + :base (fn base [b] (swap! env assoc :base (str/lower-case b)) (get @env "last")) :variable (fn resolve [var] @@ -150,11 +150,19 @@ (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 "_" + (str whole " " (.abs (.modulo numerator denominator)) "/" denominator)))) (defn format-normal [env val] @@ -171,11 +179,11 @@ places (get @env :places)] (cond (= base "hex") - (.toString val 16) + (format-base val 16) (= base "oct") - (.toString val 8) + (format-base val 8) (= base "bin") - (.toString val 2) + (format-base val 2) (= mode "fix") (if (can-fix? val places) diff --git a/src/main/grammar/calc.bnf b/src/main/grammar/calc.bnf index f6d4e05e20..ac8190e0c8 100644 --- a/src/main/grammar/calc.bnf +++ b/src/main/grammar/calc.bnf @@ -1,4 +1,4 @@ - = assignment | expr | comment | mode + = assignment | expr | comment | directive expr = add-sub [comment] comment = <#'\s*(#.*$)?'> = pow-term | mul-div | add | sub | variable @@ -32,20 +32,21 @@ number = decimal-number | hexadecimal-number | octal-number | binary-number = #'\s*0x([0-9a-fA-F]+(,[0-9a-fA-F]+)*(\.[0-9a-fA-F]*)?|[0-9a-fA-F]*\.[0-9a-fA-F]+)\s*' = #'\s*0o([0-7]+(,[0-7]+)*(\.[0-7]*)?|[0-7]*\.[0-7]+)\s*' = #'\s*0b([01]+(,[01]+)*(\.[01]*)?|[01]*\.[01]+)\s*' -mixed-number = <#'\s*'> digits <'_'> digits <#'[/_]'> digits <#'\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 - = <#'\s*\:'> ( mode-fix | mode-sci | mode-norm | mode-frac | mode-frac-i | mode-base ) <#'\s*'> [comment] -mode-fix = <#'(?i)fix(ed)?\s*'> digits -mode-sci = <#'(?i)sci(entific)?\s*'> [digits] -mode-norm = <#'(?i)norm(al)?\s*'> [digits] -mode-frac = <#'(?i)frac(tions?)?\s*'> [digits] -mode-frac-i = <#'(?i)frac(tions?)?-i(mp(roper)?)?\s*'> [digits] -mode-base = mode-hex | mode-dec | mode-oct | mode-bin - = #'(?i)hex' <#'(?i)(adecimal)?'> - = #'(?i)dec' <#'(?i)(imal)?'> - = #'(?i)oct' <#'(?i)(al)?'> - = #'(?i)bin' <#'(?i)(ary)?'> + = <#'\s*\:'> (format | base) <#'\s*'> [comment] + = <#'(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 + = #'(?i)hex' <#'(?i)(adecimal)?'> + = #'(?i)dec' <#'(?i)(imal)?'> + = #'(?i)oct' <#'(?i)(al)?'> + = #'(?i)bin' <#'(?i)(ary)?'> digits = #'\d+' \ No newline at end of file diff --git a/src/test/frontend/extensions/calc_test.cljc b/src/test/frontend/extensions/calc_test.cljc index 1b0a8335b5..7886231a9f 100644 --- a/src/test/frontend/extensions/calc_test.cljc +++ b/src/test/frontend/extensions/calc_test.cljc @@ -199,54 +199,54 @@ (deftest formatting (testing "display normal" (are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs))) - [nil "1000000"] [":norm" "1e6" ] - [nil "1000000"] [":norm 7" "1e6"] - [nil "1e+6"] [":norm 6" "1e6"] - [nil "3.14"] [":norm 3" "PI"] - [nil "3"] [":norm 1" "E"] - [nil "0.000123"] [":norm 5" "0.000123"] - [nil "1.23e-4"] [":norm 4" "0.000123"] - [nil "123400000"] [":normal 9" "1.234e8"] - [nil "1.234e+8"] [":normal 8" "1.234e8"])) + [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"] [":fix 6" "0.12345"] - [nil "0.1235"] [":fix 4" "0.12345"] - [nil "2.7183"] [":fixed 4" "E"] - [nil "0.001"] [":fix 3" "0.0005"] - [nil "4.000e-4"] [":fix 3" "0.0004"] - [nil "1.00e+21"] [":fixed 2" "1e21+0.1"])) + [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"] [":sci" "1e6"] - [nil "3.142e+0"] [":sci 3" "PI"] - [nil "3.14e+2"] [":scientific" "3.14*10^2"]))) + [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")) + 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"] [":frac" "4.375"] - [nil "-7_1/4"] [":fraction" "-7.25"] - [nil "2"] [":fractions" "19/20 + 1_1/20"] - [nil "-2"] [":frac" "19/17 - 3_2/17"] - [nil "3.14157"] [":frac" "3.14157"] - [nil "3_14157/100000"] [":frac 100000" "3.14157"])) + [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"] [":frac-i" "4.375"] - [nil "-29/4"] [":frac-imp" "-7.25"] - [nil "3.14157"] [":fractions-improper" "3.14157" ] - [nil "314157/100000"] [":frac-i 100000" "3.14157"]))) + [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" @@ -258,10 +258,11 @@ 32.0 "0b100 * 0b1000")) (testing "mixed base output" (are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs))) - ["12345" "3039"] ["12345" ":hex"] - ["12345" "30071"] ["12345" ":oct"] - ["12345" "11000000111001"]["12345" ":bin"] - [nil "100000000"] [":bin" "0b10000 * 0b10000"]))) + ["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" From 981b1ad69ee0e0f1639b8fd6a0f0eb82fcd5cc94 Mon Sep 17 00:00:00 2001 From: Gabriel Horner Date: Tue, 19 Jul 2022 21:03:14 -0400 Subject: [PATCH 6/7] Fix autorun tests doc, close #5967 --- docs/dev-practices.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/dev-practices.md b/docs/dev-practices.md index 4879508aa1..2ed8f9f887 100644 --- a/docs/dev-practices.md +++ b/docs/dev-practices.md @@ -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 From 2b94a95f1160150b1f1586a4a47c91cf2461feca Mon Sep 17 00:00:00 2001 From: charlie Date: Tue, 19 Jul 2022 13:53:52 +0800 Subject: [PATCH 7/7] fix(api): return non transferable value for plugin api --- src/main/frontend/handler/editor.cljs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index 12acb85757..e89730b20b 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/src/main/frontend/handler/editor.cljs @@ -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]