A statically-typed, expression-oriented programming language with immutable-by-default semantics.
THIS IS THE ONLY TEXT I WROTE MYSELF, ALL THE CODE AND TEXT IS WRITTEN BY CLAUDE WITH MY INPUT. I KNOW BASICALLY NOTHING ABOUT GO, AND NOTHING ABOUT IMPLEMENTING PROGRAMMING LANGUAGES. I HAD WRITTEN NO ACTUAL CODE IN THIS LANGUAGE, BECAUSE I DIDNT YET HAVE A TIME. USE AT YOUR OWN RISK
I was bored at work and started wondering if Claude could actually create and implement a programming language. Turns out, yeah. We did that. In like an hour. I'm an Android developer who mostly writes Kotlin and Java. I've read some Ruby, Python, Clojure—enough to know what I like and what pisses me off. So I just started complaining to Claude about languages. "Why does everything have null?" "Why is mutation hidden?" "Why do errors suck at telling you what went wrong?" And we started fixing it. Just messing around at first. Then it turned into a whole thing. Now we have MoonShot. No null values—you use Option[T] instead. Mutation is a type wrapper Mutable[T] so you can see exactly what can change. Errors automatically give you the method name, the input data, and what broke. It chains Result types so if anything fails, it stops and tells you exactly where. We designed the whole spec together, then I threw it at Claude Code. An hour later, boom—working interpreter. In Go, which I still can't write. The whole thing is on GitHub: https://github.com/m-o/MoonShot
Requires Go 1.21 or later.
# Build the interpreter
go build -o moonshot .
# Run a program
./moonshot examples/hello.moon
# Evaluate an expression directly
./moonshot -e 'println("Hello, World!")'Variables are immutable by default. Use def to declare:
def name = "Alice"
def age = 30
def pi = 3.14159
def active = true
Optional type annotations:
def count: Integer = 0
def message: String = "Hello"
Use Mutable[T] for mutable state and == to update:
def counter = Mutable[Integer](0)
counter == counter + 1
counter == counter + 1
println(counter) // 2
| Type | Example | Description |
|---|---|---|
Integer |
42, -17 |
64-bit signed integer |
Float |
3.14, -0.5 |
64-bit floating point |
String |
"hello" |
UTF-8 string |
Boolean |
true, false |
Boolean value |
List[T] |
[1, 2, 3] |
Immutable list |
Map[K, V] |
{"key": "value"} |
Immutable map |
Option[T] |
Some(x), None |
Optional value |
Result[T, E] |
Ok(x), Error(e) |
Success or error |
Mutable[T] |
Mutable[Integer](0) |
Mutable wrapper |
// Arithmetic
def sum = 10 + 5 // 15
def diff = 10 - 5 // 5
def prod = 10 * 5 // 50
def quot = 10 / 5 // 2
def rem = 10 % 3 // 1
// Comparison
def gt = 10 > 5 // true
def lt = 10 < 5 // false
def gte = 10 >= 10 // true
def lte = 5 <= 10 // true
// Equality (use 'is')
def eq = 10 is 10 // true
// Logical
def both = true and false // false
def either = true or false // true
def negated = not true // false
// String concatenation
def greeting = "Hello, " + "World!"
fun add(a: Integer, b: Integer) -> Integer {
return a + b
}
fun greet(name: String) -> String {
return "Hello, " + name + "!"
}
println(add(5, 3)) // 8
println(greet("Alice")) // Hello, Alice!
Anonymous functions with concise syntax:
// Single parameter
def double = { x -> x * 2 }
println(double(5)) // 10
// Multiple parameters
def add = { a, b -> a + b }
println(add(3, 4)) // 7
// Used with higher-order functions
def numbers = [1, 2, 3, 4, 5]
def doubled = numbers.map({ x -> x * 2 })
println(doubled) // [2, 4, 6, 8, 10]
def age = 20
if age >= 18 {
println("Adult")
} else {
println("Minor")
}
// If as expression
def status = if age >= 18 { "adult" } else { "minor" }
def i = Mutable[Integer](0)
while i < 5 {
println(i)
i == i + 1
}
for item in [1, 2, 3, 4, 5] {
println(item)
}
// Using range
for i in range(5) {
println(i) // 0, 1, 2, 3, 4
}
for i in range(2, 5) {
println(i) // 2, 3, 4
}
for i in range(10) {
if i is 3 {
continue // Skip 3
}
if i is 7 {
break // Stop at 7
}
println(i)
}
Lists are immutable. Operations return new lists.
def numbers = [1, 2, 3, 4, 5]
// Access by index
println(numbers[0]) // 1
// Methods
println(numbers.length()) // 5
println(numbers.append(6)) // [1, 2, 3, 4, 5, 6]
println(numbers.contains(3)) // true
// Higher-order functions
def doubled = numbers.map({ x -> x * 2 })
println(doubled) // [2, 4, 6, 8, 10]
def evens = numbers.filter({ x -> x % 2 is 0 })
println(evens) // [2, 4]
def sum = numbers.reduce({ acc, x -> acc + x }, 0)
println(sum) // 15
def found = numbers.find({ x -> x > 3 })
// found is Some(4)
Maps are immutable with string keys.
def person = {"name": "Alice", "city": "Paris"}
// Access (returns Option)
match person.get("name") {
Some(name) -> { println(name) }
None -> { println("Not found") }
}
// Methods
def updated = person.insert("age", "30")
println(updated) // {"age": 30, "city": Paris, "name": Alice}
def removed = person.remove("city")
println(person.keys()) // ["city", "name"]
println(person.values()) // [Paris, Alice]
println(person.contains("name")) // true
Define custom data types:
struct User {
name: String,
age: Integer
}
// Create instance
def alice = User { name: "Alice", age: 30 }
// Access fields
println(alice.name) // Alice
println(alice.age) // 30
// Update with .with (returns new struct)
def older = alice.with { age: 31 }
println(older) // User{age: 31, name: Alice}
Add methods to existing types:
struct User {
name: String,
age: Integer
}
extend User {
fun isAdult() -> Boolean {
return this.age >= 18
}
fun greet() -> String {
return "Hello, I'm " + this.name
}
}
def alice = User { name: "Alice", age: 30 }
println(alice.isAdult()) // true
println(alice.greet()) // Hello, I'm Alice
Represents optional values safely:
fun findUser(id: Integer) -> Option[String] {
if id is 1 {
return Some("Alice")
}
return None
}
def result = findUser(1)
match result {
Some(name) -> { println("Found: " + name) }
None -> { println("Not found") }
}
// Methods
println(result.isSome()) // true
println(result.isNone()) // false
println(result.unwrapOr("Unknown")) // Alice
Handle errors explicitly:
fun divide(a: Integer, b: Integer) -> Result[Integer, String] {
if b is 0 {
return Error("Division by zero")
}
return Ok(a / b)
}
def result = divide(10, 2)
match result {
Ok(value) -> { println("Result: " + str(value)) }
Error(msg) -> { println("Error: " + msg) }
}
// Chaining with .then and .map
def chained = divide(10, 2)
.then({ x -> divide(x, 2) })
.map({ x -> x * 10 })
Match on Option and Result types:
def value = Some(42)
match value {
Some(x) -> { println("Got: " + str(x)) }
None -> { println("Nothing") }
}
def result = Ok(100)
match result {
Ok(x) -> { println("Success: " + str(x)) }
Error(e) -> { println("Failed: " + e) }
}
// This is a single-line comment
def x = 5 // Inline comment
| Function | Description |
|---|---|
print(args...) |
Print without newline |
println(args...) |
Print with newline |
range(end) |
Generate list [0, 1, ..., end-1] |
range(start, end) |
Generate list [start, ..., end-1] |
len(x) |
Length of string, list, or map |
type(x) |
Get type name as string |
str(x) |
Convert to string |
int(x) |
Convert to integer |
float(x) |
Convert to float |
def s = "Hello, World!"
println(s.length()) // 13
println(s.upper()) // HELLO, WORLD!
println(s.lower()) // hello, world!
println(s.trim()) // Removes whitespace
println(s.contains("World")) // true
println(s.split(", ")) // ["Hello", "World!"]
Import other MoonShot files:
// utils.moon
fun helper() -> String {
return "I'm helping!"
}
// main.moon
import utils
println(utils.helper())
fun fibonacci(n: Integer) -> Integer {
if n <= 1 {
return n
}
return fibonacci(n - 1) + fibonacci(n - 2)
}
for i in range(10) {
println("fib(" + str(i) + ") = " + str(fibonacci(i)))
}
for i in range(1, 101) {
if i % 15 is 0 {
println("FizzBuzz")
} else {
if i % 3 is 0 {
println("Fizz")
} else {
if i % 5 is 0 {
println("Buzz")
} else {
println(i)
}
}
}
}
struct User {
id: Integer,
name: String,
email: String
}
extend User {
fun validate() -> Result[User, String] {
if this.name.length() is 0 {
return Error("Name cannot be empty")
}
if not this.email.contains("@") {
return Error("Invalid email")
}
return Ok(this)
}
}
def user = User { id: 1, name: "Alice", email: "alice@example.com" }
match user.validate() {
Ok(u) -> { println("Valid user: " + u.name) }
Error(msg) -> { println("Validation failed: " + msg) }
}
def data = [
{"name": "Alice", "score": "95"},
{"name": "Bob", "score": "87"},
{"name": "Charlie", "score": "92"}
]
// Calculate average score
def scores = data.map({ item ->
match item.get("score") {
Some(s) -> { int(s) }
None -> { 0 }
}
})
def total = scores.reduce({ acc, x -> acc + x }, 0)
def average = total / scores.length()
println("Average score: " + str(average))
// Find top scorer
def topScore = scores.reduce({ a, b -> if a > b { a } else { b } }, 0)
println("Top score: " + str(topScore))
Source (.moon) -> Lexer -> Parser -> Type Checker -> Evaluator -> Output
| File | Purpose |
|---|---|
token.go |
Token types and keywords |
lexer.go |
Tokenization |
ast.go |
AST node definitions |
parser.go |
Recursive descent parser with Pratt precedence |
types.go |
Type system |
checker.go |
Static type checking |
value.go |
Runtime values |
environment.go |
Variable scopes |
eval.go |
Tree-walking interpreter |
builtins.go |
Built-in functions and methods |
module.go |
Module loader |
errors.go |
Error handling |
main.go |
CLI entry point |
MoonShot provides helpful error messages:
Error in divide
Input:
Reason: Division by zero
Type errors are caught before execution:
Type error: cannot assign String to variable of type Integer
MoonShot source files use the .moon extension.