Fennel Language Working in Roblox Studio

Howdy people of the forum! I just thought I would show off that I was able to get Fennel working inside of Studio. Fennel is a Lisp like programming language. I have been trying to explore it recently because it takes a strong functional approach to programming which I really like.

Here is a quick example I wrote up that will adjust a players walkspeed.

Fennel

(local Players (game:GetService "Players"))
(local Player Players.LocalPlayer)

(fn speedBoost [amount]
    (set Player.Character.Humanoid.WalkSpeed amount)
    )
    
(speedBoost 25)

And here is the compiled Lua code that results from the above.

Lua

local Players = game:GetService("Players")
local Player = Players.LocalPlayer
local function speedBoost(amount)
  Player.Character.Humanoid.WalkSpeed = amount
  return nil
end
return speedBoost(25)

Its really cool to see how different the two languages are when they are side by side. I also wrote up my own compiler script for this using Lua 5.4. It takes a search directory and a blacklist. It then loops through the directory and finds any .fnl files that arent on the blacklist and compiles them to .lua using the fennel compiler command.

local blacklist = {"foo.fnl"}

local search = [[C:\YOUR STUFF HERE]]

local function splitString(stringToSplit, separator)
	if not separator then
		separator = "%s"
	end
	
	local newString = {}
	for character in string.gmatch(stringToSplit, "([^"..separator.."]+)") do
		table.insert(newString, character)
	end
	
	return newString
end

-- Searches recursively for .fnl files in the search directory
do
	for directory in io.popen([[where /r ]] .. search .. [[ *.fnl]]):lines() do
		for _, blacklistedString in pairs(blacklist) do
			if not string.find(directory, blacklistedString) then			
				local splitDirectory = splitString(directory, "\\")
				local fileFullName = splitDirectory[#splitDirectory]
				local fileShortName = string.sub(fileFullName, 1, string.len(fileFullName) - 4)
				local fileDestination = string.sub(directory, 1, string.len(directory) - string.len(fileFullName))
				
				io.popen([[fennel --compile ]] .. directory .. [[ > ]] .. fileDestination .. fileShortName .. [[.lua]])
			end
		end
	end
end

Its pretty neat and I think I am going to try and use the language as much as I can, maybe even use it to make my own person project for Roblox.

7 Likes

How’d you do that? That’s awesome!

1 Like

This is really cool to see! I’ve actually also been working on a similar compile script for Fennel recently to be used with Rojo, though yours seems much cleaner:

(local io (require :io))
(local os (require :os))
(local lfs (require :lfs))
(local fennel (require :fennel))

(fn map [f tbl]
  (let [t []]
    (each [k v (pairs tbl)]
      (tset t k (f v)))
    t))

(fn split [input sep]
  (do
    (var seperator (or sep "%s"))
    (local t [])
    (each [str (string.gmatch input (.. "([^" seperator "]+)"))]
      (table.insert t str))
    t))

(fn file_exists [file]
  (let [(ok err code) (os.rename file file)]
    (or ok (= code 13))))

(fn is_directory [path]
  (file_exists (.. path "/")))

(fn get_file_extension [file]
  (let [tokens (split file ".")]
    (. tokens (# tokens))))

(fn all_directory_files [dir]
  (do
    (local files [])
    (each [file (lfs.dir dir)]
      (if (and (not= file ".") (not= file ".."))
          (table.insert files (.. dir "/" file))))
    files))

(fn read_file [filepath]
  (with-open [file (io.open filepath)]
    (: file :read "*all")))

(fn compile_fennel [path]
  (let [fennel_code (read_file path)
        opts {:allowedGlobals ["game"]}
        compilation (fennel.compileString fennel_code opts)]
    compilation))

; Replaces ./src with ./out
(fn get_output_path [path]
  (.. "./out" (string.sub path 6)))

; Provides a destination .lua path for a .fnl file
(fn get_output_extension [path]
  (.. (string.sub path 1 (- (# path) 3)) "lua"))

; Provides an output destination
; ./src/shared/example.fnl -> ./out/shared/example.lua
(fn output_lua_path [path]
  (get_output_path (get_output_extension path)))

; Recursively build a directory tree, while compiling/copying files to ./out
(fn compile_directory [path tree]
  (if (is_directory path)
      (do
        (io.popen (.. "mkdir -p " (get_output_path path)))
        (doto tree
          (tset :children (map
                            (fn [file]
                              (compile_directory file {} ))
                            (all_directory_files path)))
          (tset :filetype "directory")
          (tset :path path)))
      (do
        (let [is_fnl (= "fnl" (get_file_extension path))
              code_string ((if is_fnl compile_fennel read_file) path)]
          (io.popen (.. "echo \'" code_string "\' >| " (output_lua_path path)))
          (doto tree
            (tset :compile? is_fnl)
            (tset :filetype "file")
            (tset :path path))))))

(compile_directory "./src" {})

I’ve also yet to use Fennel in a more thorough Roblox project, but I’m glad to see that other people are trying it out.

I’m wondering, does anyone use any sort of interactive repl/editor for Fennel or Lua with Roblox? I’ve been trying out Conjure for Neovim, which has been very useful so far outside of Roblox, but I’m thinking it could get somewhat complex to make it work with a Roblox runtime.

1 Like

I installed Fennel and then added it to my PATH and once it was there you can use fennel --compile from the command line to compile it Fennel code into Lua code. I wanted to automate it so instead of doing it in the command line I wrote up some Lua code that you see above which basically tells Windows “Hey run this fennel --compile command with these arguments”. Then you can run the Lua file with Lua 5.4 which is also installed and I put it inside my PATH as well.

I don’t know if this answers your question, but I use Lite XL for writing my code and then I have a macro setup to run the compiler script I setup. Basically what I do is I write some Fennel code and then I save it with ctrl+S and then I also hit alt+F to then run the compiler. I have it synced up with Rojo and that’s kinda how I do my workflow. I could probably write up a full tutorial if anyone is interested.

Oh, at one point you will make a Fennel compiler in Roblox XD, like the name of the script is Script:fn and then just write some script in it.

That would be cool if you make a pure Fennel compiler in Roblox :wink:

I kinda do that. I do whats called AOT compiling. So I compile it before I play the game. Then after I compile it creates a new .lua script. Rojo sees this script and syncs it up into Roblox.