mirror of
https://github.com/penpot/penpot.git
synced 2025-07-26 18:57:18 +02:00
🎉 Allow calculations in numeric fields
This commit is contained in:
parent
dd92e5d773
commit
fad9d2fd3a
4 changed files with 285 additions and 59 deletions
104
frontend/src/app/util/simple_math.cljs
Normal file
104
frontend/src/app/util/simple_math.cljs
Normal file
|
@ -0,0 +1,104 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.util.simple-math
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[clojure.string :refer [index-of]]
|
||||
[cuerdas.core :as str]
|
||||
[instaparse.core :as insta]
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]))
|
||||
|
||||
(def parser
|
||||
(insta/parser
|
||||
"opt-expr = '' | expr
|
||||
expr = term (<spaces> ('+'|'-') <spaces> expr)* |
|
||||
('+'|'-'|'*'|'/') <spaces> factor
|
||||
term = factor (<spaces> ('*'|'/') <spaces> term)*
|
||||
factor = number | ('(' <spaces> expr <spaces> ')')
|
||||
number = #'[0-9]*[.,]?[0-9]+%?'
|
||||
spaces = ' '*"))
|
||||
|
||||
(defn interpret
|
||||
[tree init-value]
|
||||
(let [token (first tree)
|
||||
args (rest tree)]
|
||||
(case token
|
||||
|
||||
:opt-expr
|
||||
(if (empty? args) 0 (interpret (first args) init-value))
|
||||
|
||||
:expr
|
||||
(if (index-of "+-*/" (first args))
|
||||
(let [operator (first args)
|
||||
second-value (interpret (second args) init-value)]
|
||||
(case operator
|
||||
"+" (+ init-value second-value)
|
||||
"-" (- init-value second-value)
|
||||
"*" (* init-value second-value)
|
||||
"/" (/ init-value second-value)))
|
||||
(let [value (interpret (first args) init-value)]
|
||||
(loop [value value
|
||||
rest-expr (rest args)]
|
||||
(if (empty? rest-expr)
|
||||
value
|
||||
(let [operator (first rest-expr)
|
||||
second-value (interpret (second rest-expr) init-value)
|
||||
rest-expr (-> rest-expr rest rest)]
|
||||
(case operator
|
||||
"+" (recur (+ value second-value) rest-expr)
|
||||
"-" (recur (- value second-value) rest-expr)))))))
|
||||
|
||||
:term
|
||||
(let [value (interpret (first args) init-value)]
|
||||
(loop [value value
|
||||
rest-expr (rest args)]
|
||||
(if (empty? rest-expr)
|
||||
value
|
||||
(let [operator (first rest-expr)
|
||||
second-value (interpret (second rest-expr) init-value)
|
||||
rest-expr (-> rest-expr rest rest)]
|
||||
(case operator
|
||||
"*" (recur (* value second-value) rest-expr)
|
||||
"/" (recur (/ value second-value) rest-expr))))))
|
||||
|
||||
:factor
|
||||
(if (= (first args) "(")
|
||||
(interpret (second args) init-value)
|
||||
(interpret (first args) init-value))
|
||||
|
||||
:number
|
||||
(let [value-str (str/replace (first args) "," ".")]
|
||||
(if-not (str/ends-with? value-str "%")
|
||||
(d/parse-double value-str)
|
||||
(-> value-str
|
||||
(str/replace "%" "")
|
||||
(d/parse-double)
|
||||
(/ 100)
|
||||
(* init-value))))
|
||||
|
||||
(ex/raise :type :validation
|
||||
:hint (str "Unknown token" token args)))))
|
||||
|
||||
(defn expr-eval
|
||||
[expr init-value]
|
||||
(s/assert string? expr)
|
||||
(s/assert number? init-value)
|
||||
(let [result (parser expr)]
|
||||
(if-not (insta/failure? result)
|
||||
(interpret result init-value)
|
||||
(let [text (:text result)
|
||||
index (:index result)
|
||||
expecting (->> result
|
||||
:reason
|
||||
(map :expecting)
|
||||
(filter some?))]
|
||||
(js/console.debug
|
||||
(str "Invalid value '" text "' at index " index
|
||||
". Expected one of " expecting "."))
|
||||
nil))))
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue