HackerReposA FIELD GUIDE TO OPEN SOURCE
ProSponsor the shelf♧ 0
← Back to top all-time
sctb
lumen
JavaScript2015–2024
sctb / lumen

lumen

A Lisp for Lua and JavaScript

14Distinct people
54Mentions
1Explicit recommenders
571GitHub stars
REPOSITORY AT A GLANCE

What to know before exploring

What it is

A Lisp for Lua and JavaScript

GitHub

Where it is used

No documented product use was verified in the last source check.

What people are saying

Lots of Lisp interest today! Here are a few resources. On Lisp: https://www.lurklurk.org/onlisp/onlisp.pdf Lumen - a Lisp for JavaScript and Lua: https://github.com/sctb/lumen…

shawn used a recommendation phrase. One indexed comment matches. This is a phrase match, not a verdict.

No classified warning is attached to this page. Read the discussions before treating silence as approval.

Also in these discussions

Mentioned in the same Hacker News threads. That is not proof they are substitutes.

This week on Hacker News

Nov 8–Nov 15 UTC · Direct repository links and separately labeled related project stories. Indexed through 11/15/2026, 6:36:16 PM UTC.

No direct repository links were found in the indexed HN discussions over the past seven days. Older discussions are available below.

Mentions over time

HN mentions by year54 dated mentions

Observed mentions: 2015-10-16 03:04:02 UTC to 2024-03-25 08:25:16 UTC.

0510152015-01-01 00:00:00 UTC to 2016-01-01 00:00:00 UTC (exclusive): 6 mentions; 0 submissions, 6 comments20152016-01-01 00:00:00 UTC to 2017-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2017-01-01 00:00:00 UTC to 2018-01-01 00:00:00 UTC (exclusive): 6 mentions; 1 submissions, 5 comments2018-01-01 00:00:00 UTC to 2019-01-01 00:00:00 UTC (exclusive): 17 mentions; 2 submissions, 15 comments2019-01-01 00:00:00 UTC to 2020-01-01 00:00:00 UTC (exclusive): 2 mentions; 1 submissions, 1 comments20192020-01-01 00:00:00 UTC to 2021-01-01 00:00:00 UTC (exclusive): 10 mentions; 1 submissions, 9 comments2021-01-01 00:00:00 UTC to 2022-01-01 00:00:00 UTC (exclusive): 6 mentions; 2 submissions, 4 comments2022-01-01 00:00:00 UTC to 2023-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments2023-01-01 00:00:00 UTC to 2024-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments2024-01-01 00:00:00 UTC to 2025-01-01 00:00:00 UTC (exclusive): 2 mentions; 1 submissions, 1 comments2024

Counts come from the repository’s dated source evidence. Index cutoff: 2026-11-02 08:16:59 UTC.

View mention counts and date ranges
UTC ranges include their start and exclude their end. Select a period to filter the source evidence.
PeriodStart (UTC)End (exclusive, UTC)MentionsSubmissionsComments
20152015-01-01 00:00:00 UTC2016-01-01 00:00:00 UTC606
20162016-01-01 00:00:00 UTC2017-01-01 00:00:00 UTC101
20172017-01-01 00:00:00 UTC2018-01-01 00:00:00 UTC615
20182018-01-01 00:00:00 UTC2019-01-01 00:00:00 UTC17215
20192019-01-01 00:00:00 UTC2020-01-01 00:00:00 UTC211
20202020-01-01 00:00:00 UTC2021-01-01 00:00:00 UTC1019
20212021-01-01 00:00:00 UTC2022-01-01 00:00:00 UTC624
20222022-01-01 00:00:00 UTC2023-01-01 00:00:00 UTC202
20232023-01-01 00:00:00 UTC2024-01-01 00:00:00 UTC202
20242024-01-01 00:00:00 UTC2025-01-01 00:00:00 UTC211

What Hacker News says

17 sources
Yes, the performance is by far one of the best parts of Lumen. Think of it this way. It generates exactly the code you write. It's nearly a one-to-one translation between the input forms and output code. But that's why it's fast. If you want to see just how fast, try `time LUMEN_HOST=luajit make -B` On my box, LuaJIT is able to recompile the entirety of Lumen and run all the tests in 1.3 seconds flat. As for Urn, I'd be happy to answer specific comparison questions. I've been putting together a little intro guide to Lumen, but for now the best way to jump in is to study test.l and learn by example: https://github.com/sctb/lumen/blob/master/test.l EDIT: By the way, Lumen runs on Torch too. If you want to do some hardcore machine learning, it's as easy as installing torch and running `LUMEN_HOST=th bin/lumen`. http://torch.ch/ Try evaluati
Read the rest
ng `(require 'torch)` and `(require 'nn)`, then dive into the Torch guides. You can bring Lumen's full macro system to bear on any ML problem, which greatly simplifies the boilerplate generation involved with standardizing datasets and such.
In Lumen: A Lisp for Lua and JavaScript
Right! I've been waiting a couple years for Lumen to get some traction. cracks knuckles Before I blather on about why it's worth paying attention to, why don't I just show you an implementation of the classic Trusting Trust paper: https://www.archive.ece.cmu.edu/~ganger/712.fall02/papers/p7... We'll implement this ourselves. Let's dive right in. Start off by cloning Lumen. $ git clone https://github.com/sctb/lumen $ cd lumen $ git checkout aedf2fd2c209bfc7926e0d4eae4becd8a647fb80 $ vim main.l (Commit aedf2fd is just the latest commit at the time of this writing. I make it explicit here so that anyone can follow along in the future.) Now that main.l is open, we begin by defining a global function `read-file` that simply returns a file as a string: (define-global read-file (path) ((get system 'read-file) path)) If you run `m
Read the rest
ake && bin/lumen`, you'll see the function is now defined when Lumen starts: > read-file function > (read-file "README.md") "Lumen\n=\nLumen is a very small, self-hosted Lisp for Lua and JavaScript. It provides a flexible compilation environment with an extensible reader, macros, and extensible special forms, but otherwise attempts..." Now we define `read-from-file`, which reads Lumen source code and returns it as a form that can be evaluated: (define-global read-from-file (path) (let (s ((get reader 'stream) (read-file path)) body ((get reader 'read-all) s)) `(do ,@body))) > (read-from-file "reader.l") ("do" ("define" "delimiters" ("set-of" "\"(\"" "\")\"" "\";\"" "\"\\r\"" "\"\\n\"")) ... Those are the forms defined in reader.l: https://github.com/sctb/lumen/blob/aedf2fd2c209bfc7926e0d4ea... Let's step through the forms and print them. > (step x (read-from-file "reader.l") (print (str x))) "do" ("define" "delimiters" ("set-of" "\"(\"" "\")\"" "\";\"" "\"\\r\"" "\"\\n\"")) ("define" "whitespace" ("set-of" "\" \"" "\"\\t\"" "\"\\r\"" "\"\\n\"")) ("define" "stream" ("str" "more") ("obj" more: "more" pos: 0 len: ("#" "str") string: "str")) ("define" "peek-char" ("s") ("let" ((pos: true len: true string: true) "s") ("when" ("<" "pos" "len") ("char" "string" "pos")))) ("define" "read-char" ("s") ("let" "c" ("peek-char" "s") ("if" "c" ("do" ("inc" ("get" "s" ("quote" "pos"))) "c")))) ... Pretty good! We're already doing some basic compiler-type stuff. That's Lumen's power: It's a flexible compiler. (IMO one of the finest in the world due to its simplicity.) Now, what can we do with these forms? Well, we can expand them: > (step x (expand (read-from-file "reader.l")) (print (str x))) "do" ("%local" "delimiters" ("%object" "\"(\"" true "\")\"" true "\";\"" true "\"\\n\"" true "\"\\r\"" true)) ("%local" "whitespace" ("%object" "\"\\r\"" true "\" \"" true "\"\\n\"" true "\"\\t\"" true)) ("%local-function" "stream" ("str" "more") ("return" ("%object" "\"more\"" "more" "\"pos\"" 0 "\"len\"" ("#" "str") "\"string\"" "str"))) ("%local-function" "peek-char" ("s") ("do" ("%local" "____id" "s") ("%local" "__pos" ("get" "____id" "\"pos\"")) ... We can compile them: > (print (compile (expand (read-from-file "reader.l")))) local delimiters = {["("] = true, [")"] = true, [";"] = true, ["\n"] = true, ["\r"] = true} local whitespace = {["\r"] = true, [" "] = true, ["\n"] = true, ["\t"] = true} local function stream(str, more) return {more = more, pos = 0, len = _35(str), string = str} end local function peek_char(s) local ____id9 = s local __pos6 = ____id9.pos local __len3 = ____id9.len local __string3 = ____id9.string if __pos6 < __len3 then return char(__string3, __pos6) end end local function read_char(s) local __c21 = peek_char(s) if __c21 then s.pos = s.pos + 1 return __c21 end end ... And we can switch languages: > (set target 'js) "js" > (print (compile (expand (read-from-file "reader.l")))) var delimiters = {"(": true, ")": true, ";": true, "\n": true, "\r": true}; var whitespace = {"\r": true, " ": true, "\n": true, "\t": true}; var stream = function (str, more) { return {more: more, pos: 0, len: _35(str), string: str}; }; var peek_char = function (s) { var ____id12 = s; var __pos8 = ____id12.pos; var __len4 = ____id12.len; var __string4 = ____id12.string; if (__pos8 < __len4) { return char(__string4, __pos8); } }; var read_char = function (s) { var __c28 = peek_char(s); if (__c28) { s.pos = s.pos + 1; return __c28; } }; ... Ok, on to the cool stuff. Make a file called lumen.l: (when-compiling `(do ,(read-from-file "runtime.l") ,(read-from-file "macros.l") ,(read-from-file "main.l"))) English translation: "When compiling, read the forms from runtime.l, macros.l, and main.l, join them together, then compile the result." Let's compile this file and see what happens: $ bin/lumen -c lumen.l environment = {{}} target = "lua" function nil63(x) return x == nil end function is63(x) return not nil63(x) end function no(x) return nil63(x) or x == false end function yes(x) return not no(x) end function either(x, y) if is63(x) then return x else return y end end ... Presto! We get bin/lumen.lua: https://github.com/sctb/lumen/blob/aedf2fd2c209bfc7926e0d4ea... Why does it generate Lua? Because on my system, Lumen happens to default to running on LuaJIT, and the host language is the default. If Lua wasn't installed on your system, you'd be seeing JS instead. To get a specific language, pass the `-t` parameter: $ bin/lumen -c lumen.l -t js environment = [{}]; target = "js"; nil63 = function (x) { return x === undefined || x === null; }; is63 = function (x) { return ! nil63(x); }; no = function (x) { return nil63(x) || x === false; }; yes = function (x) { return ! no(x); }; either = function (x, y) { if (is63(x)) { return x; } else { return y; } }; ... That gives us bin/lumen.js: https://github.com/sctb/lumen/blob/aedf2fd2c209bfc7926e0d4ea... Let's simplify the makefile. Open makefile in your edior and replace it with this: .PHONY: all clean test LUMEN_LUA ?= lua LUMEN_NODE ?= node LUMEN_HOST ?= $(LUMEN_LUA) LUMEN := LUMEN_HOST="$(LUMEN_HOST)" bin/lumen MODS := bin/lumen.x \ bin/reader.x \ bin/compiler.x \ bin/system.x all: $(MODS:.x=.js) $(MODS:.x=.lua) clean: @git checkout bin/*.js @git checkout bin/*.lua @rm -f obj/* bin/%.js : %.l @echo $@ @$(LUMEN) -c $< -o $@ -t js bin/%.lua : %.l @echo $@ @$(LUMEN) -c $< -o $@ -t lua test: all @echo js: @LUMEN_HOST=$(LUMEN_NODE) ./test.l @echo lua: @LUMEN_HOST=$(LUMEN_LUA) ./test.l Try it out: $ make -B test bin/lumen.js bin/reader.js bin/compiler.js bin/system.js bin/lumen.lua bin/reader.lua bin/compiler.lua bin/system.lua js: 647 passed, 0 failed lua: 647 passed, 0 failed Perfect. Our new lumen.l file fits into the compiler pipeline nicely. It may not seem like it, but we now have a tool of remarkable power. Let's see why. Open lumen.l back up. We recall it looks like this: (when-compiling `(do ,(read-from-file "runtime.l") ,(read-from-file "macros.l") ,(read-from-file "main.l"))) Let me show you what makes Lumen special. Change lumen.l to this: (define-global %lumen () (when-compiling `'(do ,(read-from-file "runtime.l") ,(read-from-file "macros.l") ,(read-from-file "main.l")))) (when-compiling `(do ,(read-from-file "runtime.l") ,(read-from-file "macros.l") ,(read-from-file "main.l"))) then compile: $ make Now change lumen.l to this: (define-global %lumen () (when-compiling `'(do ,(read-from-file "runtime.l") ,(read-from-file "macros.l") ,(read-from-file "main.l")))) (when-compiling (%lumen)) and compile again: $ make Here comes the surprise. Change lumen.l to this: (define-global %lumen () (when-compiling `',(%lumen))) (when-compiling (%lumen)) and compile: $ make Now delete the source code: $ rm runtime.l and compile: $ make -B test make -B test bin/lumen.js bin/reader.js bin/compiler.js bin/system.js bin/lumen.lua bin/reader.lua bin/compiler.lua bin/system.lua js: 647 passed, 0 failed lua: 647 passed, 0 failed What happened to the source code? It's gone! https://youtu.be/TGwZVGKG30s?t=25 Gone? What do you mean gone? I had perfectly fine source code and you mean to tell me it's gone?! Not anymore you don't. Poof. Yet lumen remains: $ make test js: 647 passed, 0 failed lua: 647 passed, 0 failed Last week I was trying to learn R, so I added a target for it: https://github.com/sctb/lumen/pull/193 Now I don't have to write R. Here's a (now very-outdated) branch that has full support for Python: https://github.com/shawwn/lumen/tree/features/python Now I don't have to write Python. This has been a short tour of why Lumen has fascinated me for the last three years, and hope you find its mysteries delightful. It is only through abstraction that we can bend computers to our will, and Lumen is a decisive step forward. Happy to answer any questions!
In Lumen: A Lisp for Lua and JavaScript
Bahaha, 3 of the 30 front page stories are about Lisp. Yessss, rise, Rise! Let the Lisp dominance commence! Take to the streets and take macros into your hearts! Demand a formal holiday to celebrate all conses! Tear off those strongly typed shackles and write a macro system to expand your grocery lists! https://github.com/sctb/lumen Today is a good day.
In Reazon – miniKanren for Emacs
shawn·Sep 11, 2018Recommendation phrase
Lots of Lisp interest today! Here are a few resources. On Lisp: https://www.lurklurk.org/onlisp/onlisp.pdf Lumen - a Lisp for JavaScript and Lua: https://github.com/sctb/lumen The Art of the Interpeter: https://dspace.mit.edu/bitstream/handle/1721.1/6094/AIM-453.... (^ this last one is very practical. If you reach the point of wanting to implement a practical interpreter, this paper will force you to consider many unexpected cases.) Simulating circuits with Lisp: https://mitpress.mit.edu/sites/default/files/sicp/full-text/... My favorite part of SICP: https://mitpress.mit.edu/sites/default/files/sicp/full-text/... War stories: http://paulgraham.com/avg.html Lisp in Jak 'n Dexter / Crash Bandicoot: https://all-things-andy-gavin.com/2011/03/12/making-crash-ba... Fun fact: Lisp is still used today in gamedev! Last of Us uses it heavily: http
Read the rest
In A micro manual for Lisp – Not the whole truth (1978) [pdf]
Emacs is a very important ecosystem that effectively uses macros. The danger of macro overcomplication is real, and emacs' conventions are the primary way they avoid introducing problems. For example, macros and functions are nearly the same thing: one runs at compile time, the other at runtime. But they both have documentation, and you can jump to a macro's definition the same way you would for a function. You can also compose them together and generate new ones on the fly, etc. Basically, a lot of the problems with macros in other languages is that macros are considered this special type of not-really-a-function. It's either templating sugarcoat, or restricted turing-noncomplete layer. But if you treat macros as literally functions that just happen to run at compile time, the problems are reduced. And if you treat them with discipline
Read the rest
and document what they do, and show examples, and add test cases, then you never have to worry -- just like normal software. One helpful rule of thumb: If you copy some functionality more than twice, it's probably time to make it a function. If you define a set of functions repeatedly -- like test cases -- or find yourself writing the same pattern over and over, or if you can generate most of your structure automatically based on e.g. "I know this is a React component", it might be time to write a macro. If this concept sounds intriguing to anyone, I highly encourage you to check out Lumen: https://github.com/sctb/lumen It's a Lisp that's literally javascript: Compiled code: https://github.com/sctb/lumen/blob/master/bin/reader.js Original code: https://github.com/sctb/lumen/blob/master/reader.l So if you understand Javascript, then there's not much to learn. And you can use it in your own projects right away. Most of Lumen's brevity is thanks to macros. (One hack for quickly understanding Lumen: read the tests. Every language feature is meticulously demo'd: https://github.com/sctb/lumen/blob/master/test.l)
In Stop Writing JavaScript Compilers. Make Macros Instead (2014)
It is decidedly not a language to get things done. I say this as someone who put himself in the unfortunate position of challenging BurntSushi (author of ripgrep) to a coding duel, bragging that I could get LuaJIT to match his XSV performance (https://github.com/BurntSushi/xsv) It took hours to figure out how to implement this in rust: https://github.com/sctb/lumen/blob/master/bin/lumen If you want to see an "expert" programmer's attempt at making sense of the language, here you go: https://gist.github.com/shawwn/4d7a89188cac72f3e591f47c84d3c... Now that it's done though, I see why people fall in love with Rust. It's really satisfying seeing Lumen's startup time drop from 200ms to 100ms. But yes, like -- dude, at one point I just wanted to write a for loop like this, to grab some environment vars: let mut lumen_host = "luajit"; l
Read the rest
et mut lua_path = ""; let mut node_path = ""; for (name, value) in env::vars() { if name == "LUMEN_HOST" { lumen_host = &value; } else if name == "LUA_PATH" { lua_path = &value; } else if name == "NODE_PATH" { node_path = &value; } } println!("{} {} {} {}", bin, lumen_host, lua_path, node_path); Good luck with that! I'm sure there's a way to coax Rust into please-allow-me-to-just-compile-this, but the borrow checker is a cruel taskmaster. Whatever the syntax is, that's not it, and spending literally an hour trying to figure out how to store an environment variable into a separate variable doesn't feel very productive. That said, Cargo is amazing. It's truly a wonderful, delightful piece of software. Cargo crates plus a dynamic language seems like a recipe for success.
In Why Rust closures are somewhat hard

Showing the 15 most recent of 17 sources. Enable JavaScript to search and browse every indexed mention.

MAKE YOURSELF AT HOME

A few shortcuts

/
Search the collection
?
Open this guide
Esc
Close dialogs and navigation

Your saved repositories and interests stay in this browser.