Smudge

The modern and fun way to program.

View the Project on GitHub

The Smudge Programming Language - Classes

Smudge And Classes

Obviously, Smudge is natively object-oriented, which means that you can use special structures, named classes, that are like models from which we can build objects named instances.

It’s useful to remember that Smudge treats classes as any other objects, so they can be easily copied (allowing creation of aliases).

Class Example

Let’s create a simple class to handle points named Point:

import std.io;

class Point {
    var x, y;
}

func main {
    // creating point (1;2)
    var p = Point(); // 'p' is an instance of 'Point'
    p.x = 1; // set 'p' x value
    p.y = 2;

    // creating point (3;6)
    var p2 = Point(); // another 'Point', independent from 'p'
    p2.x = p.x * 3; // get 'x' from 'p' and set to 'p2'
    p2.x = p.x * 3;

    // writing all in the stdout
    io << "p { " << p.x << ", " << p.y << " }" << io.ln;
    io << "p2 { " << p2.x << ", " << p2.y << " }" << io.ln;
}

In this way we can store multiple informations inside a single object, but initializing Points it’s difficult for now, let’s improve this code:

import std.io;

class Point {
    var x, y;

    func new (_x, _y){ // constructor (aka ctor)
        x = _x;
        y = _y;
    }
}

func main {
    // creating point (1;2)
    var p = Point(1, 2);

    // creating point (3;6)
    var p2 = Point(p.x * 3, p.y * 3);

    // writing all in the stdout
    io << "p { " << p.x << ", " << p.y " }" << io.ln;
    io << "p2 { " << p2.x << ", " << p2.y " }" << io.ln;
}

We’ve now semplified the ‘user’ code by implementing the constructor, which is a special method named new that is called each time an instance is built.

I’ve named the constructors parameters _x and _y, I could’ve used any names, but if I used x and y, I would have the need to type this before the assignment:

 . . .
    func new (x, y){
        /*
         * the first 'x' of the assignment is of 'this' (the current instance),
         * not the 'x' of the current scope (constructor's scope).
        */
        this.x = x;
        // same for y
        this.y = y;
    }
 . . .

Methods

Methods are like functions but they’re defined inside a class; their behavior often depends on the state of the instance they’re called in, but it’s not a general rule.

Usually they’re used to retreive or change the status of an object. They’re accessed via the operator . just like the instance variables.

import std.io, std.cast, std.system = sys;

class Log {
    var string;

    /*
     * again, constructor: a method called
     * when the object is created.
    */
    func new(str)
        string = str;

    /*
     * operator overloading: a method called
     * every time a certain operator is
     * applied to the object.
    */
    func + (str){
        /*
         * Using 'std.cast::kin()' and 'std.system::sterr()'
         * is the safest way to handle operator overloading.
        */
        if(cast.kin("abc", str))
            return Log(str + "\n" + string);
        else if(cast.kin(Log, str))
            return Log(str.string + "\n" + string);
        sys.sterr("unsupported type (" + cast.desc() + ")");
    }

    /*
     * Ordinary method: called explicitly
     *
    */
    func print(x){
        x.println(string);
    }
}

func main {
    // stored a Log in 'pm'
    var log = Log("first line") + "second";
    log += "third " + "line";
    log.print(io);

    // temporary object
    Log("Hello log!").print(io);
}

OUTPUT:

third line
second
first line
Hello log!

Special Methods

It’s now important to understand what really happens inside Smudge objects. Each of them have the following methods:

  1. <init>() : called before new() to initialize vars.
  2. new() * : constructor, initializes the object.
  3. delete() * : destructor, deinitializes the object.
  4. <gc_collect> () : alternative destructor, executed when delete() can’t be called (e.g. when object is deleted by the garbage collector).

* -> can be overloaded directly in code. All of then, though, can be overloaded natively in C++.

Usually, you shouldn’t use delete() function, because its call is not garanteed. However, the intepreter always call either delete() or <gc_collect>() when the object is destroyed (the first if it was deleted by reference counting, the other if the GC did it). So, in native libraries they can be combined to avoid memory leaks. For example, List is implemented in this way:

A simple implementation of a class could be instead:

Note:

Inheritance

Smudge supports a kind of multiple inheritance (with some restrictions) to give power and complete freedom to the developer.

Single Inheritance

Inheritance is a simple way to recycle code.

import std.io;

/*
 * class A has two methods: a() and b()
*/
class A { // A is called BASE (or either PARENT or SUPER) class
    func a
        io.println("A::a()");

    func b
        io.println("A::b()")
}

/*
 * B inherits from A methods a() and b().
 * B is a DERIVED (or CHILD) class of A.
 * B overrides method b().
*/
class B (A){
    func b
        io.println("B::b()");
}

/*
 * method B::x() is A::x()
*/
func main {
    B().a(); // calls A::b()
    B().b(); // calls B::b()
}

By default base class’ <init>() is always called, while new() and delete()
have to be called manually with the keyboard super:

import std.io;
var p = io.println;

class MyBase {
    var _ = p("MyBase's <init>()");
    func new p("MyBase's new()");
    func delete p("MyBase's delete()");
}

class MyDerived (MyBase) {
    func new
        super.new();

    func delete
        super.delete();
}

func main
    MyDerived(); // immediately destroyed

Also, one of the observable effects of the call to <init>() is that all variables declared in the base class will automatically exist in the derived, as shown in this following code snippet:

import std.io;

class Frame
    var name = "Hello, Frame!", size = (1280, 720);

class PopUp (Frame){
    /*
     * var name = "Hello, PopUp!"; -> ERROR!
     * 'name' is alredy defined in this scope!
    */

    func new {
        name = "Hello, PopUp!";
        size = (100, 100);
    }

    func print
        io.println("name: ", name, "\nsize: ", size);
}

func main
    PopUp().print();

Multiple Inheritance

Multiple inheritance in Smudge is a little different from other programming languages, and consist of specifying a Master base class and one or more Slave ones.

Usually, in Master base classes are defined variables and functions, while in Slaves only functions.

import std.io;

class MyMaster {
    var a, c = "Sum";

    func set_a(val)
        a = val;
}

class MySlave1
    func print
        io.println(c, ": ", a);

class MySlave2 {
    var my_var = "Never created in class C";

    func add(val)
        a += val;
}

class C (MyMaster, MySlave1, MySlave2); // class 'C' doesn't have var 'my_var'

func main {
    var c = C();
    c.set_a(2);
    c.add(3);
    c.print();
}

Usually, Slaves are used just like Interfaces in some other programming languages.

     
Previous Home Next