Kapusta
Kapusta is a Lisp for the Ruby runtime.
It is inspired by Fennel. Kapusta aims to bring the simplicity and joy of Lisp to Ruby. Where Fennel uses Lua's stdlib and runtime, Kapusta uses Ruby's.
For more information about Kapusta, see the official Fennel documentation and tutorials, but replace Lua with Ruby.
Features
- Compiles to readable Ruby.
- Compiled
.rbfiles don't depend on Kapusta. Run with plainruby, or load.kapfiles at runtime viarequire 'kapusta'. - Two-way Ruby interop.
Install
gem install kapusta
It installs three executables:
kapustakapfmtkapusta-ls
Use
kapusta examples/fizzbuzz.kap
or
exe/kapusta examples/fizzbuzz.kap
or
kapusta --compile examples/fizzbuzz.kap > examples/fizzbuzz.rb
ruby examples/fizzbuzz.rb
For mruby 3 compatible output, such as DragonRuby, use:
kapusta --compile --target=mruby3 examples/match.kap > examples/match-mruby3.rb
Use from Ruby
Ruby can require a .kap file and use it directly.
require 'kapusta'
Kapusta.require('./bank-account', relative_to: __FILE__)
account = BankAccount.new('Ada', 100)
See examples/bank-account.kap and examples/use_bank_account.rb.
Examples
See examples/ and examples-compiled/.
(fn ack [m n]
(if (= m 0) (+ n 1)
(= n 0) (ack (- m 1) 1)
(ack (- m 1) (ack m (- n 1)))))
(print (ack 2 3))
(print (ack 3 3))
Compiles to:
def ack(m, n)
if m == 0
n + 1
elsif n == 0
ack(m - 1, 1)
else
ack(m - 1, ack(m, n - 1))
end
end
p ack(2, 3)
p ack(3, 3)
Calls and lookup
Hash lookup uses :.
user:name
(: user :name)
(?: user :profile :name) ; safe lookup
Method calls use ..
user.name
(. user :name)
Kapusta source always uses require.
(require :app.args)
(local messages (require :app.messages))
(require "./config")
Compiled Ruby uses the Ruby form that fits:
require "app/args"
require "app/messages"
= App::Messages
require_relative "config"
For larger programs, organize code with Kapusta's Ruby host forms:
- Use
module+defnfor stateless functions. - Use
class+fnfor state or dependencies. - Avoid returning hashes of functions as the main app structure.
Comparison with Fennel
Kapusta keeps most core Fennel forms. The main differences come from Ruby's runtime and object model.
| Fennel | Kapusta |
|---|---|
| Lua stdlib | Ruby stdlib |
:foo is a Lua string |
:foo is a Ruby symbol |
(. xs 1) is the first element |
(: xs 0) is the first element |
string.format, table.insert, etc. |
use Ruby methods and stdlib instead |
(print x) is Lua's print (bare) |
(print x) is Ruby's p (inspect-style) |
(.. "x: " nil) errors at runtime |
(.. "x: " nil) produces "x: " (Ruby nil.to_s) |
with-open, tail! |
not provided |
Kapusta-specific additions:
moduleandclassfor Ruby host structure, including file-header forms(end)closes a bodyless file-header(defn name ...)or(fn class.name ..)ivaror@var/cvaror@@var/gvaror$vartry/catch/finallyplusraisefor exceptions(ruby "...")raw host escape hatch- pass Ruby keyword arguments by ending a call with a symbol-keyed hash:
(File.open path "r" {:encoding "UTF-8"}) - pass a Ruby block by ending a call with a
(fn ...)or#(...)literal:(File.open path "r" (fn [io] io.read))
Format
kapfmt --fix examples/fizzbuzz.kap
LSP
Use kapusta-ls in the editor of your choice.
Syntax highlight
For Vim, you can use vim-syntax/.