Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix/refactor: fix filters operator precedence & remove bit operators
This haven't been noticed by anybody yet but operator precedence for
the filter operator was different from what Jinja provides.  These
cases make the behavior clear:

  {{ -5 | abs }}:

     the above example outputs 5, since filters should be
     applied *after* evaluating unary operators. e.g. `(abs (- 5))`

  {{ 2 * 3 | plus1 | plus1 }}

     the above example will output 10 as the above expression gets
     compiled to something like `(* 2 (plus1 (plus1 0)))`.
  • Loading branch information
clarete committed Mar 15, 2021
1 parent 8ce5146 commit 7e6034e
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 83 deletions.
11 changes: 5 additions & 6 deletions templatel-tests.el
Expand Up @@ -90,6 +90,11 @@
;; --- Operator Precedence ---

(ert-deftest operator-precedence ()
;; unary operators have higher precedence than filter operators
(should (equal "5" (templatel-render-string "{{ -5 | abs }}" '())))
;; pipes (for filters) have higher precedence then other binary ops
(should (equal "10" (templatel-render-string "{{ 2 * 3 | plus1 | plus1 }}" '())))
;; Arithmetic
(should (equal "10" (templatel-render-string "{{ 2 * 3 + 4 }}" '())))
(should (equal "10" (templatel-render-string "{{ 80 / 2 ** 3 }}" '())))
(should (equal "5" (templatel-render-string "{{ 13 % 9 + 1 }}" '())))
Expand Down Expand Up @@ -449,12 +454,6 @@ base-end")))))
(should (equal (templatel-render-string "{{ -a }}" '(("a" . -10))) "10"))
(should (equal (templatel-render-string "{{ -a }}" '(("a" . 10))) "-10")))

(ert-deftest render-expr-all-bitlogic ()
(should (equal (templatel-render-string "{{ a & 0b11 }}" '(("a" . 10))) "2"))
(should (equal (templatel-render-string "{{ a || 0b1 }}" '(("a" . 10))) "11"))
(should (equal (templatel-render-string "{{ a ^ 0b11 }}" '(("a" . 10))) "9"))
(should (equal (templatel-render-string "{{ ~a }}" '(("a" . 10))) "-11")))

(ert-deftest render-expr-all-cmp ()
(should (equal (templatel-render-string "{{ a == 10 }}" '(("a" . 10))) "t"))
(should (equal (templatel-render-string "{{ a == 11 }}" '(("a" . 10))) ""))
Expand Down
93 changes: 16 additions & 77 deletions templatel.el
Expand Up @@ -379,12 +379,6 @@
(templatel--parser-_ scanner)
(templatel--join-chars m)))

(defun templatel--token-|| (scanner)
"Read '||' off SCANNER's input."
(let ((m (templatel--scanner-matchs scanner "||")))
(templatel--parser-_ scanner)
(templatel--join-chars m)))

(defun templatel--token-+ (scanner)
"Read '+' off SCANNER's input."
(let ((m (templatel--scanner-matchs scanner "+")))
Expand Down Expand Up @@ -463,30 +457,6 @@
(templatel--parser-_ scanner)
(templatel--join-chars m)))

(defun templatel--token-<< (scanner)
"Read '<<' off SCANNER's input."
(let ((m (templatel--scanner-matchs scanner "<<")))
(templatel--parser-_ scanner)
(templatel--join-chars m)))

(defun templatel--token->> (scanner)
"Read '>>' off SCANNER's input."
(let ((m (templatel--scanner-matchs scanner ">>")))
(templatel--parser-_ scanner)
(templatel--join-chars m)))

(defun templatel--token-& (scanner)
"Read '&' off SCANNER's input."
(let ((m (templatel--scanner-matchs scanner "&")))
(templatel--parser-_ scanner)
(templatel--join-chars m)))

(defun templatel--token-~ (scanner)
"Read '~' off SCANNER's input."
(let ((m (templatel--scanner-matchs scanner "~")))
(templatel--parser-_ scanner)
(templatel--join-chars m)))

(defun templatel--token-% (scanner)
"Read '%' off SCANNER's input."
;; This is needed or allowing a cutting point to be introduced right
Expand Down Expand Up @@ -730,12 +700,12 @@
"Unclosed bracket")
(cons "Expression" (list expr))))

;; GR: Expr <- Filter
;; GR: Expr <- Logical
(defun templatel--parser-expr (scanner)
"Read an expression from SCANNER."
(cons
"Expr"
(list (templatel--parser-filter scanner))))
(list (templatel--parser-logical scanner))))

(defun templatel--parser-cut (scanner fn msg)
"Try to parse FN off SCANNER or error with MSG.
Expand Down Expand Up @@ -784,47 +754,27 @@ operator (RATORFN)."
(lambda() (funcall randfn scanner))
"Missing operand after binary operator"))))))

;; GR: Filter <- Logical (_PIPE Logical)*
(defun templatel--parser-filter (scanner)
"Read Filter from SCANNER."
(templatel--parser-binary scanner "Filter" #'templatel--parser-logical #'templatel--token-|))

;; GR: Logical <- BitLogical ((AND / OR) BitLogical)*
;; GR: Logical <- Comparison ((AND / OR) Comparison)*
(defun templatel--parser-logical (scanner)
"Read Logical from SCANNER."
(templatel--parser-binary
scanner
nil ; "Logical"
#'templatel--parser-bit-logical
#'templatel--parser-comparison
(lambda(s)
(templatel--scanner-or
s
(list
(lambda() (templatel--token-and s))
(lambda() (templatel--token-or s)))))))

;; GR: BitLogical <- Comparison ((BAND / BXOR / BOR) Comparison)*
(defun templatel--parser-bit-logical (scanner)
"Read BitLogical from SCANNER."
(templatel--parser-binary
scanner
nil ; "BitLogical"
#'templatel--parser-comparison
(lambda(s)
(templatel--scanner-or
s
(list
(lambda() (templatel--token-& s))
(lambda() (templatel--token-^ s))
(lambda() (templatel--token-|| s)))))))

;; GR: Comparison <- BitShifting ((EQ / NEQ / LTE / GTE / LT / GT / IN) BitShifting)*
;; GR: Comparison <- Term ((EQ / NEQ / LTE / GTE / LT / GT / IN) Term)*
(defun templatel--parser-comparison (scanner)
"Read a Comparison from SCANNER."
(templatel--parser-binary
scanner
nil ; "Comparison"
#'templatel--parser-bit-shifting
#'templatel--parser-term
(lambda(s)
(templatel--scanner-or
s
Expand All @@ -837,20 +787,6 @@ operator (RATORFN)."
(lambda() (templatel--token-> s))
(lambda() (templatel--token-in s)))))))

;; GR: BitShifting <- Term ((RSHIFT / LSHIFT) Term)*
(defun templatel--parser-bit-shifting (scanner)
"Read a BitShifting from SCANNER."
(templatel--parser-binary
scanner
nil ; "BitShifting"
#'templatel--parser-term
(lambda(s)
(templatel--scanner-or
s
(list
(lambda() (templatel--token->> s))
(lambda() (templatel--token-<< s)))))))

;; GR: Term <- Factor ((PLUS / MINUS) Factor)*
(defun templatel--parser-term (scanner)
"Read Term from SCANNER."
Expand Down Expand Up @@ -880,20 +816,28 @@ operator (RATORFN)."
(lambda() (templatel--token-slash s))
(lambda() (templatel--token-dslash s)))))))

;; GR: Power <- Unary ((POWER / MOD) Unary)*
;; GR: Power <- Filter ((POWER / MOD) Filter)*
(defun templatel--parser-power (scanner)
"Read Power from SCANNER."
(templatel--parser-binary
scanner
nil ; "Power"
#'templatel--parser-unary
#'templatel--parser-filter
(lambda(s)
(templatel--scanner-or
s
(list
(lambda() (templatel--token-** s))
(lambda() (templatel--token-% s)))))))

;; GR: Filter <- Unary (_PIPE Unary)*
(defun templatel--parser-filter (scanner)
"Read Filter from SCANNER."
(templatel--parser-binary
scanner
"Filter"
#'templatel--parser-unary #'templatel--token-|))

;; GR: UnaryOp <- PLUS / MINUS / NOT / BNOT
(defun templatel--parser-unary-op (scanner)
"Read an Unary operator from SCANNER."
Expand All @@ -902,7 +846,6 @@ operator (RATORFN)."
(list
(lambda() (templatel--token-+ scanner))
(lambda() (templatel--token-- scanner))
(lambda() (templatel--token-~ scanner))
(lambda() (templatel--token-not scanner)))))

;; GR: Unary <- UnaryOp Unary / UnaryOp Primary / Primary
Expand Down Expand Up @@ -1660,10 +1603,6 @@ Otherwise its HTML entities are escaped."
;; Logic
("and" and)
("or" or)
;; Bit Logic
("&" logand)
("||" logior)
("^" logxor)
;; Comparison
("<" <)
(">" >)
Expand Down

0 comments on commit 7e6034e

Please sign in to comment.