Skip to content
EliasC edited this page Aug 1, 2014 · 19 revisions

mylittlepony Manual

The mylittlepony language and compiler are the UU-team's shot at getting a base language up and running. We want a compiler that is small and clear, allowing us to implement more interesting type systems as the Upscale work progresses.

Language Overview

Hello, World!

The obligatory hello-world program, hello.enc, looks like this:

#! /usr/bin/env encorec -run
class Main
  def main() : void {
    print "Hello, World!"
  }

You can compile this program by running encorec -clang hello.enc. Or, if the file is executable, you can run it by saying:

$ ./hello.enc
Hello, World

It is important to know that every legal mylittlepony program needs to have a class called Main, with a method called main. The runtime will allocate one object of class Main and begin execution in its main method.

Active/Passive Classes

Mylittlepony implements active and passive classes. Allocating an object from those classes gives active or passive objects, resp. To make a class active, no special keyword is necessary, as classes are active by default: class A. To make a class passive, use passive class P.

Calling a method on an active object will have that method execute concurrently (also in parallel, if enough cores are available). The return type of an active method is a future, not a value. Active classes are the default in mylittlepony.

Calling a method on a passive object will execute that method synchronously, in the calling thread. Passive objects are similar to what you'd expect in a Java-like language.

Example: Active Class

We write an active class Foo. A foo has an ID string, calling the printID method will print that string 5 times to stdout. In the main method, we create two Foo instances, and have them print their IDs

#! /usr/bin/env encorec -run
-- ex_active.enc
class Main
  def main() : void
    let obj1 = new Foo
        obj2 = new Foo
    in {
      obj1.setID("obj1");
      obj2.setID("obj2");
      obj1.printID();
      obj2.printID();
      ()
    }

class Foo
  id : string

  def setID(new_id : string) : void {
    this.id = new_id;
    ()
  }

  def printID() : void {
    let i = 0 in {
      while i < 5 {
        i = i + 1;
        print this.id
      }
    }
  }

Executing this program gives nondeterministic output:

$ ./ex_active.enc
obj2
obj1
obj2
obj1
obj2
obj1
obj2
obj1
obj2
obj1

Example: Passive Class

A passive class is declared using the passive keyword:

#! /usr/bin/env encorec -run
--ex_passive.enc
passive class Location
  x : int
  y : int
  label : string

  def init(new_x:int, new_y:int, new_label:string) : void {
    this.x = new_x;
    this.y = new_y;
    this.label = new_label;
    ()
  }

class Main
  loc : Location

  def main() : void {
    this.loc = new Location;
    this.loc.init(1,2,"a place");
    print this.loc.x;
    print this.loc.y;
    print this.loc.label
  }

Running:

$ ./ex_passive.enc
1
2
a place

Formated printing

As long as we only have unary print statements, and we have several actors that are printing multiple values at the same time (eg. an ID and a value), we have no way of discerning which output is printed by what actor. To be able to print more than one value at a time we use the printf statement:

def report(id : int) : void
    printf "ID = {}\n Value = {}\n", id, this.val

Anonymous Functions

Encore has anonymous functions:

let f = \ (i : int) -> 10*i in {
  print f(10) -- prints '100'
}

The backslash \ syntax is borrowed from Haskell and is supposed to resemble a lambda. It is followed by a comma separated list of parameter declarations, an arrow -> and an expression that is the function body.

The type of a function is declared similarly. The function f above has type int -> int. Multi-argument functions have types such as (int, string) -> bool.

For a complete example, see ex_lambdas.enc.

Polymorphism

There is limited support for polymorphic methods and functions. Type variables are written with lower case letters and do not need to be declared. If there is a type variable in the return type it must appear somewhere in the types of the arguments. Here is an example program that uses a polymorphic function:

class Main
  def main() : void
    let 
      apply = \(f : a -> b, x : a) -> f(x) 
    in
      let bump = \(x : int) -> x + 1 in
        print apply(bump, 3)

Embedding of C code

In some cases, you'll see code using the embed..end keywords. There are two ways to embed c code: as an expression, or as a toplevel embed block.

When embedding an expression, the programmer needs to assign a type to the expression; encorec will assume that this type is correct. The value an embedded expression evaluates to is the return value of the last C-statement in the embedded code.

A toplevel embed block is a block of C-code that will be inserted into the generated C code, before compiling the program using clang. These blocks are useful if you want to insert #include directives, or if you want to implement C functions.

However, we do not advocate to rely on the embed functionality too much. Code that uses embed is highly likely to break with future updates to the language (even more likely than code that doesn't use embed ;) If you want to access local variables in an embed expression, you'll need to wrap them: #{x} for accessing the local variable x. If you want to access fields, you don't wrap them, but use C's arrow operator: this->foo for this.foo.

#! /usr/bin/env encorec -run

embed
  // We're defining the sq(int) function in a toplevel embed block.
  // Each file can have no more than one toplevel embed blocks.
  int sq(int i) {
    return i*i;
  }
end

class Main
  def main() : void {
    let x = 2 in {
      -- Contrary to the toplevel embed block, an embed expression
      -- also needs to specify a certain type.
      -- In this example, the expression promises to return an int:
      print (embed int sq(#{x}); end)
    }
  }

Syntax

The syntax is documented in source code in Parser.hs.

Compiler Overview

Getting up and running

  • Download VirtualBox image from this url

  • Start the virtual machine, using encore as username and password.

  • ~/mylittlepony contains the source code for encore compiler. make test would verify that everything is set up.

  • The code in this VM image becomes obsolete as the development continues in the repo, so one github account, which could be used to access the latest state of encore, is provided: encore-image with password encore-image123