dmdscript - the EMCAScript (javascript) engine in D - adding objects

Published 2007-05-20 11:43:00
Having spent the last few weeks working with ExtJs, I'm been drinking quite a bit of Javascript Coolaid. (Actually being british, I never understood that phase, but I guess most people understand it these days.)

Doing all this a while back led me to investigate General Javascript engines, I had this weird vision that Javascript and Gtk may make an interesting combination.

As I mentioned in my last post about testing code the whole write-save-(compile)-run-test would in an ideal world be reduced to write-save-test. And since very few languages actually allow you to redefine objects, object methods, functions etc after you have actually run the application (without weird add-on etc), Javascript is pretty interesting in this area.

Anyway there are quite a few EMCAscript engines out there, most of them in C, and a few in other Bytecode or even scripting languages. But only one that is written in that new little magical language D!...

dmdscript, (also on dsource as walnut with a few hacks and the start of a new version), Has some serious benefits over all the other implementations.

- The source code is clear, readable, and not cluttered with memory allocation crap.
- It's fast
- It's small = small again making it easier to understand
- It's free of any major dependancies (apart from the compiler.. dmd or gdc )
- Writing extensions is (at least in theory) simple, and not full with learning about crazy cryptic macro's or custom memory allocation stuff (sound like anyone's favourite language?)

The only downside... - Dont expect alot of support, It looks like the project is one of Walter's little whims, that he lost interest in a bit. And licence-wise I'm not sure if GPL is the perfect licence for the project.. - but that's just a gut feeling, I'm far from a Licencing expert, but I suspect using it as a add-on for another project that is not GPL would be difficult. And I'm not sure how adding LGPL to a GPL application works either... Anyway..

I had a small amount of free time at the end of last week, Just having nailed the last few bugs in a project. So I thought I'd investigate dmdscript extensions on the pre-cursor to looking at modifying the GtkD bindings generator to create the dmdscript bindings...

First stage investigation, gave a little bit of imformation from the dmdscript site, along with a short note on the dsource Walnut project subversion server. But unfortunatly It's not really enough to get you started, and there are now 'Step by step' instructions on writing extensions, or adding Objects.

So here's my step by step guide to adding an object with methods to dmdscript... - in the extended entry..



(This assumes you have worked out how to download and install dmdscript)

Step 1 - Modify the testscript.d file.


First step is to make reference to your new classes in the 'main()' file which for dmdscript is testscript, a simple wrapper around the engine that deals with the command line interpretation. At the bottom of the file is a class called SrcFile that wraps the file[s] being included. The trick is to add call to your method that adds all your classes to the global scope.

import dtest; <<< Import the file/module that has your init code..

void compile() {
program = new Program();
Dtest_init(program.callcontext); <<<< Added.
program.compile(srcfile, buffer, null);
}

Step 2 - register all the new classes you need.

I came to the conclusion that a simple method would do for this. - rather than init functions in the classes, which is what the examples in dmdscript showed.



// register all the classes in this file..
static void Dtest_init(CallContext* cc) {
ThreadContext *tc = ThreadContext.getThreadContext();

// add constructor and prototype to our global list
// used so we can quickly access prototypes by looking the up.

// NOTE: you must make the constructor first, as the prototype
// this() method looks up on the ctorTable..
tc.ctorTable["test"] = new Dtest_Constructor(tc);
tc.protoTable["test"] = new Dtest_prototype(tc);


// Now actually register the WORD 'test' in the global namespace
cc.global.Put("test", tc.ctorTable["test"], DontEnum);


// add "test.prototype"
tc.ctorTable["test"].Put("prototype", tc.protoTable["test"],
DontEnum | DontDelete | ReadOnly);


// THIS IS FOR "new test.test()"


// first just store the constructor and prototype in the lookup table.
// AFAIK It's only put here so it can be quickly retrieved later..
// so the index name is not really that important.
tc.ctorTable["test.test"] = new Dtest_test_Constructor(tc);
tc.protoTable["test.test"] = new Dtest_test_prototype(tc);


// add the prototype "test.test.prototype.*"
tc.ctorTable["test.test"].Put("prototype", tc.protoTable["test.test"],
DontEnum | DontDelete | ReadOnly);

// now add the constructor to "test.test" , basically it adds the
// "test" property to the "test" constructor object.
tc.ctorTable["test"].Put("test", tc.ctorTable["test.test"] , 0);

}


Step 3 - Define The constructor Object.


The constructor function, (builds on the standard Javascript function, with the key difference that it contains a Construct method, that returns the new object.

  • The new object has to have the Dobject interface (hence it just extends it.)

The this() constructor is responsible for creating all the *static* methods of the class: eg. test.testworld2(). I've put all the actual implementations of these in the Dtest class. The dmdscript source usually just makes the functions (rather than static methods), but I think it saves a bit typing (eg. a prefix), and seems more natrual to actually have them defined in the class they are used with.

The logic behind this is simple - test() is a function, (which is also a constructor). test.testworld() is created by adding a property testworld to the function test(), and connecting it to a function.



class Dtest_Constructor : Dfunction
{

// register static methods..
this(ThreadContext *tc)
{
super(7, tc.Dfunction_prototype);
this.name = "test";
this.Put("testworld2",
new DnativeFunction(
&Dtest.testworld2, "testworld2", 0, Dfunction.getPrototype()
), 0);
}

// our constructor - called when new test() is called, must return an
// object. of type Dobject in the ret Value.;
void *Construct(CallContext *cc, Value *ret, Value[] arglist)
{
Dobject o;
o = new Dtest(); // can this be automated??
ret.putVobject(o);
return null;

}

}


Step 4 - Create a prototype


The prototype object is an extension of the main object (although I'm not 100% sure on the logic or necessity of this pattern at present).

It's purpose is to register the constructor
test.prototype.constructor()
and all the prototype properties.
eg.
test.prototype.testworld()
Which can also be used as
var a = new test; a.testworld();
or
test.prototype.testworld.call(new test());
and on and on....

Note the super() call which calls into the parent (which should set the classname variable.)

class Dtest_prototype : Dtest
{
// we register all our methods in here..
this(ThreadContext *tc)
{
super(tc.Dobject_prototype); // which assigns classname

this.Put(TEXT_constructor, tc.ctorTable[this.classname], DontEnum);
this.Put("testworld",
new DnativeFunction(
&Dtest.testworld, "testworld", 0, Dfunction.getPrototype()
), 0);

this.Put("query",
new DnativeFunction(
&Dtest.query, "query", 0, Dfunction.getPrototype()
), 0);

}
}



Step 5 - Finally create the object.


As per usual create the class wrapper, that must extend Dobject.. - so it can be passed around
class Dtest: Dobject 
{
Next Define all the static and instance methods (they are pretty much the same, and you can use the same real method to implement both for javascript)


Next the general prototype extension call.

// our instance methods..
static void* testworld( Dobject pthis, CallContext* cc, Dobject othis, Value* ret, Value[] arglist)
{
ret.putVstring("Hello World");
return null;
}

static void* query( Dobject pthis, CallContext* cc, Dobject othis, Value* ret, Value[] arglist)
{
int ret = mysql_query((cast(Dtest)othis).con, "select * from test");
return null;
}


// our static methods..
static void* testworld2( Dobject pthis, CallContext* cc, Dobject othis, Value* ret, Value[] arglist)
{
ret.putVstring("Hello World2");
return null;
}
Next the general constructor used by the prototype extension call.

    this(Dobject prototype)  
{
super(prototype);
this.classname = "test";
}

Now for the magic part. The class constructor that is called when you really create a new object in Javascript - The trick being, that if you are wrapping an object (or pointer of some other class - GD, Mysql , Gtc etc. you create the pointer here.. and any method of the object - in this conceptial example I've wrapped mysql connection, and shown how to retrieve it earlier.

    Mysqlconnection con;

this() // called when new Object is created...
{
ThreadContext *tc = ThreadContext.getThreadContext();
super( tc.protoTable["test"]);
this.classname = "test";
mysql_connect(con);
}
}

And finally, you will need to import all the required libs to make this work..

import dmdscript.dobject;
import dmdscript.value;
import dmdscript.script;
import dmdscript.dnative;
import dmdscript.property;
import dmdscript.threadcontext;
import dmdscript.dfunction;
import dmdscript.text;


Well, let's see If I get time to play with this more, As you can see, binding libraries is considerably simpler than any other of the P* languages....


Comments



Add a comment (requires javascript!)
Name
Email
Homepage
Comment Title
Comment
I am a link spammer?