Droid2

See the revised EBNF context free grammar here. The syntax highlighting on this page is broken because it's an old syntax.

See some up-to-date examples here.

class C(name: String): Function[Void, Void] {
    val randomName(): String { name ~ Math.random() }
    methods {
        getName(): String { name }
        get(): Void { Console.writeLine(randomName()) }
    }
}

This is a class called C with one constructor argument called name of type String . The class implements the generic interface Function[Void, Void] .

The constructor body contains a local function declaration, which can only be accessed locally inside the class, and only below it's declaration.

After that, there are two methods. The first method is unique to the class, whereas the second method is an implementation of a method defined in an interface. Both methods are visible outside the class as well as inside all the methods of the class.

Note that constructor arguments and local symbols from the constructor are visible within the methods.

Properties & More Syntax

val foo = C("Hello")
foo.getName()          # Returns "Hello"
foo.Name               # Shorthand for the above, also returns "Hello"
foo.get()              # Prints "Hello0.23" or similar
foo()                  # Shorthand for the above

In the above example, we construct the class from the previous example. We then call it's two methods in their long form and in their short form. The short form of a call to getBar() is written Bar (for any Bar). The short from of a call to get() is simply the call syntax () .

The comments starting with a single # and lasting to the end of the line are ignored by the compiler.

Note that we declared and initialized the constant "foo" on the first line, and then called methods on the object it pointed to on the subsequent lines. Since the type of the variable is obviously C , the compiler can infer this at compile time and you don't need to specify it. When the type is less obvious, the compiler will ask you to specify it if you don't.

var x = 10
while(x >= 0) {
    Console.writeLine(x)
    x = x - 1
}

This example demonstrates real variables, declared with the keyword var . You can update such variables so that they point to a new value by using the assignment operator = .

The while loop executes it's body repeatedly until the condition becomes false. The code in this example prints 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 and then stops since x is then -1 and thus no longer greater than or equal to zero.

Decrementing and incrementing is so common that there is a shorthand for it: x -= 1 and x += 1 . There are similar assignment shorthands for multiplication and division.

The examples in this subsection were code fragments, but note that only classes, objects, enums and interfaces can appear at the top level of a file. Anything else must be in a constructor or method body. It's time to see how to create the entry point of a program.

Applications

object Main {
    Console.writeLine("Hello, World!")
}

This is the classic hello world application. If saved in Main.droid , you can execute it from the command line with droid -x Main and verify that it prints Hello, World! to the screen.

An object is like a class, but with only a single instance. It cannot have any constructor arguments, but apart from that it is defined just like a class.

When the program starts, it constructs the Main object. Since it's constructor writes something to the console and then stops, that is exactly what the program does.

Like classes, objects can have methods. One of the objects in the standard library is called System , and this contains the command lines arguments that the user passed to your program.

object Main {
    val name: String = System.Arguments(1)
    Console.writeLine("Hello, " ~ name ~ "!")
}

This example greets whoever passes his name as the first command line argument. For example droid -x Main Turing would print out Hello, Turing! .

Note that we specified the type of name here. Although not strictly necessary (the compiler knows the type of System.arguments, and hence the type of the first element of it), it can sometimes make the source code more readable for programmers.

Looking back at the property example, you will notice that the usage of System is just a shorthand for System.GetArguments().Get(1) . Note that indexing is thus done through normal parenthesis (unlike in most other C-syntax-like languages where it's done with [] ).

Pure Data Classes

class Color(red: Float, green: Float, blue: Float)

This shows a pure data class representing a RGB color. When a class has no body, a getter method for each of the constructor arguments is automatically generated (in this case GetRed , GetGreen and GetBlue ), as well as a ToString method. The companion object is a good place to store common instances of this class:

object Color {
    methods {
        getPurple(): Color { Color(1.0, 0.0, 1.0) }
        getYellow(): Color { Color(1.0, 1.0, 0.0) }
        getCyan(): Color { Color(0.0, 1.0, 1.0) }
    }
}

Then Console.writeLine(Color.Yellow) will print "Color(1.0, 1.0, 0.0)" , using the generated ToString method that generates a string that resembles a constructor call. Color.Cyan.Blue equals 1.0. Note that everything that would be "static" in many other object oriented languages goes in the companion object in Droid.

Variants

Sometimes it can be useful to create variants, the simplest of which is an enumeration. They are written:

enum Season {
    Spring
    Summer
    Fall
    Winter
}

Any instance of Season is one of Spring , Summer , Fall or Winter . For such simple enumerations, two methods in the companion object are generated: ToInt and FromInt . These convert enumeration values to and from integers (In the above case, the numbers 0-3 would be valid).

The Bool type is actually just defined as enum Bool { True; False } , and true and false is just shorthand for Bool_True and Bool_False .

It is often necessary to append extra data to each of the constructors. Droid does this in it's answer to null :

enum Option[T] {
    Some(value: T)
    None
}

This denotes that instances of this class is either "some value" or "none", the last being somewhat like null in other languages. This make optional values explicit in the type system, which means you will not run into any NullPointerExceptions at runtime.

This also shows the first use of generics. The [T] states that Option is generic with respect to T . In your code, you can instantiate T to any type you like, which is then the type of value Some contains. For example, Option[Color] means "an optional color" - None , Some(Color.green) , Some(Color(1.0, 0.0, 0.0)) and so on.

Pattern Matching

In order to find out which constructor was used, you can use pattern matching. This is a generalization of the switch statement in C, making it possible to pull out data from the variants. Patterns can be recursive.

match(myOption) {
    Some(col) { Console.writeLine("Some color: " ~ col) }
    None { Console.writeLine("No color") }
}

In the above example, there are two cases. In the first, we use a pattern variable col . Such a variable matches anything and remembers what it matched, so that it can be accessed within the handler (inside the curly braces).

Note that just like classes, variants can have methods and a companion object. However, they cannot have local variables or constructor bodies.

Interfaces

The last and most simple kind of type in the type system is the interface. Interfaces specify what methods other types that implement them must have.

interface Point {
    getX(): Float
    getY(): Float
    plus(p: Point): Point
}

This interface represents 2D points, which have an x and a y coordinate. It also requires that you can add the point to another point to get a new point.

When another type implements this interface, it is required to provide an implementation for each of the methods. You can thus use an interface to handle different objects that fulfill some common contract. Generics aside, this is how subtyping works in Droid.

Note that a coordinate can be accessed like myPoint.X . Operators are syntactic sugar for method calls, for example a + b is syntactic sugar for a.plus(b) . This means that you can use the + operator to add Points together.

The first symbol in a file must be named the same as the filename (case included), except for the extension. This symbol is visible to other files in the same package and other files that use it's namespace. If the symbol is not an object, it can have a companion object of the same name that has the same visibility (public). All other top level symbols defined in the file are invisible outside of the file (private).

Functions

Droid supports anonymous functions that have full access to their environment - that is, they can read and update variables from the containing scope. The syntax aims to blur the line between user defined actions and built-in constructs.

button.onClick(fun {
    Console.writeLine("A button was clicked!")
})

This example shows a function with no arguments. When calling a method that takes only one argument, and that argument is a function, you may omit the parenthesis as above.

[1, 2, 3].each(fun(item: Int) {
    Console.writeLine("Counting to three: " ~ item)
})

This example shows usage of the "for-each" loop implemented as a method on List . The anonymous function takes one parameter item , and each calls the anonymous function for each item in the list.

Named nested functions are also supported.

fun fac(x: Int): Int {
    if(x == 0) { 1 } else { x * fac(x - 1) }
}

Namespaces

Namespaces serve to group related symbols, in a way that allow their names to be short, but still not conflict with similarly named symbols in other namespaces. All symbols declared in a file belong to exactly one namespace, which is declared at the top of the file.

namespace my.name
using gui.TextField
using gui.Label
using gui.Panel
using gui.Layout

object GuiHelper {
    methods {
        createLabeledButton(labelText: String): Panel {
            val panel = Panel(Layout.Horizontal)
            panel.insert(Label(labelText))
            panel.insert(TextField())
            panel
        }
    }
}

In the above example, GuiHelper will reside in the my.name namespace, and the file must be located in some folder my/name/. The using statement imports all symbols from a file in the given namespace - that is, gui.TextField imports all symbols originating from the file gui/TextField.droid. There is always a symbol in that file named the same as the filename (without extension).

The sole method shows that you can now use the imported symbols (and no namespace qualification is necessary). You cannot access symbols that have not been imported, unless they reside in the same namespace as your file.

You can import multiple files from a namespace in one go, for example: using gui.[TextField, Label, Panel, Layout] . You can introduce prefixes to deal with imported conflicting symbols in different namespaces: using w_ gui.Grid which is then accessed as w_Grid . You can also import object methods using droid.Console.writeLine which can then be called without Console. in front.

Sometimes you need to keep a symbol local (private) to the file, so that it doesn't get imported into other files with the other symbols. This is done by declaring it with using in front (nothing the using statment introduces are visible from other files):

using class Auxiliary() {
    # ... definition ...
}

Exception Handling

try {
    val f = File("test.txt").readText()
    while(!f.End) {
        Console.writeLine(f.readLine())
    }
    f.dispose()
} where stackTrace {
    e: IoException { 
        f.dispose()
        Exception.throw(e, stackTrace)
    }
}

In the above example, we open a file for UTF-8 input, print all it's lines to the screen, and close it. If at some point the file operations throws an IoException , we handle it by closing the file and re-throwing the exception for someone else to handle.

Note that the stack trace and the exception are separated. If you don't need the stack trace, you can use } where { without the stack trace variable. On some platforms, this might be more efficient.

Scoped Resources

In the above we handle disposal of the file by disposing it both during normal execution and when we encounter an exception. But what if another exception is thrown? Then the file won't be disposed before the next garbage collection cycle, which may be a long time from now. To avoid hogging such valuable resources, we could simply catch all exceptions, dispose, and then re-throw - but that gets really tedious. That's why there is a construct for it:

val f = scope File("test.txt").readText()
while(!f.End) {
    Console.writeLine(f.readLine())
}

The scope keyword in this context states that we only need this Disposable resource within the current scope. When exiting the scope for whatever reason, dispose() will be called on it immediately (but it will never be garbage collected before then). We thus no longer care to dispose it ourselves, and we might not even have to catch the possible IoException here.

Exceptions are filtered using pattern matching. Note that there are no checked exceptions, and that you don't need to handle or declare an exception you can't do anything useful about right now anyways. This does put some responsibility on you to document which exceptions get thrown though, with the @throw SomeException reason doc comment annotation.

The try { ... } construct is actually just a way to execute a sequence of expressions in their own scope. The where ... { ... } construct can be used after any curly braced sequence of expressions, even after method declarations.

Please note that exceptions should only be used when it makes sense not to check for the exceptional case in the immediately surrounding code. Otherwise, the Option type is preferable, since it requires the user to handle the possible error before looking at the value (or explicitly chose not to).

Further Information

Alternative Syntax

A Lua-like syntax.

enum Color do
    Red
    Green
    Blue
methods
    toHex(): String do
        switch this do
            Red do "ff0000" end
            Green do "00ff00" end
            Blue do "0000ff" end
        end
    end
end
class Point(x: Int, y: Int) do
    val length = Math.sqrt(x ^ 2 + y ^ 2)
methods
    getLength(): Int do length end
    getX(): Int do x end
    getY(): Int do y end
    add(other: Point): Point do
        Point(x + other.X, y + other.Y)
    end
end
if x > 100 do
elseif x > 50 do
else
end
for x in xs, if x >= 0 do
end
while x > 100 do
    x -= 1
end
val f = using File.read("test.txt")
# The var keyword is for mutable variables
o.map(fun(x) do x * 2 end)
# Still a strong reliance on expected types
block do
    # All executable blocks can have a catch clause
    # If break/continue is needed, a block can have a name X that 
    # can also be broken out of with break X - a structured label
catch
    e: IOException do e.printStackTrace(); System.exit(-1) end
end

It has some benefits, for example no dangling else and methods without extra indentation. Also, there's no dispute whether or not to put spaces around do end because it's required. Hopefully, everybody can also agree that do should be one the same line as the statement it belongs to. It will be forced, since an automatic semicolon will be inserted before the do if there is a line break just before it. The same rules apply: at the end of the line, if the last token wasn't a semicolon, an prefix or infix operator (including comma, dot and colon) or a starting ( or [ , a semicolon is inserted.

It has more bang for the token than C-like grammar. Consider if(....) { ... } else { ... } vs. if ... do ... else ... end . 8 vs. 4 tokens. And the English words are easier to type than parenthesis and curly braces on most keyboards. All the different types of parenthesis are then available for collection literals.