A tiny language that reads like English and compiles to Rust.
My goal with language is to create something simple enough to learn in a day, yet extensible enough to stay with you your entire life.
fn main
output
type: "ready"
payload:
service: "demo"
ok: true
Running this prints one line of JSON:
{"type":"ready","payload":{"service":"demo","ok":true}}One page. The whole language fits on this page.
One day. You can learn it in an afternoon.
One way. There is exactly one way to write each thing. No shortcuts, no alternate forms.
Fail first. If something is wrong, Moss tells you immediately and stops. One error at a time, in plain English.
Never make the writer feel dumb. Every error is phrased so an 8-year-old can read it once and know what to do.
# this is a comment
Just assign a name to a value. No keyword.
name = "ian"
count = 3
active = true
Assigning an existing name updates it:
count = count + 1
greeting = "hello, world"
Put values into strings with {name}:
message = "count is {count}"
You can put anything inside {}: numbers, booleans, other variables. Moss converts them for you.
port = 8080
ratio = 0.75
active = true
done = false
Short lists on one line:
tags = ["fast", "simple", "clear"]
Long lists as indented items:
steps =
- "connect"
- "authenticate"
- "listen"
Indented key-value pairs. No braces, no quoted keys.
config =
host: "localhost"
port: 8080
debug: true
Records can nest:
response =
type: "ready"
payload:
service: "demo"
version: "1.0"
If you need to pass a record to a function, give it a name first. That makes your code easier to read.
fn greet(name)
return "hello, {name}"
Functions without parameters skip the parentheses:
fn title
return "Moss Program"
fn answer
return 42
You can return early:
fn check(value)
if value == ""
return false
return true
fn label(code)
if code == 200
return "ok"
else if code == 404
return "missing"
else
return "unknown"
+ - * /
+ also joins strings. If either side is a string, Moss turns the other side into a string automatically:
message = "count: " + count
# if count is 3, message becomes "count: 3"
== != > >= < <=
Words, not symbols:
if ready and count > 0
output type: "go"
if not done
output type: "waiting"
Use parentheses when mixing and / or. Moss will ask you to if you don't.
Go through every item in a list:
for tag in tags
output tag
Count from one number to another:
for n in 1 to 10
output n
That's both kinds of loop in Moss. No other forms.
stop exits the loop early. skip jumps to the next item.
for code in codes
if code == 0
skip
if code == 999
stop
output code
Get an item by its position (starting at 1):
first = tags[1]
second = tags[2]
Count the items in a list:
count = length(tags)
That's it. If you need more, use a for loop.
Sends a value out as JSON.
fn main
output
type: "ready"
ok: true
Output:
{"type":"ready","ok":true}Prints plain text. For trying things out.
Hold on, that's a second way to output. Moss only has output. Use it with a string if you want to see something:
fn main
output "hello, world"
Output:
"hello, world"One output, one rule. Simpler.
Reads JSON piped in on stdin. Use dot access to reach into it.
fn main
output
message: "hello, {input.name}"
count: input.count
echo '{"name":"ada","count":3}' | moss run greet.moss{"message":"hello, ada","count":3}If nothing is piped in, input is null. Programs can be used standalone or as stages in a pipeline.
moss run source.moss | moss run transform.moss | moss run sink.mossMoss uses Rust underneath, but you don't need to know Rust to use Moss.
Useful commands:
moss run hello.mossRuns your program right now. Feels instant. Moss translates your code into Rust in the background, compiles it, and runs it. You never see the Rust.
moss build hello.mossCreates a real program file you can share with anyone. It runs on its own. They don't need Moss installed.
One-way relationship: Moss is the source, Rust is the output. You write Moss, Moss makes Rust, Rust becomes a program. You never write Rust yourself, and Rust code can't come back into Moss.
If you're curious, you can see the Rust Moss generated:
moss show-rust hello.mossThis is a great way to start learning Rust later, if you want. But you never need to.
To update a git-installed copy:
moss updateMoss errors look like a friend explaining what went wrong. No jargon. No codes. No symbols pointing at characters. Just: the filename, the line of code, and one sentence.
Mistyped a function name:
hello.moss, line 6
output greet("ian")
You're calling "greet" but there's no function named "greet" in this file.
Check the spelling, or add it above.
Used a variable before creating it:
hello.moss, line 4
message = "count: " + count
Moss doesn't know what "count" is yet.
Did you forget to create it above?
Two functions with the same name:
hello.moss, line 7
fn greet(name)
fn greet(other)
You have two functions called "greet".
Rename one, or delete the one you don't need.
Inconsistent indentation:
hello.moss, line 9
port: 8080
debug: true
The indentation changed partway through this block.
Line 8 uses 8 spaces, but line 9 uses 4 — pick one.
Ambiguous logic:
hello.moss, line 5
if ready and count > 0 or retry
Moss isn't sure which part to check first.
Try putting parentheses around the part you mean,
like: (ready and count > 0) or retry
No main function:
hello.moss
Every Moss program needs a function called "main".
Add one like this:
fn main
output "hello"
One error at a time. Fix it, run again. Moss never dumps a list of problems at you.
fn main
output "hello, world"
fn main
output
type: "ready"
ok: true
service = "demo"
fn main
output
type: "ready"
payload:
service: service
fn message(kind, data)
return
type: kind
payload: data
fn main
info =
service: "demo"
output message("ready", info)
fn respond(code)
if code == 200
output type: "success"
else if code == 404
output type: "not_found"
else
output
type: "error"
message: "unexpected status: {code}"
fn main
respond(200)
fn is_valid(name)
return not name == ""
fn greet(name)
if not is_valid(name)
return "name is required"
return "hello, {name}"
fn main
output greet("ian")
version = "1.0"
fn make_response(data)
return
type: "success"
version: version
payload: data
fn main
info =
service: "demo"
ready: true
output make_response(info)
fn main
tags = ["fast", "simple", "clear"]
output
type: "info"
tags: tags
count: length(tags)
fn main
tags = ["fast", "simple", "clear"]
for tag in tags
output
type: "tag_found"
name: tag
fn main
for n in 1 to 5
output
type: "tick"
number: n
fn main
codes = [200, 0, 404, 999, 500]
for code in codes
if code == 0
skip
if code == 999
stop
output
type: code
value: code
- Multiple files. For v1. Right now, one file per program.
- Custom error handling. For v1. Right now, if something breaks, the program stops.
- List helpers like
map,filter,first,last. For v1. In v0, use aforloop.
Moss has two jobs.
The first is Plexi apps. Small stateful tools: dashboards, forms, list managers, pipeline viewers, notification senders, agent frontends. The goal is to make Moss the simplest way to build one. The app SDK isn't here yet, but it's where Moss is headed. See the Plexi SDK roadmap.
The second is pipeline programs. Take input, make a decision, produce structured output. The thing that sits in the middle of a shell pipeline.
Moss isn't built for everything. It's not for games, custom canvas rendering, low-latency audio or video, or raw terminal control. It's not a systems language, it has no classes, and it won't replace your shell.
curl -fsSL https://raw.githubusercontent.com/ianjamesburke/moss/main/install.sh | bashThen:
moss run hello.mossThe installer clones Moss to ~/.moss/src and links moss into ~/.local/bin.
It checks for Python 3 and Rust before installing.
Homebrew packaging is not wired up in this repo yet.
Moss compiles to readable Rust using serde_json. The compiler is the Python script at compiler/moss.py; generated programs build against the Rust runtime template under runtime/.
When you write:
output
type: "ready"
ok: true
Moss generates:
use serde_json::json;
fn main() {
let value = json!({
"type": "ready",
"ok": true
});
println!("{}", value);
}Run that Rust and you get:
{"type":"ready","ok":true}You never see this Rust unless you ask. It's there if you want it: readable, ordinary, and a decent way to start learning Rust later.
