GitHub - m-o/MoonShot: Programming language implemented by Claude Code

9 min read Original article ↗

A statically-typed, expression-oriented programming language with immutable-by-default semantics.

DISCLAIMER - A PLAN SO CRAZY IT JUST MIGHT WORK:

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

WTF:

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

Building

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!")'

Language Features

Variables

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"

Mutable Variables

Use Mutable[T] for mutable state and == to update:

def counter = Mutable[Integer](0)
counter == counter + 1
counter == counter + 1
println(counter)  // 2

Data Types

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

Operators

// 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!"

Functions

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!

Lambdas

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]

Control Flow

If/Else

def age = 20

if age >= 18 {
    println("Adult")
} else {
    println("Minor")
}

// If as expression
def status = if age >= 18 { "adult" } else { "minor" }

While Loop

def i = Mutable[Integer](0)
while i < 5 {
    println(i)
    i == i + 1
}

For Loop

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
}

Break and Continue

for i in range(10) {
    if i is 3 {
        continue  // Skip 3
    }
    if i is 7 {
        break     // Stop at 7
    }
    println(i)
}

Lists

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

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

Structs

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}

Extension Methods

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

Option Type

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

Result Type

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 })

Pattern Matching

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) }
}

Comments

// This is a single-line comment

def x = 5  // Inline comment

Built-in Functions

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

String Methods

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!"]

Modules

Import other MoonShot files:

// utils.moon
fun helper() -> String {
    return "I'm helping!"
}

// main.moon
import utils
println(utils.helper())

Complete Examples

Fibonacci

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)))
}

FizzBuzz

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)
            }
        }
    }
}

User Management

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) }
}

Data Processing

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))

Architecture

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

Error Messages

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

File Extension

MoonShot source files use the .moon extension.