Thread overview
Tutorial: building objects and passing them to script functions
Sep 30, 2007
nobody
Sep 30, 2007
Derek Parnell
Nov 24, 2007
Dejan Lekic
September 30, 2007
Tutorial: building objects and passing them to script functions
----------------------------------------------------------------


  1 Introduction
  2 ECMAScript overview/refresher
  3 Building ECMAScript Object objects
  4 Executing ECMAScripts with custom tailored arguments
  5 Uses of arguments
  6 Source listing


1 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.




2 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.




3 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);




4 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
  }




5 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.




6 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);

  }
September 30, 2007
On Sun, 30 Sep 2007 10:30:20 -0400, nobody wrote:

> Tutorial: building objects and passing them to script functions

Thank you for this informative tutorial. I've taken the liberty of posting this on the Wiki4D site ...

  http://www.prowiki.org/wiki4d/wiki.cgi?DMDScript/Tutorials

-- 
Derek Parnell
Melbourne, Australia
skype: derek.j.parnell
November 24, 2007
Wow, what a great tutorial! Great!