D to Javascript converter (a hacked up dmd)

Adam D. Ruppe destructionator at gmail.com
Thu Mar 1 07:07:57 PST 2012


On Thursday, 1 March 2012 at 07:14:02 UTC, Jacob Carlborg wrote:
> BTW, how do you fit D's class based object model in 
> JavaScript's prototype based object model?

Short answer is each D object in JS has a virtual
function array and implements array as properties
attached to the object. The JS constructor for the
object populates these arrays.

Virtual calls thus look like:
obj._virtual_table[call_num]();

instead of
obj.function();

It is similar in principle to how you'd implement
it in C.

Long answer:

A D class is written out like this:

function _d_class_NAME(requested_constructor) {
      _d_class_object.call(this, null);
      // repeat for all base classes through to Object
      _d_class_baseclass.call(this, null);

      this.virtual_function_table[index] = member_function;
      // repeat for all virtual functions

      /* BTW, I'm probably going to change this implementation but 
it will have the same result */
      this.implements = ["Object", "Interface_Name", "Base_Class", 
"This_Class"];
      // (these names are actually mangled in the real thing)


      if(requested_constructor)
            requested_constructor(this, arguments);
}

function member_function(arguments..., d_this) {
     // implementation of the member function
}




Usage:

in D:
      MyClass a = new MyClass();
      a.final_function();
      a.virtual_function();

in Javascript:
     var a = new _d_class_MyClass(default_constructor);
     // most functions are free functions, like they would be 
implemented
     // in C. You pass this as a final parameter:
     MyClass_final_function(a);

     // but virtual functions have to use the table:
     a.virtual_function_table[0]();




The index into the virtual table is known by dmd ahead of
time, so plain integers are outputted.



If you override, what happens is the base class initializer
is called first, which creates its virtual functions.

Then, your class initializer simply overwrites that entry
in the table.

(Plain Javascript properties probably could have done this too,
with the base class being the prototype, but dmd already
has the infrastructure to do it C style, and getting the
Js names right in the presence of things like overloading
would have been harder to keep straight than a simple integer
index.)




Whether you access it through a base class, interface, or
whatever, the vtable is attached to the object, so it works.



The other thing you expect in classes is dynamic casting.
That's where the this.implements comes in.

If you write cast(Class) object; in D, it outputs
var n = __d_dynamic_cast(object, "Class");



The dynamic cast function is implemented in test/object.d as
a simple loop:

// given an object, should we allow the runtime cast to succeed?
// if no, return null.
Object __d_dynamic_cast(Object from, string to) {
	if(from.__d_implements) {
		foreach(s; from.__d_implements)
			if(s == to)
				return from;
		return null; // it is a D object, but not of this class
	}
	// if in doubt, let it pass since we don't want to tie hands
         // where the programmer knows what he is doing. I might
         // change this since it isn't actually that good of an 
idea.
         // (there's always reinterpret_cast if you know you want 
it)
	return from;
}


But yeah, if the class name is in the implements list, go ahead
and return the object; the cast succeeds. If not, do null.



This is used in exception handling. In D, if you write:

try {
    throw new Exception("not an error");
} catch(Error e) {
    assert(0);
}


you expect the exception to keep flying, since Exception is not
an Error.

Javascript, though, doesn't have the same concept of types.

I implemented this like so:


try {
    throw new _d_Exception(exception_string_constructor, "not an 
error");
} catch(e) {
    var was_exception_caught = false;
    var e_as_error = _d_dynamic_cast(e, "Error");
    if(e_as_error) {
        was_exception_caught = true;
        0 || _d_assert(...);
    }
    // repeat for other catch blocks, if present

    if(!was_exception_caught);
         throw e; // keep it going up the call stack
}



The dynamic cast lets the program know if we have the right
catch block, so it works like you expect it would in D.


(Javascript can do something similar with if(e instance of y),
but... I'm not 100% sure; I don't do this kind of JS often, but
I'm pretty sure instanceof only looks at the constructor - there's
no inheritance chain to look at. If we catch(Throwable), we want
Error, Exception, and all their subclasses in D.)




That initializer that sets up the vtbl also does things like
default value initialization, so int a; is == 0 like you
expect in D.


This got a little fun for the magical "null" word.

int[] a = null;
a ~= 3;

if we translated that as

var a = null;
a = a.concat(3); // fails, a is null

it is no good. JS null isn't the right type.


But, D's null knows what type it is! So, NullExp
looks like this:

if(type->ty == Taarray) // ass array
    sink("{}"); // use an object literal as this null
else if(string)
    sink("\"\"")
else if(array)
    sink("[]");
else
    sink("null");



Now, the JS variables are the right type when initialized
to D's null.

The trick will now be getting is null right... but meh
that confuses people in D itself due to implementation
details, so I think I'll just make (array is null)
mean (array === []) or something along those lines and
call it good enough.


More information about the Digitalmars-d-announce mailing list