Saffire - Programming the web since 2013

Warning: This blogpost has been posted over two years ago. That is a long time in development-world! The story here may not be relevant, complete or secure. Code might not be complete or obsoleted, and even my current vision might have (completely) changed on the subject. So please do read further, but use it with caution.
Posted on 20 Dec 2012
Tagged with:

So, Saffire started as a way to “learn” a bit about flex/bison. I’ve dealt with these systems before a long time ago (pre-2K), and i forgot lots about them. So it was about time for a refreshal. Unfortunally, looking on the internet for tutorials, almost all of them are about how to write a calculator (bison’s version of “hello world”, most probably). Very soon, I decided to try and parse my own language, with some idea’s I collected over time on how *my* favorite language should look like. Two hours later: Saffire was born.

What is Saffire?

The main attraction of Saffire is that “everything is an object”. This by itself isn’t really that unique, many programming languages use this concept. Scalars like 1, “foobar”, or even /regex/i, are objects and can be used in an object context: “foobar”.length() would return 6, since this is how many characters the string “foobar” holds. Even things like 1, which is a numerical, can have methods: 1.negate(), could return -1 (also an object).

Furthermore, I want Saffire to be verbose. I like this about PHP for instance. I do not need to learn strange combinations of characters in order to work directly with the language. Things make sense - up to a point of course. Defining a method must be done like “public method foobar(arguments)”, instead of “def”. Off course, this means more typework, but let’s be honest: the people who complain about doing more typing, are the ones that uses code completion and macro’s to begin with, so they are not affected. The rest, well, basically, I don’t care about typing 4 or 5 characters more. It’s micro-optimisation, so to speak, and results in less readable code.

Another rule: be explicit, not implicit. This means you *HAVE* to set a visibility to an attribute (I will talk about these later). So it’s: “public method foo()”, while maybe “foo() {}” would be more than enough to let Saffire figure out that you are defining a method. Yes, EVEN when you have a public visibility, you MUST say so. This is for two reasons: more clear code. Every method definition is standarized. There can be no two different ways of writing the same thing (like “public method foo()”, or “method foo()”), but it also forces developers to think about what kind of visibility they want to have. Does it really need to be public? At least, it gives you 6 characters of time to think about it.

Classes and objects

A class is a “collection” of attributes. There are currently 3 different types of attributes: constants, properties and methods.

A constant is a property that cannot be changed afterwards, and is normally written in uppercase:

public const PI = 3.141527;

Once set, it cannot be changed, plus you can also set the visibility. In this case, PI is visibile from outside the class, you can use it there as well.

Properties are also containers for information, but they can be changed during the lifetime of an object:

public property count = 0;

This initialises a public property named count, and sets it to 0. Setting no value would either implicitly set the property to Null (but, since it’s implicit, it’s not the “saffire-way”(tm), so this might/will change later).

Methods, unlike some languages, are treated like any other attributes. They contain code that can be called. Methods are defined like this:

public method foobar(String arg = "") { ... }

This will create a method called “foobar”, that accepts 1 argument, which must be a String-object. If no argument is given during a call, it will use the default argument of “”. More about methods later on.

So, these are the three attribute types that can be stored in a class. But how to define a class:

class foobar extends a_parent_class implements an_interface {
    public const PI = 3.1415;
    protected const CONST_1 = true;
    protected const CONST_2 = false;
public property count = 0; 
    public method print(String arg) { ... } 
}

Saffire has more or less the same (verbose) structure on objects as PHP. This also mean that Saffire is single-inheritance only. There cannot be 2 parent-classes.

However, again, something implicit, which might-or-might-not change: whenever a class does not extend another class, it will implictly extend a class named “Base”. This means that in effect, EVERY class in Saffire will use the “Base” class. This has some nifty features, since we can store generic functionality inside the base class, for instance:

base.name(); // Returns the name of the current class
base.methods(); // Returns all the method attributes from the class
base.parent; // Returns the parent class of the clas
base.id(); // Returns the identifier of the class
base.refcount(); // Returns the how many times this class is referenced.

etc. The base class also takes care of cloning and instantiation for instance.

Playing around with attributes

Consider the following:

bar = foo();
bar.func1("baz");

This would instantiate the “foo” class to object “bar”, and we call funct1() (a method attribute) from bar. But the following would work too:

bar = foo();
baz = bar.func1;
baz("hello world");

Baz is a variable that holds the attribute bar.func1 (an attribute is an attribute, so it can hold any constant, property OR method!). When we call baz, we are, in effect, calling the method bar1.func. This allows us to do dynamic calls, but we can do more nifty (or nasty, depending on context) stuff:

bar = foo();
bar.func1, bar.func2 = bar.func2, bar.func1;

The “a,b = b,a” format is merely a nifty way to swap two variables without a third variable. In effect, this swaps the two methods. An example:

class foo {
    public method func1() {
        io.print("func A");
    }
    public method func2() {
        io.print("func B");
    }
}

bar = foo();
bar.func1();
bar.func1, bar.func2 = bar.func2, bar.func1;
bar.func1();

// Output:
func A
func B

Can you see why this is happening? Because the func1 holds a method-object, which we swap with func2. We still call the same function-attribute, but it has different code underneat. Since we do everything with objects, we can do all kind of tricks:

foo.bar()();

This will call the foo.bar() method. It should return another method-attribute, since we are calling the result as well.

class foo {
    public method bar() {
        return self.baz();
    }

    public method baz() {
        io.print("hello world");
    }
}

foo.bar()();
// "hello world"

This can be used for everything. As long as something is callable, you can use () to call it. The same thing applies with subscriptions: if it’s a data structure, you can subscribe it. This means, that this COULD be valid code:

foo.config()[bar.getkey()]("hello world");

foo.config() returns a hash-structure. Bar.getkey() a string. The index “key” in the hash-structure is a callable method, so it just calls this with the argument “hello world”. Nifty!

Operator overloading

Operator overloading is something that allows you to overload operators :). For instance, consider the following code:

a = 1 + 3

Variable a should become 4, right? But what about this:

a = "foo" + "bar";

Would a become “foobar”? It seems logical. But whatabout this:

a = "foo" - "o";

Will this work? Will a become “fo”, or maybe “o”. Probably, it doesn’t work. But this would make sense again:

a = "foo" * 2; // "foofoo";

So, depending on the arguments given, it will do custom things with operators to make them more sensible.

a = Color("yellow") + Color("blue");

It makes sense if a because a color(“green”), right? With operator overloading this can be done with ease.

Method overloading

Method overloading makes that you can have the same method(name), but with different arguments.

class foo {
    public method bar(String arg) { ... }
    public method bar(Numerical arg) { ... }
}

Here we can call the bar-method, but depending on the argument given, it would either call the first or second method. This can be helpfull in order to avoid structures like this:

public method bar(arg) {
    if (arg.class == "String") {
        // do this
    } else if (arg.class == "Numerical") {
        // do that
    }
}

Method overloading can be complex, and need some rules in order to fight some ambiguous situations. But quite frankly, if you happen to be in such a situation, you are probably abusing method overloading to begin with.

Features

Most of these features can be found in one language or another, but not a lof of them has ALL these features. This is what makes Saffire special (we hope). We take the best of the current (imperative) languages out there, and try to mix them up into a language that can be used for most purposes.

More Saffire stuff

But Saffire is more than just a language. It will be a complete infrastructure. How do we deal with saffire modules - packages of code that can be included in your own programs, such as database connectivity, frameworks, even simple codeblocks that allow you to easily send emails, or deal with files. Some of these modules are packaged into the Saffire Module Generic Library (SMGL - or, smeagol). If you need to deal with files, console, etc, it’s probably already in there. But custom modules can easily be installed and updated through Saffire as well. As a bonus, we try and keep everything as secure as possible: modules are cryptographically signed in order to protect your system so you know that what you download, is actually what the programmer of that modules has created.

The future

Of course, Saffire is far from finished. The whole module repository system is not yet up and running (we want to create this in Saffire itself!). And we are still dealing with lots of core language functionality we need to implement. But with the speed we are currently working, we are making big steps and it won’t take long before we can show you actually running code (well, actually, we already can, but we tend to break things down and redo some of the internals :p).