From 8d72c38b1b332055285c274d62fe01c9fd83eb84 Mon Sep 17 00:00:00 2001 From: codeboost Date: Mon, 13 Jun 2011 17:06:42 +0200 Subject: [PATCH] Implement throw() support --- DOCUMENTATION.md | 126 ++++++++++++++++++++++++++++++++++++++-- beaparser.js | 10 ++-- beautils.js | 1 + classconvert.js | 13 ++++- src/beaparser.coffee | 9 +-- src/beautils.coffee | 10 +++- src/classconvert.coffee | 13 ++++- src/test.coffee | 9 ++- test.js | 7 ++- 9 files changed, 177 insertions(+), 21 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index a6f36aa..9440087 100755 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -252,6 +252,121 @@ This is because members of objects are also cast from Javascript and 'width' wil TODO: fix the exception so that it shows the correct invalid member. + +Inheritance +=========== + +Bea makes it possible to override C++ virtual functions from Javascript. +This means that when a native object calls a virtual function on an exposed class, that function can execute in javascript. + +Consider the following code: + + //C++ : example + //An abstract class with pure virtual function(s) + class INotify{ + public: + virtual ~INotify(){} + virtual void onJobFinished(int id, bool err) = 0; //pure virtual function + }; + + //An object which takes a pointer to a INotify: + class JobExecuter{ + public: + //At some point, the INotify::onJobFinished() will be called + int startJob(INotify* notify); + }; + +In C++, you would implement INotify, by subclassing it and implementing onJobFinished. Then you would use an object instance of the newly implemented +class as a parameter to JobExecuter::startJob(). +But in Javascript, you would do this: + + //Javascript + //Create an instance of the abstract class + var myNotify = new INotify(); + //Implement the virtual function + myNotify.onJobFinished = function(id, err){ + log ("Job " + id + " finished: " + err); + } + + myJobExec = new JobExecuter(); + + //Use the instance as parameter + var id = myJobExec.startJob(myNotify); + + +Now, when JobExecuter calls INotify::onJobFinished(), our Javascript function will be called. +This is possible because of what Bea does behind the scenes. + +If a @class contains virtual functions, Bea will generate a hidden derived class, which implement two versions of the virtual function: + + class D_INotify: public INotify{ + + public: + void onJobDone(int id, int err){ + //Lookup 'onJobDone' in the script context + //Convert arguments and call onJobDone() in the script or throw 'pure virtual function call', if function is pure + //Return whatever the JS function returns + } + + void d_onJobDone(int id, int err){ + //Call BaseClass::onJobDone(id, err); + //or throw Exception 'pure virtual function call' + } + }; + +The class D_INotify is exposed to the script, instead of INotify. + +onJobDone() is the function which is called by the native (C++) objects. It looks up the function in the javascript instance and calls it if +found. If the script does not override the function, the version from the base class is called. If the base class does not implement +it (eg. it is a pure virtual function), then an exception is thrown. + +d_onJobDone() is the function called by the script. It basically forwards the call to the base class version of the function. +If the base class does not implement it, an exception is thrown. + + +Multiple inheritance +==================== + +Bea allows multiple inheritance to be delcared. The base classes must be declared *before* the derived class. + + @class Base1 + void baseFunction() + + @class Base2 + virtual void base2Function() + + @class MyClass : public Base1, public Base2 + void myClassFunction() + + @class MyClass1: public Base1, public Base2 + @no-override + void myClass1Function() + + +MyClass will 'inherit' the methods from Base1 and Base2. The virtual functions can be overriden by the Javascript. +MyClass1 will 'inherit' the methods from Base1 and Base2, however the virtual functions can not be overriden from Javascript. +The @no-override directive tells the Bea compiler to treat all functions (including inherited ones) as non-virtual and will not +generate hidden subclasses. + + +Inheritance Cost +================ + +Just like all good things in life, being able to override C++ virtual functions from Javascript comes at a cost. +When a native object calls a virtual function on our objects, some additional processing must take place: +- The V8 engine is locked +- There is a lookup of the function in the javascript context +- The arguments are converted from native to JS +- The JS function is called and it`s return value is converted to native +- V8 is unlocked + +So a virtual function implemented in javascript may be quite expensive. +It is a good idea to implement performance-critical objects in C++ and expose them as non-virtual @classes to javascript. + + + + + Defining types ============== @@ -298,7 +413,7 @@ Tells the compiler to generate conversion functions which cast from int to size_ //... } -Note that the type (after resolving typedef's) must be different but cast-able from the castfrom type, otherwise the C++ compiler will be unable to specialize it: +Note that the type (after resolving typedefs) must be different but cast-able from the castfrom type, otherwise the C++ compiler will be unable to specialize it: //C++ typedef int int32; @@ -307,7 +422,7 @@ Note that the type (after resolving typedef's) must be different but cast-able f type int32 castfrom int In the C++ code, int32 will resolve to 'int' and the generated bea::Convert specialization will fail to compile, because it basically means bea::Convert. -typedef'd types must be entered simply as +typedef types must be entered simply as type int32 @@ -366,7 +481,8 @@ This can be called from Javascript: //Javascript MyClass.processPoint({x: 100, y: 50}); - + + @postAllocator ============== @@ -376,7 +492,7 @@ This can be called from Javascript: This directive lets you enter C++ code which will execute after your object has been allocated and wrapped into the javascript prototype object. This is guranteed to be the first method called after __constructor and before returning the new object to Javascript. It will generate 'v8::Handle __postAllocator(const v8::Arguments& args)'. -Refer to your object's 'this' through the generator-inserted '_this' variable in the @postAllocator code. +Refer to your object`s 'this' through the generator-inserted '_this' variable in the @postAllocator code. You will also use @postAllocator to make your object indexable (eg. accessible like object[index] from Javascript). Use args.This()->SetIndexedPropertiesToExternalArrayData(_this->yourPointer, kExternalUnsignedByteArray, _this->yourPointerSize); @@ -390,3 +506,5 @@ Example: args.This()->SetIndexedPropertiesToExternalArrayData(_this->datastart, kExternalUnsignedByteArray, bytes); //Make this object indexable V8::AdjustAmountOfExternalAllocatedMemory(sizeof(*_this) + bytes); //Tell the garbage collector the amount of external memory in use + + \ No newline at end of file diff --git a/beaparser.js b/beaparser.js index 7d352e7..351c01f 100644 --- a/beaparser.js +++ b/beaparser.js @@ -94,12 +94,14 @@ this.curNode.addChild(node); } else if (level < this.curNode.level) { tmp = this.curNode; - while (tmp.level > level) { + while (tmp && tmp.level > level) { tmp = tmp.parent; } - tmp.parent.addChild(node); - } else { - throw "Invalid indent on line " + (linenumber + 1) + ": '" + txt + "'"; + if (tmp && tmp.parent) { + tmp.parent.addChild(node); + } else { + throw "Invalid indent on line " + (linenumber + 1) + ": '" + txt + "'"; + } } return this.curNode = node; }; diff --git a/beautils.js b/beautils.js index 0592411..5519c70 100644 --- a/beautils.js +++ b/beautils.js @@ -138,6 +138,7 @@ })(); parseDeclaration = function(str, namespace) { var arg, args, argsEnd, argsStart, decla, fnArgs, fnDec, isPure, isStatic, isVirtual, parseArgs, _i, _len; + str = str.replace(/\s+throw\(\)\s*;*/, ''); argsStart = str.indexOf('('); argsEnd = str.lastIndexOf(')'); if (argsStart === -1 || argsEnd === -1) { diff --git a/classconvert.js b/classconvert.js index fe13479..d8e2029 100644 --- a/classconvert.js +++ b/classconvert.js @@ -402,7 +402,7 @@ fn.add("v8::HandleScope v8scope; v8::Handle v8retVal;"); cif = fn.add(new CodeBlock.CodeBlock("if (bea_derived_hasOverride(\"" + vfunc.name + "\"))")); arglist = _.map(vfunc.args, __bind(function(arg) { - return snippets.ToJS(this.nativeType(arg.type), arg.name, ''); + return snippets.ToJS(this.nativeType(arg.type), this.castArgument(arg), ''); }, this)); if (vfunc.args.length > 0) { cif.add("v8::Handle v8args[" + vfunc.args.length + "] = {" + (arglist.join(', ')) + "};"); @@ -499,6 +499,17 @@ } return nativeType; }; + ClassConverter.prototype.castVariable = function(type, varName) { + if (this.typeManager.isWrapped(type)) { + if (!type.isPointer) { + return '&' + varName; + } + } + return varName; + }; + ClassConverter.prototype.castArgument = function(arg) { + return this.castVariable(arg.type, arg.name); + }; ClassConverter.prototype.convertArg = function(arg, narg) { var argType, argv, nativeType; nativeType = this.nativeType(arg.type); diff --git a/src/beaparser.coffee b/src/beaparser.coffee index ea9767f..68b94da 100755 --- a/src/beaparser.coffee +++ b/src/beaparser.coffee @@ -81,11 +81,12 @@ class BeaParser else if level < @curNode.level #walk up until we find the parent tmp = @curNode - while tmp.level > level + while tmp && tmp.level > level tmp = tmp.parent - tmp.parent.addChild node - else - throw "Invalid indent on line " + (linenumber + 1) + ": '" + txt + "'" + if tmp && tmp.parent + tmp.parent.addChild node + else + throw "Invalid indent on line " + (linenumber + 1) + ": '" + txt + "'" @curNode = node diff --git a/src/beautils.coffee b/src/beautils.coffee index c153a57..6299c21 100755 --- a/src/beautils.coffee +++ b/src/beautils.coffee @@ -139,17 +139,21 @@ class Argument if cast then @type.cast = expandCast cast, @type parseDeclaration = (str, namespace) -> - + + #maybe we need to keep in mind that throw() was present + #but for now, we just ignore it... + #TODO: Figure throw() out + + str = str.replace /\s+throw\(\)\s*;*/, '' argsStart = str.indexOf '(' argsEnd = str.lastIndexOf ')' return false if argsStart is -1 or argsEnd is -1 - - args = trim str.slice(argsStart + 1, argsEnd) decla = trim(str.slice(0, argsStart)) + isPure = /\s*=\s*0/.test str parseArgs = (args) -> diff --git a/src/classconvert.coffee b/src/classconvert.coffee index 694dbf1..dffa668 100755 --- a/src/classconvert.coffee +++ b/src/classconvert.coffee @@ -104,8 +104,6 @@ class ClassConverter @baseType = @classType @nativeClassName = @options.derivedPrefix + @nativeClassName @classType = new beautils.Type @nativeClassName, targetNamespace - #@options.typeMgr.addDerivedClass(@baseType, @nativeType) - if not @isStatic @options.typeManager.addClassType @classType @@ -430,7 +428,7 @@ class ClassConverter cif = fn.add new CodeBlock.CodeBlock "if (bea_derived_hasOverride(\"#{vfunc.name}\"))" arglist = _.map vfunc.args, (arg) => - snippets.ToJS(@nativeType(arg.type), arg.name, '') + snippets.ToJS(@nativeType(arg.type), @castArgument(arg), '') if vfunc.args.length > 0 cif.add "v8::Handle v8args[#{vfunc.args.length}] = {#{arglist.join(', ')}};" @@ -533,6 +531,15 @@ class ClassConverter nativeType + #returns the proper cast for an argument name + castVariable: (type, varName) -> + if @typeManager.isWrapped type + if !type.isPointer then return '&' + varName + return varName + + castArgument: (arg) -> + return @castVariable arg.type, arg.name + #Create conversion code for a function argument convertArg: (arg, narg) -> nativeType = @nativeType arg.type diff --git a/src/test.coffee b/src/test.coffee index bb0440d..d13d312 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -1,6 +1,7 @@ util = require 'util' _ = require 'underscore' +debugIt = require('./debugit').debugIt; a1 = [{one: "1"}, {two: "2"}, {three: "3"}] a2 = [{one: "1"}, {two: "2"}, {three: "3"}, {four: "4"}] @@ -8,4 +9,10 @@ a2 = [{one: "1"}, {two: "2"}, {three: "3"}, {four: "4"}] res = _.isEqual a1, a2 -console.log res \ No newline at end of file + +debugIt -> + + fs = require 'fs' + +console.log res + diff --git a/test.js b/test.js index a226e26..2319ff7 100644 --- a/test.js +++ b/test.js @@ -1,7 +1,8 @@ (function() { - var a1, a2, res, util, _; + var a1, a2, debugIt, res, util, _; util = require('util'); _ = require('underscore'); + debugIt = require('./debugit').debugIt; a1 = [ { one: "1" @@ -23,5 +24,9 @@ } ]; res = _.isEqual(a1, a2); + debugIt(function() { + var fs; + return fs = require('fs'); + }); console.log(res); }).call(this);