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.