diff options
| author | 2024-06-12 18:39:44 -0500 | |
|---|---|---|
| committer | 2024-06-12 18:39:44 -0500 | |
| commit | c879dea904f18319166edced72191b6db8a91186 (patch) | |
| tree | f7d11ea9fd8ab9c4cc41ed0b23d7c0946625fbca | |
| parent | Update for latest bundle-tools branch of janet (diff) | |
| parent | Add spork/infix for easy infix syntax with Janet. (diff) | |
Merge branch 'master' into bundle-hooksbundle-hooks
| -rw-r--r-- | spork/http.janet | 2 | ||||
| -rw-r--r-- | spork/infix.janet | 126 | ||||
| -rw-r--r-- | spork/init.janet | 1 | ||||
| -rw-r--r-- | test/suite-infix.janet | 74 |
4 files changed, 202 insertions, 1 deletions
diff --git a/spork/http.janet b/spork/http.janet index cd489ca..33ac3f8 100644 --- a/spork/http.janet +++ b/spork/http.janet @@ -341,7 +341,7 @@ (case (type x) :function x :number (let [msg (get status-messages x)] - (assert x (string "unknown http status code when making middleware: " x)) + (assert msg (string "unknown http status code when making middleware: " x)) (fn mw [&] {:status x :body msg})) :string (bytes-to-mw x) :buffer (bytes-to-mw x) diff --git a/spork/infix.janet b/spork/infix.janet new file mode 100644 index 0000000..5a3f960 --- /dev/null +++ b/spork/infix.janet @@ -0,0 +1,126 @@ +### +### infix.janet - A macro for infix syntax in Janet. Useful for math. +### +### Examples: +### +### ($$ a + b ** 2) ---> (+ a (math/pow b 2)) +### ($$ (a + b) ** 2) ---> (math/pow (+ a b) 2) +### ($$ y[2] + y[3]) ---> (+ (in y 2) (in y 3)) +### ($$ a > b and ,(good? z)) ---> (and (> a b) (good? z)) +### +### Syntax is as follows: +### +### Binary operators <<, >>, >>>, =, !=, <, <=, >, >=, &, ^, bor, band, and, or, +### +, -, *, /, %, ** are supported. Operator precedence is in the +### `precedence table below (higher means more tightly binding). All +### operators are left associative except ** (math/pow), which is right +### associative. +### +### Unary prefix operators !, -, bnot, not, ++, -- are supported. +### No unary postfix operators are supported. +### +### Square brackets can be used for indexing. +### +### Normal parentheses are used for making subgroups +### +### You can "escape" infix syntax use a quote or unquote (comma) +### + +(def- precedence + {'>> 9 + '<< 9 + '>>> 9 + '= 8 + '!= 8 + 'not= 8 + '< 8 + '<= 8 + '>= 8 + '> 8 + '& 7 + '^ 6 + 'bor 5 + 'band 5 + 'and 4 + 'or 3 + '+ 10 + '- 10 + '* 20 + '/ 20 + '% 20 + '** 30 + '! 40 + 'not 40 + 'bnot 40 + '++ 40 + '-- 40}) + +(def- right-associative + {'** true}) + +(def- unary + {'! true '- true 'bnot true 'not true '++ true '-- true}) + +(def- replacements + {'** math/pow + '>> brshift + '<< blshift + '>>> brushift + '^ bxor + '! not + '!= not= + '& band}) + +(defn- tup? [x] (and (tuple? x) (= (tuple/type x) :parens))) +(defn- brak? [x] (and (tuple? x) (= (tuple/type x) :brackets))) + +(defn- parse-tokens + [raw-tokens] + # Allow breaking out of infix syntax with ' or , + (when (= 'quote (first raw-tokens)) + (break raw-tokens)) + (when (= 'unquote (first raw-tokens)) + (break (get raw-tokens 1))) + (def tokens + (keep-syntax + raw-tokens + (map |(if (tup? $) (parse-tokens $) $) raw-tokens))) + (var i -1) + (defn eat [] (get tokens (++ i))) + (defn uneat [] (-- i)) + (defn parse-expression + [lhs min-prec] + (when (get unary lhs) + (break (parse-expression + (keep-syntax raw-tokens [(get replacements lhs lhs) + (parse-expression (eat) (get precedence lhs 0))]) + min-prec))) + (def op (eat)) + (def prec (get precedence op 0)) + (cond + (nil? op) lhs # done + + (brak? op) # array subscripting (highest precedence) + (let [index (parse-tokens op)] + (parse-expression [in lhs index] min-prec)) + + (zero? prec) (errorf "expected binary operator, got %p" op) + + ((if (get right-associative op) >= >) prec min-prec) # climb precendence + (let [next-token (eat) + rhs (parse-expression next-token prec) + real-op (get replacements op op)] + (parse-expression (keep-syntax raw-tokens [real-op lhs rhs]) min-prec)) + + :else # lower precedence + (do (uneat) lhs))) + (def ret (parse-expression (eat) 0)) + (when (= nil ret) + (errorf "expected non-empty expression, got %p" raw-tokens)) + ret) + +(defmacro $$ + "Use infix syntax for writing expressions in a more familiar manner. Useful for writing mathematic expressions." + [& body] + (def res (parse-tokens body)) + res) diff --git a/spork/init.janet b/spork/init.janet index 8c6b585..d07b4e9 100644 --- a/spork/init.janet +++ b/spork/init.janet @@ -10,6 +10,7 @@ (import ./htmlgen :export true) (import ./http :export true) (import ./httpf :export true) +(import ./infix :export true) (import ./mdz :export true) (import ./misc :export true) (import ./msg :export true) diff --git a/test/suite-infix.janet b/test/suite-infix.janet new file mode 100644 index 0000000..29eab14 --- /dev/null +++ b/test/suite-infix.janet @@ -0,0 +1,74 @@ +(use ../spork/test) +(use ../spork/infix) + +(start-suite) + +# Basic tests +(assert (deep= '(+ 1 2) (macex1 '($$ 1 + 2)))) +(assert (deep= ~(,math/pow 1 2) (macex1 '($$ 1 ** 2)))) +(assert (= ($$ 1 - 2 - 3 - 4) (- 1 2 3 4))) +(assert (= ($$ 1 + 2 + 3 + 4) (+ 1 2 3 4))) +(assert (= ($$ 1 * 2 * 3 * 4) (* 1 2 3 4))) +(assert (= ($$ 1 / 2 / 3 / 4) (/ 1 2 3 4))) +(assert (= ($$ 1 % 2 % 3 % 4) (% 1 2 3 4))) +(assert (= ($$ 2 ** 3 ** 4 + 1) (+ 1 (math/pow 2 (math/pow 3 4))))) + +# Examples +(def a 123123) +(def b 12391) +(def y [10 20 30 40]) +(def z :thing) +(defn good? [z] (not z)) +(assert (= ($$ a + b ** 2) (+ a (math/pow b 2)))) +(assert (= ($$ (a + b) ** 2) (math/pow (+ a b) 2))) +(assert (= ($$ y[2] + y[3]) (+ (in y 2) (in y 3)))) +(assert (= ($$ a > b and ,(good? z)) (and (> a b) (good? z)))) + +# Logic (and or) +(assert (= ($$ true and nil) nil)) +(assert (= ($$ true and not nil) true)) +(assert (= ($$ false or not false) true)) +(assert (= ($$ false or true and not false) true)) +(assert (= ($$ false or true and ! false) true)) + +# Bit operations +(assert (= ($$ 1 << 1) 2)) +(assert (= ($$ 1 >> 1) 0)) +(assert (= ($$ 0xFF00 & 0xFF) 0)) +(assert (= ($$ 0xFF00 band 0xFF) 0)) +(assert (= ($$ 0xFF00 bor 0xFF) 0xFFFF)) +(assert (= ($$ 0xFF00 ^ 0xFF) 0xFFFF)) +(assert (= ($$ 0xFF0 ^ 0x0FF) 0xF0F)) +(assert (= ($$ 0xFF00 bor 0xFF bor 0x10000) 0x1FFFF)) + +# Array indexing +(def an-array [:a :b :c 1 2 3]) +(assert (= :b ($$ an-array[1]))) +(assert-error "out of bounds" ($$ an-array[100])) + +# Mutation with ++ and -- +(var a 0) +(assert (= 11 ($$ ++ a + 10))) +(assert (= 10 ($$ -- a + 10))) + +# Comparisons +(assert (= true ($$ 100 > 20))) +(assert (= false ($$ 10 > 20))) +(assert (= true ($$ 100 >= 20))) +(assert (= true ($$ 20 >= 20))) +(assert (= false ($$ 10 >= 20))) +(assert (= true ($$ 0 < 20))) +(assert (= false ($$ 20 < 20))) +(assert (= false ($$ 40 < 20))) +(assert (= true ($$ 0 <= 20))) +(assert (= true ($$ 20 <= 20))) +(assert (= false ($$ 40 <= 20))) +(assert (= true ($$ :a = :a))) +(assert (= false ($$ :b = :a))) +(assert (= false ($$ :a != :a))) +(assert (= true ($$ :b != :a))) +(assert (= false ($$ :a not= :a))) +(assert (= true ($$ :b not= :a))) +(assert ($$ 10 <= 20 and 30 < 40)) + +(end-suite) |
