Quickstart#
A tour of Marslang in about twenty minutes: install it, run a program, and meet values, functions, loops, collections, families, errors, and packages. Every example below runs as shown, and the output under it is what it prints.
Install#
irm https://marslang.kevin-z.com/install.ps1 | iex
curl -fsSL https://marslang.kevin-z.com/install.sh | sh
Open a new terminal and check it worked:
marslang --version
For editing, install the Marslang extension
for Visual Studio Code: it highlights .mars files and marks errors as you save.
Hello, world#
Save this as hello.mars:
func m{
out("hello, world");
}
marslang hello.mars
hello, world
A program starts at the function m. out prints its arguments separated by
spaces, then a new line; slout prints without the new line. Statements end
with ;, and blocks are wrapped in { }.
Values and variables#
func m{
name = "Mars";
moons = 2;
gravity = 3.72;
out(name, "has", moons, "moons and gravity", gravity);
moons = moons + 1;
out(moons);
year (int) = 687;
fixed radius = 3389.5;
out(year, radius);
}
Mars has 2 moons and gravity 3.72
3
687 3389.5
name = value; creates a variable, or updates one that already exists. A type
in parentheses, year (int) = 687;, is checked every time the variable is
assigned, and fixed stops it from being assigned again. Without a type, a
variable can hold anything.
Numbers come in three kinds: int (whole, 32-bit), longint (whole, 64-bit),
and float. Whole-number arithmetic never overflows silently:
func m{
out(7 / 2, 7 % 2, 2 ** 10);
big (int) = 2147483647;
out(big + 1);
}
3.5 1 1024
error: RangeError: int overflow
at m (main.mars:4)
A program that fails prints error:, the kind of error, and a message, then
where it happened: the function, file, and line, through every call that led
there. Then it stops.
Functions#
func add(int a, int b) => a + b;
func describe(string planet, int moons){
if (moons == 0){
ret planet + " has no moons";
}
ret planet + " has " + string(moons) + " moons";
}
func m{
out(add(2, 3));
out(describe("Venus", 0));
out(describe("Mars", 2));
}
5
Venus has no moons
Mars has 2 moons
Parameters are written type name. A function whose body is one expression uses
=>; a longer one uses a block and ret to return a value. + joins strings,
and string(...) turns a number into one.
Decisions and loops#
func m{
for (i = 1, i <= 15, i++){
if (i % 15 == 0){
out("FizzBuzz");
} elif (i % 3 == 0){
out("Fizz");
} elif (i % 5 == 0){
out("Buzz");
} else {
slout(i, " ");
}
}
}
1 2 Fizz
4 Buzz
Fizz
7 8 Fizz
Buzz
11 Fizz
13 14 FizzBuzz
The other loops:
func m{
n = 3;
while (n > 0){
slout(n, " ");
n = n - 1;
}
out("liftoff");
repeat 3 {
slout("*");
}
out("");
}
3 2 1 liftoff
***
break; leaves a loop and continue; skips to its next round. Conditions treat
false, null, and zero as false and everything else as true.
Collections#
func m{
planets = arr("Mercury", "Venus", "Earth");
planets.add("Mars");
out(planets.len(), planets.iget(3));
for (planet, planets){
slout(planet.len(), " ");
}
out("");
moons = map();
moons.set("Earth", 1);
moons.set("Mars", 2);
for (planet, moons){
out(planet, moons.get(planet));
}
out(moons.get("Venus"));
}
4 Mars
7 5 5 4
Earth 1
Mars 2
null
arr makes an array, and iget(i) reads position i, counting from 0. map()
makes a map from keys to values, and looping over it visits the keys in the
order they were added. There are also set(...) and pair(a, b). Types can
describe what a collection holds: scores (map[string,int]) = map();.
Families#
A family is Marslang's class: it bundles data with the functions, called methods, that work on it.
family Account{
func init(string owner){
me.owner = owner;
me.balance = 0;
}
func deposit(int amount){
me.balance = me.balance + amount;
ret me;
}
func describe() => me.owner + ": " + string(me.balance);
}
family Savings(Account){
func add_interest(){
me.deposit(me.balance / 10);
}
}
func m{
acct = Account("Ada");
acct.deposit(50).deposit(25);
out(acct.describe());
savings = Savings("Grace");
savings.deposit(200);
savings.add_interest();
out(savings.describe());
}
Ada: 75
Grace: 220
Calling a family's name makes an instance and runs its init. Inside a method,
me is that instance. family Savings(Account) inherits every method of
Account. To hide a helper method from outside code, see
private methods.
Errors#
takepkg std.Error;
func withdraw(int balance, int amount){
if (amount > balance){
err(Error.RangeError, "not enough money");
}
ret balance - amount;
}
func m{
run{
out(withdraw(100, 30));
out(withdraw(100, 300));
out("not reached");
} handle(Error.RangeError e){
out("refused:", e.message);
} then{
out("done");
}
}
70
refused: not enough money
done
err(Family, message) raises an error. run watches a block, the first
handle that matches the error runs, and then always runs at the end. The
errors the interpreter raises, like division by zero, are caught the same way.
You can declare your own: family NotFound(Error.Base){}. See
std.Error.
Packages#
The standard library comes with the interpreter:
takepkg std.math;
takepkg std.strings;
func m{
out(math.sqrt(2.0));
out(math.gcd(12, 18));
out(strings.upper("mars"));
}
1.4142135623730951
6
MARS
Your own files are packages too. With geometry.mars next to your program:
fixed PI_ISH (float) = 3.14;
func area(float r) => PI_ISH * r * r;
import it by its name:
takepkg geometry;
func m{
out(geometry.area(2.0));
}
It prints 12.56. Folders work like Python's: takepkg shapes.circle; loads
shapes/circle.mars, a folder with an init.mars is a package, and names
starting with _ stay private to their file. See
packages.
Where next#
- The language reference covers everything above in detail.
- Strings count characters the way people do:
"๐จโ๐ฉโ๐งโ๐ฆ".len()is 1. - The standard library documents each
stdpackage. - The changelog lists what each release added.