Skip to content

Dere752/lumen-lang

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lumen

A small, dynamically-typed programming language with a tree-walking interpreter,
written from scratch in JavaScript — zero dependencies.

CI


What is Lumen?

Lumen is a complete little programming language I built to understand how programming languages actually work — from raw source text all the way to execution. It implements the classic three-stage pipeline:

source code ──▶  Lexer  ──▶  tokens  ──▶  Parser  ──▶  AST  ──▶  Interpreter  ──▶  result

No parser generators, no libraries — every stage is hand-written and unit-tested.

A taste

# Recursive Fibonacci
fn fib(n) {
  if (n < 2) { return n; }
  return fib(n - 1) + fib(n - 2);
}

let i = 0;
while (i <= 10) {
  print("fib(" + str(i) + ") = " + str(fib(i)));
  i = i + 1;
}
$ lumen examples/fib.lum
fib(0) = 0
fib(1) = 1
...
fib(10) = 55

Features

  • Values: numbers, strings, booleans, null
  • Variables: let x = 5; with lexical scoping
  • Operators: + - * / %, comparisons, and / or (short-circuit), !
  • Control flow: if / else if / else, while
  • Functions: first-class, recursive, with closures that capture their environment
  • Built-ins: print, len, str, num, type, clock
  • Comments: # like this
  • An interactive REPL and a file runner
  • Clear lexer / parser / runtime error messages with line numbers

How it works

Stage File Responsibility
Lexer src/lexer.js Scans characters into tokens (numbers, strings, identifiers, operators)
Parser src/parser.js Recursive-descent parser that builds an AST and enforces operator precedence
Environment src/environment.js Lexical scopes — the mechanism behind closures
Interpreter src/interpreter.js Tree-walking evaluator: executes the AST, manages scope and return

The parser climbs precedence levels (assignment → or → and → equality → comparison → term → factor → unary → call → primary), and closures work by having each function keep a reference to the environment it was defined in.

Getting started

git clone https://github.com/Dere752/lumen-lang.git
cd lumen-lang

# run a program
node src/index.js examples/fizzbuzz.lum

# start the REPL
node src/index.js

Language tour

# variables and arithmetic
let name = "Lumen";
let answer = 6 * 7;

# functions are values; closures capture state
fn makeCounter() {
  let count = 0;
  return fn() { count = count + 1; return count; };
}
let next = makeCounter();
print(next());   # 1
print(next());   # 2

# control flow
let n = 15;
if (n % 15 == 0) { print("FizzBuzz"); }
else if (n % 3 == 0) { print("Fizz"); }
else { print(str(n)); }

More in examples/: fib.lum, fizzbuzz.lum, closures.lum.

Testing

Unit tests run on Node's built-in test runner (zero dependencies):

npm test

They cover the lexer, parser and interpreter — operator precedence, control flow, recursion, closures, built-ins, and both runtime and parse errors. CI runs the suite on Node 20 & 22 via GitHub Actions.

Project structure

src/
  token.js        token types & keywords
  lexer.js        source text  -> tokens
  parser.js       tokens       -> AST
  environment.js  lexical scopes (closures)
  interpreter.js  AST          -> execution
  run.js          lex + parse + interpret a string
  index.js        CLI: REPL and file runner
examples/         sample Lumen programs
test/             unit tests

What I learned

Building Lumen taught me how a language is layered: tokenizing, parsing with correct precedence and associativity, representing programs as trees, and evaluating them with proper scoping. Implementing closures and return (via stack unwinding) made abstract concepts from compiler theory concrete.

License

MIT © Ali Dere

About

A small dynamically-typed programming language with a tree-walking interpreter, written from scratch in JavaScript (zero dependencies).

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors