# -*- mode: org -*- Fennel Memo Features: - Lisp-style language (with macros) - Compiles to Lua - Transparent Lua FFI - nice REPL (see ,help) Non-lispy things: - No CONS cells or lisp-style lists - Real symbols are only used in macros - Keyword-like syntax is just syntactic sugar for strings Filename normally end by .fnl (or .fnlm for macros) * Syntax - ; starts a comment line - (): lists, function calls etc. (as in Lisp) - {}: tables [=dictionaries], e.g. {:a 1 :b 2}, ": var" is the same as ":var var" - []: sequences ("sequential tables") - "": strings (can contain newlines, otherwise as in Lua) - numbers: as in Lua (.inf, -.inf, .nan, -.nan) - symbols: - cannot start with : or digits - cannot contain the delimiters above, i.e., () {} [] "" - cannot contain the prefixes below, i.e., # ` , - cannot contain whitespaces and ' ~ ; @ - & can be in symbols but not in identifiers - . and : can only be used in the pattern a.b.c.d:method ("multi symbols") - :symbol is the same as "symbol" - prefixes: - #foo -> (hashfn foo) - `foo -> (quote foo) - ,foo -> (unquote foo) * Multi-values - a multi-value is like a spliced list before splicing - so when mval is 1 2, the expression (+ mval) is the same as (+ 1 2) - but if not at final position, only the first is used, e.g. (table.pack (values 1 2) 3) - normally as the result of a (values ...) call, or the ... variable in a function call - (pick-values n mval) takes the first n values - (select n mval) skips the first n-1 values - (select :# mval) returns the number of values in the multi-value * Functions - return value is the final form (can be multiple values using (values val1 val2 ...)) - (fn name [arg1 arg2 ...] body...) - called with less arguments -> rest is nil - called with more arguments -> rest is ignored - if the final argument is ..., it can be used as a multi-value variable containing the rest - for variable arguments & can also be used (see Binding/let) - name is optional - name can be var.field, where var is a table -> field is set to the function - lambda / λ: same as fn, but throws error if any args are nil except those starting with ? - e.g. (lambda [x ?y z] ...) - #: anonymous function of a single expression, arguments are $/$1 $2 ... $9 - e.g. #$ (identity function), #{:a $}, #[$2 $1], #(+ $ $2), #$.foo (returns .foo of the arg) - (partial f ...): creates a new function by filling the first args * Binding - (let [a 1 b 2] body...) - immutable - automatic destructuring - in sequences & is used to bind the rest: [a b & c] - in sequences &as is used to bind the whole: [a b &as all] - (local var val) - like let, but without a body (recommended at the top of the source) - (var var val) - like local, but the variable is mutable - (set var val) - sets local mutable variables (introduced by var) - can change the field of a table even if the table was created by let or local - if the field is dynamic (in a variable) the (. var fieldname-var) can be used - (case val pat1 expr1 pat2 expr2 ...) - pats are pattern-matching and can contain variables (that will be bound) - if a pattern is a symbol not starting with _ or ?, it is checked not to be nil - single _ pattern at the end is the idiomatic default branch - returns nil if nothing matches - a pattern can be a where-clause of the form (where pattern condition) - the pattern can contain (= var) which checks the match with a variable in an outer scope - in this case a condition is not necessary - (match val pat1 expr1 pat2 expr2 ...) - like case, but symbols in patterns refer to those in outer scope - if the existing binding was nil (which means, in Lua, that it does not exist) it is rebound - compare: (let [x 1] (case 2 x (+ x 40) _ :foo)) ; here x is a new variable bound to 2 (let [x 1] (match 2 x (+ x 40) _ :foo)) ; here x is the same variable so does not match (let [x 1] (case 2 (where (= x)) (+ x 40) _ :foo)) ; same as the match expression above - (case-try val pat1 expr1 pat2 expr2 ... (catch (_ err1) handler1 (_ err2) handler2 ...)) - like case, but errors are handled by the handlers at the end - if there was an error, but no catch-case matches, the result is nil - if there was no error, but no pattern matches, the result is val - (match-try val pat1 expr1 pat2 expr2 ... (catch (_ err1) handler1 (_ err2) handler2 ...)) - like case-try but with match semantics * Control flow - (if cond1 expr1 cond2 expr2 ... else-expr) - else-expr is optional - (when cond body...) - (each [val1 val2 ... iterator] body...) - most often (each [key val (pairs tbl)] ...) or (each [idx val (ipairs seq)] ...) - early termination with &until: (each [... &until condition] body...) - (for [var start stop step] body...) - step is 1 by default - early termination with &until: (for [... &until condition] body...) - (while condition body...) - (do body...) - sequential evaluation - (-> val expr1 expr2 ...): threading, always as first arg (symbols ~ one-element lists) - (-?> val expr1 expr2 ...): same, but short-circuits at the first nil - (->> val expr1 expr2 ...): threading, always as last arg (symbols ~ one-element lists) - (-?>> val expr1 expr2 ...): same, but short-circuits at the first nil - (doto val expr1 expr2 ...): like ->, but always uses val as the first argument * Operations - boolean: and or not - arithmetic: + - * / // % ^ (// requires Lua 5.3+) - comparison: > < >= <= = not= - bitwise: lshift rshift band bor bxor bnot (requires Lua 5.3+) - strings: .. (concatenation) - general: (length table/string) ** Tables - (. table field1 field2 ...): nested lookup - (?. table field1 field2 ...): safe nested lookup (short-circuits at the first nil value) - (: table field arg1 arg2 ...): same as (table.field table arg1 arg2 ...) - shorthand: (table:field arg1 arg2) - (icollect [val1 val2 ... iterator] body...) - like an "each" loop, but the results of the body are collected in a sequence - can use early termination with &until - can put the results in an existing sequence with &into (can be a value or a variable) e.g. (icollect [_ x (ipairs [1 2]) &into [1]] (+ x 1)) ; -> [1 2 3] - (collect [val1 val2 ... iterator] body...) - like icollect, but body should return (value key val) and is collected in a table - (fcollect [var start stop step] body...) - like icollect, but with a for-loop (step is 1 by default) - (accumulate [acc init val1 val2 ... iterator] body...) - like fold or reduce - can use early termination with &until - (faccumulate [acc init var start stop step] body...) - the for-loop version of accumulate (step is 1 by default) * Macros - (import-macros binding1 module1 ...) - load module and bind its functions as macros - e.g. (import-macros {: aif} :my-macros) loads the aif macro from my-macros.fnlm as aif - e.g. (import-macros mm :my-macros) loads all macros from my-macros.fnlm into table mm - (macros {:macro1 fn1 :macro2 fn2 ...}) - (macro my-macro my-fn): same as (macros {:my-macro my-fn}) - (macrodebug expr): prints the expansion form (at compile time) - adding # to the end of a variable makes it auto-gensym - (eval-compiler body...): evaluates at compile time ** Compile-time functions - (list a b c ...): a compile-time-only data structure for code - symbols: sym (string -> symbol) gensym - predicates: list? sym? table? sequence? varg? multi-sym? comment? in-scope? - pack / unpack (converting between multi-values and sequences) - view (fennel.view table serializer) - assert-compile - macroexpand * Miscellaneous - (include :some.embedded.module): loads a module at compile time - usually better to use Lua's require - require can be treated this way with the --require-as-include option - (with-open [var1 val1 var2 val2 ...] body...) - like let, but no destructuring, and each variable will be called with the :close method - (tail! expr): checks that expr is at tail position - (assert-repl condition description): start a repl when condition is false or nil - normal asserts can be treated this way with the --assert-as-repl option - possible to recover with ,return val - (lua string): explicit Lua code * Documentation - optional docstrings (after the arguments) in fn / lambda / λ / macro - can be replaced by a more general metadata table (:fnl/docstring is the docstring key) - in the REPL: ,doc name