Last update March 7, 2012

DMDScript /
Tutorials



DMDScript Tutorials

Building objects and passing them to script functions

This article originally appeared in the DigitalMars newsgroup "DMDScript" by 'nobody' Post:DMDScript/339

Introduction

Before starting I wanted to thank Walter Bright for creating DMDScript. It felt like a huge stroke of luck as I tend to use JavaScript on an almost weekly basis for all kinds of little things. When DMDScript was released I knew it was only a matter of time before I would find a way to make use of it.

I have noticed in this newsgroup there is a fair amount of information about implementing new object types but very little about interacting with DMDScript data structures. In fact the Program class is missing a version of execute which allows you to pass in a custom Darray and this omission seems to suggest that building a Darray oneself is too much trouble. This tutorial is designed to help people with minimal ECMAScript background understand how simple it is to interact with ECMASCript objects from D.

It is my hope that this tutorial will help people considering using DMDScript understand what they might get out of it and help people who have decided to use DMDScript get more out of it.

ECMAScript overview/refresher

ECMAScript is a loosely typed language; all variables are declared using the "var" keyword as if it were a C type:
var i = 0, pi = Math.PI, str = "Hello Script!";

Functions need only declare the variable's name:

   function foo(i,pi,str)
   {
     println(str + (i+pi));
   }

Functions are variadic; every function call has an implicit parameter array named arguments:

   function bar()
   {
     if(arguments.length == 3)
       return foo(arguments[0], arguments[1], arguments[2])
   }

   bar(i,pi,str); // Hello Script!4.141592653589793

When a script is evaluated the entire text makes up the body of the unnamed global function:

   function <unamedGlobalFunction>()
   {
   // Script's first line

   // Script's body here

   // Script's last line
   }

One of the more interesting aspects of scripts is properties:

   var struct_ish =
   {
   	width: 8,
   	height: 12,
   	cells: new Array(8*12)
   };

At the core objects map properties to their corresponding values. Array objects illustrate this well; they have a length property whose value indicates its size and the things populating the array are also accessed by properties:

   vary arrEx = new Array();
   arrEx[0] = 1;
   arrEx["apple"] = Math.PI;
   arrEx[Math.E] = "mc^2";

   Math.E*arrEx["apple"] + arrEx[0]; // 9.539734222673566

Even more interesting is taking a look at how functions are used to script OOP functionality with prototypes. However, this section was meant to be just long enough to ensure a solid enough base for following the rest of the tutorial.

Building ECMAScript Object objects

Now as promised it is time to start working on building objects in D and passing them as parameters to a script function. The first thing to cover is loading a script file, compiling it and executing it:
   char[] buff = cast(char[]) read(scriptFilename);
   Program prong = new Program();
   prog.compile(scriptFilename, buff, null);
   apogee);

Three steps in four lines! Of course there are tons of ECMAScript interpreters out there so just loading a script and running it isn't yet worth the extra effort. The first thing to try is automatically calling a script function:

   char[] buff = cast(char[]) read(scriptFilename) ~ 
                    "smain(arguments);";

This addition will take the arguments passed to the global function and pass them right along to the smain function. The null passed to the Program's execute function can be replaced with an array of strings and those will be passed to smain:

   void main(char[][] args)
   {
     char[] scriptFilename = "script.js";
     char[] buff = cast(char[]) read(scriptFilename) ~ 
                      "smain(arguments);";
     Program prog = new Program();
     prog.compile(scriptFilename, buff, null);
     prog.execute(args);
   }

The drawback with this simple approach is that if you wanted to pass some particular value to smain then unless it happens to be an array of strings you can't. Now consider the following:

   {
   	width: 8,
   	height: 12,
   	cells: new Array(8*12)
   }

This is simply an object with three properties: width, height and cells. Actually constructing this means looking through the dobject.d, darray.d and value.d to discover how they are instantiated and used. Here are the interesting bits:

   // from script.d
   alias char tchar;
   alias tchar[] d_string;
   alias double d_number;

   // from property.d
   DontDelete = 0x004,

   class Dobject
   {
     this(Dobject prototype) { }

     Put(d_string PropertyName, Value* value, uint attributes) { }

     Put(d_string PropertyName, Dobject o, uint attributes) { }

     Get(d_string PropertyName)
   }


   class Darray : Dobject
   {
     this()

     this(Dobject prototype) { }

     Put(d_uint32 index, Value* value, uint attributes)

     Get(d_uint32 index)
   }


   struct Value
   {
     void putVnumber(d_number n) { }

     void putVobject(Dobject o) { }

     void putVstring(d_string s) { }
   }

So Dobject's Put function can be used to attach the numbers to the first two properties:

   Dobject dobj = new Dobject(null);
   dobj.Put("width",  new Value, DontDelete);
   dobj.Get("width").putVnumber(8);
   dobj.Put("height", new Value, DontDelete);
   dobj.Get("height").putVnumber(12);

Since Darray is a subclass of Dobject the other Put function can attach the Darray to the last property. Likewise Darray's Put function can attach Values to an Array object:

   Darray darr = new Darray();
   for(d_uint32 i = 0; i < 8*12; i+=1)
   {
     darr.Put(i, new Value, DontDelete);
     darr.Get(i).putVstring("x");
   }

   dobj.Put("cells", darr, DontDelete);

Now dobj is constructed and ready to be passed to smain. Since the actual argument being passed (arguments) will be a Darray there remains the issue of stuffing dobj into a Darray:

   Darray arguments = new Darray();
   arguments.Put(0, new Value, DontDelete);
   arguments.Get(0).putVobject(dobj);

Executing ECMAScripts with custom tailored arguments

Now with the arguments actually created the only obstacle that remains is actually getting the script to execute with the custom arguments. The ideal solution would be for the Program class to overload execute:
   class Program
   {
     void execute(char[][] args)
     void execute(Darray args)
   }

The problem is that overloading requires making a change to program.d and every time a new version of DMDScript is installed the code has to be changed and the library recompiled. That sort of change is more of a feature request. Instead it is much simpler to just copy the code into a new function. After replacing all the implicit and explicit references to this the only remaining change is to comment out a few lines and Put args directly into dglobal:

   void execute(Program p, Darray args)
   {
     // SNIP
     //  Darray arguments;
     Dobject dglobal = cc.global;
     Program program_save;

	// Set argv and argc for execute
        //  arguments = new Darray();
     dglobal.Put(TEXT_arguments, args, DontDelete | DontEnum);
    //  arguments.length.putVnumber(args.length);
    //  for (d_uint32 i = 0; i < args.length; i++)
    //  {
    //    arguments.Put(i, &args[i], DontEnum);
    //  }
     // SNIP
   }

Uses of arguments

The ability to pass custom tailored arguments to a script function opens a few doors. The most obvious is that one should be able to pass arbitrarily complex data structures into a script function.

The most interesting door that is opened is a direct result of the fact function arguments are passed by reference. This means that once an arguments Darray is created then any changes made by the script are visible from D. Since changes are visible in D they can be passed along from one script to another, or to the same script between multiple executions. So it is fully possible to write a script to initialize the arguments and have another script operate on them:

   void main(char[][] args)
   {
     char[] iFilename = "init.js";
     char[] sFilename = "script.js";
     char[] buffI = cast(char[]) read(iFilename) ~ 
                         "sinit(arguments);";
     char[] buffS = cast(char[]) read(sFilename) ~ 
                         "smain(arguments);";
 
     Program pI = new Program(), pS = new Program();
     prog.compile(iFilename, buffI, null);
     prog.compile(iFilename, buffS, null);
 
     Darray arguments = new Darray();
 
     execute(pI, arguments);
     execute(pS, arguments);
   }

Finally, a word of warning:

 Be warned when multithreading, threads are subtle and quick to anger.

Source listing

   import dmdscript.darray;
   import dmdscript.program;
   import dmdscript.dobject;
   import dmdscript.property;
   import dmdscript.script;
   import dmdscript.opcodes;
   import dmdscript.text;
   import dmdscript.value;

   import std.file;
   import std.stdio;

   import std.c.stdlib;

   void main(char[][] args)
   {
     char[] scriptFilename = "script.js";
     char[] buff = cast(char[]) read(scriptFilename) ~ 
           "smain(arguments);";

     Program prog = new Program();
     prog.compile(scriptFilename, buff, null);

     Dobject dobj = new Dobject(null);
     dobj.Put("width",  new Value, DontDelete);
     dobj.Get("width").putVnumber(8);
     dobj.Put("height", new Value, DontDelete);
     dobj.Get("height").putVnumber(12);

     Darray darr = new Darray();
     for(d_uint32 i = 0; i < 8*12; i+=1)
     {
       darr.Put(i, new Value, DontDelete);
       darr.Get(i).putVstring("x");
     }

     dobj.Put("cells", darr, DontDelete);

     Darray arguments = new Darray();
     arguments.Put(0, new Value, DontDelete);
     arguments.Get(0).putVobject(dobj);

     execute(prog, arguments);
   }


   void execute(Program p, Darray args)
   {
     // ECMA 10.2.1
     //writef("Program.execute(argc = %d, argv = %p)\n", argc,
                    argv);
     //writef("Program.execute()\n");

     p.initContext();

     Value[] locals;
     Value ret;
     Value* result;
     CallContext *cc = p.callcontext;
     //  Darray arguments;
     Dobject dglobal = cc.global;
     Program program_save;

     // Set argv and argc for execute
     //  arguments = new Darray();
     dglobal.Put(TEXT_arguments, args, DontDelete | DontEnum);
     //  arguments.length.putVnumber(args.length);
     //  for (d_uint32 i = 0; i < args.length; i++)
     //  {
     //    arguments.Put(i, &args[i], DontEnum);
     //  }

     Value[] p1;
     Value* v;
     version (Win32)    // eh and alloca() not working under linux
     {
       if (p.globalfunction.nlocals < 128)
         v = cast(Value*)alloca(p.globalfunction.nlocals * 
                  Value.sizeof);
     }
     if (v)
         locals = v[0 .. p.globalfunction.nlocals];
     else
     {
         p1 = new Value[p.globalfunction.nlocals];
         locals = p1;
     }

     // Instantiate global variables as properties of global
     // object with 0 attributes
     p.globalfunction.instantiate(cc.variable, 0);


     program_save = p.getProgram();
     try
     {
       p.setProgram(p);
       ret.putVundefined();
       result = cast(Value*)IR.call(cc, cc.global, 
           p.globalfunction.code, &ret, locals.ptr);
     }
     finally
     {
       p.setProgram(program_save);
     }

     if (result)
     {
       ErrInfo errinfo;

       result.getErrInfo(&errinfo, cc.linnum);
       cc.linnum = 0;
       delete p1;
       throw new ScriptException(&errinfo);
     }

     delete p1;
   }


script.js
   function smain(args)
   {
     println("|arguments|: " + arguments.length);
     println("|args|: " + args.length);

     println("----------------");

     for(key in args[0])
       println("args[0]."+key+": " + args[0][key]);

     println("----------------");

     println("args[0].cells: " + args[0].cells);
     println("args[0].width: " + args[0].width);
     println("args[0].height: " + args[0].height);

   }

FrontPage | News | TestPage | MessageBoard | Search | Contributors | Folders | Index | Help | Preferences | Edit

Edit text of this page (date of last change: March 7, 2012 19:08 (diff))