Sunday, November 11, 2018

Inheritance with chakracore-delphi

I've just pushed some updates to chakracore-delphi.

A new feature worth mentioning is support for inheritance (I mean Javascript-style, "prototypical inheritance") in both directions. Suppose the following hierarchy:

First, here is Shape the "superclass", defined in Javascript:

function Shape(x, y) {
this.x = x;
this.y = y;
}
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
};
view raw shape.js hosted with ❤ by GitHub


Next comes Circle, a "subclass" of Shape, defined in Javascript as well:

function Circle(x, y, r) {
Shape.call(this, x, y);
this.r = r;
}
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
view raw circle.js hosted with ❤ by GitHub


The following is Rectangle, defined in Pascal, as a "subclass" of Shape:

type
TRectangle = class(TNativeObject)
class procedure InitializeInstance(AInstance: JsValueRef; Args: PJsValueRef; ArgCount: Word); override;
class function InitializePrototype(AConstructor: JsValueRef): JsValueRef; override;
end;
class procedure TRectangle.InitializeInstance(AInstance: JsValueRef; Args: PJsValueRef; ArgCount: Word);
var
ArgsArray: PJsValueRefArray absolute Args;
ShapeCtr: JsValueRef;
begin
// Shape.call(x, y);
ShapeCtr := JsGetProperty(JsGlobal, 'Shape');
JsCallFunction(ShapeCtr, [AInstance, ArgsArray^[0], ArgsArray^[1]]);
// this.w = w;
JsSetProperty(AInstance, UnicodeString('w'), ArgsArray^[2]);
// this.h = h;
JsSetProperty(AInstance, UnicodeString('h'), ArgsArray^[3]);
end;
class function TRectangle.InitializePrototype(AConstructor: JsValueRef): JsValueRef;
var
ShapeCtr, ShapePrototype: JsValueRef;
begin
ShapeCtr := JsGetProperty(JsGlobal, 'Shape');
ShapePrototype := JsGetProperty(ShapeCtr, 'prototype');
// Rectangle.prototype = Object.create(Shape.prototype);
Result := JsCreateObject(ShapePrototype);
end;
view raw rectangle.pas hosted with ❤ by GitHub


Here's Square, defined in Javascript again, as a "subclass" of Rectangle:

function Square(x, y, w) {
Rectangle.call(this, x, y, w, w);
}
Square.prototype = Object.create(Rectangle.prototype);
Square.prototype.constructor = Square;
view raw square.js hosted with ❤ by GitHub


And finally here are the tests, demonstrating that
- (Javascript) Circle descends from (Javascript) Shape
- (Pascal) Rectangle descends from (Javascript) Shape
- (Javascript) Square descends from (Pascal) Rectangle

procedure TNativeClassTestCase.TestInheritance;
// - Shape (x, y) => Object
// - Circle (x, y, r) => Shape (x, y)
// - Rectangle (x, y, w, h) => Shape (x, y)
// - Square (x, y, w) => Rectangle (x, y, w, w)
Runtime: TChakraCoreRuntime;
Context: TChakraCoreContext;
ShapeObj, CircleObj, RectangleObj, SquareObj: JsValueRef;
begin
Runtime := nil;
Context := nil;
try
Runtime := TChakraCoreRuntime.Create([]);
Context := TChakraCoreContext.Create(Runtime);
Context.Activate;
TRectangle.Project('Rectangle');
JsRunScript(SScript, SName);
// var shapeObj = new Shape(10, 10);
ShapeObj := JsNew('Shape', [IntToJsNumber(10), IntToJsNumber(10)]);
CheckValueType(JsObject, ShapeObj, 'shapeObj value type');
CheckTrue(JsInstanceOf(ShapeObj, 'Shape'), 'shapeObj instanceof Shape');
CheckFalse(JsInstanceOf(ShapeObj, 'Circle'), 'shapeObj instanceof Circle');
CheckFalse(JsInstanceOf(ShapeObj, 'Rectangle'), 'shapeObj instanceof Rectangle');
CheckFalse(JsInstanceOf(ShapeObj, 'Square'), 'shapeObj instanceof Square');
CheckEquals(10, JsNumberToInt(JsGetProperty(ShapeObj, 'x')), 'shapeObj.x before move');
CheckEquals(10, JsNumberToInt(JsGetProperty(ShapeObj, 'y')), 'shapeObj.y before move');
JsCallFunction('move', [IntToJsNumber(10), IntToJsNumber(10)], ShapeObj);
CheckEquals(20, JsNumberToInt(JsGetProperty(ShapeObj, 'x')), 'shapeObj.x after move');
CheckEquals(20, JsNumberToInt(JsGetProperty(ShapeObj, 'y')), 'shapeObj.y after move');
// var circleObj = new Circle(10, 10, 10);
CircleObj := JsNew('Circle', [IntToJsNumber(10), IntToJsNumber(10), IntToJsNumber(10)]);
CheckValueType(JsObject, CircleObj, 'circleObj value type');
CheckTrue(JsInstanceOf(CircleObj, 'Shape'), 'circleObj instanceof Shape');
CheckTrue(JsInstanceOf(CircleObj, 'Circle'), 'circleObj instanceof Circle');
CheckFalse(JsInstanceOf(CircleObj, 'Rectangle'), 'circleObj instanceof Rectangle');
CheckFalse(JsInstanceOf(CircleObj, 'Square'), 'circleObj instanceof Square');
CheckEquals(10, JsNumberToInt(JsGetProperty(CircleObj, 'x')), 'circleObj.x before move');
CheckEquals(10, JsNumberToInt(JsGetProperty(CircleObj, 'y')), 'circleObj.y before move');
JsCallFunction('move', [IntToJsNumber(10), IntToJsNumber(10)], CircleObj);
CheckEquals(20, JsNumberToInt(JsGetProperty(CircleObj, 'x')), 'circleObj.x after move');
CheckEquals(20, JsNumberToInt(JsGetProperty(CircleObj, 'y')), 'circleObj.y after move');
// var rectangleObj = new Rectangle(10, 10, 60, 40);
RectangleObj := JsNew('Rectangle', [IntToJsNumber(10), IntToJsNumber(10), IntToJsNumber(60), IntToJsNumber(40)]);
CheckValueType(JsObject, RectangleObj, 'rectangleObj value type');
CheckTrue(JsInstanceOf(RectangleObj, 'Shape'), 'rectangleObj instanceof Shape');
CheckFalse(JsInstanceOf(RectangleObj, 'Circle'), 'rectangleObj instanceof Circle');
CheckTrue(JsInstanceOf(RectangleObj, 'Rectangle'), 'rectangleObj instanceof Rectangle');
CheckFalse(JsInstanceOf(RectangleObj, 'Square'), 'rectangleObj instanceof Square');
CheckEquals(10, JsNumberToInt(JsGetProperty(RectangleObj, 'x')), 'rectangleObj.x before move');
CheckEquals(10, JsNumberToInt(JsGetProperty(RectangleObj, 'y')), 'rectangleObj.y before move');
JsCallFunction('move', [IntToJsNumber(10), IntToJsNumber(10)], RectangleObj);
CheckEquals(20, JsNumberToInt(JsGetProperty(RectangleObj, 'x')), 'rectangleObj.x after move');
CheckEquals(20, JsNumberToInt(JsGetProperty(RectangleObj, 'y')), 'rectangleObj.y after move');
// var squareObj = new Square(10, 10, 20);
SquareObj := JsNew('Square', [IntToJsNumber(10), IntToJsNumber(10), IntToJsNumber(20)]);
CheckValueType(JsObject, SquareObj, 'squareObj value type');
CheckTrue(JsInstanceOf(SquareObj, 'Shape'), 'squareObj instanceof Shape');
CheckFalse(JsInstanceOf(SquareObj, 'Circle'), 'squareObj instanceof Circle');
CheckTrue(JsInstanceOf(SquareObj, 'Rectangle'), 'squareObj instanceof Rectangle');
CheckTrue(JsInstanceOf(SquareObj, 'Square'), 'squareObj instanceof Square');
CheckEquals(10, JsNumberToInt(JsGetProperty(SquareObj, 'x')), 'squareObj.x before move');
CheckEquals(10, JsNumberToInt(JsGetProperty(SquareObj, 'y')), 'sqaureObj.y before move');
JsCallFunction('move', [IntToJsNumber(10), IntToJsNumber(10)], SquareObj);
CheckEquals(20, JsNumberToInt(JsGetProperty(SquareObj, 'x')), 'squareObj.x after move');
CheckEquals(20, JsNumberToInt(JsGetProperty(SquareObj, 'y')), 'squareObj.y after move');
finally
Context.Free;
Runtime.Free;
end;
end;


As always, you can grab the latest code on GitHub. Cheers!