diff --git a/.claude/API_DOCs/fennel.md b/.claude/API_DOCs/fennel.md new file mode 100644 index 0000000..785e0d8 --- /dev/null +++ b/.claude/API_DOCs/fennel.md @@ -0,0 +1,1922 @@ +# Fennel Reference + +This document covers the syntax, built-in macros, and special forms +recognized by the Fennel compiler. It does not include built-in Lua +functions; see the [Lua reference manual][1] or the [Lua primer][3] +for that. This is not an introductory text; see the [tutorial][7] for +that. If you already have a piece of Lua code you just want +to see translated to Fennel, use [antifennel][8]. + +A macro is a function which runs at compile time and transforms some +Fennel code into different Fennel. A special form (or special) is a +primitive construct which emits Lua code directly. When you are +coding, you don't need to care about the difference between built-in +macros and special forms; it is an implementation detail. + +Remember that Fennel relies completely on Lua for its runtime. +Everything Fennel does happens at compile-time, so you will need to +familiarize yourself with Lua's standard library functions. Thankfully +it's much smaller than almost any other language. + +The one exception to this compile-time rule is the `fennel.view` +function which returns a string representation of any Fennel data +suitable for printing. But this is not part of the language itself; it +is a library function which can be used from Lua just as easily. + +Fennel source code should be UTF-8-encoded text. + +## Syntax + +`(parentheses)`: used to delimit lists, which are primarily used to +denote calls to functions, macros, and specials. Lists are a +compile-time construct; they are not used at runtime. For example: +`(print "hello world")` + +`{curly brackets}`: used to denote key/value table literals, also +known as dictionaries. For example: `{:a 1 :b 2}` In a table if you +have a string key followed by a symbol of the same name as the string, +you can use `:` as the key and it will be expanded to a string +containing the name of the following symbol. + +```fennel +{: this} ; is shorthand for {:this this} +``` + +`[square brackets]`: used to denote sequential +tables, which can be used for literal data structures and also in +specials and macros to delimit where new identifiers are introduced, +such as argument lists or let bindings. For example: `[1 2 3]` + +The syntax for numbers is the [same as Lua's][6], except that underscores +may be used to separate digits for readability. Non-ASCII digits are +not yet supported. Infinity and negative infinity are represented as +`.inf` and `-.inf`. NaN and negative Nan are `.nan` and `-.nan`. + +The syntax for strings uses double-quotes `"` around the +string's contents. Double quotes inside a string must be escaped with +backslashes. The syntax for these is [the same as Lua's][6], except +that strings may contain newline characters. Single-quoted or long +bracket strings are not supported. + +Fennel has a lot fewer restrictions on identifiers than Lua. +Identifiers are represented by symbols, but identifiers are not +exactly the same as symbols; some symbols are used by macros for +things other than identifiers. Symbols may not begin with digits or a +colon, but may have digits anywhere else. Beyond that, any unicode characters are +accepted as long as they are not unprintable or whitespace, one of the +delimiter characters mentioned above, one of the a prefix characters +listed below, or one of these reserved characters: + +* single quote: `'` +* tilde: `~` +* semicolon: `;` +* at: `@` + +Underscores are allowed in identifier names, but dashes are preferred +as word separators. By convention, identifiers starting with +underscores are used to indicate that a local is bound but not meant +to be used. + +The ampersand character `&` is allowed in symbols but not in +identifiers. This allows it to be reserved for macros, like the +behavior of `&as` in destructuring. + +Symbols that contain a dot `.` or colon `:` are considered +"multi symbols". The part of the symbol before the first dot or colon is +used as an identifier, and the part after the dot or colon is a field +looked up on the local identified. A colon is only allowed before the +final segment of a multi symbol, so `x.y:z` is valid but `a:b.c` is +not. Colon multi symbols can only be used for method calls. + +Fennel also supports certain kinds of strings that begin with a colon +as long as they don't contain any characters which wouldn't be allowed +in a symbol, for example `:fennel-lang.org` is another way of writing +the string "fennel-lang.org". + +Spaces, tabs, newlines, vertical tabs, form feeds, and carriage +returns are counted as whitespace. Non-ASCII whitespace characters are +not yet supported. + +Certain prefixes are expanded by the parser into longhand equivalents: + +* `#foo` expands to `(hashfn foo)` +* `` `foo `` expands to `(quote foo)` +* `,foo` expands to `(unquote foo)` + +A semicolon and everything following it up to the end of the line is a +comment. + +## Functions + +### `fn` function + +Creates a function which binds the arguments given inside the square +brackets. Will accept any number of arguments; ones in excess of the +declared ones are ignored, and if not enough arguments are supplied to +cover the declared ones, the remaining ones are given values of `nil`. + +Example: + +```fennel +(fn pxy [x y] + (print (+ x y))) +``` + +Giving it a name is optional; if one is provided it will be bound to +it as a local. The following mean exactly the same thing; the first is +preferred mostly for indentation reasons, but also because it allows +recursion: + +```fennel +(fn pxy [x y] + (print (+ x y))) + +(local pxy (fn [x y] + (print (+ x y)))) +``` + + +Providing a name that's a table field will cause it to be inserted in +a table instead of bound as a local: + +```fennel +(local functions {}) + +(fn functions.p [x y z] + (print (* x (+ y z)))) + +;; equivalent to: +(set functions.p (fn [x y z] + (print (* x (+ y z))))) +``` + +Like Lua, functions in Fennel support tail-call optimization, allowing +(among other things) functions to recurse indefinitely without overflowing +the stack, provided the call is in a tail position. + +The final form in this and all other function forms is used as the +return value. + +### `lambda`/`λ` nil-checked function + +Creates a function like `fn` does, but throws an error at runtime if +any of the listed arguments are nil, unless its identifier begins with `?`. + +Example: + +```fennel +(lambda [x ?y z] + (print (- x (* (or ?y 1) z)))) +``` + +Note that the Lua runtime will fill in missing arguments with nil when +they are not provided by the caller, so an explicit nil argument is usually +no different than omitting an argument. + +Programmers coming from other languages in which it is an error to +call a function with a different number of arguments than it is +defined with often get tripped up by the behavior of `fn`. This is +where `lambda` is most useful. + +The `lambda`, `case`, `case-try`, `match` and `match-try` forms are the only +place where the `?foo` notation is used by the compiler to indicate that a nil +value is allowed, but it is a useful notation elsewhere to communicate intent +anywhere a new local is introduced. + +The `λ` form is an alias for `lambda` and behaves identically. + +### Docstrings and metadata + +The `fn`, `lambda`, `λ` and `macro` forms accept an optional docstring. + +```fennel +(fn pxy [x y] + "Print the sum of x and y" + (print (+ x y))) + +(λ pxyz [x ?y z] + "Print the sum of x, y, and z. If y is not provided, defaults to 0." + (print (+ x (or ?y 0) z))) +``` + +These are ignored by default outside of the REPL, unless metadata +is enabled from the CLI (`---metadata`) or compiler options `{useMetadata=true}`, +in which case they are stored in a metadata table along with the arglist, +enabling viewing function docs via the `doc` macro. + +``` +;; this only works in the repl +>> ,doc pxy +(pxy x y) + Print the sum of x and y +``` + +Docstrings and other metadata can also be accessed via functions on the fennel +API with `fennel.doc` and `fennel.metadata`. + +*(Since 1.1.0)* + +All forms that accept a docstring will also accept a metadata table in +the same place: + +```fennel +(fn add [...] + {:fnl/docstring "Add arbitrary amount of numbers." + :fnl/arglist [a b & more]} + (case (values (select :# ...) ...) + (0) 0 + (1 a) a + (2 a b) (+ a b) + (_ a b) (add (+ a b) (select 3 ...)))) +``` + +Here the arglist is overridden by that in the metadata table (note +that the contents of the table are implicitly quoted). Calling `,doc` +command in the REPL prints specified argument list of the next form: + +``` +>> ,doc add +(add a b & more) + Add arbitrary amount of numbers. +``` + +*(Since 1.3.0)* + +Arbitrary metadata keys are allowed in the metadata table syntax: + +```fennel +(fn foo [] + {:deprecated "v1.9.0" + :fnl/docstring "*DEPRECATED* use foo2"} + ;; old way to do stuff + ) + +(fn foo2 [x] + {:added "v2.0.0" + :fnl/docstring "Incompatible but better version of foo!"} + ;; do stuff better, now with x! + x) +``` + +In this example, the `deprecated` and `added` keys are used to store a +version of a hypothetical library on which the functions were +deprecated or added. External tooling then can leverage this +information by using Fennel's metadata API: + +``` +>> (local {: metadata} (require :fennel)) +>> (metadata:get foo :deprecated) +"v1.9.0" +>> (metadata:get foo2 :added) +"v2.0.0" +``` + +Such metadata can be any data literal, including tables, with the only +restriction that there are no side effects. Fennel's lists are +disallowed as metadata values. + +*(Since 1.3.1)* + +For editing convenience, the metadata table literals are allowed after docstrings: + +``` fennel +(fn some-function [x ...] + "Docstring for some-function." + {:fnl/arglist [x & xs] + :other :metadata} + (let [xs [...]] + ;; ... + )) +``` + +In this case, the documentation string is automatically inserted to +the metadata table by the compiler. + +The whole metadata table can be obtained by calling `metadata:get` +without the `key` argument: + +``` +>> (local {: metadata} (require :fennel)) +>> (metadata:get some-function) +{:fnl/arglist ["x" "&" "xs"] + :fnl/docstring "Docstring for some-function." + :other "metadata"} +``` + +Fennel itself only uses the `fnl/docstring` and `fnl/arglist` metadata +keys but third-party code can make use of arbitrary keys. + +### Hash function literal shorthand + +It's pretty easy to create function literals, but Fennel provides +an even shorter form of functions. Hash functions are anonymous +functions of one form, with implicitly named arguments. All +of the below functions are functionally equivalent: + +```fennel +(fn [a b] (+ a b)) +``` + +```fennel +(hashfn (+ $1 $2)) ; implementation detail; don't use directly +``` + +```fennel +#(+ $1 $2) +``` + +This style of anonymous function is useful as a parameter to higher +order functions. It's recommended only for simple one-line functions +that get passed as arguments to other functions. + +The current implementation only allows for hash functions to use up to +9 arguments, each named `$1` through `$9`, or those with varargs, +delineated by `$...` instead of the usual `...`. A lone `$` in a hash +function is treated as an alias for `$1`. + +Hash functions are defined with the `hashfn` macro or special character `#`, +which wraps its single argument in a function literal. For example, + +```fennel +#$3 ; same as (fn [x y z] z) +#[$1 $2 $3] ; same as (fn [a b c] [a b c]) +#{:a $1 :b $2} ; same as (fn [a b] {:a a :b b}) +#$ ; same as (fn [x] x) (aka the identity function) +#val ; same as (fn [] val) +#[:one :two $...] ; same as (fn [...] ["one" "two" ...]) +``` + +Hash arguments can also be used as parts of multisyms. For instance, +`#$.foo` is a function which will return the value of the "foo" key in +its first argument. + +Unlike regular functions, there is no implicit `do` in a hash +function, and thus it cannot contain multiple forms without an +explicit `do`. The body itself is directly used as the return value +rather than the last element in the body. + +### `partial` partial application + +Returns a new function which works like its first argument, but fills +the first few arguments in place with the given ones. This is related +to currying but different because calling it will call the underlying +function instead of waiting till it has the "correct" number of args. + +Example: + +```fennel +(fn add-print [x y] (print (+ x y))) +(partial add-print 2) +``` + +This example returns a function which will print a number that is 2 +greater than the argument it is passed. + + +## Binding + +### `let` scoped locals + +Introduces a new scope in which a given set of local bindings are used. + +Example: + +```fennel +(let [x 89 + y 198] + (print (+ x y 12))) ; => 299 +``` + +These locals cannot be changed with `set` but they can be shadowed by +an inner `let` or `local`. Outside the body of the `let`, the bindings +it introduces are no longer visible. The last form in the body is used +as the return value. + +Any time you bind a local, you can destructure it if the value is a table: + +Example: + +```fennel +(let [[a b c] [1 2 3]] + (+ a b c)) ; => 6 +``` + +*(Since 1.5.0)*: If the left-hand side and the right-hand side are +both table literals, the actual table allocation will be optimized +away, and `a` will be bound directly to 1 without any allocation. + +If a table key is a string with the same name as the local you want to +bind to, you can use shorthand of just `:` for the key name followed +by the local name. This works for both creating tables and destructuring them. + +Example: + +```fennel +(let [{:msg message : val} {:msg "hello there" :val 19}] + (print message) + val) ; prints "hello there" and returns 19 +``` + +When destructuring a sequential table, you can capture all the remainder +of the table in a local by using `&`: + +Example: + +```fennel +(let [[a b & c] [1 2 3 4 5 6]] + (table.concat c ",")) ; => "3,4,5,6" +``` +*(Since 1.3.0)*: This also works with function argument lists, but it +has a small performance cost, so it's recommended to use `...` instead +in cases that are sensitive to overhead. + +When destructuring a non-sequential table, you can capture the +original table along with the destructuring by using `&as`: + +Example: + +```fennel +(let [{:a a :b b &as all} {:a 1 :b 2 :c 3 :d 4}] + (+ a b all.c all.d)) ; => 10 +``` + +For backwards-compatibility, you can also bind multiple values with +parentheses in any context that supports destructuring. This is not necessary +in current versions of Fennel, but older versions before 1.5.0 did not +optimize away tables in destructuring, so this was required for efficient +binding. + +Example: + +```fennel +(let [(x y z) (table.unpack [10 9 8])] + (+ x y z)) ; => 27 +``` + +### `local` declare local + +Introduces a new local inside an existing scope. Similar to `let` but +without a body argument. Recommended for use at the top-level of a +file for locals which will be used throughout the file. + +Example: + +```fennel +(local tau-approx 6.28318) +``` + +Supports destructuring. + +### `case` pattern matching + +*(Since 1.3.0)* + +Evaluates its first argument, then searches thru the subsequent +pattern/body clauses to find one where the pattern matches the value, +and evaluates the corresponding body. Pattern matching can be thought +of as a combination of destructuring and conditionals. + +**Note**: Lua also has "patterns" which are matched against strings +similar to how regular expressions work in other languages; these are +two distinct concepts with similar names. + +Example: + +```fennel +(case mytable + 59 :will-never-match-hopefully + [9 q 5] (print :q q) + [1 a b] (+ a b)) +``` + +In the example above, we have a `mytable` value followed by three +pattern/body clauses. + +The first clause will only match if `mytable` is 59. + +The second clause will match if `mytable` is a table with 9 as its first +element, any non-nil value as its second value and 5 as its third element; if +it matches, then it evaluates `(print :q q)` with `q` bound to the second +element of `mytable`. + +The final clause will only match if `mytable` has 1 as its first element and +two non-nil values after it; if so then it will add up the second and third +elements. + +If no clause matches, the form evaluates to nil. + +Patterns can be tables, literal values, or symbols. Any symbol that +doesn't start with `_` or `?` is implicitly checked to be not `nil`. +Symbols can be repeated in an expression to check for the same value. + +Example: + +```fennel +(case mytable + ;; the first and second values of mytable are the same non-nil value + [a a] (* a 2) + ;; the first and second values are not nil and are not the same value + [a b] (+ a b)) +``` + +It's important to note that expressions are checked *in order!* In the above +example, since `[a a]` is checked first, we can be confident that when `[a b]` +is checked, the two values must be different. Had the order been reversed, +`[a b]` would always match as long as they're not `nil` - even if they have the +same value! + +You may allow a symbol to optionally be `nil` by prefixing it with `?`. + +Example: + +```fennel +(case mytable + ;; not-nil, maybe-nil + [a ?b] :maybe-one-maybe-two-values + ;; maybe-nil == maybe-nil, both are nil or both are the same value + [?a ?a] :maybe-none-maybe-two-same-values + ;; maybe-nil, maybe-nil + [?a ?b] :maybe-none-maybe-one-maybe-two-values) +``` + +Symbols prefixed by an `_` are ignored and may stand in as positional +placeholders or markers for "any" value - including a `nil` value. A single `_` +is also often used at the end of a `case` expression to define an "else" style +fall-through value to indicate that local needs to be non-nil but its value is +not used other than that. + +Example: + +```fennel +(case mytable + ;; not-nil, anything + [a _b] :maybe-one-maybe-two-values + ;; anything, anything (different to the previous ?a example!) + ;; note this is effectively the same as [] + [_a _a] :maybe-none-maybe-one-maybe-two-values + ;; anything, anything + ;; this is identical to [_a _a] and in this example would never actually match. + [_a _b] :maybe-none-maybe-one-maybe-two-values + ;; when no other clause matched, in this case any non-table value + _ :no-match) +``` + +Tables can be nested, and they may be either sequential (`[]` style) or +key/value (`{}` style) tables. Sequential tables will match if they have at +least as many elements as the pattern. (To allow an element to be nil, see `?` +and `_` as above.) Tables will *never* fail to match due to having too many +elements - this means `[]` matches *any* table, not just an *empty* table. You can +use `&` to capture all the remaining elements of a sequential table, just like +`let`. + +```fennel +(case mytable + {:subtable [a b ?c] :depth depth} (* b depth) + _ :unknown) +``` + +You can also match against multiple return values using +parentheses. (These cannot be nested, but they can contain tables.) +This can be useful for error checking. + +```fennel +(case (io.open "/some/file") + (nil msg) (report-error msg) + f (read-file f)) +``` + +#### Guard Clauses + +Sometimes you need to match on something more general than a structure +or specific value. In these cases you can use guard clauses: + +```fennel +(case [91 12 53] + (where [a b c] (= 5 a)) :will-not-match + (where [a b c] (= 0 (math.fmod (+ a b c) 2)) (= 91 a)) c) ; -> 53 +``` + +In this case the pattern should be wrapped in parentheses but the +first thing in the parentheses is the `where` symbol. Each form after +the pattern is a condition; all the conditions must evaluate to true +for that pattern to match. + +If several patterns share the same body and guards, such patterns can +be combined with `or` special in the `where` clause: + +```fennel +(case [5 1 2] + (where (or [a 3 9] [a 1 2]) (= 5 a)) "Either [5 3 9] or [5 1 2]" + _ "anything else") +``` + +This is essentially equivalent to: + +```fennel +(case [5 1 2] + (where [a 3 9] (= 5 a)) "Either [5 3 9] or [5 1 2]" + (where [a 1 2] (= 5 a)) "Either [5 3 9] or [5 1 2]" + _ "anything else") +``` + +However, patterns which bind variables should not be combined with +`or` if different variables are bound in different patterns or some +variables are missing: + +``` fennel +;; bad +(case [1 2 3] + ;; Will fail to compile because `b' is not present in the first + ;; pattern but the guard still uses it. + (where (or [a 1 2] [a b 3]) (< a 0) (< b 1)) + :body) + +;; ok +(case [1 2 3] + (where (or [a b 2] [a b 3]) (< a 0) (<= b 1)) + :body) +``` + +#### Binding Pinning + +Symbols bound inside a `case` pattern are independent from any existing +symbols in the current scope, that is - names may be re-used without +consequence. + +Example: + +```fennel +(let [x 1] + (case [:hello] + ;; `x` is simply bound to the first value of [:hello] + [x] x)) ; -> :hello +``` + +Sometimes it may be desirable to match against an existing value in the outer +scope. To do this we can "pin" a binding inside the pattern with an existing +outer binding with the unary `(= binding-name)` form. The unary `(= binding-name)` +form is *only* valid in a `case` pattern and *must* be inside a `(where)` +guard. + +Example: + +```fennel +(let [x 1] + (case [:hello] + ;; 1 != :hello + (where [(= x)]) x + _ :no-match)) ; -> no-match + +(let [x 1] + (case [1] + ;; 1 == 1 + (where [(= x)]) x + _ :no-match)) ; -> 1 + +(let [pass :hunter2] + (case (user-input) + (where (= pass)) :login + _ :try-again!)) +``` + +Pinning is only required inside the pattern. Outer bindings are automatically +available inside guards and bodies as long as the name has not been rebound in +the pattern. + +**Note:** The `case` macro can be used in place of the `if-let` macro +from Clojure. The reason Fennel doesn't have `if-let` is that `case` +makes it redundant. + +### `match` pattern matching + +`match` is conceptually equivalent to `case`, except symbols in the patterns are +always pinned with outer-scope symbols if they exist. + +It supports all the same syntax as described in `case` except the pin +(`(= binding-name)`) expression, as it is always performed. + +> Be careful when using `match` that your symbols are not accidentally the same +> as any existing symbols! If you know you don't intend to pin any existing +> symbols you should use the `case` expression. + +```fennel +(let [x 95] + (match [52 85 95] + [b a a] :no ; because a=85 and a=95 + [x y z] :no ; because x=95 and x=52 + [a b x] :yes)) ; a and b are fresh values while x=95 and x=95 +``` + +Unlike in `case`, if an existing binding has the value `nil`, the `?` prefix is +not necessary - it would instead create a new un-pinned binding! + +Example: + +```fennel +(let [name nil + get-input (fn [] "Dave")] + (match (get-input) + ;; name already exists as nil, "Dave" != nil so this *wont* match + name (.. "Hello " name) + ?no-input (.. "Hello anonymous"))) ; -> "Hello anonymous" +``` + +**Note:** Prior to Fennel 0.9.0 the `match` macro used infix `?` +operator to test patterns against the guards. While this syntax is +still supported, `where` should be preferred instead: + +``` fennel +(match [1 2 3] + (where [a 2 3] (< 0 a)) "new guard syntax" + ([a 2 3] ? (< 0 a)) "obsolete guard syntax") +``` + +### `case-try` for matching multiple steps + +Evaluates a series of pattern matching steps. The value from the first +expression is matched against the first pattern. If it matches, the first +body is evaluated and its value is matched against the second pattern, etc. + +If there is a `(catch pat1 body1 pat2 body2 ...)` form at the end, any mismatch +from the steps will be tried against these patterns in sequence as a fallback +just like a normal `case`. If no `catch` pattern matches, nil is returned. + +If there is no catch, the mismatched value will be returned as the value of the +entire expression. + +```fennel +(fn handle [conn token] + (case-try (conn:receive :*l) + input (parse input) + (where (command-name params (= token))) (commands.get command-name) + command (pcall command (table.unpack params)) + (catch + (_ :timeout) nil + (_ :closed) (pcall disconnect conn "connection closed") + (_ msg) (print "Error handling input" msg)))) +``` + +This is useful when you want to perform a series of steps, any of which could +fail. The `catch` clause lets you keep all your error handling in one +place. Note that there are two ways to indicate failure in Fennel and Lua: +using the `assert`/`error` functions or returning nil followed by some data +representing the failure. This form only works on the latter, but you can use +`pcall` to transform `error` calls into values. + +### `match-try` for matching multiple steps + +Equivalent to `case-try` but uses `match` internally. See `case` and `match` +for details on the differences between these two forms. + +Unlike `case-try`, `match-try` will pin values in a given `catch` block with +those in the original steps. + +```fennel +(fn handle [conn token] + (match-try (conn:receive :*l) + input (parse input) + (command-name params token) (commands.get command-name) + command (pcall command (table.unpack params)) + (catch + (_ :timeout) nil + (_ :closed) (pcall disconnect conn "connection closed") + (_ msg) (print "Error handling input" msg)))) +``` + +### `var` declare local variable + +Introduces a new local inside an existing scope which may have its +value changed. Identical to `local` apart from allowing `set` to work +on it. + +Example: + +```fennel +(var x 83) +``` + + +Supports destructuring. + +### `set` set local variable or table field + +Changes the value of a variable introduced with `var`. Will not work +on globals or `let`/`local`-bound locals. Can also be used to change a +field of a table, even if the table is bound with `let` or `local`. If +the table field name is static, use `tbl.field`; if the field name is +dynamic, use `(. tbl field)`. + +Examples: + +```fennel +(set x (+ x 91)) ; var + +(let [t {:a 4 :b 8}] ; static table field + (set t.a 2) t) ; => {:a 2 :b 8} + +(let [t {:supported-chars {:x true}} + field1 :supported-chars + field2 :y] ; dynamic table field + (set (. t field1 field2) true) t) ; => {:supported-chars {:x true :y true}} +``` + +This supports destructuring too. + +### `tset` set table field + +Sets the field of a given table to a new value. + +Example: + +```fennel +(let [tbl {:d 32} field :d] + (tset tbl field 19) tbl) ; => {:d 19} +``` + +You can provide multiple successive field names to perform nested +sets. For example: + +```fennel +(let [tbl {:a {:b {}}} field :c] + (tset tbl :a :b field "d") tbl) ; => {:a {:b {:c "d"}}} +``` + +Since 1.5.0, `tset` is mostly redundant because `set` can be used for +table fields. The main exception is that `tset` works with `doto` and +`set` does not. + +### `with-open` bind and auto-close file handles + +While Lua will automatically close an open file handle when it's garbage collected, +GC may not run right away; `with-open` ensures handles are closed immediately, error +or no, without boilerplate. + +The usage is similar to `let`, except: +- destructuring is disallowed (symbols only on the left-hand side) +- every binding should be a file handle or other value with a `:close` method. + +After executing the body, or upon encountering an error, `with-open` +will invoke `(value:close)` on every bound variable before returning the results. + +Normally the body is implicitly wrapped in a function and run with `xpcall` so +that all bound handles are closed before it re-raises the error. However you +can use `--to-be-closed` to make it use the native functionality in Lua 5.4+ +which will do the same thing without `xpcall` interfering with stack traces. + +Example: + +```fennel +;; Basic usage +(with-open [fout (io.open :output.txt :w) fin (io.open :input.txt)] + (fout:write "Here is some text!\n") + ((fin:lines))) ; => first line of input.txt + +;; This demonstrates that the file will also be closed upon error. +(var fh nil) +(local [ok err] + [(pcall #(with-open [file (io.open :test.txt :w)] + (set fh file) ; you would normally never do this + (error :whoops!)))]) +(io.type fh) ; => "closed file" +[ok err] ; => [false ""] +``` + + +### `pick-values` emit exactly n values + +Discards all values after the first n when dealing with multi-values (`...`) +and multiple returns. Useful for composing functions that return multiple values +with variadic functions. Expands to a `let` expression that binds and re-emits +exactly n values, e.g. + +```fennel +(pick-values 2 (func)) +``` +expands to +```fennel +(let [[_0_ _1_] [(func)]] (values _0_ _1_)) +``` + +Example: + +```fennel +(pick-values 0 :a :b :c :d :e) ; => nil +[(pick-values 2 (table.unpack [:a :b :c]))] ;-> ["a" "b"] + +(fn add [x y ...] + (let [sum (+ (or x 0) (or y 0))] + (if (= ... nil) + sum + (add sum ...)))) + +(add (pick-values 2 10 10 10 10)) ; => 20 +(->> [1 2 3 4 5] (table.unpack) (pick-values 3) (add)) ; => 6 +``` + +**Note:** If n is greater than the number of values supplied, n values will still be emitted. +This is reflected when using `(select "#" ...)` to count varargs, but tables `[...]` +ignore trailing nils: + +```fennel +(select :# (pick-values 5 "one" "two")) ; => 5 +[(pick-values 5 "one" "two")] ; => ["one" "two"] +``` + +## Flow Control + +### `if` conditional + +Checks a condition and evaluates a corresponding body. Accepts any +number of condition/body pairs; if an odd number of arguments is +given, the last value is treated as a catch-all "else". Similar to +`cond` in other lisps. + +Example: + +```fennel +(let [x (math.random 64)] + (if (= 0 (% x 10)) + "multiple of ten" + (= 0 (% x 2)) + "even" + "I dunno, something else")) +``` + +All values other than nil or false are treated as true. + +### `when` single side-effecting conditional + +Takes a single condition and evaluates the rest as a body if it's not +nil or false. This is intended for side-effects. The last form in the +body is used as the return value. + +Example: + +```fennel +(when launch-missiles? + (power-on) + (open-doors) + (fire)) +``` + +### `each` general iteration + +Runs the body once for each value provided by the iterator. Commonly +used with `ipairs` (for sequential tables) or `pairs` (for any table +in undefined order) but can be used with any iterator. Returns nil. + +Example: + +```fennel +(each [key value (pairs mytbl)] + (print "executing key") + (print (f value))) +``` + +Any loop can be terminated early by placing an `&until` clause at the +end of the bindings: + +```fennel +(local out []) +(each [_ value (pairs tbl) &until (< max-len (length out))] + (table.insert out value)) +``` + +**Note:** prior to fennel version 1.2.0, `:until` was used instead of `&until`; +the old syntax is still supported for backwards compatibility. + +Most iterators return two values, but `each` will bind any number. See +[Programming in Lua][4] for details about how iterators work. + +### `for` numeric loop + +Counts a number from a start to stop point (inclusive), evaluating the +body once for each value. Accepts an optional step. Returns nil. + +Example: + +```fennel +(for [i 1 10 2] + (log-number i) + (print i)) +``` + +This example will print all odd numbers under ten. + +Like `each`, loops using `for` can also be terminated early with an +`&until` clause. The clause is checked before each iteration of the +body; if it is true at the beginning then the body will not run at all. + +```fennel +(var x 0) +(for [i 1 128 &until (maxed-out? x)] + (set x (+ x i))) +``` + +### `while` good old while loop + +Loops over a body until a condition is met. Uses a native Lua `while` +loop. Returns nil. + +Example: + +```fennel +(var done? false) +(while (not done?) + (print :not-done) + (when (< 0.95 (math.random)) + (set done? true))) +``` + +### `do` evaluate multiple forms returning last value + +Accepts any number of forms and evaluates all of them in order, +returning the last value. This is used for inserting side-effects into +a form which accepts only a single value, such as in a body of an `if` +when multiple clauses make it so you can't use `when`. Some lisps call +this `begin` or `progn`. + +```fennel +(if launch-missiles? + (do + (power-on) + (open-doors) + (fire)) + false-alarm? + (promote lt-petrov)) +``` + +Some other forms like `fn` and `let` have an implicit `do`. + +## Data + +### operators + +* `and`, `or`, `not`: boolean +* `+`, `-`, `*`, `/`, `//`, `%`, `^`: arithmetic +* `>`, `<`, `>=`, `<=`, `=`, `not=`: comparison +* `lshift`, `rshift`, `band`, `bor`, `bxor`, `bnot`: bitwise operations + +These all work as you would expect, with a few caveats. The bitwise operators +are only available in Lua 5.3+, unless you use the `--use-bit-lib` flag or +the `useBitLib` flag in the options table, which lets them be used in +LuaJIT. The integer division operator (`//`) is only available in Lua 5.3+. + +They all take any number of arguments, as long as that number is fixed +at compile-time. For instance, `(= 2 2 (table.unpack [2 5]))` will evaluate +to `true` because the compile-time number of values being compared is 3. +Multiple values at runtime will not be taken into account. + +Note that these are all special forms which cannot be used as +higher-order functions. + +### `..` string concatenation + +Concatenates its arguments into one string. Will coerce numbers into +strings, but not other types. + +Example: + +```fennel +(.. "Hello" " " "world" 7 "!!!") ; => "Hello world7!!!" +``` + +String concatenation is subject to the same compile-time limit as the +operators above; it is not aware of multiple values at runtime. + +### `length` string or table length + +Returns the length of a string or table. Note that the length of a +table with gaps (nils) in it is undefined; it can return a number +corresponding to any of the table's "boundary" positions between nil +and non-nil values. If a table has nils and you want to know the last +consecutive numeric index starting at 1, you must calculate it +yourself with `ipairs`; if you want to know the maximum numeric key in +a table with nils, you can use `table.maxn` on Lua <= 5.2. + +Example: + +```fennel +(+ (length [1 2 3 nil 8]) (length "abc")) ; => 6 or 8 +``` + + +### `.` table lookup + +Looks up a given key in a table. Multiple arguments will perform +nested lookup. + +Example: + +```fennel +(. mytbl myfield) +``` + + +Example: + +```fennel +(let [t {:a [2 3 4]}] (. t :a 2)) ; => 3 +``` + + +Note that if the field name is a string known at compile time, you +don't need this and can just use `mytbl.field`. + +### Nil-safe `?.` table lookup + +Looks up a given key in a table. Multiple arguments will perform +nested lookup. If any of subsequent keys is not present, will +short-circuit to `nil`. + +Example: + +```fennel +(?. mytbl myfield) +``` + + +Example: + +```fennel +(let [t {:a [2 3 4]}] (?. t :a 4 :b)) ; => nil +(let [t {:a [2 3 4 {:b 42}]}] (?. t :a 4 :b)) ; => 42 +``` + +### `icollect`, `collect` table comprehension macros + +The `icollect` macro takes a "iterator binding table" in the format that `each` +takes, and returns a sequential table containing all the values produced by +each iteration of the macro's body. This is similar to how `map` works in +several other languages, but it is a macro, not a function. + +If the value is nil, it is omitted from the return table. This is analogous to +`filter` in other languages. + +```fennel +(icollect [_ v (ipairs [1 2 3 4 5 6])] + (if (< 2 v) (* v v))) +;; -> [9 16 25 36] + +;; equivalent to: +(let [tbl []] + (each [_ v (ipairs [1 2 3 4 5 6])] + (tset tbl (+ (length tbl) 1) (if (< 2 v) (* v v)))) + tbl) +``` + +The `collect` macro is almost identical, except that the +body should return two things: a key and a value. + +```fennel +(collect [k v (pairs {:apple "red" :orange "orange" :lemon "yellow"})] + (if (not= v "yellow") + (values (.. "color-" v) k))) +;; -> {:color-orange "orange" :color-red "apple"} + +;; equivalent to: +(let [tbl {}] + (each [k v (pairs {:apple "red" :orange "orange"})] + (if (not= v "yellow") + (match [(.. "color-" v) k] + [key value] (tset tbl key value)))) + tbl) +``` + +If the key and value are given directly in the body of `collect` and +not nested in an outer form, then the `values` can be omitted for brevity: + +```fennel +(collect [k v (pairs {:a 85 :b 52 :c 621 :d 44})] + k (* v 5)) +``` + +Like `each` and `for`, the table comprehensions support an `&until` +clause for early termination. + +Both `icollect` and `collect` take an `&into` clause which allows you +put your results into an existing table instead of starting with an +empty one: + +```fennel +(icollect [_ x (ipairs [2 3]) &into [9]] + (* x 11)) +;; -> [9 22 33] +``` + +**Note:** Prior to fennel version 1.2.0, `:into` was used instead of `&into`; +the old syntax is still supported for backwards compatibility. + + +### `accumulate` iterator accumulation + +Runs through an iterator and performs accumulation, similar to `fold` +and `reduce` commonly used in functional programming languages. +Like `collect` and `icollect`, it takes an iterator binding table +and an expression as its arguments. The difference is that in +`accumulate`, the first two items in the binding table are used as +an "accumulator" variable and its initial value. +For each iteration step, it evaluates the given expression and +its value becomes the next accumulator variable. +`accumulate` returns the final value of the accumulator variable. + +Example: + +```fennel +(accumulate [sum 0 + i n (ipairs [10 20 30 40])] + (+ sum n)) ; -> 100 +``` + +The `&until` clause is also supported here for early termination. + +### `faccumulate` range accumulation +*(Since 1.3.0)* + +Identical to accumulate, but instead of taking an iterator and the same bindings +as `each`, it accepts the same bindings as `for` and will iterate the numerical range. +Accepts `&until` just like `for` and `accumulate`. + +Example: + +```fennel +(faccumulate [n 0 i 1 5] (+ n i)) ; => 15 +``` + +### `fcollect` range comprehension macro + +*(Since 1.1.1)* + +Similarly to `icollect`, `fcollect` provides a way of building a +sequential table. Unlike `icollect`, instead of an iterator it +traverses a range, as accepted by the `for` special. The `&into` and +`&until` clauses work the same as in `icollect`. + +Example: + +```fennel +(fcollect [i 0 10 2] + (if (> i 2) (* i i))) +;; -> [16 36 64 100] + +;; equivalent to: +(let [tbl {}] + (for [i 0 10 2] + (if (> i 2) + (table.insert tbl (* i i)))) + tbl) +``` + +### `values` multi-valued return + +Returns multiple values from a function. Usually used to signal +failure by returning nil followed by a message. + +Example: + +```fennel +(fn [filename] + (if (valid-file-name? filename) + (open-file filename) + (values nil (.. "Invalid filename: " filename)))) +``` + +## Other + +### `:` method call + +Looks up a function in a table and calls it with the table as its +first argument. This is a common idiom in many Lua APIs, including +some built-in ones. + +Just like Lua, you can perform a method call by calling a function +name where `:` separates the table variable and method name. + +Example: + +```fennel +(let [f (assert (io.open "hello" "w"))] + (f:write "world") + (f:close)) +``` + +In the example above, `f:write` is a single multisym. If the name of +the method or the table containing it isn't fixed, you can use `:` +followed by the table and then the method's name to allow it to be a +dynamic string instead: + +Example: + +```fennel +(let [f (assert (io.open "hello" "w")) + method1 :write + method2 :close] + (: f method1 "world") + (: f method2)) +``` + +Both of these examples are equivalent to the following: + +```fennel +(let [f (assert (io.open "hello" "w"))] + (f.write f "world") + (f.close f)) +``` + +Unlike Lua, there's nothing special about defining functions that get +called this way; typically it is given an extra argument called `self` +but this is just a convention; you can name it anything. + +```fennel +(local t {}) + +(fn t.enable [self] + (set self.enabled? true)) + +(t:enable) +``` + +### `->`, `->>`, `-?>` and `-?>>` threading macros + +The `->` macro takes its first value and splices it into the second +form as the first argument. The result of evaluating the second form +gets spliced into the first argument of the third form, and so on. + +Example: + +```fennel +(-> 52 + (+ 91 2) ; (+ 52 91 2) + (- 8) ; (- (+ 52 91 2) 8) + (print "is the answer")) ; (print (- (+ 52 91 2) 8) "is the answer") +``` + +The `->>` macro works the same, except it splices it into the last +position of each form instead of the first. + +`-?>` and `-?>>`, the thread maybe macros, are similar to `->` & `->>` +but they also do checking after the evaluation of each threaded +form. If the result is false or nil then the threading stops and the result +is returned. `-?>` splices the threaded value as the first argument, +like `->`, and `-?>>` splices it into the last position, like `->>`. + +This example shows how to use them to avoid accidentally indexing a +nil value: + +```fennel +(-?> {:a {:b {:c 42}}} + (. :a) + (. :missing) + (. :c)) ; -> nil +(-?>> :a + (. {:a :b}) + (. {:b :missing}) + (. {:c 42})) ; -> nil +``` + +While `->` and `->>` pass multiple values thru without any trouble, +the checks in `-?>` and `-?>>` prevent the same from happening there +without performance overhead, so these pipelines are limited to a +single value. + +> Note that these have nothing to do with "threads" used for +> concurrency; they are named after the thread which is used in +> sewing. This is similar to the way that `|>` works in OCaml and Elixir. + +### `doto` + +Similarly, the `doto` macro splices the first value into subsequent +forms. However, it keeps the same value and continually splices the +same thing in rather than using the value from the previous form for +the next form. + +```fennel +(doto (io.open "/tmp/err.log") + (: :write contents) + (: :close)) + +;; equivalent to: +(let [x (io.open "/tmp/err.log")] + (: x :write contents) + (: x :close) + x) +``` + +The first form becomes the return value for the whole expression, and +subsequent forms are evaluated solely for side-effects. + +### `tail!` + +Tail calls will be optimized automatically. However, the `tail!` form +asserts that its argument is called in a tail position. You can use +this when the code depends on tail call optimization; that way if the +code is changed so that the recursive call is no longer in the tail position, +it will cause a compile error instead of overflowing the stack later on +large data sets. + +```fennel +(fn process-all [data i] + (case (process (. data i)) + :done (print "Process completed.") + :next (process-all data (+ i 1)) + :skip (do (tail! (process-all data (+ i 2))) +;; ^^^^^ Compile error: Must be in tail position + (print "Skipped" (+ i 1))))) +``` + +### `include` + +```fennel +(include :my.embedded.module) +``` + +Loads Fennel/Lua module code at compile time and embeds it in the +compiled output. The module name must resolve to a string literal +during compilation. The bundled code will be wrapped in a function +invocation in the emitted Lua and set on +`package.preload[modulename]`; a normal `require` is then emitted +where `include` was used to load it on demand as a normal module. + +In most cases it's better to use `require` in your code and use the +`requireAsInclude` option in the API documentation and the +`--require-as-include` CLI flag (`fennel --help`) to accomplish this. + +The `require` function is not part of Fennel; it comes from +Lua. However, it works to load Fennel code. See the [Modules and +multiple files](tutorial#modules-and-multiple-files) section in the +tutorial and [Programming in Lua][5] for details about `require`. + +The `include` macro and hence `--require-as-include` support +semi-dynamic compile-time resolution of module paths similarly to +`import-macros`. See the [relative +require](tutorial#relative-require) section in the tutorial for more +information. + +### `assert-repl` + +*(Since 1.4.0)* + +Sometimes it's helpful for debugging purposes to drop a repl right +into the middle of your code to see what's really going on. You can +use the `assert-repl` macro to do this: + +```fnl +(let [input (get-input) + value []] + (fn helper [x] + (table.insert value (calculate x))) + (assert-repl (transform helper value) "could not transform")) +``` + +This works as a drop-in replacement for the built-in `assert` function, but +when the condition is false or nil, instead of an error, it drops into a repl +which has access to all the locals that are in scope (`input`, `value`, and +`helper` in the example above). + +Note that this is meant for use in development and will not work with +ahead-of-time compilation unless your build also includes Fennel as a +library. + +If you use the `--assert-as-repl` flag when running Fennel, calls to +`assert` will be replaced with `assert-repl` automatically. + +**Note:** In Fennel 1.4.0, `assert-repl` accepted an options table for +`fennel.repl` as an optional third argument. This was removed as a bug in +1.4.1, as it broke compatibility with `assert`. + +The REPL spawned by `assert-repl` applies the same default options as +`fennel.repl`, which as of Fennel 1.4.1 can be configured from the API. See the +[Fennel API reference](api.md#customize-repl-default-options) for details. + +#### Recovering from failed assertions + +You can `,return EXPRESSION` from the repl to replace the original +failing condition with a different arbitrary value. Returning false or +nil will trigger a regular `assert` failure. + +**Note:** Currently, only a single value can be returned from the REPL this +way. While `,return` can be used to make a failed assertion recover, if the +calling code expects multiple return values, it may cause unexpected +behavior. + +## Macros + +All forms which introduce macros do so inside the current scope. This +is usually the top level for a given file, but you can introduce +macros into nested scopes as well. Note that macros are a +compile-time construct; they do not exist at runtime. As such macros +cannot be exported at the bottom of a module like functions and other values. + +### `import-macros` load macros from a separate module + +Loads a module at compile-time and binds its functions as local macros. + +A macro module exports any number of functions which take code forms +as arguments at compile time and emit lists which are fed back into +the compiler as code. Macro modules are searched for in filenames +ending in `.fnl` *or* `.fnlm`. The module calling `import-macros` gets +whatever functions have been exported to use as macros. For instance, +here is a macro module which implements `when2` in terms of `if` and `do`: + +```fennel +(fn when2 [condition body1 & rest-body] + (assert body1 "expected body") + `(if ,condition + (do ,body1 ,(unpack rest-body)))) + +{:when2 when2} +``` + +For a full explanation of how this works see [the macro guide](/macros.md). +All forms in Fennel are normal tables you can use `table.insert`, +`ipairs`, destructuring, etc on. The backtick on the third line +creates a template list for the code emitted by the macro, and the +comma serves as "unquote" which splices values into the +template. + +Assuming the code above is in the file "my-macros.fnl" then it turns this input: + +```fennel +(import-macros {: when2} :my-macros) + +(when2 (= 3 (+ 2 a)) + (print "yes") + (finish-calculation)) +``` + +and transforms it into this code at compile time by splicing the arguments +into the backtick template: + +```fennel +(if (= 3 (+ 2 a)) + (do + (print "yes") + (finish-calculation))) +``` + +The `import-macros` macro can take any number of binding/module-name +pairs. It can also bind the entire macro module to a single name +rather than destructuring it. In this case you can use a dot to call +the individual macros inside the module: + +```fennel +(import-macros mine :my-macros) + +(mine.when2 (= 3 (+ 2 a)) + (print "yes") + (finish-calculation)) +``` + +Note that all macro code runs at compile time, which happens before +runtime. Locals which are in scope at runtime are not visible during +compile-time. So this code will not work: + +```fennel +(local (module-name file-name) ...) +(import-macros mymacros (.. module-name ".macros")) +``` + +However, this code will work, provided the module in question exists: + +```fennel +(import-macros mymacros (.. ... ".macros")) +``` + +See "Compiler API" below for details about additional functions visible +inside compiler scope which macros run in. + +### Macro module searching + +By default, Fennel will search for macro modules similarly to how it +searches for normal runtime modules: by walking thru entries on +`fennel.macro-path` and checking the filesystem for matches. However, +in some cases this might not be suitable, for instance if your Fennel +program is packaged in some kind of archive file and the modules do +not exist as distinct files on disk. + +To support this case you can add your own searcher function to the +`fennel.macro-searchers` table. For example, assuming `find-in-archive` +is a function which can look up strings from the archive given a path: + +```fennel +(local fennel (require :fennel)) + +(fn my-searcher [module-name] + (let [filename (.. "src/" module-name ".fnl")] + (match (find-in-archive filename) + code (values (partial fennel.eval code {:env :_COMPILER}) + filename)))) + +(table.insert fennel.macro-searchers my-searcher) +``` + +The searcher function should take a module name as a string and return +two values if it can find the macro module: a loader function which will +return the macro table when called, and an optional filename. The +loader function will receive the module name and the filename as arguments. + +### `macros` define several macros + +Defines a table of macros. Note that inside the macro definitions, you +cannot access variables and bindings from the surrounding code. The +macros are essentially compiled in their own compiler +environment. Again, see the "Compiler API" section for more details +about the functions available here. + +```fennel +(macros {:my-max (fn [x y] + `(let [x# ,x y# ,y] + (if (< x# y#) y# x#)))}) + +(print (my-max 10 20)) +(print (my-max 20 10)) +(print (my-max 20 20)) +``` + +### `macro` define a single macro + +```fennel +(macro my-max [x y] + `(let [x# ,x y# ,y] + (if (< x# y#) y# x#))) +``` + +If you are only defining a single macro, this is equivalent to the +previous example. The syntax mimics `fn`. + +### `macrodebug` print the expansion of a macro + +```fennel +(macrodebug (-> abc + (+ 99) + (< 0) + (when (os.exit)))) +; -> (if (< (+ abc 99) 0) (do (os.exit))) +``` + +Call the `macrodebug` macro with a form and it will repeatedly expand +top-level macros in that form and print out the resulting form. Note +that the resulting form will usually not be sensibly indented, so you +might need to copy it and reformat it into something more readable. + +Note that this prints at compile-time since `macrodebug` is a macro. + +### Macro gotchas + +It's easy to make macros which accidentally evaluate their arguments +more than once. This is fine if they are passed literal values, but if +they are passed a form which has side-effects, the result will be unexpected: + +```fennel +(var v 1) +(macros {:my-max (fn [x y] + `(if (< ,x ,y) ,y ,x))}) + +(fn f [] (set v (+ v 1)) v) + +(print (my-max (f) 2)) ; -> 3 since (f) is called twice in the macro body above +``` + +In order to prevent [accidental symbol capture][2], you may not bind a +bare symbol inside a backtick as an identifier. Appending a `#` on +the end of the identifier name as above invokes "auto gensym" which +guarantees the local name is unique. + +```fennel +(macros {:my-max (fn [x y] + `(let [x2 ,x y2 ,y] + (if (< x2 y2) y2 x2)))}) + +(print (my-max 10 20)) +; Compile error in 'x2' unknown:?: macro tried to bind x2 without gensym; try x2# instead +``` + +`macros` is useful for one-off, quick macros, or even some more complicated +macros, but be careful. It may be tempting to try and use some function +you have previously defined, but if you need such functionality, you +should probably use `import-macros`. + +For example, this will not compile in strict mode! Even when it does +allow the macro to be called, it will fail trying to call a global +`my-fn` when the code is run: + +```fennel +(fn my-fn [] (print "hi!")) + +(macros {:my-max (fn [x y] + (my-fn) + `(let [x# ,x y# ,y] + (if (< x# y#) y# x#)))}) +; Compile error in 'my-max': attempt to call global '__fnl_global__my_2dfn' (a nil value) +``` + +See the [macro guide][9] for more details about writing macros. + +### `eval-compiler` + +Evaluate a block of code during compile-time with access to compiler +scope. This gives you a superset of the features you can get with +macros, but you should use macros if you can. + +Example: + +```fennel +(eval-compiler + (each [name (pairs _G)] + (print name))) +``` + +This prints all the functions available in compiler scope. + +### Compiler Environment + +Inside `eval-compiler`, `macros`, or `macro` blocks, as well as +`import-macros` modules, the functions listed below are visible to +your code. The predicate functions are meant to be used on AST nodes +that macros accept as arguments. + +* `list` - return a list, which is a special kind of table used for code. +* `sequence` - return a sequence AST node +* `sym` - turn a string into a symbol. +* `gensym` - generates a unique symbol for use in macros, accepts an optional prefix string. +* `list?` - is the argument a list? Returns the argument or `false`. +* `sym?` - is the argument a symbol? Returns the argument or `false`. +* `table?` - is the argument a non-list table? Returns the argument or `false`. +* `sequence?` - is the argument a non-list _sequential_ table (created + with `[]`, as opposed to `{}`)? Returns the argument or `false`. +* `varg?` - is this a `...` symbol which indicates var args? Returns a special + table describing the type or `false`. +* `multi-sym?` - a multi-sym is a dotted symbol which refers to a table's + field. Returns a table containing each separate symbol, or + `false`. +* `comment?` - is the argument a comment? Comments are only included + when `opts.comments` is truthy. +* `view` - `fennel.view` table serializer. + +* `assert-compile` - works like `assert` but takes a list/symbol as its third + argument in order to provide pinpointed error messages. + +The following functions standardize Lua globals that change between 5.1-5.4. To +limit common Lua-compatibility boilerplate such as +`(local unpack (or _G.unpack table.unpack))` from macro +code, the following helpers are present in the macro environment: + +* `unpack` - `_G.unpack` in Lua 5.1/LuaJit, `table.unpack` in Lua >= 5.2 +* `pack` - Equivalent to `table.pack` available in Lua 5.2 and up. + `(pack :a nil :c nil nil)` -> `{1 :a 3 :c :n 5}`. Useful for reliably storing + and correctly reproducing multi-values that contain `nil`. + +These functions can be used from within macros only, not from any +`eval-compiler` call: + +* `in-scope?` - does the symbol refer to an in-scope local? Returns the symbol or `nil`. +* `macroexpand` - performs macroexpansion on its argument form; returns an AST. + +#### Note: Compile-time List implementation + +Note that lists are compile-time concepts that don't exist at runtime; they +are implemented as tables which have a special metatable to distinguish them +from regular tables defined with square or curly brackets. Similarly symbols +are tables with a string entry for their name and a marker metatable. You +can use `tostring` to get the name of a symbol. + +#### Sandboxing + +Inside macros or `eval-compiler`, by default there are only two ways that +code can interact with "the outside world"; you can call `print` in order to +debug, and you can call `io.open` in *read* mode on files inside the current +directory or its subdirectories. The rest of the `io` table and the entire +`os` table is not accessible. + +You can loosen these restrictions by passing `{:compiler-env _G}` in the +options table when using the compiler API or setting `--no-compiler-sandbox` +on the command line to get full access. + +Please note that the sandbox is not suitable to be used as a robust +security mechanism. It has not been audited and should not be relied +upon to protect you from running untrusted code. + +Note that other internals of the compiler exposed in compiler scope +but not listed above are subject to change. + +## `lua` Escape Hatch + +There are some cases when you need to emit Lua output from Fennel in +ways that don't match Fennel's semantics. For instance, if you are +porting an algorithm from Lua that uses early returns, you may want +to do the port as literally as possible first, and then come back to +it later to make it idiomatic. You can use the `lua` special form to +accomplish this: + +```fennel +(fn find [tbl pred] + (each [key val (pairs tbl)] + (when (pred val) + (lua "return key")))) +``` + +Lua code inside the string can refer to locals which are in scope; +however note that it must refer to the names after mangling has been +done, because the identifiers must be valid Lua. The Fennel compiler +will change `foo-bar` to `foo_bar` in the Lua output in order for it +to be valid, as well as other transformations. When in doubt, inspect +the compiler output to see what it looks like. For example the +following Fennel code: + +```fennel +(local foo-bar 3) +(let [foo-bar :hello] + (lua "print(foo_bar0 .. \" world\")")) +``` + +will produce this Lua code: + +```lua +local foo_bar = 3 +local foo_bar0 = "hello" +print(foo_bar0 .. " world") +return nil +``` + +Normally in these cases you would want to emit a statement, in which +case you would pass a string of Lua code as the first argument. But +you can also use it to emit an expression if you pass in a string as +the second argument. + +Note that this should only be used in exceptional or temporary circumstances, +and if you are able to avoid it, you should. + +## Deprecated Forms + +The `#` form is a deprecated alias for `length`, and `~=` is a +deprecated alias for `not=`, kept for backwards compatibility. + +### `require-macros` load macros with less flexibility + +*(Deprecated in 0.4.0)* + +The `require-macros` form is like `import-macros`, except it imports +all macros without making it clear what new identifiers are brought +into scope. It is strongly recommended to use `import-macros` instead. + +### `pick-args` create a function of fixed arity + +*(Deprecated 0.10.0)* + +Like `pick-values`, but takes an integer `n` and a function/operator +`f`, and creates a new function that applies exactly `n` arguments to `f`. + +### `global` set global variable + +*(Deprecated in 1.1.0)* + +Sets a global variable to a new value. Note that there is no +distinction between introducing a new global and changing the value of +an existing one. This supports destructuring. + +Example: + +```fennel +(global prettyprint (fn [x] (print (fennel.view x)))) +``` + +Using `global` adds the identifier in question to the list of allowed +globals so that referring to it later on will not cause a compiler error. +However, globals are also available in the `_G` table, and accessing +them that way instead is recommended for clarity. + +### Rest destructuring metamethod + +*(Deprecated in 1.4.1, will be removed in future versions)* + +If a table implements `__fennelrest` metamethod it is used to capture the +remainder of the table. It can be used with custom data structures +implemented in terms of tables, which wish to provide custom rest +destructuring. The metamethod receives the table as the first +argument, and the amount of values it needs to drop from the beginning +of the table, much like table.unpack + +Example: + +```fennel +(local t [1 2 3 4 5 6]) +(setmetatable + t + {:__fennelrest (fn [t k] + (let [res {}] + (for [i k (length t)] + (tset res (tostring (. t i)) (. t i))) + res))}) +(let [[a b & c] t] + c) ;; => {:3 3 :4 4 :5 5 :6 6} +``` + +[1]: https://www.lua.org/manual/5.1/ +[2]: https://gist.github.com/nimaai/2f98cc421c9a51930e16#variable-capture +[3]: https://fennel-lang.org/lua-primer +[4]: https://www.lua.org/pil/7.1.html +[5]: https://www.lua.org/pil/8.1.html +[6]: https://www.lua.org/manual/5.4/manual.html#3.1 +[7]: https://fennel-lang.org/tutorial +[8]: https://fennel-lang.org/see +[9]: https://fennel-lang.org/macros diff --git a/CLAUDE.md b/CLAUDE.md index 61540f9..fa70ecf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,6 +6,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co This is a two-player cooperative cleaning game built with LÖVE2D using Fennel (a Lisp dialect that compiles to Lua). +Fennel docs are available locally at `.claude/API_DOCs/fennel.md` + ## Key Architecture ### Fennel + LÖVE2D Setup @@ -41,7 +43,7 @@ Games define lifecycle callbacks (all optional): ### Running the Game ```bash -love ./two_player_cleaning_game +love ./game ``` LÖVE2D must be installed and in your PATH. See [Getting Started](https://love2d.org/wiki/Getting_Started) for installation.