puts the awe in awesome

ooc is a modern, object-oriented, functional-ish, high-level, low-level, sexy programming language. It's translated to pure C with a source-to-source compiler. It strives to be powerful, modular, extensible, portable, yet simple and fast.

It is currently under heavy development - a lot cool stuff is happening . Join us at #ooc-lang, follow us on twitter, hack with us on github!

ooc is distributed under the BSD license.

A tour in ooc land

"Hi, softer world =)" println()

That's right. No need for main(), this code will be put in the load function of the module. And now what's the magic behind that?

ooc is statically typed. Which means a variable can't change type. You can declare the type of a variable like this:

age: Int

You can even declare several variables, if you want. And assign them, of course

x, y, z : Float
x = 3.0; y = 1.0; z = 6.0

But the compiler is smarter than that - why specify the type when he can infer it?

age := 36 // this is an Int
name := "Norma Jeane" // and this.. a String =)

Yup, you guessed it: the parallel thingies start single line comments.

IMPORTANT NOTE: The := operator is decl-assign, e.g. the type of the declared variable is inferred from the right-hand-side expression. Regular assignment is =, as in C, Java, etc.

Sometimes it's more convenient to regroup variables into a class:

Vector3f: class {
  x, y, z : Float
}
 
vec := Vector3f new()
vec x = 3.14
vec y = 6.18
vec z = 42.0 // hey, Java/C/C++/C# guys: look! no dots =)
printf("Our vector is (%f, %f, %f)\n", vec x, vec y, vec z)

To access a 'member' of the object (like x, y, or z of a Vector3f), just say the name of the object, and the name of the member.

A class is useful because it can contain functions. Functions are a list of things to do. You can call them with parenthesis. Here's a small function:

sayHello: func {
  "Hello =)" println()
}
 
sayHello() // let's call it

Functions can also return things.

theAnswer: func -> Int {
  42
}
 
printf("The answer is %d\n", theAnswer()) // yup, that's the printf from C

If you want to be pedantic, you'd write 'return 42' instead. But in absence of a return in a non-void function, the last value is returned. You should always say what you want to return with that little arrow. That way, the compiler can tell you when you forgot to return something, for example.

Of course, functions can also have arguments.

add: func(a, b : Int) -> Int {
  a + b
}
 
printf("1 + 2 = %d\n", add(1, 2))

So back to our Vector class. We can add a 'length' function which will be very useful.

use math
 
Vector3f: class {
  x, y, z : Float
 
  length: func -> Float { sqrt(x * x + y * y + z * z) }
}
 
diagonal := Vector3f new()
diagonal x = 1.0
diagonal y = 1.0
diagonal z = 1.0
printf("The length of a cube's long diagonal is: %.2f\n", diagonal length())

You still following? Dots allow you to do several things on an object in a single line, for example:

me := RandomGuy new()
me eatBreakfast() .drinkCoffee() .yawn() .goBackToBed()

Some smart people call that "message chaining". It's different from cascade calling, though, because eatBreakfast() and his friends don't return anything. Cascade calls are often mis-used in languages that lack chaining (is there a Java in the room?)

Anyway. Back to our Vector class. Even though my friend Steve will be angry at me - I have to tell you you can add constructors to a class. They look like this:

Vector3f: class {
  x, y, z : Float
  init: func(x, y, z : Float) {
    this x = x // 'this' is called 'self' in some other languages
    this y = y
    this z = z
  }
}

Woohoo isn't that lengthy? I'd like to shorten it a little. For example, it's a waste of syntax to say that the arguments x, y, and z are floats. Hey, they are members already. You can shorten this a bit:

Vector3f: class {
  x, y, z : Float
  init: func(.x, .y, .z) {
    this x = x
    this y = y
    this z = z
  }
}

That's shorter but I still don't like it. We are doing a really repetitive work. It feels like it should be the machine's work. Well, let's make it work for us:

Vector3f: class {
  x, y, z : Float
  init: func(=x, =y, =z) {}
}

What just happened? A little bit of syntactic sugar, and our func(tion) is now a one-liner. Neato =) We can now create vectors like this:

vec := Vector3f new(6.43, 2.7, 4.3)
printf("Got (%f, %f, %f)\n", vec x, vec y, vec z)

(Yes, ooc has consistent constructor semantics, which means that new isn't a keyword, but a static function.)

Hmm now more about classes. Classes can be abstract. That's a complicated word for a simple concept - it means they can't be 'instantiated' (i.e. you can't create objects of that type). What use then? Well you can 'subclass' it, giving you a common interface, and allowing a (poor) form of code re-use.

Animal: abstract class {
  shout: abstract func
}
 
Dog: class extends Animal {
  shout: func { "Woof, woof!" println() }
}
 
Cat: class extends Animal {
  shout: func { "Meoooww!" println() }
}

Now the nice thing is: you can define a function that takes an animal, like that:

poke: func (animal: Animal) {
  animal shout()
}

(Like Io, ooc uses a space instead of a dot to separate an object from its member/method you want to access/call. The above is equivalent to 'animal.shout()' in Java, for example)

And you can pass it all kinds of animal =)

poke(Dog new())
poke(Cat new())

In ooc, we only have single inheritance, e.g. a class only has one super-class. But what if you want to share several common interfaces? For that, there's a thing precisely called an interface. It's like an abstract class, but it can contain only abstract methods.

Let's try a practical example:

Printable: interface {
  print: func
}
 
Cloneable: interface {
  clone: func -> This // hehe, smart use of 'This'. Explained later.
}
 
Document: class {
  data: String
  init: func(=data)
}
 
implement Cloneable for Document {
  clone: func -> This { Document new(data) }
}
 
implement Printable for Document {
  print: func { data println() }
}
 
// somewhere else in the code
list := ArrayList<Printable> new()
list add(Document new("This is a test document"))
list add(somethingElse)
 
// foreach: iterate over collection
for(printable in list) {
  printable print()
}

Uh oh I'm sorry I got excited while writing code and I walked over a few great features pretty fast. Let's take it one at a time. This refers to the type you are currently defining. More on that later. ArrayList<Printable> new() makes use of something very useful called.. generics!

If you come from C++ land (bless your soul), you probably know them as templates. And if you come from Java land, you know the name already - but might be frustrated by their weakness in your language. For example, in ooc you can perfectly do this:

import text/StringBuffer
 
printRealType: func <T> (arg: T) {
  printf("We've got an arg of type %s and size %d, %d\n", T name, T size, T instanceSize)
}
 
number : Short = 42
printRealType(number)
printRealType("Hey there")
printRealType(StringBuffer new())
// for a class, size != instanceSize, because objects are references in ooc
// instanceSize is the amount of memory a variable eats

You can also (but that's a bad habit) compare it with another class. That's right, you can access the class of anything:

print: func <T> (arg: T) {
  if(T == Int) {
    printf("%d\n", arg as Int) // 'as' allows casting
  } else if(T == String) {
    printf("%s\n", arg as String)
  }
}
 
print(1984)
print("George Orwell")

But the most interesting (and maybe most idiomatic) use of generics is in collection classes, such as ArrayList, SparseList, HashMap, etc. In fact, their definition looks something like:

ArrayList: class <T> {
  data: T* // a pointer to T's
}

In a world without generics, you had to do strange things. You could use 'Pointer' to represent pretty much anything. But then you had to cast everything out of get() methods. Now you can do:

ints := SparseList<Int> new()
ints add(13)
lucky := ints get(0)

.. and the compiler knows for sure that 'lucky' is an Int.

Now onto another fine feature. Foreaches. Foreaches is like.. a regular For that gave up alcohol, decided to shave daily, and bought a new suit. It's prettier, and more effective. Where a For would look like:

for(i = 0 : Int; i < 10; i += 1) {
  printf("%d\n", i)
}

... a Foreach looks like:

for(i in 0..10) {
  printf("%d\n", i)
}

As Mr. Christophe himself would say: "less syntactic noise, baby!"

And of course, you can use it with collections:

import structs/ArrayList
 
list := ArrayList<Int> new()
for(i in 0..10) list add(i) // oh yeah no needs for brackets
for(i in list) printf("%d\n", list[i]) // operator overloading ftw

Hmm what else is left.. oh yeah =) Covers. In ooc, you can "Cover" a primitive type, for example here is such a cover in ooc:

String: cover from Char*

It simply says that whenever I use the type 'String', I really mean 'Char*'. (in C, it really is a typedef). But the interesting thing, is now we can add functions to that cover:

String: cover from Char* {
  length: func -> Int { strlen(this) }
}

And now you can do things like:

n := "Beer" length()
printf("Beer is %d times better than water\n", n)

Oh you could also cover Int. Let's have some fun:

Int: cover from int {
  negate: func -> This { -this }
}
 
printf("The opposite of 42 is %d\n", 42 negate())

What was that? 'This' is the type you're currently defining. E.g. in this case it's 'Int'. But it saves repeating the same specific name over and over. And when your girlfriend will ask you to rename 'Int' to 'FluffyNumber' because it's 'cute' (that's what she said), you'll only have one occurence of 'Int' to replace.

And now some more goodies. Can haz operator overloading? yup.

Vector3f: class { x, y, z : Float; init: func(=x, =y, =z) }
 
operator + (u, v: Vector3f) -> Vector3f {
  Vector3f new(u x + v x, u y + v y, u z + v z)
}
 
operator -= (u, v: Vector3f) {
  u x -= v x
  u y -= v y
  u z -= v z
}
 
operator as (v: Vector3f) -> String {
  "(%d, %d, %d)" format(v x, v y, v z)
}

And then you can do some nice things like:

Vector3f new(1, 2, 3) as String println() // prints (1, 2, 3)
xAxis := Vector3f new(1, 0, 0)
yAxis := Vector3f new(0, 1, 0)
diagonal := xAxis + yAxis
diagonal += Vector3f new(0, 0, 1)

*phew* so many things already. Go grab some coffee, if you think it's long.

Functions in ooc can have the same name, but different arguments. It's called polymorphism. But in that case, you must define suffixes for these functions, so that in the generated C code they don't conflict

add: func ~ints (a, b: Int) -> Int {
  a + b
}
 
add: func ~floats (a, b: Float) -> Float {
  a + b
}
 
// and now we can use:
add(3, 5) // will use add~ints
add(2.14, 4) // will use add~floats
add~ints(3.14, 6.2) // will use add~ints and cast everything to Int

C programmers are probably worried at this point - they haven't seen free() calls yet! Yeah, ooc has a Garbage Collector. By default, objects are allocated with the gc, and some time after they've not been used, they're collected, and freed. That's one big worry off your shoulders - and no, it's not necessarily slower. Go read the internets before you say things benchmarks could make you regret ;) If you want to allocate garbage-collected memory yourself, just use gc_malloc, e.g.:

myRawArray := gc_malloc(Int size * 100) as Int*

There's also gc_realloc and gc_calloc (but no gc_free)

Now about organization. In big programs, you'd probably want to split your program in several modules. These modules can be organized in packages. Packages are just folders. Modules are source files. And you can declare any number of classes, functions, and variables, in a a module.

For example we could do a module called 'secret' in 'my/little/secret.ooc', containing the code:

tellSecret: func { "The cake is a lie" println() }

And we could use it from our main application, e.g. secret-teller, in 'secret-teller.ooc',

import my/little/secret
 
tellSecret()

..and when you launch "ooc secret-teller.ooc" it also compiles my/little/secret.ooc, and produces the executable file "secret-teller". That's right - you don't need header files (.h), you don't need to compile them separately - just import everything you need from your main source file, and everything will be compiled just fine.

This way, you can modularize your app/libraries correctly.

Oh, and for libraries there's another trick. Take for example ooc-gtk.

use gtk
import gtk/[Gtk, Window] // acts like: import gtk/Gtk, gtk/Window
 
main: func {
  w := Window new("Hi, world")
  w setUSize(800, 600) .connect("destroy", exit) .showAll()
  Gtk main()
}

This simple line 'use gtk' refers to a file 'gtk.use' which should be somewhere in your sourcepath, and which content looks like:

Name: GTK+ 2.0
Description: GNU User interface Toolkit
Pkgs: gtk+-2.0
Includes: gtk/gtk.h, gdk/gdk.h

So it acts like you imported "gtk/gtk" and "gdk/gdk" yourself, and compiled with:

$ooc myApp.ooc `pkg-config gtk+-2.0`

Of course that's only grazing the surface of the utility of .use files

There's still a lot of things I didn't cover, like... Static members/functions in classes:

Constants: class {
  PI := const static 3.14159265
}
 
printf("PI roughly equals %f\n", Constants PI)

Pointer operations (postfix: @ = deref, & = addressOf)

i := 42 // i is an int
ptr : Int*
ptr = i& // ptr is now a pointer to i
ptr@ = 24 // the value of ptr is set to 24
printf("i is now %d\n", i) // hint: i = 24 now.
 
raw := gc_malloc(sizeof(Int) * 10) as Int*
raw[3] = 14 // array notation
(raw + 3)@ = 14 // pointer notation

References:

addThree(number: Int@) {
  number += 3
}
 
main: func {
  i := 42
  addThree(i&) // difference from C++. Here everything's explicit
  printf("Now i = %d\n", i) // hint: i = 45 now.
}

Including C headers

include stdio, stdlib // those are included by default, anyway
include ./myheader // if myheader.h is in the same directory

Cover-ing a C type/structure so you can access it fields, and externing a C function so you can call it:

include time // equivalent to #include <time.h> in C
 
TimeT: cover from time_t
Tm: cover from struct tm {
   tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, tm_wday: extern Int
}
 
main: func {
  timestamp := time() // timestamp is now a TimeT
  t := localtime(timestamp&) // t is now a Tm*
  printf("It's %dh%d.", t@ tm_hour, t@ tm_min)
}

Aliasing a C symbol to another name:

// we have to alias the name because functions can't start with
// an uppercase letter
gc_malloc: extern(GC_malloc) func (SizeT) -> Pointer

Most of the features above are already implemented, or in the process of being implemented. Check out the code at github Stay tuned on the #ooc-lang channel on Freenode, on the blog, and follow us on twitter!

 
 
Creative Commons License 2010, nddrylliog & the ooc crew