How to use Fennel in runtime scripts without compiling to Lua

Fennel is a Scheme-like programming language that compiles to and is fully interoperable with Lua.

I only started using Lua when I started using Neovim v0.5 nightly releases, and I didn’t find the language entirely to my taste. However, I am quite a big fan of Lisp and Lisp-like languages, and I found that I enjoyed writing Fennel more than plain Lua, at least until I got more acclimated to the “Lua way” of doing things.

One of Fennel’s features is that it itself is a pure Lua module. And moreover, it ships with its own package searcher that can be used to require() Fennel source files as if they were Lua source files.

What this means is that, with the right configuration magic, I can drop Fennel files anywhere in my runtime path and use them from Neovim scripts. My current setup is configured to look for them under fennel/*.fnl, but of course they can be anywhere, even intermingled with Lua files under lua/*.fnl if you want!

Yes, this is going to be slower at load time than pre-compiling to plain Lua, but Fennel + LuaJIT is fast enough that it’s not noticeable for the small stuff I’ve written so far.

If this sounds as exciting to you as it did to me, here is my Lua script that I use to get this setup working: lua/fennel-init.lua. This file lives under ~/.config/nvim. In order to activate this Fennel integration, just add :lua require('fennel-init') to init.vim.

Hopefully this is useful to some other people!

Note that this setup is incompatible with plugins that intend to use the Aniseed framework, because they ship with fennel/*.fnl files that are not meant to be loaded directly, but instead loaded by the Aniseed system. I love the idea of Aniseed (and frankly I think its innovations should be merged upstream into Fennel itself), but I don’t currently use it, so this isn’t a problem for me. If you wanted to eliminate this incompatibility, you can adjust the fennel/*.fnl path in my config to be anything else, perhaps lua/*.fnl as I mentioned above.

Also note that my dotfiles are published under the MIT License; see here for full license terms.


TL,DR? Put this file in ~/.config/nvim/lua/fennel-init.lua, then you can require Fennel files from Neovim as if they were plain Lua files.

9 Likes

Thanks for writing this! I’ve been passively waiting for a guide to get started with fennel using neovim. In my other fennel projects adding the search paths includes luarocks dirs to add packages. Looking at your script it seems to be targeted at specifically local files which I take it means the best practice is just stashing a fennel lib locally?

Why is the load package function necessary?

I’m also a bit curious on why you don’t use aniseed, I’ve been using it for my (very nascent) experiments with fennel. I’m really excited that other people are (getting-excited/about fennel) too.

Thanks for writing this! I’ve been passively waiting for a guide to get started with fennel using neovim. In my other fennel projects adding the search paths includes luarocks dirs to add packages. Looking at your script it seems to be targeted at specifically local files which I take it means the best practice is just stashing a fennel lib locally?

Why is the load package function necessary?

Fennel itself (with fennel.searcher) takes care of loading modules from standard Lua locations, including those managed by Luarocks. My script is meant to be in addition to that functionality. You can drop a Lua file into lua/*.lua anywhere in the runtime path, so my objective was to be able to do the same with Fennel and not have to pre-compile to Lua first.

I don’t use Aniseed because it looked somewhat complicated to me as a Lua newbie, and I wanted to start with something simple where I could just drop in a Fennel script and have it transparently available inside Neovim.

I also have a :Fennel command that’s analogous to :lua, and :FennelCall which lets you execute a top-level Fennel form but without the enclosing parentheses, just to make it easier to type.

Basically, Aniseed is on my to-do list. And like I said above, I think Aniseed having first-class “modules” (that aren’t just Lua tables) is a great feature.

I also think that my system can and should work side-by-side with Aniseed, but I will have to do a bit of research on how exactly Aniseed works (and is meant to be used) before I personally feel comfortable recommending that they be used together.

1 Like

Aniseed author here :smiley: thanks for the kind words, great to see people experimenting with Fennel! For the record, you can totally configure the aniseed.env module (the part of the project responsible for managing Fennel based dotfiles) to load from another directory or module other than fnl. So fnl/aniseed/*.fnl for example. Should all be under :h aniseed.

So a little config should untangle the two. I love the simplicity of your approach! It was something I certainly considered and could support to be honest, I just realised how much time it takes to load the compiler and translate all of my config (there’s a lot), I kinda need that to be done AOT personally.

If you eventually really like Aniseed but really want no AOT step that could certainly be arraged with a little work :slight_smile:

2 Likes

Update 1: I am using Aniseed now! Thank you, Olical.

Update 2: Someone made a much more “professional” version of this, called Hotpot. Use that instead of this.

Update 3: Here are the Fennel and FennelCall commands I mentioned:

command! -nargs=* Lua lua loadstring('print('..<q-args>..')')()
command! -nargs=* LuaInspect lua loadstring('print(vim.inspect('..<q-args>..'))')()
command! -nargs=* LuaVimEval echo luaeval(<q-args>)

lua <<LUA
local luarocks_path = vim.env.HOME .. '/.luarocks/share/lua/5.1/?.lua'
package.path = luarocks_path .. ';' .. package.path
LUA
command! -nargs=* Fennel lua require('fennel').eval('(print '..<q-args>..')')
command! -nargs=* FennelCall lua require('fennel').eval('(print ('..<q-args>..'))')
command! -nargs=* FennelInspect lua require('fennel').eval('(print (vim.inspect '..<q-args>..'))')
" TODO? FennelVimEval - ugly quoting.