Published 2007-07-31 23:08:00
class A.X extend B.Y {
var a;
function A() {}
function C() {}
}
Into
A.X = function () {}
(function() {
foreach(i in B.Y.prototype)
{ A.prototype[i] = B.prototype[i];
}
).call())
A.X.prototype.a = false;
A.X.prototype.C = function() {}
While testing this with a real application I kept throwing up an message like "error getting Object on Undefined". - Which was not very helpfull. The reality was that often I mistyped the class name B.Y or A.X. But given the line numbering was not accurate (see later) and the message was not helpful, tracking down the problem proved quite difficult.
One cure to this problem lay in introducing Warnings - when a variable is looked up and not found, in Javascript it's type is "undefined" - So I added warnings in the interpreter to Flag up when that happened and show the line and the variable name that was missing.function A(v1, v2, v3) {So basically typeof() needed to silence those errors. The solution to this was to add an extra opcode. IRwarningHide(int). This was set to 1 before an typeof() call fetched the variables in the argument, and set to 0 after completion. I guess this could be used to set other runtime flags - and renamed to something more general.
v3 = typeof(v3) == "undefined" ? false : v3;
....
Include support
Originally I'm sure I read in the Javascript 2 specification that the include would load and execute the code when it encountered an include statement. After reading the ecma4 spec, it is clear that include basically imports the code in the external file exactly into the location where the import statement is. - and executes during the execution phase of the script.
class MyClass {
....
}
include "MyClassSubs.js";
>>>
>>> class MyClass.test { ..... }
>>>
If the include statement goes before 'class MyClass', then MyClass will not exist when the interperter tries to create MyClass.test.
There turned out to be a number of other issues with include process - First off was the fact that I had to work out how the code should deal with filenames. When compiling the AST into IRcodes, dmdscript combines the opcode with the line number into 4 bytes (a uint). The actual opcode only uses 1 byte (actually there are really only about 90 opcodes) there is 1 byte of padding and 2 bytes that store the line number.
Normally dmdscript had assumed that you just combined all the code together into one big file, and linenumbers where just offset's of the last line. (or in the case of my initial include implementation) started at 1 for each file.
So when an error got reported at runtime, the opcode executer just spat out the line number of the code that failed but had no idea in which file it came from. To fix this, involved keeping a small file registry and using the high bytes of the linenumber to store the file ID during compilation. and using the padding byte to store the file number in the IR opcodes.
Obviously this has a slight snag in the fact you can
only have 255 files included. and each file can have at most 65535
lines. (It' would probably not take too much to fix this.. - just
modify the
'combine' method in irstate.d and fix the struct in opcodes)
Scoping fixes
I spent quite a while trying to solve a simple scoping bug with nested functions. And I'm still not sure how reliable the fix is.
This is the problem:
function A() {
var c = "fred";
function d() {
println("c is " + c);
}
d();
}
A();
Should just print "c is fred", however, since a normal function definiton hides the outside scope (except globals). This was failing. I ended up flagging this type of locally defined functions. Then when scope is created during the Call() method, copying all of the inherited scope, rather than just the root scope. - I still think this needs more looking at, but the kludge works for the time being with simple nested functions.
Killer debuggingEven with all the improvements with error reporting on includes, there where still times when I was testing when the program would just segfault. (and before I sorted the line numbers out, I needed a way to work out what was going on - so I could understand the flow). The fun thing I found was print() method in the opcodes.d file. Which dumps an opcode and arugments to a readable string. By adding a conditional call in the opcode run loop to call this I can now do opcode level logging. Just run any of the cli versions of the code with the "-d" option. You can see the file, line number of the all the executed opcodes. making it simple to spot where things go wrong.
At present it also dumps the all tokens, top level Statements as the AST sees them, and opcode runtime. (probably needs a bit of fine tuning..)