Moose is a new programming language. This repo also includes an interpreter written in Swift.
Note: We only support macOS, iOS and Linux. On Windows you can use WSL.
You need to install swift, after which you can run:
swift run Moose
Or if you want to have an optimized build you can build and run with:
swift build -c release
./.build/release/Moose
The interpreter also accepts a single file:
./.build/release/Moose Examples/ticktacktoe.moose
At the moment we have already implemented all features we wanted. However, please consider that this was mainly a learning experience. We might continue working on it and playing with things that are interesting to us.
The rest of this file is and introduction to the language which tries to introduce the core ideas.
Furthermore, there are some examples in the Examples folder and there is a full documentation of the quite small standard library in Stdlib.md
There are only 4 built in types in Moose: Int, Float, String, Bool
Moose provides two types of variables: immutable and mutable variables.
Mutable variables are variables that can be reassigned over and over again. To declare a mutable variable, the keyword mut
must be used.
mut a = 2
a = 4
a +: 1
mut b: Int
b = 2
While mutable variables need to be declared using the mut
keyword, immutable variables donβt have such a keyword. They also have to be assigned directly on the declaration, since the declaration without explicit initialisation is initialised with nil
.
a = 3;
a = 2; // Error, cannot reassign immutable variable
b: Int;
b; // nil
b = 3; // Error, cannot reassign immutable variable
b: Int = 3;
b; // 3
Every object can be nil
, and since even basic types are objects they can be nil
too.
cookie: Int = nil; // Basic types can be nil
strudel: Int; // Implizitly without assigning a value a variable is automatically nil.
To make working with null more ergonomic, we introduced the double questionmark operator ??
breakfast: String = nil;
println(breakfast ?? "Melage and semmel");
// This code is just syntactic sugar and is equivalent to
println(breakfast != nil ? breakfast : "Melage and semmel");
Lists are arrays that can grow and shrink dynamically. Moreover there is a special syntax to create them, with square brackets. Lists also only contain objects of a single type.
wishlist: [String] = ["Computer", "Bicycle", "Teddybear"];
println(wishlist[0]); // "Computer"
println(wishlist[-1]); // "Teddybear"
wishlist.append("car");
wishlist.append(["Aircraftcarrier", "Worlddomination"]);
Dictionaries (aka Hashtables or Hashmaps) are also build in.
ages: {String:Int} = {
"Flo": 23,
"Paul": 24,
"Clemens": 7,
}
println(ages["Paul"]) // 24
println(ages.contains("Paul")) // true
ages.remove("Paul")
println(ages.contains("Paul")) // false
Like go and rust we donβt require Parenteses around the condition but do require braces around the body.
age = 23
if age > 18 {
println("Please enter")
} else if age == 18 {
println("Finally you can enter")
} else {
println("I am sorry come back in a year")
}
// Sometimes you need to discriminate against Pauls
name = "Paul"
if (age > 12) && (name != "Paul") {
println("Welcome to the waterpark")
}
for
is the only loop in the language and is used as a foreach, C-Style for loop or like a while loop.
// For-each style
for i in range(10) {
println("I was here")
}
// C-style loop
for i = 0; i < 100; i +: 3 {
println(i)
}
// while style loop
for true {
println("I will never terminate")
}
Comments are written with //
, which comments out the rest of the line.
a = 3; // this is a comment
func myFunction(age: Int, name: String) > String {
return "Hi I am " + name + age.toString()
}
println(myFunction(42, "Alan Turing"))
Beside classic functions, Moose provides the possibility to write own operators for own types. In fact, all operators have a function implementation. There are 3 types of operator functions: prefix
, infix
and postfix
// What if JS was right with adding numbers to strings?
infix + (a: String, b: Int) > String {
return a + b.toString()
}
// A prefix + makes numbers positive
prefix + (a: Int) > Int {
if a < 0 {
return a * -1
}
return a
}
a = "4"
b = 12
c = a + b
println(c) // 412
mut d = -2
println(+d) // 2
Each object has a default implementation of represent()
that returns a string with all the fields and their values of the object. By assigning the object to a String
variable or a parameter, the represent()
function is called internally.
p = Person("Alan Turing", 41)
println(p)
// equivalent to
println(p.represent())
// Person: {name: "Alan Turing", age: 41}
class Person {
name: String
age: Int
func hello() {
// Instead of this, Moose uses me
println("Hey I am " + me.name)
}
}
// Classes have a default constructor in which all all fields have to be filled out
anna = Person("Anna", 74)
anna.hello()
Moose supports single class inheritance.
class Employee < Person {
work: String
func hello() > String {
print("Hi I am " + me.name + " and I work at " + me.work)
}
}
catrin = Employee("Google", "Catrin", 56 )
catrin.hello()
You extend existing classes by using the extend
construct.
extend Person {
func wasBorn() > Int {
return 2022 - me.age
}
}
j = Person("Johannes", 23)
println(j.wasBorn())
In Moose, tuples are a standard data type, like Int, String and others. It also allows programmers to return multiple values from a function and offers the possibility of unpacking.
person = ("luis", 17, false)
// Without unpacking
name1 = person.0
age1 = person.1
married1 = person.2
// With unpacking
(name2, age2, married2) = person
println(name1 == name2)
Unpacking is not only possible for tuples themselves, but also for objects, where the tuple contains the first n object fields.
p = Person("Alan Turing", 41)
(name, age) = p
Accessing data of an object is not only possible for List
s, but for all datatypes that implement the getItem(Int)
method. Similarly the setItem(Int, Any)
allows to write at an index.
class Person {
name: String
func getItem(i: Int) > (String) {
return name[i]
}
}
p = Person("Alan Turing")
println(p[0]) // "A"
Moose has two types of error handling. Unlike exceptions panics are unrecoverable errors, which will cause the interpreter stop execution and exit with a non-zero exit code.
a: Int = nil
b = a + 3 // NIlUsagePanic
l = [1, 2, 3]
x = l[9000] // OutOfBoundsPanic
For recoverable errors the convention is to return a tuple, where the last element is a string with an error message. If the error is nil everything succeeded, whereas if an error occurred it will be a message describing the reason.
(mail, err) = open("mail.txt")
if err != nil {
println("Oh no I cannot read the mail because: " + err)
exit(1)
}
println(mail)