Skip to content

Commit

Permalink
feat: array access operator (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
erdos committed Aug 20, 2021
1 parent 246cd2a commit afcd15e
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 3 deletions.
6 changes: 6 additions & 0 deletions docs/Syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ After applying Stencil with input data `{"customerName": "John Doe"}` you get th

<img src="screenshot-substitution-after.png"/>

## Nested field access

You can reference values from nested fields by writing a dot separator between the field names. For example: `customer.name` can be used to reference the value `"John"` in `{"customer": {"name": "John"}}`

When the field name contains special characters, it may not be possible to reference it in a syntactically correct way directly. In these cases, the special curly braces operator can be used for property access. Syntax is `field1[field2]`. The expression `field2` will also be evaluated, so you need to quote the expression if it is not a dynamic vale. For example: `customer['name']`.

## Control structures

You can embed control structures in your templates to implement advanced
Expand Down
20 changes: 18 additions & 2 deletions src/stencil/infix.clj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
\^ :power
\( :open
\) :close
\[ :open-bracket
\] :close-bracket
\! :not
\= :eq
\< :lt
Expand Down Expand Up @@ -51,6 +53,7 @@
{:open -999
;;;:close -998
:comma -998
:open-bracket -999

:or -21
:and -20
Expand Down Expand Up @@ -88,8 +91,8 @@
(case (first characters)
\" (read-until \") ;; programmer quotes
\' (read-until \') ;; programmer quotes
\“(read-until \”) ;; english double quotes
\‘(read-until \’) ;; english single quotes
\“ (read-until \”) ;; english double quotes
\‘ (read-until \’) ;; english single quotes
\’ (read-until \’) ;; hungarian single quotes (felidezojel)
\„ (read-until \”) ;; hungarian double quotes (macskakorom)
(fail "No string literal" {:c (first characters)}))))
Expand Down Expand Up @@ -191,13 +194,25 @@
(= :open e0)
(recur next-expr (conj opstack :open) result (inc parentheses) (conj functions nil))

(= :open-bracket e0)
(recur next-expr (conj opstack :open-bracket) result (inc parentheses) functions)

(instance? FnCall e0)
(recur next-expr (conj opstack :open) result
(inc parentheses)
(conj functions {:fn (:fn-name e0)
:args (if (= :close (first next-expr)) 0 1)}))
;; (recur next-expr (conj opstack :fncall) result (conj functions {:fn e0}))

(= :close-bracket e0)
(let [[popped-ops [_ & keep-ops]]
(split-with (partial not= :open-bracket) opstack)]
(recur next-expr
keep-ops
(into result (concat popped-ops [:get]))
(dec parentheses)
functions))

(= :close e0)
(let [[popped-ops [_ & keep-ops]]
(split-with (partial not= :open) opstack)]
Expand Down Expand Up @@ -292,6 +307,7 @@
(def-reduce-step :gt [s0 s1] (> s1 s0))
(def-reduce-step :gte [s0 s1] (>= s1 s0))
(def-reduce-step :power [s0 s1] (Math/pow s1 s0))
(def-reduce-step :get [a b] (get b (if (vector? b) a (str a))))

(defn eval-rpn
([bindings default-function tokens]
Expand Down
28 changes: 27 additions & 1 deletion test/stencil/infix_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
(into (vals infix/ops))
(into (vals infix/ops2))
(into (keys infix/operation-tokens))
(disj :open :close :comma))
(disj :open :close :comma :open-bracket :close-bracket))
known-ops (set (filter keyword? (keys (methods @#'infix/reduce-step))))]
(is (every? known-ops ops)))))

Expand Down Expand Up @@ -113,6 +113,32 @@
(is (= "abcd" (run "'ab' + 'c' + 'd'")))
(is (= "abc123" (run "'abc' + 1 + 23")))))

(deftest nested-field-access
(is (= 1 (run "a.b" {"a" {"b" 1}})))
(is (= 2 (run "a['b-c']" {"a" {"b-c" 2}})))
(is (= 3 (run "a[1]" {"a" {"1" 3}})))
(is (= 4 (run "a['1']" {"a" {"1" 4}})))
(is (= 5 (run "a[(1)]" {"a" {"1" 5}})))
(is (= 6 (run "(a)[1]" {"a" {"1" 6}})))

(testing "multiple expressions"
(is (= 7 (run "a[a[1]+4]" {"a" {"1" 2 "6" 7}})))
(is (= 8 (run "a[1][2]" {"a" {"1" {"2" 8}}}))))

(testing "syntax error"
(is (thrown? ExceptionInfo (run "[3]" {})))
(is (thrown? ExceptionInfo (run "a[[3]]" {})))
(is (thrown? ExceptionInfo (run "a[1,2]" {}))))

(testing "key is missing from input"
(is (= nil (run "a[1]" {"a" {"2" 2}})))
(is (= nil (run "a[1]" {}))))

(testing "array access"
(is (= nil (run "a[1]" {"a" []})))
(is (= nil (run "a['1']" {"a" ["x" "y" "z"]})))
(is (= "y" (run "a[1]" {"a" ["x" "y" "z"]})))))

(deftest logical-operators
(testing "Mixed"
(is (true? (run "3 = 3 && 4 == 4"))))
Expand Down

0 comments on commit afcd15e

Please sign in to comment.