mirror of https://github.com/svaarala/duktape.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
563 lines
14 KiB
563 lines
14 KiB
/* FIXME: bound function cases */
|
|
|
|
/*
|
|
* Parsing tests for 'new' expressions. These are rather detailed because
|
|
* the grammar for the following productions is a bit awkward:
|
|
*
|
|
* - LeftHandSideExpression
|
|
* - CallExpression
|
|
* - NewExpression
|
|
* - MemberExpression
|
|
*/
|
|
|
|
var t;
|
|
|
|
/*===
|
|
Basic1
|
|
Basic1
|
|
Basic2 arg1 arg2
|
|
Basic3 arg1 arg2
|
|
Basic3 arg1 arg2
|
|
Basic4 arg1 arg2
|
|
===*/
|
|
|
|
/* Basic cases where constructor is from a MemberExpression (but not another 'new') */
|
|
|
|
function Basic1() {
|
|
}
|
|
Basic1.prototype = { 'name': 'Basic1' };
|
|
|
|
function Basic2(x,y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
Basic2.prototype = { 'name': 'Basic2' };
|
|
|
|
basic3 = {
|
|
'constructor': function(x,y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
};
|
|
basic3.constructor.prototype = { 'name': 'Basic3' };
|
|
|
|
try {
|
|
// without parenthesis (arguments)
|
|
eval("t = new Basic1; print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
// with no arguments (semantically same as above)
|
|
eval("t = new Basic1(); print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
// with arguments
|
|
eval("t = new Basic2('arg1', 'arg2'); print(t.name, t.x, t.y);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
// constructor from a property access (MemberExpression)
|
|
eval("t = new basic3.constructor ('arg1', 'arg2'); print(t.name, t.x, t.y);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
// constructor from a property access (MemberExpression), bracket syntax
|
|
eval("t = new basic3['constructor'] ('arg1', 'arg2'); print(t.name, t.x, t.y);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
// constructor from a function expression (from MemberExpression)
|
|
// Note: here the 'name' property is not inherited from prototype
|
|
eval("t = new function(x,y) {this.x=x;this.y=y;this.name='Basic4';} ('arg1', 'arg2'); print(t.name, t.x, t.y);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
/*===
|
|
TypeError
|
|
getTarget/f
|
|
===*/
|
|
|
|
/* The target for 'new' may be any MemberExpression, but it cannot be a
|
|
* CallExpression. Thus:
|
|
*
|
|
* new foo() ()
|
|
*
|
|
* Will be parsed as ( new foo() ) (). For the intended effect, parenthesis
|
|
* are needed:
|
|
*
|
|
* new (foo()) ()
|
|
*/
|
|
|
|
function getTarget() {
|
|
var f = function() {
|
|
}
|
|
f.prototype = { 'name': 'getTarget/f' };
|
|
return f;
|
|
}
|
|
|
|
try {
|
|
// this will evaluate "new getTarget ()" and then attempt to call the
|
|
// instance, which is not callable -> TypeError
|
|
eval("t = new getTarget() (); print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
// this will first evaluate "getTarget()" to get the constructor,
|
|
// and then call the constructor
|
|
eval("t = new (getTarget()) (); print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
/*===
|
|
NewNew2 arg1
|
|
===*/
|
|
|
|
/* The 'new' operator may occur multiple times, although this is
|
|
* quite unusual. It binds more loosely than MemberExpressions
|
|
* and acts right associative.
|
|
*
|
|
* Example below:
|
|
*
|
|
* new new Foo()
|
|
* === new (new Foo())
|
|
*
|
|
* This is derived as (using parenthesis for illustration):
|
|
*
|
|
* NewExpression => new NewExpression
|
|
* => new (MemberExpression)
|
|
* => new (new MemberExpression Arguments)
|
|
* => new (new Foo ())
|
|
*/
|
|
|
|
function NewNew1(x) {
|
|
function f() {
|
|
// snatch 'x' from closure
|
|
this.x = x;
|
|
}
|
|
f.prototype = { 'name': 'NewNew2' };
|
|
return f;
|
|
}
|
|
NewNew1.prototype = { 'name': 'NewNew1' };
|
|
|
|
try {
|
|
eval("t = new new NewNew1 ('arg1'); print(t.name, t.x);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
/*===
|
|
Bal1A called, this.name=Bal1A
|
|
Bal1A
|
|
Bal1A called, this.name=Bal1A
|
|
Bal1A
|
|
Bal1B called, this.name=Bal1B
|
|
Bal1B/f called, this.Number.POSITIVE_INFINITY=Infinity
|
|
Bal1B/f return value
|
|
Bal1C called, this.name=Bal1C
|
|
Bal1C/f called, this.Number.POSITIVE_INFINITY=Infinity
|
|
Bal1C/f/g called, this.Number.POSITIVE_INFINITY=Infinity
|
|
Bal1C/f/g return value
|
|
===*/
|
|
|
|
/*
|
|
* Parenthesis "balance tests".
|
|
*
|
|
* new Foo basic case
|
|
* new Foo () parens associated with 'new', not a func call
|
|
* new Foo () () (new Foo ()) ()
|
|
* new Foo () () () ((new Foo ()) ()) ()
|
|
*/
|
|
|
|
function Bal1A() {
|
|
// printing out this.name indicates whether we're being
|
|
// called as a constructor or as a function
|
|
print("Bal1A called, this.name=" + this.name);
|
|
}
|
|
Bal1A.prototype = { 'name': 'Bal1A' };
|
|
|
|
function Bal1B() {
|
|
print("Bal1B called, this.name=" + this.name);
|
|
|
|
function f() {
|
|
// f() is called as a normal function, this is bound to the
|
|
// global object; printing out a specific value accessible
|
|
// through the global object proves the point.
|
|
|
|
print("Bal1B/f called, this.Number.POSITIVE_INFINITY=" + this.Number.POSITIVE_INFINITY);
|
|
return "Bal1B/f return value";
|
|
}
|
|
f.name = "Bal1B/f"; // not inherited
|
|
|
|
// Returned value replaces default constructed object; this return
|
|
// value does not have Bal1B.prototype as its internal prototype.
|
|
return f;
|
|
}
|
|
Bal1B.prototype = { 'name': 'Bal1B' }; // has no effect
|
|
|
|
function Bal1C() {
|
|
print("Bal1C called, this.name=" + this.name);
|
|
|
|
function f() {
|
|
print("Bal1C/f called, this.Number.POSITIVE_INFINITY=" + this.Number.POSITIVE_INFINITY);
|
|
function g() {
|
|
print("Bal1C/f/g called, this.Number.POSITIVE_INFINITY=" + this.Number.POSITIVE_INFINITY);
|
|
return "Bal1C/f/g return value";
|
|
}
|
|
return g;
|
|
}
|
|
f.name = "Bal1C/f"; // not inherited
|
|
return f;
|
|
}
|
|
Bal1C.prototype = { 'name': 'Bal1C' }; // has no effect
|
|
|
|
try {
|
|
eval("var t = new Bal1A; print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
eval("var t = new Bal1A (); print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
eval("var t = new Bal1B () (); print(t);"); // here 't' is return value from Bal1B/f
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
eval("var t = new Bal1C () () (); print(t);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
/*===
|
|
Bal2A called, this.name=Bal2A
|
|
Bal2A/f called, this.name=Bal2A/f
|
|
Bal2A/f
|
|
Bal2B called, this.name=Bal2B
|
|
Bal2B/f called, this.name=Bal2B/f
|
|
Bal2B/f/g called, this.name=Bal2B/f/g
|
|
Bal2B/f/g
|
|
Bal2B called, this.name=Bal2B
|
|
Bal2B/f called, this.name=Bal2B/f
|
|
Bal2B/f/g called, this.name=Bal2B/f/g
|
|
Bal2B/f/g
|
|
Bal2B called, this.name=Bal2B
|
|
Bal2B/f called, this.name=Bal2B/f
|
|
Bal2B/f/g called, this.name=Bal2B/f/g
|
|
Bal2B/f/g
|
|
Bal2B called, this.name=Bal2B
|
|
Bal2B/f called, this.name=Bal2B/f
|
|
Bal2B/f/g called, this.name=Bal2B/f/g
|
|
Bal2B/f/g
|
|
Bal2C called, this.name=Bal2C
|
|
Bal2C/f called, this.name=Bal2C/f
|
|
Bal2C/f/g called, this.name=Bal2C/f/g
|
|
Bal2C/f/g/h called, this.Number.POSITIVE_INFINITY=Infinity
|
|
Bal2C/f/g/h return value
|
|
===*/
|
|
|
|
/*
|
|
* Parenthesis and 'new' "balance tests".
|
|
*
|
|
* new new Foo new (new Foo)
|
|
* new new new Foo new (new (new Foo))
|
|
* new new new Foo () new (new (new Foo ()))
|
|
* new new new Foo () () new (new (new Foo ()) ())
|
|
* new new new Foo () () () new (new (new Foo ()) ()) () up to this point, parens associated with 'new' calls
|
|
* new new new Foo () () () () (new (new (new Foo ()) ()) ()) () last is a func call
|
|
*/
|
|
|
|
function Bal2A() {
|
|
print('Bal2A called, this.name=' + this.name);
|
|
|
|
function f() {
|
|
print("Bal2A/f called, this.name=" + this.name);
|
|
}
|
|
f.prototype = { 'name': 'Bal2A/f' };
|
|
|
|
// return value (instance) of inner ("new Bal2A") is used as the
|
|
// constructor for the outer "new" call.
|
|
return f;
|
|
}
|
|
Bal2A.prototype = { 'name': 'Bal2A' };
|
|
|
|
function Bal2B() {
|
|
print('Bal2B called, this.name=' + this.name);
|
|
|
|
function f() {
|
|
print('Bal2B/f called, this.name=' + this.name);
|
|
|
|
function g() {
|
|
print('Bal2B/f/g called, this.name=' + this.name);
|
|
}
|
|
g.prototype = { 'name': 'Bal2B/f/g' };
|
|
|
|
return g;
|
|
}
|
|
f.prototype = { 'name': 'Bal2B/f' };
|
|
|
|
return f;
|
|
}
|
|
Bal2B.prototype = { 'name': 'Bal2B' };
|
|
|
|
function Bal2C() {
|
|
print('Bal2C called, this.name=' + this.name);
|
|
|
|
function f() {
|
|
print('Bal2C/f called, this.name=' + this.name);
|
|
|
|
function g() {
|
|
print('Bal2C/f/g called, this.name=' + this.name);
|
|
|
|
function h() {
|
|
print('Bal2C/f/g/h called, this.Number.POSITIVE_INFINITY=' + this.Number.POSITIVE_INFINITY);
|
|
return 'Bal2C/f/g/h return value';
|
|
}
|
|
|
|
return h;
|
|
}
|
|
g.prototype = { 'name': 'Bal2C/f/g' };
|
|
|
|
return g;
|
|
}
|
|
f.prototype = { 'name': 'Bal2C/f' };
|
|
|
|
return f;
|
|
}
|
|
Bal2C.prototype = { 'name': 'Bal2C' };
|
|
|
|
try {
|
|
eval("t = new new Bal2A; print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
eval("t = new new new Bal2B; print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
eval("t = new new new Bal2B (); print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
eval("t = new new new Bal2B () (); print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
eval("t = new new new Bal2B () () (); print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
eval("t = new new new Bal2C () () () (); print(t);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
/*===
|
|
Bal3A called, this.name=Bal3A, x=1, y=2, z=3
|
|
Bal3A/f called, this.name=Bal3A/f, x=4, y=5
|
|
z is from outer scope: z=3
|
|
Bal3A/f/g called, this.Number.POSITIVE_INFINITY=Infinity, a=6, b=7
|
|
the following are from outer scope: x=4, y=5, z=3
|
|
Bal3A/f/g return value
|
|
===*/
|
|
|
|
/* Parenthesis association test with argument values:
|
|
*
|
|
* new new Foo (1,2,3) (4,5) (6,7) last parens are a call
|
|
*/
|
|
|
|
function Bal3A(x,y,z) {
|
|
print("Bal3A called, this.name=" + this.name +
|
|
", x=" + x + ", y=" + y + ", z=" + z);
|
|
|
|
function f(x,y) {
|
|
print("Bal3A/f called, this.name=" + this.name +
|
|
", x=" + x + ", y=" + y);
|
|
print("z is from outer scope: z=" + z);
|
|
|
|
function g(a,b) {
|
|
print("Bal3A/f/g called, this.Number.POSITIVE_INFINITY=" + this.Number.POSITIVE_INFINITY +
|
|
", a=" + a + ", b=" + b);
|
|
print("the following are from outer scope: " +
|
|
"x=" + x + ", y=" + y + ", z=" + z);
|
|
|
|
return "Bal3A/f/g return value";
|
|
}
|
|
|
|
// replace created object with function g
|
|
return g;
|
|
}
|
|
f.prototype = { 'name': 'Bal3A/f' };
|
|
|
|
return f;
|
|
}
|
|
Bal3A.prototype = { 'name': 'Bal3A' };
|
|
|
|
try {
|
|
eval("t = new new Bal3A (1,2,3) (4,5) (6,7); print(t);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
/*===
|
|
Misc1 called, this.name=Misc1
|
|
foo getter called
|
|
Misc1/f called, this.name=Misc1/f
|
|
Misc1/f
|
|
Misc1 called, this.name=Misc1
|
|
foo getter called
|
|
Misc1/f called, this.name=Misc1/f
|
|
Misc1/f
|
|
Misc2 called, this.name=Misc2
|
|
foo getter called
|
|
Misc2/f called, this.name=Misc2/f, x=1, y=2
|
|
Misc2/f
|
|
===*/
|
|
|
|
/*
|
|
* Consider the following:
|
|
*
|
|
* new new Foo () . foo
|
|
*
|
|
* If we start from LeftHandSideExpression and choose CallExpression,
|
|
* it is not possible to create any 'new' expression without arguments,
|
|
* like the outer 'new' above. More specifically:
|
|
*
|
|
* LeftHandSideExpression => CallExpression
|
|
* => CallExpression '.' IdentifierName
|
|
* => MemberExpression '.' IdentifierName
|
|
*
|
|
* At this point there is no way to expand MemberExpression and create
|
|
* two 'new' tokens and only one set of arguments.
|
|
*
|
|
* If we start from LeftHandSideExpression and choose NewExpression
|
|
* (brackets for emphasis):
|
|
*
|
|
* LeftHandSideExpression => NewExpression
|
|
* => 'new' NewExpression
|
|
* => 'new' MemberExpression
|
|
* => 'new' [ MemberExpression '.' IdentifierName ]
|
|
* => 'new' [ [ 'new' MemberExpression Arguments ] '.' IdentifierName ]
|
|
* => 'new' [ [ 'new' 'Foo' '(' ')' ] '.' 'foo' ]
|
|
*
|
|
* So, in other words, we get:
|
|
*
|
|
* new new Foo () . foo == new ((new Foo ()) . foo)
|
|
*
|
|
* And:
|
|
*
|
|
* new new Foo () . foo () == new ((new Foo ()) . foo) ()
|
|
*
|
|
* where the last parens are related to the outer 'new'.
|
|
*/
|
|
|
|
function Misc1() {
|
|
print('Misc1 called, this.name=' + this.name);
|
|
|
|
function f() {
|
|
print('Misc1/f called, this.name=' + this.name);
|
|
}
|
|
f.prototype = { 'name': 'Misc1/f' };
|
|
|
|
// return an object whose 'foo' property is read by the caller
|
|
// and used as the next constructor
|
|
return { get foo() { print ('foo getter called'); return f } };
|
|
}
|
|
Misc1.prototype = { 'name': 'Misc1' };
|
|
|
|
function Misc2() {
|
|
print('Misc2 called, this.name=' + this.name);
|
|
|
|
function f(x,y) {
|
|
print('Misc2/f called, this.name=' + this.name +
|
|
', x=' + x + ', y=' + y);
|
|
}
|
|
f.prototype = { 'name': 'Misc2/f' };
|
|
|
|
// return an object whose 'foo' property is read by the caller
|
|
// and used as the next constructor
|
|
return { get foo() { print ('foo getter called'); return f } };
|
|
}
|
|
Misc2.prototype = { 'name': 'Misc2' };
|
|
|
|
try {
|
|
eval("t = new new Misc1 () . foo; print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
// exactly same, explicit parens
|
|
eval("t = new ((new Misc1 ()) . foo); print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
try {
|
|
// add arguments to outer 'new'
|
|
eval("t = new new Misc2 () . foo (1,2); print(t.name);");
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|
|
/*===
|
|
1 2 3 4
|
|
1 2 undefined undefined
|
|
true
|
|
===*/
|
|
|
|
/* If a constructor returns a replacement value, the internal prototype
|
|
* of that object will not be set by the 'new' call.
|
|
*/
|
|
function Cons1() {
|
|
this.foo = 1;
|
|
this.bar = 2;
|
|
}
|
|
Cons1.prototype = { quux: 3, baz: 4 };
|
|
|
|
function Cons2() {
|
|
return { foo: 1, bar: 2 };
|
|
}
|
|
Cons2.prototype = { quux: 3, baz: 4 };
|
|
|
|
try {
|
|
t = new Cons1();
|
|
print(t.foo, t.bar, t.quux, t.baz);
|
|
|
|
// the internal prototype of 't' will be Object.prototype here
|
|
t = new Cons2();
|
|
print(t.foo, t.bar, t.quux, t.baz);
|
|
print(Object.getPrototypeOf(t) === Object.prototype);
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
|