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:
privateprotectedpublicinternal
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.
3 Comments
Leave a comment
In your first code comparison (about
this)... Shouldn't onlyfn2()be part ofFoo.prototypebecause 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.... I know that you wrote about access protection later on, but I think at least privates should be inaccessible. You could as well implement
internalby 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: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.
There are problems with implementing
privateby wrapping in a closure.privateandprotectedwould be implemented completely differently and offer completely different guarantees about accessibility.internalin 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 -
secureandprivileged.securecan be applied to member variables, properties and function and means truly hidden from access by generating its declaration in the class constructor closure.securemembers can only be accessed by othersecureorprivilegedmembers - and they must calling from the same class instance.privilegedonly applies to member functions, can be combined with thepublicmodifier and grants access to secure members.privilegedfunctions 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
privateand 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 asvars outside the top level closure.