/Moose

Primary LanguageSwiftMIT LicenseMIT

Moose

Moose is a new programming language. This repo also includes an interpreter written in Swift.

Build it yourself

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/TicTacToe/main.moose

Status

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.

Types

There are only 4 built in types in Moose: Int, Float, String, Bool

Variables

Moose provides two types of variables: immutable and mutable variables.

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

Immutable Variables

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

Nil

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: Str = nil;
println(breakfast ?? "Melage and semmel");

// This code is just sytactic sugar and is equivalent to
println(breakfast != nil ? breakfast : "Melage and semmel");

Lists

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.appendAll(["Aircraftcarrier", "Worlddomination"]);

Dicts

Dictionaries (aka Hashtables or Hashmaps) are also build in.

ages: {Str:Int} = {
    "Flo": 23,
    "Paul": 24,
    "Clemens": 7,
}

println(ages["Paul"]) // 24

println(ages.contains("Paul")) // true
ages.remove("Paul")
println(ages.contains("Paul")) // false

If Else

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

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

Comments are written with //, which comments out the rest of the line.

a = 3; // this is a comment

Functions and Operators

Classic Functions

func myFunction(age: Int, name: String) > String {
    return "Hi I am " + name + age.toString()
}

println(myFunction(42, "Alan Turing"))

Operator Functions

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

Print Objects

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}

Classes and Objects

class Person {
    name: Str
    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()

Inheritance

Moose supports single class inheritance.

class Employee < Person {
    work: Str

    func hello() {
        super.hello()
        println("And I work at " + me.work)
    }
}

catrin = Employee("Catrin", 56, "Google")

Extending Classes

You extend existing classes by using the extend construct.

extend Person {
    friend: Person
    func add(friend: Person) {
        me.friend = friend
    }
}

a = "Hello"
str.add(" World")

println(str) // "Hello World"


## Tuple

In Moose, tuples are a standard data type, like Int, String and others. It allows the programmer to return multiple values from a function and also offers the possibility of destructuring.

```dart
postfix / (a: [Int]) > ([Int], [Int]) {
    firstHalf = a[0:(a.len/2)]
    secondHalf = a[(a.len/2):a.len-1)]
    return (first, second)
}

l = [1,2,3,4]
(a,b) = l/ // destruction of tupel

println(a) // [1,2]
println(b) // [3,4]

Destructuring of Objects

Destructuring 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

Indexing und Unpacking

Accessing data of an object is not only possible for List s, but for all datatypes that implement the indexing method.

class Person {
    name: String

    func getItem(i: Int) > (String) {
        return name[i]
    }
}

p = Person("Alan Turing")

println(p[1]) // "A"

[a,b,c] = p // unpack indexed object
println(b) // "l"

It is also possible to unpack the rest of an indexed object by providing the len() function for that respective class.

extend Person {
    func len() > Int {
        return self.name.len()
    }
}

[a, rest..] = p
println(rest) // ["l", "a", "n", " ", "T", "u", "r", "i", "n", "g"]

Error handling and Panics

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)