1;; This module contains all the built-in Fennel macros. Unlike all the other 2;; modules that are loaded by the old bootstrap compiler, this runs in the 3;; compiler scope of the version of the compiler being defined. 4 5;; The code for these macros is somewhat idiosyncratic because it cannot use any 6;; macros which have not yet been defined. 7 8;; TODO: some of these macros modify their arguments; we should stop doing that, 9;; but in a way that preserves file/line metadata. 10 11(fn ->* [val ...] 12 "Thread-first macro. 13Take the first value and splice it into the second form as its first argument. 14The value of the second form is spliced into the first arg of the third, etc." 15 (var x val) 16 (each [_ e (ipairs [...])] 17 (let [elt (if (list? e) e (list e))] 18 (table.insert elt 2 x) 19 (set x elt))) 20 x) 21 22(fn ->>* [val ...] 23 "Thread-last macro. 24Same as ->, except splices the value into the last position of each form 25rather than the first." 26 (var x val) 27 (each [_ e (ipairs [...])] 28 (let [elt (if (list? e) e (list e))] 29 (table.insert elt x) 30 (set x elt))) 31 x) 32 33(fn -?>* [val ...] 34 "Nil-safe thread-first macro. 35Same as -> except will short-circuit with nil when it encounters a nil value." 36 (if (= 0 (select "#" ...)) 37 val 38 (let [els [...] 39 e (table.remove els 1) 40 el (if (list? e) e (list e)) 41 tmp (gensym)] 42 (table.insert el 2 tmp) 43 `(let [,tmp ,val] 44 (if (not= nil ,tmp) 45 (-?> ,el ,(unpack els)) 46 ,tmp))))) 47 48(fn -?>>* [val ...] 49 "Nil-safe thread-last macro. 50Same as ->> except will short-circuit with nil when it encounters a nil value." 51 (if (= 0 (select "#" ...)) 52 val 53 (let [els [...] 54 e (table.remove els 1) 55 el (if (list? e) e (list e)) 56 tmp (gensym)] 57 (table.insert el tmp) 58 `(let [,tmp ,val] 59 (if (not= ,tmp nil) 60 (-?>> ,el ,(unpack els)) 61 ,tmp))))) 62 63(fn ?dot [tbl ...] 64 "Nil-safe table look up. 65Same as . (dot), except will short-circuit with nil when it encounters 66a nil value in any of subsequent keys." 67 (let [head (gensym :t) 68 lookups `(do (var ,head ,tbl) ,head)] 69 (each [_ k (ipairs [...])] 70 ;; Kinda gnarly to reassign in place like this, but it emits the best lua. 71 ;; With this impl, it emits a flat, concise, and readable set of if blocks. 72 (table.insert lookups (# lookups) `(if (not= nil ,head) 73 (set ,head (. ,head ,k))))) 74 lookups)) 75 76(fn doto* [val ...] 77 "Evaluates val and splices it into the first argument of subsequent forms." 78 (let [name (gensym) 79 form `(let [,name ,val])] 80 (each [_ elt (ipairs [...])] 81 (let [elt (if (list? elt) elt (list elt))] 82 (table.insert elt 2 name) 83 (table.insert form elt))) 84 (table.insert form name) 85 form)) 86 87(fn when* [condition body1 ...] 88 "Evaluate body for side-effects only when condition is truthy." 89 (assert body1 "expected body") 90 `(if ,condition 91 (do 92 ,body1 93 ,...))) 94 95(fn with-open* [closable-bindings ...] 96 "Like `let`, but invokes (v:close) on each binding after evaluating the body. 97The body is evaluated inside `xpcall` so that bound values will be closed upon 98encountering an error before propagating it." 99 (let [bodyfn `(fn [] 100 ,...) 101 closer `(fn close-handlers# [ok# ...] 102 (if ok# ... (error ... 0))) 103 traceback `(. (or package.loaded.fennel debug) :traceback)] 104 (for [i 1 (length closable-bindings) 2] 105 (assert (sym? (. closable-bindings i)) 106 "with-open only allows symbols in bindings") 107 (table.insert closer 4 `(: ,(. closable-bindings i) :close))) 108 `(let ,closable-bindings 109 ,closer 110 (close-handlers# (_G.xpcall ,bodyfn ,traceback))))) 111 112(fn into-val [iter-tbl] 113 (var into nil) 114 (for [i (length iter-tbl) 2 -1] 115 (if (= :into (. iter-tbl i)) 116 (do (assert (not into) "expected only one :into clause") 117 (set into (table.remove iter-tbl (+ i 1))) 118 (table.remove iter-tbl i)))) 119 (assert (or (not into) 120 (sym? into) 121 (table? into) 122 (list? into)) 123 "expected table, function call, or symbol in :into clause") 124 (or into [])) 125 126(fn collect* [iter-tbl key-expr value-expr ...] 127 "Returns a table made by running an iterator and evaluating an expression that 128returns key-value pairs to be inserted sequentially into the table. This can 129be thought of as a table comprehension. The body should provide two 130expressions (used as key and value) or nil, which causes it to be omitted from 131the resulting table. 132 133For example, 134 (collect [k v (pairs {:apple \"red\" :orange \"orange\"})] 135 v k) 136returns 137 {:red \"apple\" :orange \"orange\"} 138 139Supports an :into clause after the iterator to put results in an existing table. 140Supports early termination with an :until clause." 141 (assert (and (sequence? iter-tbl) (>= (length iter-tbl) 2)) 142 "expected iterator binding table") 143 (assert (not= nil key-expr) "expected key and value expression") 144 (assert (= nil ...) 145 "expected 1 or 2 body expressions; wrap multiple expressions with do") 146 (let [kv-expr (if (= nil value-expr) key-expr `(values ,key-expr ,value-expr))] 147 `(let [tbl# ,(into-val iter-tbl)] 148 (each ,iter-tbl 149 (match ,kv-expr 150 (k# v#) (tset tbl# k# v#))) 151 tbl#))) 152 153(fn icollect* [iter-tbl value-expr ...] 154 "Returns a sequential table made by running an iterator and evaluating an 155expression that returns values to be inserted sequentially into the table. 156This can be thought of as a \"list comprehension\". If the body returns nil 157that element is omitted from the resulting table. 158 159For example, 160 (icollect [_ v (ipairs [1 2 3 4 5])] (when (not= v 3) (* v v))) 161returns 162 [1 4 16 25] 163 164Supports an :into clause after the iterator to put results in an existing table. 165Supports early termination with an :until clause." 166 (assert (and (sequence? iter-tbl) (>= (length iter-tbl) 2)) 167 "expected iterator binding table") 168 (assert (not= nil value-expr) "expected table value expression") 169 (assert (= nil ...) 170 "expected exactly one body expression. Wrap multiple expressions with do") 171 `(let [tbl# ,(into-val iter-tbl)] 172 ;; believe it or not, using a var here has a pretty good performance boost: 173 ;; https://p.hagelb.org/icollect-performance.html 174 (var i# (length tbl#)) 175 (each ,iter-tbl 176 (let [val# ,value-expr] 177 (when (not= nil val#) 178 (set i# (+ i# 1)) 179 (tset tbl# i# val#)))) 180 tbl#)) 181 182(fn accumulate* [iter-tbl accum-expr ...] 183 "Accumulation macro. 184It takes a binding table and an expression as its arguments. 185In the binding table, the first symbol is bound to the second value, being an 186initial accumulator variable. The rest are an iterator binding table in the 187format `each` takes. 188It runs through the iterator in each step of which the given expression is 189evaluated, and its returned value updates the accumulator variable. 190It eventually returns the final value of the accumulator variable. 191 192For example, 193 (accumulate [total 0 194 _ n (pairs {:apple 2 :orange 3})] 195 (+ total n)) 196returns 197 5" 198 (assert (and (sequence? iter-tbl) (>= (length iter-tbl) 4)) 199 "expected initial value and iterator binding table") 200 (assert (not= nil accum-expr) "expected accumulating expression") 201 (assert (= nil ...) 202 "expected exactly one body expression. Wrap multiple expressions with do") 203 (let [accum-var (table.remove iter-tbl 1) 204 accum-init (table.remove iter-tbl 1)] 205 `(do (var ,accum-var ,accum-init) 206 (each ,iter-tbl 207 (set ,accum-var ,accum-expr)) 208 ,accum-var))) 209 210(fn partial* [f ...] 211 "Returns a function with all arguments partially applied to f." 212 (assert f "expected a function to partially apply") 213 (let [bindings [] 214 args []] 215 (each [_ arg (ipairs [...])] 216 (if (or (= :number (type arg)) 217 (= :string (type arg)) 218 (= :boolean (type arg)) 219 (= `nil arg)) 220 (table.insert args arg) 221 (let [name (gensym)] 222 (table.insert bindings name) 223 (table.insert bindings arg) 224 (table.insert args name)))) 225 (let [body (list f (unpack args))] 226 (table.insert body _VARARG) 227 `(let ,bindings 228 (fn [,_VARARG] 229 ,body))))) 230 231(fn pick-args* [n f] 232 "Creates a function of arity n that applies its arguments to f. 233 234For example, 235 (pick-args 2 func) 236expands to 237 (fn [_0_ _1_] (func _0_ _1_))" 238 (if (and _G.io _G.io.stderr) 239 (_G.io.stderr:write 240 "-- WARNING: pick-args is deprecated and will be removed in the future.\n")) 241 (assert (and (= (type n) :number) (= n (math.floor n)) (>= n 0)) 242 (.. "Expected n to be an integer literal >= 0, got " (tostring n))) 243 (let [bindings []] 244 (for [i 1 n] 245 (tset bindings i (gensym))) 246 `(fn ,bindings 247 (,f ,(unpack bindings))))) 248 249(fn pick-values* [n ...] 250 "Like the `values` special, but emits exactly n values. 251 252For example, 253 (pick-values 2 ...) 254expands to 255 (let [(_0_ _1_) ...] 256 (values _0_ _1_))" 257 (assert (and (= :number (type n)) (>= n 0) (= n (math.floor n))) 258 (.. "Expected n to be an integer >= 0, got " (tostring n))) 259 (let [let-syms (list) 260 let-values (if (= 1 (select "#" ...)) ... `(values ,...))] 261 (for [i 1 n] 262 (table.insert let-syms (gensym))) 263 (if (= n 0) `(values) 264 `(let [,let-syms ,let-values] 265 (values ,(unpack let-syms)))))) 266 267(fn lambda* [...] 268 "Function literal with nil-checked arguments. 269Like `fn`, but will throw an exception if a declared argument is passed in as 270nil, unless that argument's name begins with a question mark." 271 (let [args [...] 272 has-internal-name? (sym? (. args 1)) 273 arglist (if has-internal-name? (. args 2) (. args 1)) 274 docstring-position (if has-internal-name? 3 2) 275 has-docstring? (and (> (length args) docstring-position) 276 (= :string (type (. args docstring-position)))) 277 arity-check-position (- 4 (if has-internal-name? 0 1) 278 (if has-docstring? 0 1)) 279 empty-body? (< (length args) arity-check-position)] 280 (fn check! [a] 281 (if (table? a) 282 (each [_ a (pairs a)] 283 (check! a)) 284 (let [as (tostring a)] 285 (and (not (as:match "^?")) (not= as "&") (not= as "_") 286 (not= as "...") (not= as "&as"))) 287 (table.insert args arity-check-position 288 `(_G.assert (not= nil ,a) 289 ,(: "Missing argument %s on %s:%s" :format 290 (tostring a) 291 (or a.filename :unknown) 292 (or a.line "?")))))) 293 294 (assert (= :table (type arglist)) "expected arg list") 295 (each [_ a (ipairs arglist)] 296 (check! a)) 297 (if empty-body? 298 (table.insert args (sym :nil))) 299 `(fn ,(unpack args)))) 300 301(fn macro* [name ...] 302 "Define a single macro." 303 (assert (sym? name) "expected symbol for macro name") 304 (local args [...]) 305 `(macros {,(tostring name) (fn ,(unpack args))})) 306 307(fn macrodebug* [form return?] 308 "Print the resulting form after performing macroexpansion. 309With a second argument, returns expanded form as a string instead of printing." 310 (let [handle (if return? `do `print)] 311 `(,handle ,(view (macroexpand form _SCOPE))))) 312 313(fn import-macros* [binding1 module-name1 ...] 314 "Binds a table of macros from each macro module according to a binding form. 315Each binding form can be either a symbol or a k/v destructuring table. 316Example: 317 (import-macros mymacros :my-macros ; bind to symbol 318 {:macro1 alias : macro2} :proj.macros) ; import by name" 319 (assert (and binding1 module-name1 (= 0 (% (select "#" ...) 2))) 320 "expected even number of binding/modulename pairs") 321 (for [i 1 (select "#" binding1 module-name1 ...) 2] 322 ;; delegate the actual loading of the macros to the require-macros 323 ;; special which already knows how to set up the compiler env and stuff. 324 ;; this is weird because require-macros is deprecated but it works. 325 (let [(binding modname) (select i binding1 module-name1 ...) 326 scope (get-scope) 327 macros* (_SPECIALS.require-macros `(import-macros ,modname) 328 scope {} binding1)] 329 (if (sym? binding) 330 ;; bind whole table of macros to table bound to symbol 331 (tset scope.macros (. binding 1) macros*) 332 ;; 1-level table destructuring for importing individual macros 333 (table? binding) 334 (each [macro-name [import-key] (pairs binding)] 335 (assert (= :function (type (. macros* macro-name))) 336 (.. "macro " macro-name " not found in module " 337 (tostring modname))) 338 (tset scope.macros import-key (. macros* macro-name)))))) 339 nil) 340 341;;; Pattern matching 342 343(fn match-values [vals pattern unifications match-pattern] 344 (let [condition `(and) 345 bindings []] 346 (each [i pat (ipairs pattern)] 347 (let [(subcondition subbindings) (match-pattern [(. vals i)] pat 348 unifications)] 349 (table.insert condition subcondition) 350 (each [_ b (ipairs subbindings)] 351 (table.insert bindings b)))) 352 (values condition bindings))) 353 354(fn match-table [val pattern unifications match-pattern] 355 (let [condition `(and (= (_G.type ,val) :table)) 356 bindings []] 357 (each [k pat (pairs pattern)] 358 (if (= pat `&) 359 (let [rest-pat (. pattern (+ k 1)) 360 rest-val `(select ,k ((or table.unpack _G.unpack) ,val)) 361 subcondition (match-table `(pick-values 1 ,rest-val) 362 rest-pat unifications match-pattern)] 363 (if (not (sym? rest-pat)) 364 (table.insert condition subcondition)) 365 (assert (= nil (. pattern (+ k 2))) 366 "expected & rest argument before last parameter") 367 (table.insert bindings rest-pat) 368 (table.insert bindings [rest-val])) 369 (= k `&as) 370 (do 371 (table.insert bindings pat) 372 (table.insert bindings val)) 373 (and (= :number (type k)) (= `&as pat)) 374 (do 375 (assert (= nil (. pattern (+ k 2))) 376 "expected &as argument before last parameter") 377 (table.insert bindings (. pattern (+ k 1))) 378 (table.insert bindings val)) 379 ;; don't process the pattern right after &/&as; already got it 380 (or (not= :number (type k)) (and (not= `&as (. pattern (- k 1))) 381 (not= `& (. pattern (- k 1))))) 382 (let [subval `(. ,val ,k) 383 (subcondition subbindings) (match-pattern [subval] pat 384 unifications)] 385 (table.insert condition subcondition) 386 (each [_ b (ipairs subbindings)] 387 (table.insert bindings b))))) 388 (values condition bindings))) 389 390(fn match-pattern [vals pattern unifications] 391 "Takes the AST of values and a single pattern and returns a condition 392to determine if it matches as well as a list of bindings to 393introduce for the duration of the body if it does match." 394 ;; we have to assume we're matching against multiple values here until we 395 ;; know we're either in a multi-valued clause (in which case we know the # 396 ;; of vals) or we're not, in which case we only care about the first one. 397 (let [[val] vals] 398 (if (or (and (sym? pattern) ; unification with outer locals (or nil) 399 (not= "_" (tostring pattern)) ; never unify _ 400 (or (in-scope? pattern) (= :nil (tostring pattern)))) 401 (and (multi-sym? pattern) (in-scope? (. (multi-sym? pattern) 1)))) 402 (values `(= ,val ,pattern) []) 403 ;; unify a local we've seen already 404 (and (sym? pattern) (. unifications (tostring pattern))) 405 (values `(= ,(. unifications (tostring pattern)) ,val) []) 406 ;; bind a fresh local 407 (sym? pattern) 408 (let [wildcard? (: (tostring pattern) :find "^_")] 409 (if (not wildcard?) (tset unifications (tostring pattern) val)) 410 (values (if (or wildcard? (string.find (tostring pattern) "^?")) true 411 `(not= ,(sym :nil) ,val)) [pattern val])) 412 ;; guard clause 413 (and (list? pattern) (= (. pattern 2) `?)) 414 (let [(pcondition bindings) (match-pattern vals (. pattern 1) 415 unifications) 416 condition `(and ,(unpack pattern 3))] 417 (values `(and ,pcondition 418 (let ,bindings 419 ,condition)) bindings)) 420 ;; multi-valued patterns (represented as lists) 421 (list? pattern) 422 (match-values vals pattern unifications match-pattern) 423 ;; table patterns 424 (= (type pattern) :table) 425 (match-table val pattern unifications match-pattern) 426 ;; literal value 427 (values `(= ,val ,pattern) [])))) 428 429(fn match-condition [vals clauses] 430 "Construct the actual `if` AST for the given match values and clauses." 431 (if (not= 0 (% (length clauses) 2)) ; treat odd final clause as default 432 (table.insert clauses (length clauses) (sym "_"))) 433 (let [out `(if)] 434 (for [i 1 (length clauses) 2] 435 (let [pattern (. clauses i) 436 body (. clauses (+ i 1)) 437 (condition bindings) (match-pattern vals pattern {})] 438 (table.insert out condition) 439 (table.insert out `(let ,bindings 440 ,body)))) 441 out)) 442 443(fn match-val-syms [clauses] 444 "How many multi-valued clauses are there? return a list of that many gensyms." 445 (let [syms (list (gensym))] 446 (for [i 1 (length clauses) 2] 447 (let [clause (if (and (list? (. clauses i)) (= `? (. clauses i 2))) 448 (. clauses i 1) 449 (. clauses i))] 450 (if (list? clause) 451 (each [valnum (ipairs clause)] 452 (if (not (. syms valnum)) 453 (tset syms valnum (gensym))))))) 454 syms)) 455 456(fn match* [val ...] 457 ;; Old implementation of match macro, which doesn't directly support 458 ;; `where' and `or'. New syntax is implemented in `match-where', 459 ;; which simply generates old syntax and feeds it to `match*'. 460 (let [clauses [...] 461 vals (match-val-syms clauses)] 462 (assert (= 0 (math.fmod (length clauses) 2)) 463 "expected even number of pattern/body pairs") 464 ;; protect against multiple evaluation of the value, bind against as 465 ;; many values as we ever match against in the clauses. 466 (list `let [vals val] (match-condition vals clauses)))) 467 468;; Construction of old match syntax from new syntax 469 470(fn partition-2 [seq] 471 ;; Partition `seq` by 2. 472 ;; If `seq` has odd amount of elements, the last one is dropped. 473 ;; 474 ;; Input: [1 2 3 4 5] 475 ;; Output: [[1 2] [3 4]] 476 (let [firsts [] 477 seconds [] 478 res []] 479 (for [i 1 (length seq) 2] 480 (let [first (. seq i) 481 second (. seq (+ i 1))] 482 (table.insert firsts (if (not= nil first) first `nil)) 483 (table.insert seconds (if (not= nil second) second `nil)))) 484 (each [i v1 (ipairs firsts)] 485 (let [v2 (. seconds i)] 486 (if (not= nil v2) 487 (table.insert res [v1 v2])))) 488 res)) 489 490(fn transform-or [[_ & pats] guards] 491 ;; Transforms `(or pat pats*)` lists into match `guard` patterns. 492 ;; 493 ;; (or pat1 pat2), guard => [(pat1 ? guard) (pat2 ? guard)] 494 (let [res []] 495 (each [_ pat (ipairs pats)] 496 (table.insert res (list pat `? (unpack guards)))) 497 res)) 498 499(fn transform-cond [cond] 500 ;; Transforms `where` cond into sequence of `match` guards. 501 ;; 502 ;; pat => [pat] 503 ;; (where pat guard) => [(pat ? guard)] 504 ;; (where (or pat1 pat2) guard) => [(pat1 ? guard) (pat2 ? guard)] 505 (if (and (list? cond) (= (. cond 1) `where)) 506 (let [second (. cond 2)] 507 (if (and (list? second) (= (. second 1) `or)) 508 (transform-or second [(unpack cond 3)]) 509 :else 510 [(list second `? (unpack cond 3))])) 511 :else 512 [cond])) 513 514(fn match-where [val ...] 515 "Perform pattern matching on val. See reference for details. 516 517Syntax: 518 519(match data-expression 520 pattern body 521 (where pattern guard guards*) body 522 (where (or pattern patterns*) guard guards*) body)" 523 (let [conds-bodies (partition-2 [...]) 524 else-branch (if (not= 0 (% (select "#" ...) 2)) 525 (select (select "#" ...) ...)) 526 match-body []] 527 (each [_ [cond body] (ipairs conds-bodies)] 528 (each [_ cond (ipairs (transform-cond cond))] 529 (table.insert match-body cond) 530 (table.insert match-body body))) 531 (if else-branch 532 (table.insert match-body else-branch)) 533 (match* val (unpack match-body)))) 534 535{:-> ->* 536 :->> ->>* 537 :-?> -?>* 538 :-?>> -?>>* 539 :?. ?dot 540 :doto doto* 541 :when when* 542 :with-open with-open* 543 :collect collect* 544 :icollect icollect* 545 :accumulate accumulate* 546 :partial partial* 547 :lambda lambda* 548 :pick-args pick-args* 549 :pick-values pick-values* 550 :macro macro* 551 :macrodebug macrodebug* 552 :import-macros import-macros* 553 :match match-where} 554