A Language Called [something] - The Story So Far - Part II

Saturday, 6 August 2011

Part II of describing what's been implemented in this language compiler I'm working on.

This post is about Prefix a modern language that compiles to JavaScript. A language where C# is the inspiration - not the goal. Read more on the Prefix Project Page.

Automatic 'this' references

Because this language is strongly typed, the compiler knows when to automatically insert this references when accessing member variables, methods and properties.

extern dynamic Console;

class Foo
{
    public string fn1()
    {
        return fn2();
    }

    string fn2()
    {
        return prop1;
    }

    string prop1
    {
        get
        {
            return _field1;
        }
    }

    string _field1 = "Hello World";
}

Console.WriteLine(new Foo().fn1());
(function() {

// class Foo
var Foo = function()
{
};

Foo.prototype._field1 = "Hello World"

// System.String Foo.fn1()
Foo.prototype.fn1 = function()
{
    return this.fn2();
};

// System.String Foo.fn2()
Foo.prototype.fn2 = function()
{
    return this.get_prop1();
};

// System.String Foo.get_prop1()
Foo.prototype.get_prop1 = function()
{
    return this._field1;
};


Console.WriteLine(new Foo().fn1());

})();

(This was actually shown in yesterday's post but I neglected to mention it)

Explicit Casts

Explicit casts convert an object reference between two different types without performing a runtime check.

The compiler only allows casting between types that could be possible. So you can't cast an int to string and you can only cast between class if one derives from the other.

class Foo
{
}

class Bar : Foo
{
}

Foo x;
Bar y = (Bar)x;
(function() {

// class Foo
var Foo = function()
{
};


// class Bar
var Bar = function()
{
};

Bar.prototype = new Foo();

var x;
var y=x;

})();

Note:

  • explicit casts are a compile time only operation - there's no JavaScript code actually associated with them.
  • explicit casts are a little different to C# which does do a runtime check and will throw an exception if the cast fails.
  • you can always explicitly cast to and from dynamic, so if you really really need to cast something that doesn't make sense to the compiler you can. eg: ((string)((dynamic)23))

Actually, there is one case where code is generated on an explicit cast - when casting a double to int, in which case it's rounded:

extern dynamic Console;

double x=23.9;
int y = (int)x;
Console.WriteLine(y.toString());
(function() {

var x=23.9;
var y=((x)|0);
Console.WriteLine(y.toString());

})();

Dynamic Casts with as and is

A dynamic cast converts an object reference from one type to another, but uses JavaScript's instanceof operator to check the conversion and produces null if the object is of the wrong type. Dynamic casts use the as keyword:

extern dynamic Console;

class Foo
{
}

class Bar : Foo
{
}

Foo x = new Bar();
Bar y = x as Bar;
Console.WriteLine((y!=null).toString());
(function() {

// class Foo
var Foo = function()
{
};


// class Bar
var Bar = function()
{
};

Bar.prototype = new Foo();


var x=new Bar();
var y=(x instanceof Bar) ? x : null;
Console.WriteLine((y!==null).toString());

})();

The is is essentially the same as the JavaScript instanceof operator, it checks if an instance is of a particular type.

extern dynamic Console;

class Foo
{
}

class Bar : Foo
{
}

Foo x = new Bar();
Console.WriteLine((x is Bar).toString());
(function() {

// class Foo
var Foo = function()
{
};


// class Bar
var Bar = function()
{
};

Bar.prototype = new Foo();


var x=new Bar();
Console.WriteLine((x instanceof Bar).toString());

})();

Inline JavaScript

This is a bit of a hack, but it's there and works. You can inline just about any JavaScript by quoting it in backticks. This is not a free form text pass through though. In the context of the input program, the backtick quoted JavaScript is treated as an expression element that returns something typed dynamic.

This lets you embed plain JavaScript just about anywhere and is great for testing while I'm still building other parts of the language:

// Use `backticks` to embed Javascript expressions and statements directly into
// the generated output.  Highly discouraged, and will be eventually removed
// but useful for testing and until we support more stuff natively.

// JavaScript array
dynamic x=`["Hello", "World"]`;

// JavaScript code
`
for (var i in x)
{
    Console.WriteLine(x[i]);
}
`;
(function() {

var x=["Hello", "World"];

for (var i in x)
{
    Console.WriteLine(x[i]);
}
;

})();

Access Protection

Most of the class member access protection is working. This includes the keywords:

  • private
  • protected
  • public
  • internal

Note that non-public protection levels aren't enforced outside the language. In other words they're a compile time only directive that the compiler enforces with respect to other code in the same compilation. So, private members can definitely be accessed from external JavaScript (if you let the object out of your compiled script), or by casting an object reference to dynamic.

That said, these keywords are excellent hints for minification and the compiler will obfuscate these symbols. So you can't reliably get around this restriction from code, but you wouldn't rely on this for a banking application.

Protection levels on classes and global functions are parsed, but not implemented correctly yet.

Namespaces and Nested Classes

Namespaces and Nested Classes are supported. Note that the naming of these is properly maintained in the generated JavaScript code.

extern dynamic Console;

namespace MyCoolLibrary
{
    class Outer
    {
        class Inner
        {
            public void test()
            {
                Console.WriteLine("Hello World");
            }
        }
    }
}

var x = new MyCoolLibrary.Outer.Inner();
x.test();
(function() {

// namespace MyCoolLibrary
var MyCoolLibrary = new function()
{
    // class MyCoolLibrary.Outer
    var Outer = function()
    {
    };
    
    this.Outer = Outer;
    
    // class MyCoolLibrary.Outer.Inner
    Outer.Inner = function()
    {
    };
    
    //  MyCoolLibrary.Outer.Inner.test()
    Outer.Inner.prototype.test = function()
    {
        Console.WriteLine("Hello World");
    };
    
    
}();
var x=new MyCoolLibrary.Outer.Inner();
x.test();

})();

That's It So Far

I think that covers most of what's implemented so far. There's 150+ (and growing) unit tests and while I know there are some issues I think it's reasonably solid.

« A Language Called [something] - The Story So Far A Language Called [something] - more language features »

3 Comments

In your first code comparison (about this)... Shouldn't only fn2() be part of Foo.prototype because others are private and should be contained inside your wrapping function closure? that would actually make them private and not accessible from the Foo class instance. Otherwise you can always access privates from the Javascript perspective.

6 August 2011 12:29 PM

... I know that you wrote about access protection later on, but I think at least privates should be inaccessible. You could as well implement internal by privates within the same function scope... I don't know what internal in your language means because you probably don't have a construct of a project or assembly. If they're related to a single file then public/internal/private could as well be implemented as:

// "internal" closure
function() {

    // define internally visible members - public to this closure

    // public class - visible in global scope as actually public because it lacks "var"
    PublicClass = (function() {

        // private class members

        // constructor
        var classDef = function() {
        }

        // define public class members
        classDef.prototype....

        return classDef;
    })();

    var InternalClass = (function(){
        same as above
    })();
}

I can see that my internals are actually not part of a particular class but I suppose with a bit of thinking (and maybe another nested closure) that could be done as well.

6 August 2011 12:44 PM

There are problems with implementing private by wrapping in a closure.

  • the semantics become quite different from what is traditionally understood by the private keyword. eg: private variables are usually accessible from other instances of the same class, but they wouldn't be if wrapped in a closure.
  • all non-private member functions that need access to the private variable need to be declared in the class constructor and can't be put in the prototype. This introduces memory and performance overhead that you wouldn't expect by simply declaring something as private.
  • it means private and protected would be implemented completely differently and offer completely different guarantees about accessibility.

internal in my language means accessible to any file in the current compile run, but otherwise private to the outside world - by which I mean possibly obfuscated, or possibly not accessible at all. The compiler can compile multiple input files into a single JavaScript file - which is analogous to a .NET assembly.

Having said all that, properly hidden members could be implemented with two new access modifiers - secure and privileged.

  • secure can be applied to member variables, properties and function and means truly hidden from access by generating its declaration in the class constructor closure. secure members can only be accessed by other secure or privileged members - and they must calling from the same class instance.
  • privileged only applies to member functions, can be combined with the public modifier and grants access to secure members. privileged functions would be declared in the class constructor but also added to the class instance making them publicly accessible.

I quite like this because it's a different concept to private and the new keywords indicate that these are something quite different.

Finally, regarding your example, wouldn't doing it like that cause all class instances to share the same private variable instances - ie: they've become static private. Also, you mention the lacking "var" statement, the compiler already automatically declares public entities as vars outside the top level closure.

8 August 2011 11:32 PM

Leave a comment

Name (required)
Email (required, not shown, for gravatar)
Website (optional)
Your Message
Leave these blank: