diff --git a/README.md b/README.md
index 9100fe5..d0ae153 100644
--- a/README.md
+++ b/README.md
@@ -107,6 +107,7 @@ Any expression can be used as an operand for any operator. axel-f has the same o
- [x] [AND](https://support.office.com/en-us/article/and-function-5f19b2e8-e1df-4408-897a-ce285a19e9d9)
- [x] [OR](https://support.office.com/en-us/article/or-function-7d17ad14-8700-4281-b308-00b131e22af0)
- [x] [NOT](https://support.office.com/en-us/article/not-function-9cfc6011-a054-40c7-a140-cd4ba2d87d77)
+- [x] [ARABIC](https://support.office.com/en-us/article/arabic-function-9a8da418-c17b-4ef9-a657-9370a30a674f)
- [x] [CLEAN](https://support.office.com/en-us/article/clean-function-26f3d7c5-475f-4a9c-90e5-4b8ba987ba41)
- [x] [CHAR](https://support.office.com/en-us/article/char-function-bbd249c8-b36e-4a91-8017-1c133f9b837a)
- [x] [CODE](https://support.office.com/en-us/article/code-function-c32b692b-2ed0-4a04-bdd9-75640144b928)
@@ -120,6 +121,9 @@ Any expression can be used as an operand for any operator. axel-f has the same o
- [x] [LOWER](https://support.office.com/en-us/article/lower-function-3f21df02-a80c-44b2-afaf-81358f9fdeb4)
- [x] [MID](https://support.office.com/en-us/article/mid-midb-functions-d5f9e25c-d7d6-472e-b568-4ecb12433028)
- [x] [PROPER](https://support.office.com/en-us/article/proper-function-52a5a283-e8b2-49be-8506-b2887b889f94)
+- [x] [REGEXEXTRACT](https://support.google.com/docs/answer/3098244?hl=en&ref_topic=3105625)
+- [x] [REGEXMATCH](https://support.google.com/docs/answer/3098292?hl=en&ref_topic=3105625)
+- [x] [REGEXREPLACE](https://support.google.com/docs/answer/3098245?hl=en&ref_topic=3105625)
- [x] [REPLACE](https://support.office.com/en-us/article/replace-replaceb-functions-8d799074-2425-4a8a-84bc-82472868878a)
- [x] [REPT](https://support.office.com/en-us/article/rept-function-04c4d778-e712-43b4-9c15-d656582bb061)
- [x] [RIGHT](https://support.office.com/en-us/article/right-rightb-functions-240267ee-9afa-4639-a02b-f19e1786cf2f)
@@ -127,8 +131,11 @@ Any expression can be used as an operand for any operator. axel-f has the same o
- [x] [SEARCH](https://support.office.com/en-us/article/search-searchb-functions-9ab04538-0e55-4719-a72e-b6f54513b495)
- [x] [SPLIT](https://support.google.com/docs/answer/3094136)
- [x] [SUBSTITUTE](https://support.office.com/en-us/article/substitute-function-6434944e-a904-4336-a9b0-1e58df3bc332)
+- [x] [T](https://support.office.com/en-us/article/t-function-fb83aeec-45e7-4924-af95-53e073541228)
- [x] [TRIM](https://support.office.com/en-us/article/trim-function-410388fa-c5df-49c6-b16c-9e5630b479f9)
- [x] [UPPER](https://support.office.com/en-us/article/upper-function-c11f29b3-d1a3-4537-8df6-04d0049963d6)
+- [x] [VALUE](https://support.office.com/en-us/article/value-function-257d0108-07dc-437d-ae1c-bc2d3953d8c2)
+- [x] [TEXTJOIN](https://support.office.com/en-us/article/textjoin-function-357b449a-ec91-49d0-80c3-0e8fc845691c)
- [x] [COUNT](https://support.office.com/en-us/article/count-function-a59cd7fc-b623-4d93-87a4-d23bf411294c)
- [x] [IF](https://support.office.com/en-us/article/if-function-69aed7c9-4e8a-4755-a9bc-aa8bbff73be2)
@@ -145,7 +152,6 @@ In addition we have special functions for accessing the data in context: `OBJREF
- [ ] [ADDRESS](https://support.office.com/en-us/article/address-function-d0c26c0d-3991-446b-8de4-ab46431d4f89)
- [ ] [AMORDEGRC](https://support.office.com/en-us/article/amordegrc-function-a14d0ca1-64a4-42eb-9b3d-b0dededf9e51)
- [ ] [AMORLINC](https://support.office.com/en-us/article/amorlinc-function-7d417b45-f7f5-4dba-a0a5-3451a81079a8)
-- [ ] [ARABIC](https://support.office.com/en-us/article/arabic-function-9a8da418-c17b-4ef9-a657-9370a30a674f)
- [ ] [AREAS](https://support.office.com/en-us/article/areas-function-8392ba32-7a41-43b3-96b0-3695d2ec6152)
- [ ] [ASC](https://support.office.com/en-us/article/asc-function-0b6abf1c-c663-4004-a964-ebc00b723266)
- [ ] [ASIN](https://support.office.com/en-us/article/asin-function-81fb95e5-6d6f-48c4-bc45-58f955c6d347)
@@ -517,7 +523,6 @@ In addition we have special functions for accessing the data in context: `OBJREF
- [ ] [SUMXMY2](https://support.office.com/en-us/article/sumxmy2-function-9d144ac1-4d79-43de-b524-e2ecee23b299)
- [ ] [SWITCH](https://support.office.com/en-us/article/switch-function-47ab33c0-28ce-4530-8a45-d532ec4aa25e)
- [ ] [SYD](https://support.office.com/en-us/article/syd-function-069f8106-b60b-4ca2-98e0-2a0f206bdb27)
-- [ ] [T](https://support.office.com/en-us/article/t-function-fb83aeec-45e7-4924-af95-53e073541228)
- [ ] [TAN](https://support.office.com/en-us/article/tan-function-08851a40-179f-4052-b789-d7f699447401)
- [ ] [TANH](https://support.office.com/en-us/article/tanh-function-017222f0-a0c3-4f69-9787-b3202295dc6c)
- [ ] [TBILLEQ](https://support.office.com/en-us/article/tbilleq-function-2ab72d90-9b4d-4efe-9fc2-0f81f2c19c8c)
@@ -528,7 +533,6 @@ In addition we have special functions for accessing the data in context: `OBJREF
- [ ] [T.DIST.RT](https://support.office.com/en-us/article/tdistrt-function-20a30020-86f9-4b35-af1f-7ef6ae683eda)
- [ ] [TDIST](https://support.office.com/en-us/article/tdist-function-630a7695-4021-4853-9468-4a1f9dcdd192)
- [ ] [TEXT](https://support.office.com/en-us/article/text-function-20d5ac4d-7b94-49fd-bb38-93d29371225c)
-- [ ] [TEXTJOIN](https://support.office.com/en-us/article/textjoin-function-357b449a-ec91-49d0-80c3-0e8fc845691c)
- [ ] [TIME](https://support.office.com/en-us/article/time-function-9a5aff99-8f7d-4611-845e-747d0b8d5457)
- [ ] [TIMEVALUE](https://support.office.com/en-us/article/timevalue-function-0b615c12-33d8-4431-bf3d-f3eb6d186645)
- [ ] [T.INV](https://support.office.com/en-us/article/tinv-function-2908272b-4e61-4942-9df9-a25fec9b0e2e)
@@ -545,7 +549,6 @@ In addition we have special functions for accessing the data in context: `OBJREF
- [ ] [TYPE](https://support.office.com/en-us/article/type-function-45b4e688-4bc3-48b3-a105-ffa892995899)
- [ ] [UNICHAR](https://support.office.com/en-us/article/unichar-function-ffeb64f5-f131-44c6-b332-5cd72f0659b8)
- [ ] [UNICODE](https://support.office.com/en-us/article/unicode-function-adb74aaa-a2a5-4dde-aff6-966e4e81f16f)
-- [ ] [VALUE](https://support.office.com/en-us/article/value-function-257d0108-07dc-437d-ae1c-bc2d3953d8c2)
- [ ] [VAR](https://support.office.com/en-us/article/var-function-1f2b7ab2-954d-4e17-ba2c-9e58b15a7da2)
- [ ] [VAR.P](https://support.office.com/en-us/article/varp-function-73d1285c-108c-4843-ba5d-a51f90656f3a)
- [ ] [VAR.S](https://support.office.com/en-us/article/vars-function-913633de-136b-449d-813e-65a00b2b990b)
diff --git a/deps.edn b/deps.edn
index b509d01..7a37c5b 100644
--- a/deps.edn
+++ b/deps.edn
@@ -19,7 +19,7 @@
:test {:extra-paths ["test"]
:extra-deps {org.clojure/test.check {:mvn/version "RELEASE"}}}
- :coverage {:extra-deps {cloverage {:mvn/version "1.0.11"}}
+ :coverage {:extra-deps {cloverage {:mvn/version "RELEASE"}}
:main-opts ["-m" "cloverage.coverage"
"--src-ns-path" "src"
"--test-ns-path" "test"
diff --git a/pom.xml b/pom.xml
index 09558bc..89f6804 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
io.xapix
axel-f
- 0.2.1
+ 0.2.2-SNAPSHOT
axel-f
diff --git a/release-js/package.json b/release-js/package.json
index 11e730d..9d508a0 100644
--- a/release-js/package.json
+++ b/release-js/package.json
@@ -1,6 +1,6 @@
{
"name": "axel-f",
- "version": "0.2.1",
+ "version": "0.2.2-SNAPSHOT",
"description": "axel-f is an engine that can evaluate excel-like expressions.",
"homepage": "https://github.com/xapix-io/axel-f",
"author": "Kirill Chernyshov (https://github.com/DeLaGuardo)",
diff --git a/src/axel_f/error.cljc b/src/axel_f/error.cljc
index 6f606d8..89cc015 100644
--- a/src/axel_f/error.cljc
+++ b/src/axel_f/error.cljc
@@ -13,3 +13,9 @@
arg-position
(when arg-position " ")
"expects number values. But '" value "' is a text and cannot be coerced to a number."))
+
+(defn format-not-a-string-error [fnname arg-position value]
+ (str "Function " fnname " parameter "
+ arg-position
+ (when arg-position " ")
+ "expects text values. But '" value "' is a number and cannot be coerced to a string."))
diff --git a/src/axel_f/functions.cljc b/src/axel_f/functions.cljc
index 8e383c3..104202e 100644
--- a/src/axel_f/functions.cljc
+++ b/src/axel_f/functions.cljc
@@ -66,6 +66,10 @@
:desc "Returns the text with the non-printable ASCII characters removed."
:args [{:desc "The text whose non-printable characters are to be removed."}]}
+ "ARABIC" {:impl #'text/arabic-fn
+ :desc "Computes the value of a Roman numeral."
+ :args [{:desc "The Roman numeral to format, whose value must be between 1 and 3999, inclusive."}]}
+
"CHAR" {:impl #'text/char-fn
:desc "Convert a number into a character according to the current Unicode table."
:args [{:desc "The number of the character to look up from the current Unicode table in decimal format."}]}
@@ -74,7 +78,7 @@
:desc "Returns the numeric Unicode map value of the first character in the string provided."
:args [{:desc "The string whose first character's Unicode map value will be returned."}]}
- "CONCATENATE" {:impl (partial text/join-fn "")
+ "CONCATENATE" {:impl (partial text/textjoin-fn "" false)
:desc "Appends strings to one another."
:args [{:desc "The initial string."}
{:desc "More strings to append in sequence."
@@ -99,7 +103,8 @@
{:desc "The character within arg2 at which to start the search."
:opt true}]}
- "JOIN" {:impl #'text/join-fn
+ "JOIN" {:impl (fn [delimeter & args]
+ (apply text/textjoin-fn delimeter false args))
:desc "Concatenates the elements of one or more one-dimensional arrays using a specified delimiter."
:args [{:desc "The character or string to place between each concatenated value."}
{:desc "The value or values to be appended using arg1."}
@@ -131,6 +136,22 @@
:desc "Capitalizes each word in a specified string."
:args [{:desc "The text which will be returned with the first letter of each word in uppercase and all other letters in lowercase."}]}
+ "REGEXEXTRACT" {:impl #'text/regexextract-fn
+ :desc "Extracts matching substrings according to a regular expression."
+ :args [{:desc "The input text."}
+ {:desc "The first part of arg1 that matches this expression will be returned."}]}
+
+ "REGEXMATCH" {:impl #'text/regexmatch-fn
+ :desc "Whether a piece of text matches a regular expression."
+ :args [{:desc "The text to be tested against the regular expression."}
+ {:desc "The regular expression to test the text against."}]}
+
+ "REGEXREPLACE" {:impl #'text/regexreplace-fn
+ :desc "Replaces part of a text string with a different text string using regular expressions."
+ :args [{:desc "The text, a part of which will be replaced."}
+ {:desc "The regular expression. All matching instances in text will be replaced."}
+ {:desc "The text which will be inserted into the original text."}]}
+
"REPLACE" {:impl #'text/replace-fn
:desc "Replaces part of a text string with a different text string."
:args [{:desc "The text, a part of which will be replaced."}
@@ -177,6 +198,10 @@
{:desc "The instance of arg2 within arg1 to replace with arg3. By default, all occurrences of arg2 are replaced; however, if arg4 is specified, only the indicated instance of arg2 is replaced."
:opt true}]}
+ "T" {:impl #'text/t-fn
+ :desc "Returns string arguments as text."
+ :args [{:desc "The argument to be converted to text."}]}
+
"TRIM" {:impl #'text/trim-fn
:desc "Removes leading, trailing, and repeated spaces in text."
:args [{:desc "The text or reference to a cell containing text to be trimmed."}]}
@@ -185,6 +210,19 @@
:desc "Converts a specified string to uppercase."
:args [{:desc "The string to convert to uppercase."}]}
+ "VALUE" {:impl #'text/value-fn
+ :desc "Converts a string in any of the date, time or number formats that axel-f understands into a number."
+ :args [{:desc "The string containing the value to be converted."}]}
+
+ "TEXTJOIN" {:impl #'text/textjoin-fn
+ :desc "Combines the text from multiple strings and/or arrays, with a specifiable delimiter separating the different texts."
+ :args [{:desc "A string, possibly empty, or a reference to a valid string. If empty, text will be simply concatenated."}
+ {:desc "A boolean; if TRUE, empty strings selected in the text arguments won't be included in the result."}
+ {:desc "Any text item. This could be a string, or an array of strings in a range."}
+ {:desc "Additional text item(s)."
+ :opt true
+ :repeatable true}]}
+
"COUNT" {:impl #'stat/count-fn
:desc "Returns a count of the number of numeric values in a dataset."
:args [{:desc "The first value or range to consider when counting."}
diff --git a/src/axel_f/functions/text.cljc b/src/axel_f/functions/text.cljc
index 3a60627..039b5be 100644
--- a/src/axel_f/functions/text.cljc
+++ b/src/axel_f/functions/text.cljc
@@ -21,8 +21,29 @@
(or (get cmap pattern) pattern)
(string/escape pattern cmap))))
-;; TODO
-;; (defn arabic-fn [roman-numeral])
+(def roman-numerals
+ {\I 1 \V 5 \X 10 \L 50
+ \C 100 \D 500 \M 1000 "IV" 4
+ "IX" 9 "XL" 40 "XC" 90 "CD" 400
+ "CM" 900})
+
+(defn arabic-fn [s]
+ (let [sign (if (string/starts-with? s "-")
+ -1 1)
+ s (partition-all 2 (vec (string/replace-first s #"-" "")))]
+ (loop [acc 0 current (first s) other (rest s)]
+ (if current
+ (let [is-pair? (get roman-numerals (apply str current))
+ value (or is-pair?
+ (get roman-numerals (first current)))]
+ (if is-pair?
+ (recur (+ acc value) (first other) (rest other))
+ (let [new-other (->> (concat current other)
+ flatten
+ rest
+ (partition-all 2))]
+ (recur (+ acc value) (first new-other) (rest new-other)))))
+ (* sign acc)))))
;; TODO
;; (defn asc-fn [])
@@ -69,12 +90,6 @@
;; TODO
;; (defn fixed-fn [])
-(defn join-fn [delimeter & items]
- (->> items
- flatten
- (map coercion/excel-str)
- (string/join delimeter)))
-
(defn left-fn
([text] (left-fn text 1))
([text number]
@@ -113,14 +128,53 @@
(defn proper-fn [text]
(string/replace (coercion/excel-str text) #"\w*" string/capitalize))
-;; TODO
-;; (defn regexextract-fn [])
+(defn regexextract-fn [text regular-expression]
+ (cond
+ (not (string? text))
+ (throw (error/error "#VALUE!"
+ (error/format-not-a-string-error "REGEXEXTRACT" 1 text)))
-;; TODO
-;; (defn regexmatch-fn [])
+ (not (string? regular-expression))
+ (throw (error/error "#VALUE!"
+ (error/format-not-a-string-error "REGEXEXTRACT" 2 regular-expression)))
+
+ :otherwise
+ (let [res (re-find (re-pattern regular-expression)
+ text)]
+ (cond
+ (string? res) res
+ (vector? res) (second res)
+ :otherwise res))))
+
+(defn regexmatch-fn [text regular-expression]
+ (cond
+ (not (string? text))
+ (throw (error/error "#VALUE!"
+ (error/format-not-a-string-error "REGEXMATCH" 1 text)))
-;; TODO
-;; (defn regexreplace-fn [])
+ (not (string? regular-expression))
+ (throw (error/error "#VALUE!"
+ (error/format-not-a-string-error "REGEXMATCH" 2 regular-expression)))
+
+ :otherwise
+ (boolean (regexextract-fn text regular-expression))))
+
+(defn regexreplace-fn [text regular-expression replacement]
+ (cond
+ (not (string? text))
+ (throw (error/error "#VALUE!"
+ (error/format-not-a-string-error "REGEXREPLACE" 1 text)))
+
+ (not (string? regular-expression))
+ (throw (error/error "#VALUE!"
+ (error/format-not-a-string-error "REGEXREPLACE" 2 regular-expression)))
+
+ (not (string? replacement))
+ (throw (error/error "#VALUE!"
+ (error/format-not-a-string-error "REGEXREPLACE" 3 replacement)))
+
+ :otherwise
+ (string/replace text (re-pattern regular-expression) replacement)))
(defn replace-fn
[text position length new-text]
@@ -155,11 +209,7 @@
(if-let [n (coercion/excel-number n)]
(if (<= 0 n 3999)
(let [n (int n)
- alphabet (sort-by val >
- {\I 1 \V 5 \X 10 \L 50
- \C 100 \D 500 \M 1000 "IV" 4
- "IX" 9 "XL" 40 "XC" 90 "CD" 400
- "CM" 900})]
+ alphabet (sort-by val > roman-numerals)]
(loop [res "" n n]
(if (zero? n) res
(let [[rom arab] (some #(when (<= (val %) n) %) alphabet)]
@@ -215,8 +265,9 @@
(throw (error/error "#VALUE!"
(error/format-not-a-number-error "SUBSTITUTE" 4 occurrence)))))
-;; TODO
-;; (defn t-fn [])
+(defn t-fn [value]
+ (when (string? value)
+ value))
;; TODO
;; (defn text-fn [])
@@ -231,8 +282,20 @@
coercion/excel-str
string/upper-case))
-;; TODO
-;; (defn value-fn [])
+(defn value-fn [s]
+ (or
+ (when (and (seqable? s)
+ (empty? s))
+ 0)
+ (when-not (boolean? s)
+ (coercion/excel-number s))
+ (throw (error/error "#VALUE!" (str "VALUE parameter '" (coercion/excel-str s) "' cannot be parsed to number.")))))
-;; TODO
-;; (defn textjoin-fn [])
+(defn textjoin-fn [delimeter ignore-empty & items]
+ (->> items
+ flatten
+ (map coercion/excel-str)
+ (filter (if ignore-empty
+ not-empty
+ identity))
+ (string/join delimeter)))
diff --git a/test/axel_f/functions_test.cljc b/test/axel_f/functions_test.cljc
index ddde55e..4be4841 100644
--- a/test/axel_f/functions_test.cljc
+++ b/test/axel_f/functions_test.cljc
@@ -283,6 +283,67 @@
(t/is (= "Foo-Bar.Baz"
(sut/run "PROPER(\"foo-bar.baz\")")))))
+(t/deftest regexextract-function-test
+ (t/testing "REGEXEXTRACT function"
+ (t/is (= "826.25"
+ (sut/run "REGEXEXTRACT('The price today is $826.25', '[0-9]*\\.[0-9]+[0-9]+')")))
+
+ (t/is (= "Content"
+ (sut/run "REGEXEXTRACT('(Content) between brackets', '\\(([A-Za-z]+)\\)')")))
+
+ (t/is (= nil
+ (sut/run "REGEXEXTRACT('FOO', '[a-z]+')")))
+
+ (t/is (= {:type "#VALUE!"
+ :reason "Function REGEXEXTRACT parameter 1 expects text values. But '123' is a number and cannot be coerced to a string."}
+ (sut/run "REGEXEXTRACT(123, '123')")))
+
+ (t/is (= {:type "#VALUE!"
+ :reason "Function REGEXEXTRACT parameter 2 expects text values. But '123' is a number and cannot be coerced to a string."}
+ (sut/run "REGEXEXTRACT('123', 123)")))))
+
+(t/deftest regexmatch-function-test
+ (t/testing "REGEXMATCH function"
+ (t/is (= true
+ (sut/run "REGEXMATCH('The price today is $826.25', '[0-9]*\\.[0-9]+[0-9]+')")))
+
+ (t/is (= true
+ (sut/run "REGEXMATCH('(Content) between brackets', '\\(([A-Za-z]+)\\)')")))
+
+ (t/is (= false
+ (sut/run "REGEXMATCH('FOO', '[a-z]+')")))
+
+ (t/is (= {:type "#VALUE!"
+ :reason "Function REGEXMATCH parameter 1 expects text values. But '123' is a number and cannot be coerced to a string."}
+ (sut/run "REGEXMATCH(123, '123')")))
+
+ (t/is (= {:type "#VALUE!"
+ :reason "Function REGEXMATCH parameter 2 expects text values. But '123' is a number and cannot be coerced to a string."}
+ (sut/run "REGEXMATCH('123', 123)")))))
+
+(t/deftest regexreplace-function-test
+ (t/testing "REGEXREPLACE function"
+ (t/is (= "The price today is $0.00"
+ (sut/run "REGEXREPLACE('The price today is $826.25', '[0-9]*\\.[0-9]+[0-9]+', '0.00')")))
+
+ (t/is (= "Word between brackets"
+ (sut/run "REGEXREPLACE('(Content) between brackets', '\\(([A-Za-z]+)\\)', 'Word')")))
+
+ (t/is (= "FOO"
+ (sut/run "REGEXREPLACE('FOO', '[a-z]+', 'OOF')")))
+
+ (t/is (= {:type "#VALUE!"
+ :reason "Function REGEXREPLACE parameter 1 expects text values. But '123' is a number and cannot be coerced to a string."}
+ (sut/run "REGEXREPLACE(123, '123', '321')")))
+
+ (t/is (= {:type "#VALUE!"
+ :reason "Function REGEXREPLACE parameter 2 expects text values. But '123' is a number and cannot be coerced to a string."}
+ (sut/run "REGEXREPLACE('123', 123, '321')")))
+
+ (t/is (= {:type "#VALUE!"
+ :reason "Function REGEXREPLACE parameter 3 expects text values. But '321' is a number and cannot be coerced to a string."}
+ (sut/run "REGEXREPLACE('123', '123', 321)")))))
+
(t/deftest replace-function-test
(t/testing "REPLACE function"
(t/is (= "abcde*k"
@@ -325,6 +386,56 @@
(t/is (= "Price"
(sut/run "RIGHT(\"Price\", 10)")))))
+(t/deftest arabic-function-test
+ (t/testing "ARABIC function"
+
+ (t/are [x y] (= x (sut/run (str "ARABIC('" y "')")))
+ 0 ""
+ 5 "V"
+ 9 "IX"
+ 12 "XII"
+ 16 "XVI"
+ 29 "XXIX"
+ 44 "XLIV"
+ 45 "XLV"
+ 68 "LXVIII"
+ 83 "LXXXIII"
+ 97 "XCVII"
+ 99 "XCIX"
+ 500 "D"
+ 501 "DI"
+ 649 "DCXLIX"
+ 798 "DCCXCVIII"
+ 891 "DCCCXCI"
+ 1000 "M"
+ 1004 "MIV"
+ 1006 "MVI"
+ 1023 "MXXIII"
+ 2014 "MMXIV"
+ 3999 "MMMCMXCIX"
+ -5 "-V"
+ -9 "-IX"
+ -12 "-XII"
+ -16 "-XVI"
+ -29 "-XXIX"
+ -44 "-XLIV"
+ -45 "-XLV"
+ -68 "-LXVIII"
+ -83 "-LXXXIII"
+ -97 "-XCVII"
+ -99 "-XCIX"
+ -500 "-D"
+ -501 "-DI"
+ -649 "-DCXLIX"
+ -798 "-DCCXCVIII"
+ -891 "-DCCCXCI"
+ -1000 "-M"
+ -1004 "-MIV"
+ -1006 "-MVI"
+ -1023 "-MXXIII"
+ -2014 "-MMXIV"
+ -3999 "-MMMCMXCIX")))
+
(t/deftest roman-function-test
(t/testing "ROMAN function"
@@ -411,6 +522,14 @@
:reason "Function SUBSTITUTE parameter 4 expects number values. But 'baz' is a text and cannot be coerced to a number."}
(sut/run "SUBSTITUTE(1, \"foo\", \"bar\", \"baz\")")))))
+(t/deftest t-function-text
+ (t/testing "T function"
+
+ (t/is (= "foo"
+ (sut/run "T('foo')")))
+
+ (t/is (nil? (sut/run "T(123)")))))
+
(t/deftest trim-function-test
(t/testing "TRIM function"
(t/is (= "more spaces"
@@ -425,6 +544,22 @@
(sut/run "UPPER(1)")))
))
+(t/deftest value-function-test
+ (t/testing "VALUE function"
+
+ (t/is (= 123.1
+ (sut/run "VALUE('123.1')")))
+
+ (t/is (= 0
+ (sut/run "VALUE('')")))
+
+ (t/is (= 0
+ (sut/run "VALUE(0)")))
+
+ (t/is (= {:type "#VALUE!"
+ :reason "VALUE parameter 'TRUE' cannot be parsed to number."}
+ (sut/run "VALUE(TRUE)")))))
+
(t/deftest count-function-test
(let [context {:data [1 2 3]}]
(t/testing "COUNT function"