瀏覽代碼

Merge branch 'master' into whiteboards

Peng Xiao 3 年之前
父節點
當前提交
ec4164b044

+ 1 - 1
.github/workflows/build-desktop-release.yml

@@ -13,7 +13,7 @@ on:
           - beta
           - beta
           - nightly
           - nightly
           - non-release
           - non-release
-        default: "beta"
+        default: "non-release"
       git-ref:
       git-ref:
         description: "Release Git Ref (Which branch or tag to build?)"
         description: "Release Git Ref (Which branch or tag to build?)"
         required: true
         required: true

+ 7 - 4
docs/contributing-to-translations.md

@@ -67,9 +67,9 @@ you're hoping to have this list drop to zero.
 
 
 Almost all translations are pretty quick. The only exceptions to this are the keys `:tutorial/text` and `:tutorial/dummy-notes`. These reference files that are part of the onboarding tutorial. Most languages don't have this translated. If you are willing to do this, we would be happy to have this translated.
 Almost all translations are pretty quick. The only exceptions to this are the keys `:tutorial/text` and `:tutorial/dummy-notes`. These reference files that are part of the onboarding tutorial. Most languages don't have this translated. If you are willing to do this, we would be happy to have this translated.
 
 
-## Fix Mistakes
+## Fix Untranslated
 
 
-There is a lot to translate and sometimes we make mistakes. For example, we may leave a string untranslated. To see what translation keys are still left in English:
+There is a lot to translate and sometimes we forget to translate a string. To see what translation keys are still left in English:
 
 
 ```
 ```
 $ bb lang:duplicates
 $ bb lang:duplicates
@@ -82,8 +82,11 @@ Keys with duplicate values found:
 |                               :no |               No |
 |                               :no |               No |
 ```
 ```
 
 
-Sometimes, we typo the translation key. If that happens, the github CI job will
-detect this error and helpfully show you what was typoed.
+## Fix Mistakes
+
+Sometimes, we typo the translation key. If that happens, the github CI step of
+`bb lang:invalid-translations` will detect this error and helpfully show you
+what was typoed.
 
 
 ## Add a Language
 ## Add a Language
 
 

+ 8 - 2
docs/dev-practices.md

@@ -5,7 +5,8 @@ This page describes development practices for this codebase.
 ## Linting
 ## Linting
 
 
 Most of our linters require babashka. Before running them, please install
 Most of our linters require babashka. Before running them, please install
-https://github.com/babashka/babashka#installation.
+https://github.com/babashka/babashka#installation. To invoke all the linters in
+this section, run `bb dev:lint`.
 
 
 ### Clojure code
 ### Clojure code
 
 
@@ -49,7 +50,7 @@ and understand them. To run this linter:
 bb lint:large-vars
 bb lint:large-vars
 ```
 ```
 
 
-To configure the linter, see its `config` var.
+To configure the linter, see the `[:tasks/config :large-vars]` path of bb.edn.
 
 
 ### Datalog linting
 ### Datalog linting
 
 
@@ -60,6 +61,11 @@ queries and rules. Our queries are linted through clj-kondo and
 [datalog-parser](https://github.com/lambdaforge/datalog-parser). clj-kondo will
 [datalog-parser](https://github.com/lambdaforge/datalog-parser). clj-kondo will
 error if it detects an invalid query.
 error if it detects an invalid query.
 
 
+### Invalid translations
+
+Our translations can be configured incorrectly. We can catch some of these
+mistakes [as noted here](./contributing-to-translations.md#fix-mistakes).
+
 ## Testing
 ## Testing
 
 
 We have unit and end to end tests.
 We have unit and end to end tests.

+ 1 - 1
libs/src/LSPlugin.ts

@@ -228,7 +228,7 @@ export type SimpleCommandKeybinding = {
 
 
 export type SettingSchemaDesc = {
 export type SettingSchemaDesc = {
   key: string
   key: string
-  type: 'string' | 'number' | 'boolean' | 'enum' | 'object'
+  type: 'string' | 'number' | 'boolean' | 'enum' | 'object' | 'heading'
   default: string | number | boolean | Array<any> | object | null
   default: string | number | boolean | Array<any> | object | null
   title: string
   title: string
   description: string // support markdown
   description: string // support markdown

+ 2 - 0
src/electron/electron/core.cljs

@@ -43,6 +43,8 @@
                    :win    win})))
                    :win    win})))
 
 
 (defn open-url-handler
 (defn open-url-handler
+  "win - the main window instance (first renderer process)
+   url - the input URL"
   [win url]
   [win url]
   (.info logger "open-url" (str {:url url}))
   (.info logger "open-url" (str {:url url}))
 
 

+ 12 - 10
src/electron/electron/url.cljs

@@ -2,7 +2,7 @@
   (:require [electron.handler :as handler]
   (:require [electron.handler :as handler]
             [electron.state :as state]
             [electron.state :as state]
             [electron.window :as win]
             [electron.window :as win]
-            [electron.utils :refer [send-to-renderer] :as utils]
+            [electron.utils :refer [send-to-renderer send-to-focused-renderer] :as utils]
             [clojure.string :as string]
             [clojure.string :as string]
             [promesa.core :as p]))
             [promesa.core :as p]))
 
 
@@ -58,22 +58,24 @@
       (graph-identifier-error-handler graph-identifier))))
       (graph-identifier-error-handler graph-identifier))))
 
 
 (defn- x-callback-url-handler
 (defn- x-callback-url-handler
-  [^js parsed-url]
+  "win - a window used for fallback (main window is prefered)"
+  [^js win parsed-url]
   (let [action (.-pathname parsed-url)]
   (let [action (.-pathname parsed-url)]
     (cond
     (cond
       (= action "/quickCapture")
       (= action "/quickCapture")
       (let [[url title content] (get-URL-decoded-params parsed-url ["url" "title" "content"])]
       (let [[url title content] (get-URL-decoded-params parsed-url ["url" "title" "content"])]
-        (send-to-renderer "quickCapture" {:url url
-                                          :title title
-                                          :content content}))
+        (send-to-focused-renderer "quickCapture" {:url url
+                                                  :title title
+                                                  :content content} win))
 
 
       :else
       :else
-      (send-to-renderer "notification" {:type "error"
-                                        :payload (str "Unimplemented x-callback-url action: `"
-                                                      action
-                                                      "`.")}))))
+      (send-to-focused-renderer "notification" {:type "error"
+                                                :payload (str "Unimplemented x-callback-url action: `"
+                                                              action
+                                                              "`.")} win))))
 
 
 (defn logseq-url-handler
 (defn logseq-url-handler
+  "win - the main window"
   [^js win parsed-url]
   [^js win parsed-url]
   (let [url-host (.-host parsed-url)] ;; return "" when no pathname provided
   (let [url-host (.-host parsed-url)] ;; return "" when no pathname provided
     (cond
     (cond
@@ -81,7 +83,7 @@
       (send-to-renderer win "loginCallback" (.get (.-searchParams parsed-url) "code"))
       (send-to-renderer win "loginCallback" (.get (.-searchParams parsed-url) "code"))
 
 
       (= "x-callback-url" url-host)
       (= "x-callback-url" url-host)
-      (x-callback-url-handler parsed-url)
+      (x-callback-url-handler win parsed-url)
 
 
       ;; identifier of graph in local
       ;; identifier of graph in local
       (= "graph" url-host)
       (= "graph" url-host)

+ 10 - 2
src/electron/electron/utils.cljs

@@ -6,7 +6,7 @@
             [cljs-bean.core :as bean]
             [cljs-bean.core :as bean]
             ["electron" :refer [app BrowserWindow]]))
             ["electron" :refer [app BrowserWindow]]))
 
 
-(defonce *win (atom nil))
+(defonce *win (atom nil)) ;; The main window
 (defonce mac? (= (.-platform js/process) "darwin"))
 (defonce mac? (= (.-platform js/process) "darwin"))
 (defonce win32? (= (.-platform js/process) "win32"))
 (defonce win32? (= (.-platform js/process) "win32"))
 (defonce linux? (= (.-platform js/process) "linux"))
 (defonce linux? (= (.-platform js/process) "linux"))
@@ -112,7 +112,8 @@
 
 
 (defn send-to-renderer
 (defn send-to-renderer
   "Notice: pass the `window` parameter if you can. Otherwise, the message
   "Notice: pass the `window` parameter if you can. Otherwise, the message
-  will not be received if there's no focused window."
+  will not be received if there's no focused window.
+   Use `send-to-focused-renderer` instead if you want to set a window for fallback"
   ([kind payload]
   ([kind payload]
    (send-to-renderer (get-focused-window) kind payload))
    (send-to-renderer (get-focused-window) kind payload))
   ([window kind payload]
   ([window kind payload]
@@ -120,6 +121,13 @@
      (.. ^js window -webContents
      (.. ^js window -webContents
          (send (name kind) (bean/->js payload))))))
          (send (name kind) (bean/->js payload))))))
 
 
+(defn send-to-focused-renderer
+  "Try to send to focused window. If no focused window, fallback to the `fallback-win`"
+  ([kind payload fallback-win]
+   (let [focused-win (get-focused-window)
+         win         (if focused-win focused-win fallback-win)]
+     (send-to-renderer win kind payload))))
+
 (defn get-graph-dir
 (defn get-graph-dir
   "required by all internal state in the electron section"
   "required by all internal state in the electron section"
   [graph-name]
   [graph-name]

+ 43 - 31
src/main/frontend/components/block.cljs

@@ -2727,29 +2727,32 @@
   [state]
   [state]
   (let [[config query] (:rum/args state)
   (let [[config query] (:rum/args state)
         repo (state/get-current-repo)
         repo (state/get-current-repo)
-        result-atom (atom nil)
-        query-atom (if (:dsl-query? config)
-                     (let [q (:query query)
-                           form (safe-read-string q false)]
-                       (cond
-                          ;; Searches like 'foo' or 'foo bar' come back as symbols
-                         ;; and are meant to go directly to full text search
-                         (and (util/electron?) (symbol? form)) ; full-text search
-                         (p/let [blocks (search/block-search repo (string/trim (str form)) {:limit 30})]
-                           (when (seq blocks)
-                             (let [result (db/pull-many (state/get-current-repo) '[*] (map (fn [b] [:block/uuid (uuid (:block/uuid b))]) blocks))]
-                               (reset! result-atom result))))
-
-                         (symbol? form)
-                         (atom nil)
-
-                         :else
-                         (query-dsl/query (state/get-current-repo) q)))
-                     (db/custom-query query))
+        result-atom (or (:query-atom state) (atom nil))
+        [full-text-search? query-atom] (if (:dsl-query? config)
+                                         (let [q (:query query)
+                                               form (safe-read-string q false)]
+                                           (cond
+                                             ;; Searches like 'foo' or 'foo bar' come back as symbols
+                                             ;; and are meant to go directly to full text search
+                                             (and (util/electron?) (symbol? form)) ; full-text search
+                                             [true
+                                              (p/let [blocks (search/block-search repo (string/trim (str form)) {:limit 30})]
+                                                (when (seq blocks)
+                                                  (let [result (db/pull-many (state/get-current-repo) '[*] (map (fn [b] [:block/uuid (uuid (:block/uuid b))]) blocks))]
+                                                    (reset! result-atom result))))]
+
+                                             (symbol? form)
+                                             [false (atom nil)]
+
+                                             :else
+                                             [false (query-dsl/query (state/get-current-repo) q)]))
+                                         [false (db/custom-query query)])
         query-atom (if (instance? Atom query-atom)
         query-atom (if (instance? Atom query-atom)
                      query-atom
                      query-atom
                      result-atom)]
                      result-atom)]
-    (assoc state :query-atom query-atom)))
+    (assoc state
+           :query-atom query-atom
+           :full-text-search? full-text-search?)))
 
 
 (rum/defcs ^:large-vars/cleanup-todo custom-query* < rum/reactive
 (rum/defcs ^:large-vars/cleanup-todo custom-query* < rum/reactive
   {:will-mount trigger-custom-query!
   {:will-mount trigger-custom-query!
@@ -2809,21 +2812,30 @@
         [:div.custom-query.mt-4 (get config :attr {})
         [:div.custom-query.mt-4 (get config :attr {})
          (ui/foldable
          (ui/foldable
           [:div.custom-query-title.flex.justify-between.w-full
           [:div.custom-query-title.flex.justify-between.w-full
-           [:div [:span.title-text (cond
-                               (vector? title) title
-                               (string? title) (inline-text config
-                                                            (get-in config [:block :block/format] :markdown)
-                                                            title)
-                               :else title)]
+           [:div.flex.items-center
+            (when (:full-text-search? state)
+              [:a.control.fade-link.mr-1.inline-flex
+               {:title "Refresh query result"
+                :on-mouse-down (fn [e]
+                                 (util/stop e)
+                                 (trigger-custom-query! state))}
+               (ui/icon "refresh" {:style {:font-size 20}})])
+            [:span.title-text (cond
+                                (vector? title) title
+                                (string? title) (inline-text config
+                                                             (get-in config [:block :block/format] :markdown)
+                                                             title)
+                                :else title)]
            [:span.opacity-60.text-sm.ml-2.results-count
            [:span.opacity-60.text-sm.ml-2.results-count
             (str (count transformed-query-result) " results")]]
             (str (count transformed-query-result) " results")]]
+
            ;;insert an "edit" button in the query view
            ;;insert an "edit" button in the query view
            (when-not built-in?
            (when-not built-in?
-            [:a.opacity-70.hover:opacity-100.svg-small.inline
-                      {:on-mouse-down (fn [e]
-                                        (util/stop e)
-                                        (editor-handler/edit-block! current-block :max (:block/uuid current-block)))}
-                      svg/edit])]
+             [:a.opacity-70.hover:opacity-100.svg-small.inline
+              {:on-mouse-down (fn [e]
+                                (util/stop e)
+                                (editor-handler/edit-block! current-block :max (:block/uuid current-block)))}
+              svg/edit])]
           (fn []
           (fn []
             [:div
             [:div
              (when (and current-block (not view-f) (nil? table-view?))
              (when (and current-block (not view-f) (nil? table-view?))

+ 7 - 1
src/main/frontend/components/plugins.css

@@ -465,6 +465,12 @@
         right: 8px;
         right: 8px;
       }
       }
 
 
+      .heading-item {
+        margin: 12px 12px 6px;
+        font-weight: bold;
+        border-bottom: 1px solid var(--ls-border-color, #738694);
+      }
+
       .desc-item {
       .desc-item {
         padding: 12px 12px 6px;
         padding: 12px 12px 6px;
 
 
@@ -783,7 +789,7 @@
         max-height: 80vh;
         max-height: 80vh;
         overflow-y: auto;
         overflow-y: auto;
       }
       }
-      
+
       .menu-link {
       .menu-link {
         padding: 3px 5px;
         padding: 3px 5px;
       }
       }

+ 7 - 0
src/main/frontend/components/plugins_settings.cljs

@@ -74,6 +74,12 @@
     [:small.pl-1.flex-1 description]
     [:small.pl-1.flex-1 description]
     [:div.pl-1 (edit-settings-file pid nil)]]])
     [:div.pl-1 (edit-settings-file pid nil)]]])
 
 
+(rum/defc render-item-heading
+  [{:keys [title]}]
+
+  [:div.heading-item
+   [:h2 title]])
+
 (rum/defc settings-container
 (rum/defc settings-container
   [schema ^js pl]
   [schema ^js pl]
   (let [^js _settings (.-settings pl)
   (let [^js _settings (.-settings pl)
@@ -107,6 +113,7 @@
            #{:boolean} (render-item-toggle val desc update-setting!)
            #{:boolean} (render-item-toggle val desc update-setting!)
            #{:enum} (render-item-enum val desc update-setting!)
            #{:enum} (render-item-enum val desc update-setting!)
            #{:object} (render-item-object val desc pid)
            #{:object} (render-item-object val desc pid)
+           #{:heading} (render-item-heading desc)
 
 
            [:p (str "#Not Handled#" key)]))]
            [:p (str "#Not Handled#" key)]))]
 
 

+ 15 - 2
src/main/frontend/extensions/calc.cljc

@@ -40,11 +40,18 @@
     :sub        (fn sub [a b] (-> a (.minus b)))
     :sub        (fn sub [a b] (-> a (.minus b)))
     :mul        (fn mul [a b] (-> a (.multipliedBy b)))
     :mul        (fn mul [a b] (-> a (.multipliedBy b)))
     :div        (fn div [a b] (-> a (.dividedBy 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]
     :log        (fn log [a]
                   #?(:clj (java.lang.Math/log10 a) :cljs (bn/BigNumber (js/Math.log10 a))))
                   #?(:clj (java.lang.Math/log10 a) :cljs (bn/BigNumber (js/Math.log10 a))))
     :ln         (fn ln [a]
     :ln         (fn ln [a]
                   #?(:clj (java.lang.Math/log a) :cljs (bn/BigNumber (js/Math.log 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]
     :sin        (fn sin [a]
                   #?(:clj (java.lang.Math/sin a) :cljs (bn/BigNumber(js/Math.sin a))))
                   #?(:clj (java.lang.Math/sin a) :cljs (bn/BigNumber(js/Math.sin a))))
     :cos        (fn cos [a]
     :cos        (fn cos [a]
@@ -61,6 +68,7 @@
                   (swap! env assoc var val)
                   (swap! env assoc var val)
                   val)
                   val)
     :toassign   str/trim
     :toassign   str/trim
+    :comment    (constantly nil)
     :variable   (fn resolve [var]
     :variable   (fn resolve [var]
                   (let [var (str/trim var)]
                   (let [var (str/trim var)]
                     (or (get @env var)
                     (or (get @env var)
@@ -79,12 +87,17 @@
      (catch #?(:clj Exception :cljs js/Error) e
      (catch #?(:clj Exception :cljs js/Error) e
        e))))
        e))))
 
 
+(defn assign-last-value [env val]
+  (when-not (nil? val)
+    (swap! env assoc "last" val))
+  val)
+
 (defn eval-lines [s]
 (defn eval-lines [s]
   {:pre [(string? s)]}
   {:pre [(string? s)]}
   (let [env (new-env)]
   (let [env (new-env)]
     (mapv (fn [line]
     (mapv (fn [line]
             (when-not (str/blank? line)
             (when-not (str/blank? line)
-              (eval env (parse line))))
+              (assign-last-value env (eval env (parse line)))))
           (str/split-lines s))))
           (str/split-lines s))))
 
 
 ;; ======================================================================
 ;; ======================================================================

+ 7 - 4
src/main/frontend/handler/page.cljs

@@ -5,6 +5,7 @@
             [datascript.core :as d]
             [datascript.core :as d]
             [frontend.commands :as commands]
             [frontend.commands :as commands]
             [frontend.config :as config]
             [frontend.config :as config]
+            [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
             [frontend.date :as date]
             [frontend.db :as db]
             [frontend.db :as db]
             [logseq.db.schema :as db-schema]
             [logseq.db.schema :as db-schema]
@@ -719,8 +720,9 @@
       (fn [chosen _click?]
       (fn [chosen _click?]
         (state/clear-editor-action!)
         (state/clear-editor-action!)
         (let [wrapped? (= "[[" (gp-util/safe-subs edit-content (- pos 2) pos))
         (let [wrapped? (= "[[" (gp-util/safe-subs edit-content (- pos 2) pos))
-              chosen (if (string/starts-with? chosen "New page: ") ;; FIXME: What if a page named "New page: XXX"?
-                       (subs chosen 10)
+              prefix (str (t :new-page) ": ")
+              chosen (if (string/starts-with? chosen prefix) ;; FIXME: What if a page named "New page: XXX"?
+                       (string/replace-first chosen prefix "")
                        chosen)
                        chosen)
               chosen (if (and (util/safe-re-find #"\s+" chosen) (not wrapped?))
               chosen (if (and (util/safe-re-find #"\s+" chosen) (not wrapped?))
                        (util/format "[[%s]]" chosen)
                        (util/format "[[%s]]" chosen)
@@ -740,8 +742,9 @@
                                            :forward-pos forward-pos})))
                                            :forward-pos forward-pos})))
       (fn [chosen _click?]
       (fn [chosen _click?]
         (state/clear-editor-action!)
         (state/clear-editor-action!)
-        (let [chosen (if (string/starts-with? chosen "New page: ")
-                       (subs chosen 10)
+        (let [prefix (str (t :new-page) ": ")
+              chosen (if (string/starts-with? chosen prefix)
+                       (string/replace-first chosen prefix "")
                        chosen)
                        chosen)
               page-ref-text (get-page-ref-text chosen)]
               page-ref-text (get-page-ref-text chosen)]
           (editor-handler/insert-command! id
           (editor-handler/insert-command! id

+ 15 - 11
src/main/grammar/calc.bnf

@@ -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
 add = add-sub <'+'> mul-div
 sub = add-sub <'-'> mul-div
 sub = add-sub <'-'> mul-div
 <mul-div> = pow-term | mul | div
 <mul-div> = pow-term | mul | div
 mul = mul-div <'*'> pow-term
 mul = mul-div <'*'> pow-term
 div = mul-div <'/'> pow-term
 div = mul-div <'/'> pow-term
 <pow-term> = 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*'>
 log = <#'\s*'> <'log('> expr <')'> <#'\s*'>
 ln = <#'\s*'> <'ln('> 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*'>
 sin = <#'\s*'> <'sin('> expr <')'> <#'\s*'>
 cos = <#'\s*'> <'cos('> expr <')'> <#'\s*'>
 cos = <#'\s*'> <'cos('> expr <')'> <#'\s*'>
 tan = <#'\s*'> <'tan('> expr <')'> <#'\s*'>
 tan = <#'\s*'> <'tan('> expr <')'> <#'\s*'>
 atan = <#'\s*'> <'atan('> expr <')'> <#'\s*'>
 atan = <#'\s*'> <'atan('> expr <')'> <#'\s*'>
 acos = <#'\s*'> <'acos('> expr <')'> <#'\s*'>
 acos = <#'\s*'> <'acos('> expr <')'> <#'\s*'>
 asin = <#'\s*'> <'asin('> 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
 <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*'>
 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
 assignment = toassign <#'\s*'> <'='> <#'\s*'> expr

+ 67 - 27
src/test/frontend/extensions/calc_test.cljc

@@ -19,7 +19,11 @@
       98123      "98123"
       98123      "98123"
       1.0        " 1.0 "
       1.0        " 1.0 "
       22.1124131 "22.1124131"
       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"
     (testing "even when they have the commas in the wrong place"
       (are [value expr] (= value (run expr))
       (are [value expr] (= value (run expr))
         98123      "9812,3"
         98123      "9812,3"
@@ -62,15 +66,6 @@
       2.0           "2*100%"
       2.0           "2*100%"
       0.01          "2%/2"
       0.01          "2%/2"
       500e3         "50% * 1e6"))
       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"
   (testing "operator precedence"
     (are [value expr] (= value (run expr))
     (are [value expr] (= value (run expr))
       1     "1 + 0 * 2"
       1     "1 + 0 * 2"
@@ -90,9 +85,39 @@
       12.3     "123.0e-1"
       12.3     "123.0e-1"
       -12.3    "-123.0e-1"
       -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))
     (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 )"
       1.0  "cos( 0 * 1 )"
       0.0  "sin( 1 -1 )"
       0.0  "sin( 1 -1 )"
       0.0  "atan(tan(0))"
       0.0  "atan(tan(0))"
@@ -101,14 +126,9 @@
       0.0  "acos(cos(0))"
       0.0  "acos(cos(0))"
       5.0  "2 * log(10) + 3"
       5.0  "2 * log(10) + 3"
       1.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
 (deftest variables
   (testing "variables can be remembered"
   (testing "variables can be remembered"
@@ -116,7 +136,8 @@
                             (calc/eval env (calc/parse expr))
                             (calc/eval env (calc/parse expr))
                             (= final-env (into {} (for [[k v] @env] [k (convert-bigNum v)]))))
                             (= final-env (into {} (for [[k v] @env] [k (convert-bigNum v)]))))
       {"a" 1}        "a = 1"
       {"a" 1}        "a = 1"
-      {"a" -1}        "a = -1"
+      {"a" -1}       "a = -1"
+      {"k9" 27}       "k9 = 27"
       {"variable" 1} "variable = 1 + 0 * 2"
       {"variable" 1} "variable = 1 + 0 * 2"
       {"x" 1}        "x= 2 * 1 - 1 "
       {"x" 1}        "x= 2 * 1 - 1 "
       {"y" 4}        "y =8 / 4 + 2 * 1 - 25 * 0 / 1"
       {"y" 4}        "y =8 / 4 + 2 * 1 - 25 * 0 / 1"
@@ -128,6 +149,7 @@
                             (calc/eval env (calc/parse expr))
                             (calc/eval env (calc/parse expr))
                             (= final-env (into {} (for [[k v] @env] [k (convert-bigNum v)]))))
                             (= final-env (into {} (for [[k v] @env] [k (convert-bigNum v)]))))
       {"a_a" 1}         "a_a = 1"
       {"a_a" 1}         "a_a = 1"
+      {"_foo" 1}        "_foo = 1"
       {"x_yy_zzz" 1}    "x_yy_zzz= 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"
   (testing "variables can be reused"
@@ -150,15 +172,33 @@
       {"a" 2 "b" 2}        ["a = 1" "b = a + 1" "a = b"]
       {"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"])))
       {"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
 (deftest failure
   (testing "expressions that don't match the spec fail"
   (testing "expressions that don't match the spec fail"
     (are [expr] (calc/failure? (calc/eval (calc/new-env) (calc/parse expr)))
     (are [expr] (calc/failure? (calc/eval (calc/new-env) (calc/parse expr)))
       "foo_ ="
       "foo_ ="
       "foo__ ="
       "foo__ ="
       "oo___ ="
       "oo___ ="
-      "                        "
-      "bar_2  = 2 + 4"
-      "bar_2a = 3 + 4"
-      "foo_ = "
-      "foo__  ="
-      "foo_3  = a")))
+      " . "
+      "_ = 2"
+      "__ = 4"
+      "foo_3  = _")))