Rake is a pragmatic, statically-typed programming language designed for readability, safety, and developer joy. It combines Python-style indentation with Rust-inspired type safety and Go-like batteries-included tooling.
println("Hello, Rake!")
fun fib(n: Int) -> Int
match n
0 => 0
1 => 1
_ => fib(n - 1) + fib(n - 2)
for i in 0..11
println("fib({i}) = {fib(i)}")
- Indentation syntax — Significant indentation, no braces, no semicolons
- Static typing — Type inference with optional type annotations
- Immutable by default —
name = valfor bindings,mut name = valfor mutable variables - Expression-oriented —
ifis an expression, pattern matching withmatch - Pipeline operator —
expr |> functionchains transformations - First-class functions — Functions as values, closures with captures
- Algebraic data types — Structs with named fields
- Built-in collections — Arrays, Maps with method syntax
- Rich expression syntax — Arithmetic, comparison, logical, range operators
- Batteries included —
println, string/array/map methods, JSON, File I/O, DateTime - Type checked — Static type checker catches errors before runtime
cargo build --release
./target/release/rake examples/hello.rakeOr use the REPL:
cargo runname: Int = 42 # immutable, with type annotation
name = "hello" # immutable, type inferred, rebindable
mut count = 0 # mutable variable
count := count + 1 # reassignment (colon-eq)fun add(a: Int, b: Int) -> Int
a + b
result = add(3, 4) # => 7
# Pipeline
result = 3 |> add(4) # => 7
# Closures
fun make_counter()
mut count = 0
fun inner()
count := count + 1
count
inner
counter = make_counter()
println(counter()) # => 1# If is an expression
x = 10
msg = if x > 5
"big"
else
if x > 0
"positive"
else
"small or negative"
# While loop
mut i = 0
while i < 5
println(i)
i := i + 1
# For loop over range
for item in 0..5
println(item)fun describe(n: Int) -> String
match n
0 => "none"
1 => "one"
_ => "many"struct Point
x: Int
y: Int
p = Point { x: 10, y: 20 }
println(p.x, p.y)arr = [1, 2, 3, 4, 5]
println(arr[0]) # => 1
println(arr.len()) # => 5
println(arr.push(6)) # => [1, 2, 3, 4, 5, 6]
map = map_new()
m = map.set("key", "value")
println(m.get("key")) # => "value"- Lexer with indentation-based syntax
- Recursive descent Pratt parser
- AST definitions
- Tree-walk interpreter
- Static type checker
- Built-in functions (print, println, len, int, float, string)
- Variables and assignment
- Functions with parameters and return types
- If/else control flow
- While and for loops
- Structs with field access
- Arrays with indexing
- Closures with captures
- REPL for interactive use
- Range literals (
0..10) - Multi-line strings (
"""syntax) - String interpolation (
"Hello {name}") - Pattern matching (
matchexpressions) - Error handling
- Modules and imports
- Enum types (sum types)
- Type aliases
- Redesigned syntax:
fun,mut,:=,|>,ifas expression - Method call syntax (
obj.method(args)) - Map type (
map_new,map.set,map.get, etc.) - JSON parsing and stringification
- File I/O (
read_file,write_file,file_exists) - DateTime (
now()) - String methods (split, trim, contains, etc.)
- Array methods (push, pop, contains, etc.)
- Error handling
- Modules and imports
- Formatter (
rake fmt) - Linter
- Language Server Protocol (LSP)
- Package manager integration
- Error messages with spans and suggestions
- Debugger support
- Bytecode compiler and VM
- LLVM backend for native compilation
- WASM compilation target
- JIT compilation
- AOT compilation with optimization
- Algebraic effects for error handling and async
- Compile-time execution (Zig-style comptime)
- Borrow checker for memory safety without GC
- Optional GC for rapid prototyping
- CSP-style concurrency (Go-style goroutines)
- Interop with C/Rust libraries
- Gradual typing modes
Built with Rust (edition 2021) from scratch by a team of one — DeepSeek V4 Flash Free.
src/
├── main.rs # CLI entry point, REPL, file runner
├── token.rs # Token types and definitions
├── lexer.rs # Lexer / tokenizer with indent handling
├── ast.rs # Abstract Syntax Tree definitions
├── parser.rs # Recursive descent Pratt parser
├── types.rs # Type system and type environment
├── checker.rs # Static type checker
└── eval.rs # Tree-walk interpreter
cargo build
cargo run examples/hello.rake
cargo run # REPL modeRake was born in a single intense development session. Here's how it happened:
Phase 1 — Design (conversation with the human) The human asked what kind of language I would design. I described "Rake" — a pragmatic language combining the best ideas from Python (indentation syntax, readability), Rust (safety, immutability by default), and Go (simplicity, batteries-included tooling). The human gave the green light: "开始吧" (let's begin).
Phase 2 — Foundation (files 1-4)
- Setup Rust project with Cargo
- Defined the token system — all keywords, operators, literals, and indentation tokens
- Built the lexer — a character-by-character tokenizer that handles Python-style significant indentation (INDENT/DEDENT tokens), string escapes, comments, and CRLF line endings
Phase 3 — Parsing (files 5-6)
- Defined the full AST — expressions, statements, types
- Built a recursive descent Pratt parser — handles operator precedence, function calls, field access, array indexing, struct literals
- The biggest challenge: getting Pratt parsing right for postfix operators (function calls, field access). Fixed a subtle bug where
prec < min_preccaused RParen to be treated as an infix operator
Phase 4 — Semantics (files 7-9)
- Type system with Int, Float, String, Bool, Nil, Fn, Struct, Array, Any types
- Static type checker that validates assignments, function calls, condition types
- Tree-walk interpreter supporting all language features
- Built-in functions (print, println, len, int, float, string)
Phase 5 — Testing & Polish
- Wrote comprehensive example programs (hello.rake, fib.rake)
- Fixed struct literal parsing (handled
Ident { ... }in prefix parser) - Fixed assignment parsing (handled
ident = exprat statement level) - Cleaned up debug output, added .gitignore
- Committed and published to GitHub
Range Literals
- Added
DotDottoken (..) andExpr::Rangein the AST - Lexer recognizes
..asDotDot(two consecutive dots, not a float) - Parser handles
..at precedence level 4 with left-associative binding - Evaluator produces
Value::Arrayfrom start..end (end-exclusive) for i in 0..10works automatically since for-in accepts arrays
Multi-line Strings
- Lexer detects
"""at string start and enters multi-line mode - Reads until closing
""", preserving newlines and indentation - Embedded
"or""inside are literal unless followed by"completing the triple
String Interpolation
- Parser-level desugaring of
{name}and{name.field}inside strings - Converts
"Hello {name}"into"Hello " + name(a Binary(Add) chain) - Supports
{identifier}and{obj.field}patterns - Evaluator auto-converts non-string operands to string in
+operations - Full expression interpolation (
{call()}) deferred to future v0.2.x
Pattern Matching
match exprwith indented arm blocks- Supports literal patterns (Int, String, Bool, Nil), wildcard (
_), and binding patterns - Binding patterns introduce variables in the arm scope
- Non-exhaustive matches produce a runtime error
- Type checker validates all arms return consistent types
Syntax Redesign
- Replaced
fnwithfun, removedlet/varkeywords name = exprcreates immutable binding;mut name = exprcreates mutable binding:=(colon-eq) for reassignment (instead of=)ifis now an expression (then/else both required, returns value)else ifhandled via nestedifin else block;elifkeyword removed- Added
|>pipe-right operator at lowest precedence (expr |> fun) matchkeyword preserved with cleaner arm syntax
Standard Library — Method Calls
- Added
Expr::MethodCall—obj.method(args)parsing with.precedence - String methods:
len,trim,split,contains,starts_with,ends_with,to_upper,to_lower,is_empty - Array methods:
push,pop,len,contains,is_empty - Map methods:
get,set,remove,keys,values,len,contains,is_empty
Standard Library — Builtins
map_new()— create empty mapread_file(path)— read file contents as stringwrite_file(path, content)— write string to filefile_exists(path)— check file existencejson_parse(string)— parse JSON into Valuejson_stringify(value)— serialize Value to JSON stringnow()— current date/time as DateTime struct
Notable Bugfix
- Fixed
parse_match_exprfailing on blank lines after match body, which caused recursive functions withmatchinsideforloops to be undefined at runtime
- ~3500 lines of Rust
- 8 source modules
- v0.3: ~1100 new lines
MIT
Built with DeepSeek V4 Flash Free — an AI language model that can design, implement, and ship a programming language in a single conversation.