Fennel Language
Fennel is a Lisp that compiles to Lua. It preserves Lua runtime semantics while providing Lisp syntax, expression-oriented forms, pattern matching, and macros.
This skill is language-first: prioritize semantics, forms, idioms, and reasoning. Do not assume tooling or project setup unless explicitly requested.
Use this skill when
- writing, editing, reviewing, or refactoring Fennel code
- translating between Lua and Fennel
- explaining Fennel syntax, forms, macros, or runtime behavior
- debugging compile-time and runtime issues
- evaluating idiomatic quality and semantic correctness
Non-negotiable Fennel model
- Fennel is fully interoperable with Lua; compiled output should not add runtime overhead.
- Everything is expression-oriented.
- Tables are the core runtime data structure and are mutable.
:namesyntax is a string literal, not a keyword type.nilmeans absence; setting a table key tonilremoves that key.- Prefer
letandlocalfor immutable locals; usevaronly for intentional reassignment. fnhas no arity checks;lambdaenforces required arguments.- Operators (
+,=,and, etc.) and..are special forms, not higher-order functions. - Operator argument counts are fixed at compile-time.
- Pattern matching is ordered: first matching clause wins.
- In
case,?xmeans maybe-nil binding and_xmeans ignored binding. - Sequential patterns are prefix matches;
[]matches any table. matchauto-pins existing bindings;casebinds fresh names unless explicitly pinned.- Native multiple return values are common and affect composition behavior.
- Iterators are foundational (
each,for,icollect,collect,accumulate) rather than seq abstractions. - Macros are compile-time transformations running in compiler scope, not runtime scope.
- Macro hygiene matters: use gensym (
name#) for helper locals. - Modules are plain returned values (usually tables) with explicit exports.
Coding defaults
- Keep code idiomatic to both Fennel readers and Lua runtime constraints.
- Favor simple, explicit forms over clever macro-heavy abstractions.
- Use
iffor value branches andwhenfor side effects. - Keep
varlifetimes tight. - Prefer destructuring where it improves readability.
- Prefer static field/method syntax (
foo.bar,foo:bar) when possible. - Handle expected failures with
(values nil err)andcase/case-trypatterns. - Reserve
error/assertfor unrecoverable paths or boundary failures. - Treat
luaescape hatch as temporary or constrained interop aid. - Avoid deprecated or legacy compatibility forms in new code.
Macro defaults
- Write a macro only when syntax transformation is required.
- Design macro expansion first, then macro call shape.
- Prefer quasiquote/unquote templates over manual
list/symconstruction. - Reject malformed macro inputs with
assert-compileand source-aware forms. - Avoid caller-scope assumptions; require runtime dependencies inside expansion when needed.
Clojure-to-Fennel guardrails
- Do not assume
clojure.coreruntime facilities. - Do not assume dynamic var/binding semantics.
- Do not assume persistent collection semantics or seq-first APIs.
- Translate to iterator-native and table-native patterns.
- Keep module export and visibility model explicit and Lua-friendly.
Reference map
- Complete language semantics and forms: reference.md
- Style, naming, layout, and module conventions: style-guide.md
- Macro authoring, hygiene, AST behavior, and diagnostics: macro-guide.md
- Clojure assumption corrections and translation model: differences-with-clojure.md
Working approach
- Preserve project conventions when they differ from defaults.
- Avoid broad refactors unless requested.
- Prefer exact semantics over surface-level translation.
- If uncertain about a form or edge case, verify against
references/reference.mdbefore emitting code.