/interpreter/tags/at_build150307/test/edu/vub/at/objects/natives/NATObjectClosureTest.java
Java | 529 lines | 295 code | 57 blank | 177 comment | 4 complexity | 42986768941102c053ad462c2f58aa5d MD5 | raw file
1/** 2 * AmbientTalk/2 Project 3 * NATObjectClosureTest.java created on Jul 25, 2006 at 10:51:44 AM 4 * (c) Programming Technology Lab, 2006 - 2007 5 * Authors: Tom Van Cutsem & Stijn Mostinckx 6 * 7 * Permission is hereby granted, free of charge, to any person 8 * obtaining a copy of this software and associated documentation 9 * files (the "Software"), to deal in the Software without 10 * restriction, including without limitation the rights to use, 11 * copy, modify, merge, publish, distribute, sublicense, and/or 12 * sell copies of the Software, and to permit persons to whom the 13 * Software is furnished to do so, subject to the following 14 * conditions: 15 * 16 * The above copyright notice and this permission notice shall be 17 * included in all copies or substantial portions of the Software. 18 * 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 * OTHER DEALINGS IN THE SOFTWARE. 27 */ 28package edu.vub.at.objects.natives; 29 30import edu.vub.at.AmbientTalkTest; 31import edu.vub.at.eval.Evaluator; 32import edu.vub.at.exceptions.InterpreterException; 33import edu.vub.at.exceptions.XIllegalOperation; 34import edu.vub.at.exceptions.XUndefinedField; 35import edu.vub.at.objects.ATContext; 36import edu.vub.at.objects.ATMethod; 37import edu.vub.at.objects.ATObject; 38import edu.vub.at.objects.grammar.ATStatement; 39import edu.vub.at.objects.grammar.ATSymbol; 40import edu.vub.at.objects.natives.grammar.AGBegin; 41import edu.vub.at.objects.natives.grammar.AGDefFunction; 42import edu.vub.at.objects.natives.grammar.AGSelf; 43import edu.vub.at.objects.natives.grammar.AGSymbol; 44 45/** 46 * AmbientTalk/2 is a dually scoped programming language, providing access to both the lexical 47 * scope methods and objects are defined in, as well as a dynamic scope which follows the 48 * parent chain of an object. Moreover, the language features the notion of closures and methods 49 * which have important semantic differences, and a few additional concepts. 50 * 51 * This test suite documents the proper binding semantics for variables, self and super inside 52 * methods and closures. 53 * 54 * @author smostinc 55 */ 56public class NATObjectClosureTest extends AmbientTalkTest { 57 58 /** 59 * This class is a special statement class used to test the correct scoping of method 60 * invocation from the java level, rather than by executing ambienttalk code directly. 61 * It is to be instantiated with the expected values and then passed into a method. 62 */ 63 private class AGScopeTest extends NATNil implements ATStatement { 64 65 private ATObject scope_; 66 private ATObject self_; 67 private ATObject super_; 68 69 public AGScopeTest(ATObject scope, ATObject self, ATObject zuper) { 70 scope_ = scope; 71 self_ = self; 72 super_ = zuper; 73 } 74 75 public ATObject meta_eval(ATContext ctx) throws InterpreterException { 76 // SCOPE-test 77 // Is the current callframe lexically connected to the expected scope 78 ATObject lexEnv = ctx.base_getLexicalScope(); 79 while (lexEnv != scope_) { 80 if(lexEnv == NATNil._INSTANCE_) { 81 fail("Lexical scope not found"); 82 break; 83 } 84 lexEnv = lexEnv.meta_getLexicalParent(); 85 } 86 87 // SELF-tests 88 // Is the current value of self consistent with our expectations 89 assertEquals(self_, ctx.base_getSelf()); 90 // Is the expected value of self accessible through the pseudovariable 91 assertEquals(self_, AGSelf._INSTANCE_.meta_eval(ctx)); 92 93 // SUPER-tests 94 // Is the current value of super consistent with our expectations 95 assertEquals(super_, ctx.base_getLexicalScope().meta_lookup(NATObject._SUPER_NAME_)); 96 97 return this; 98 } 99 100 public ATStatement asStatement() { 101 return this; 102 } 103 104 public ATMethod transformToMethodNamed(ATSymbol name) throws InterpreterException { 105 return new NATMethod( 106 name, 107 NATTable.EMPTY, 108 new AGBegin(NATTable.atValue(new ATObject[] { this }))); 109 } 110 111 } 112 113 public static void main(String[] args) { 114 junit.swingui.TestRunner.run(NATObjectClosureTest.class); 115 } 116 117 /** 118 * When defining an object, the programmer can choose to create either a method or a closure. 119 * In the context of an object which is not part of a dynamic hierarchy, there is no scoping 120 * difference between both solutions: both prefer the definitions in the object to the outer 121 * lexical scope, and in this particular case, their self and super bindings are identical. 122 * 123 * Note that the next test illustrates the esential difference between closures (who capture 124 * self and super) and methods (who leave them late bound). 125 */ 126 public void testOrphanObjectScope() { 127 evalAndReturn( 128 "def scope := \"outer\"; \n" + 129 "def orphan := object: {" + 130 " def scope := \"inner\";" + 131 " def method() { \n" + 132 " if: !(scope == \"inner\") then: { fail() }; \n" + 133 " if: (self == nil) then: { fail() }; \n" + 134 " if: !(super == nil) then: { fail() }; \n" + 135 " }; \n" + 136 " def closure := { \n" + 137 " if: !(scope == \"inner\") then: { fail() }; \n" + 138 " if: (self == nil) then: { fail() }; \n" + 139 " if: !(super == nil) then: { fail() }; \n" + 140 " }; \n" + 141 "};" + 142 "orphan.method(); \n" + 143 "orphan.closure(); \n"); 144 } 145 146 /** 147 * When defining an object, the programmer can choose to create either a method or a closure. 148 * The fundamental difference between both is the way they treat self and super. A closure 149 * will create bindings for these variables upon creation, whereas a method leaves them late 150 * bound. This implies that the binding for self in a closure is never late bound, as this 151 * test illustrates. Note that super is statically bound in both cases. 152 */ 153 public void testParentObjectScope() { 154 evalAndReturn( 155 "def scope := \"outer\"; \n" + 156 "def parent := object: { \n" + 157 " def scope := \"parent\"; \n" + 158 " def parent := self; \n" + 159 " def method() { \n" + 160 " if: (self.scope == \"parent\") then: { fail() }; \n" + 161 " if: (self == parent) then: { fail() }; \n" + 162 163 // without prefix : use lexical binding 164 " if: !(scope == \"parent\") then: { fail() }; \n" + 165 " if: !(super == nil) then: { fail() }; \n" + 166 " }; \n" + 167 " def closure := { \n" + 168 " if: !(self.scope == \"parent\") then: { fail() }; \n" + 169 " if: !(self == parent) then: { fail() }; \n" + 170 171 // without prefix : use lexical binding 172 " if: !(scope == \"parent\") then: { fail() }; \n" + 173 " if: !(super == nil) then: { fail() }; \n" + 174 " }; \n" + 175 "}; \n" + 176 "def child := extend: parent with: { \n" + 177 " def scope := \"child\"; \n" + 178 "}; \n" + 179 "child.method(); \n" + 180 "child.closure(); \n"); 181 } 182 183 /** 184 * Closures are created when defining a function inside an object's method as well. This 185 * test illustrates that these closures also capture their self and super bindings at 186 * creation time. In this case, note that the closure is created only when the method 187 * is executed, yielding behaviour somewhat similar to late binding. 188 */ 189 public void testNestedClosureScope() { 190 evalAndReturn( 191 "def scope := \"outer\"; \n" + 192 "def parent := object: { \n" + 193 " def scope := \"parent\"; \n" + 194 " def method(invoker) { \n" + 195 " def scope := \"method\"; \n" + 196 " def nestedClosure() { \n" + 197 " if: !(self.scope == invoker.scope) then: { fail() }; \n" + 198 " if: !(self == invoker) then: { fail() }; \n" + 199 200 // without prefix : use lexical binding 201 " if: !(scope == \"method\") then: { fail() }; \n" + 202 " if: !(super == nil) then: { fail() }; \n" + 203 " }; \n" + 204 205 // return the first class closure 206 " nestedClosure; \n" + 207 " }; \n" + 208 "}; \n" + 209 "def child := extend: parent with: { \n" + 210 " def scope := \"child\"; \n" + 211 "}; \n" + 212 "parent.method(parent)(); \n" + 213 "child.method(child)(); \n"); 214 } 215 216 /** 217 * When objects are lexically nested, the nested object has lexical access to the methods 218 * of the enclosing object. This test method illustrates that such a lexical invocation 219 * is equivalent to a direct invocation on the enclosing object, with respect to the 220 * bindings for the self and super variables. 221 * 222 * Design Principle : through lexical access the self binding cannot be set to objects 223 * which are not part of the dynamic object chain. 224 */ 225 public void testInvokeLexicallyVisibleMethod() { 226 evalAndReturn( 227 "def outer := object: { \n" + 228 " def scope := \"outer\"; \n" + 229 " def outer := self; \n" + 230 " def method() { \n" + 231 " if: !(scope == \"outer\") then: { fail() }; \n" + 232 " if: !(self == outer) then: { fail() }; \n" + 233 " if: !(super == nil) then: { fail() }; \n" + 234 " }; \n" + 235 " def inner := object: { \n" + 236 " def test() { \n" + 237 " method(); \n" + 238 " }; \n" + 239 " }; \n" + 240 "}; \n" + 241 "outer.inner.test(); \n"); 242 } 243 244 /** 245 * The previous test illustrated that it is possible to perform a lexical invocation 246 * of methods in an enclosing object. This test tries to perform a similar feat, yet 247 * calls a method which is not defined by the enclosing object, but by its parent. 248 * This invocation will fail. 249 * 250 * Design Principle : lexical invocation is strictly limited to methods and closures 251 * which are lexically visible. 252 * 253 */ 254 public void testLexicallyInvokeInheritedMethod() { 255 evalAndTestException( 256 "def outer := object: { \n" + 257 " def scope := \"outer\"; \n" + 258 " def outer := self; \n" + 259 " def method() { \n" + 260 " if: !(scope == \"outer\") then: { fail() }; \n" + 261 " if: !(self == outer) then: { fail() }; \n" + 262 " if: !(super == nil) then: { fail() }; \n" + 263 " }; \n" + 264 "}; \n" + 265 "def outerChild := extend: outer with: { \n" + 266 " def inner := object: { \n" + 267 " def test() { \n" + 268 " method(); \n" + 269 " }; \n" + 270 " }; \n" + 271 "}; \n" + 272 "outerChild.inner.test(); \n", 273 XUndefinedField.class); 274 } 275 276 /** 277 * AmbientTalk introduces first class delegation using the ^ symbol. This feature ensures 278 * that the self of a message is late bound. This test illustrates the late binding of self 279 * for delegated invocations, as opposed to oridinary invocations. 280 */ 281 public void testDelegatedMethodScope() { 282 evalAndReturn( 283 "def scope := \"outer\"; \n" + 284 "def parent := object: { \n" + 285 " def scope := \"parent\"; \n" + 286 " def method(invoker) { \n" + 287 " if: !(self.scope == invoker.scope) then: { fail() }; \n" + 288 " if: !(self == invoker) then: { fail() }; \n" + 289 290 // without prefix : use lexical binding 291 " if: !(scope == \"parent\") then: { fail() }; \n" + 292 " if: !(super == nil) then: { fail() }; \n" + 293 " }; \n" + 294 "}; \n" + 295 "def child := extend: parent with: { \n" + 296 " def scope := \"child\"; \n" + 297 " def test() { \n" + 298 " super.method( super ); \n" + 299 " super^method( self ); \n" + 300 " }; \n" + 301 "}; \n" + 302 "child.test(); \n"); 303 } 304 305 /** 306 * Methods can be added to an object using external method definitions. These definitions 307 * provide access to the dynamic parent chain of objects, as any normal method would. The 308 * difference is that the external method has access to its own lexical scope and not to the 309 * one of the object it is being inserted to. 310 * 311 * Design Principle: self and super in an external methods are confined to the dynamich 312 * inheritance chain of the object they are being inserted to, and are subject to the 313 * same constraints as those bindings in internal methods. 314 * Design Principle: Methods (including external ones) have unqualified access to their 315 * surrounding lexical scope, and to this scope only. 316 */ 317 public void testExternalMethodScope() { 318 evalAndReturn( 319 "def scope := \"outer\"; \n" + 320 "def parent := object: { \n" + 321 " def scope := \"parent\"; \n" + 322 "}; \n" + 323 "def child := extend: parent with: { \n" + 324 " def scope := \"child\"; \n" + 325 "}; \n" + 326 // isolates have no scope transfer 327 "def extender := isolate: {" + 328 " def scope := \"extender\"; \n" + 329 " def extend(object) {" + 330 " def object.method(invoker) { \n" + 331 " if: !(self.scope == invoker.scope) then: { fail() }; \n" + 332 " if: !(self == invoker) then: { fail() }; \n" + 333 334 // without prefix : use lexical binding 335 " if: !(scope == \"extender\") then: { fail() }; \n" + 336 " if: !(super == object.super) then: { fail() }; \n" + 337 " }; \n" + 338 " }; \n" + 339 "}; \n" + 340 341 "extender.extend(parent); \n" + 342 "child.method(child); \n" + 343 "extender.extend(child); \n" + 344 "child.method(child); \n"); 345 } 346 347 /** 348 * Isolates are objects which have no access to variables in their lexical scope (hence 349 * their name). This kind of object can thus be safely passed by copy to another actor. 350 * This test method illustrates that isolates have no access to their lexical but that 351 * they can access copies of outlying variables using ad hoc syntax. 352 */ 353 public void testIsolateScope() { 354 evalAndReturn( 355 "def invisible := \"can't see me\"; \n" + 356 "def copiedVar := 42; \n" + 357 "def test := isolate: { | copiedVar | \n" + 358 " def testLexicalVariableCopy() { \n" + 359 " copiedVar; \n" + 360 " }; \n" + 361 " def attemptLexicalVisiblity() { \n" + 362 " invisible; \n" + 363 " }; \n" + 364 "}; \n" + 365 366 // as the variables are copied, subsequent assignments are not observed 367 "copiedVar := 23; \n" + 368 "if: !(test.testLexicalVariableCopy() == 42) then: { fail(); }; \n"); 369 370 // attempting to use a variable that was not copied will fail 371 evalAndTestException( 372 "test.attemptLexicalVisiblity()", 373 XUndefinedField.class); 374 } 375 376 /** 377 * Since external definitions inherently are equipped access to their lexical scope, 378 * and isolates are prohibited access to any form of lexical scope so that they can 379 * be copied between actors, these mechanisms are irreconcilable. 380 * 381 * Design Principle: isolates have a sealed scope which can not be extended from the 382 * outside by means of external method definitions. 383 */ 384 public void testExternalDefinitionOnIsolates() { 385 evalAndTestException( 386 "def i := isolate: { nil }; \n" + 387 "def i.method() { nil }; \n", 388 XIllegalOperation.class); 389 } 390 391 392 /** 393 * NATIVE TEST: Tests the validity of the various scope pointers in a context object when 394 * applying a method defined in and invoked upon an orphan object. 395 * 396 * - covers meta_invoke & meta_select for method lookup 397 * - covers closure creation in meta_select 398 * - covers context initialisation at closure creation 399 * - covers closure application 400 */ 401 public void testMethodInvocation() { 402 try { 403 ATObject object = new NATObject(ctx_.base_getLexicalScope()); 404 405 AGScopeTest expectedValues = 406 new AGScopeTest(object, object, object.base_getSuper()); 407 408 ATSymbol scopeTest = AGSymbol.jAlloc("scopeTest"); 409 410 object.meta_addMethod(expectedValues.transformToMethodNamed(scopeTest)); 411 412 object.meta_invoke(object, scopeTest, NATTable.EMPTY); 413 } catch (InterpreterException e) { 414 e.printStackTrace(); 415 fail(); 416 } 417 } 418 419 /** 420 * NATIVE TEST: Tests the validity of the various scope pointers in a context object when 421 * applying a method in a simple hierarchy of objects. 422 * 423 * - covers meta_invoke & meta_select for method lookup with dynamic chains 424 * - covers proper self semantics at closure creation 425 * - covers super semantics during method application 426 */ 427 public void testDelegatedMethodInvocation() { 428 try { 429 NATObject parent = new NATObject(ctx_.base_getLexicalScope()); 430 431 NATObject child = new NATObject(parent, ctx_.base_getLexicalScope(), NATObject._IS_A_); 432 433 AGScopeTest lateBoundSelfTest = new AGScopeTest(parent, child, parent.base_getSuper()); 434 AGScopeTest superSemanticsTest = new AGScopeTest(child, child, child.base_getSuper()); 435 436 ATSymbol lateBoundSelf = AGSymbol.alloc(NATText.atValue("lateBoundSelf")); 437 ATSymbol superSemantics = AGSymbol.alloc(NATText.atValue("superSemantics")); 438 439 parent.meta_addMethod(lateBoundSelfTest.transformToMethodNamed(lateBoundSelf)); 440 child.meta_addMethod(superSemanticsTest.transformToMethodNamed(superSemantics)); 441 442 child.meta_invoke(child, lateBoundSelf, NATTable.EMPTY); 443 child.meta_invoke(child, superSemantics, NATTable.EMPTY); 444 } catch (InterpreterException e) { 445 e.printStackTrace(); 446 fail(); 447 } 448 } 449 450 /** 451 * NATIVE TEST: Makes a simple extension of an orphan object using a closure. Tests the 452 * correct scoping of methods with objects created using meta_extend 453 * 454 * - covers meta_extend for object extension. 455 * - covers method definition using AGDefMethod 456 */ 457 public void testExtend() { 458 try { 459 NATObject parent = new NATObject(ctx_.base_getLexicalScope()); 460 461 ATSymbol superSemantics = AGSymbol.alloc(NATText.atValue("superSemantics")); 462 463 AGScopeTest superSemanticsTest = new AGScopeTest(null, null, null); 464 465 // We explicitly need to write out the construction of this object extension 466 // extend: parent with: { def superSemantics() { #superSemanticsTest } }; 467 ATObject child = OBJLexicalRoot._INSTANCE_.base_extend_with_(parent, 468 new NATClosure( 469 new NATMethod(AGSymbol.alloc(NATText.atValue("lambda")), NATTable.EMPTY, 470 new AGBegin(NATTable.atValue(new ATObject[] { 471 new AGDefFunction(superSemantics, NATTable.EMPTY, 472 new AGBegin( 473 NATTable.atValue(new ATObject[] { superSemanticsTest })))}))), 474 ctx_.base_getLexicalScope(), 475 ctx_.base_getLexicalScope())); 476 477 superSemanticsTest.scope_ = child; 478 superSemanticsTest.self_ = child; 479 superSemanticsTest.super_ = child.base_getSuper(); 480 481 ATSymbol lateBoundSelf = AGSymbol.alloc(NATText.atValue("lateBoundSelf")); 482 AGScopeTest lateBoundSelfTest = new AGScopeTest(parent, child, parent.base_getSuper()); 483 484 parent.meta_addMethod(lateBoundSelfTest.transformToMethodNamed(lateBoundSelf)); 485 486 child.meta_invoke(child, lateBoundSelf, NATTable.EMPTY); 487 child.meta_invoke(child, superSemantics, NATTable.EMPTY); 488 } catch (InterpreterException e) { 489 e.printStackTrace(); 490 fail(); 491 } 492 493 } 494 495 /** 496 * NATIVE TEST: Tests whether the definition of an external method refers to the correct 497 * bindings for: 498 * 499 * - lexically accessed variables 500 * - the value of 'self' 501 * - the value of 'super' 502 */ 503 public void testExternalMethodBindings() throws InterpreterException { 504 /* 505 * Test code: 506 * 507 * def hostParent := object: { nil } 508 * def host := extend: hostParent with: { nil } 509 * def extender := object: { 510 * def extend(o) { 511 * def x := 5; 512 * def o.m() { assert(lex==extender); assert(self==host); assert(super==hostParent) } 513 * } 514 * } 515 */ 516 ATObject hostParent = new NATObject(); 517 ATObject host = new NATObject(hostParent, Evaluator.getGlobalLexicalScope(), NATObject._IS_A_); 518 ATObject extender = new NATObject(); 519 520 ctx_.base_getLexicalScope().meta_defineField(AGSymbol.jAlloc("scopetest"), new AGScopeTest(extender, host, hostParent)); 521 ATObject methodBody = evalAndReturn("`{def o.m() { #scopetest }}"); 522 523 extender.meta_addMethod(new NATMethod(AGSymbol.jAlloc("extend"), 524 NATTable.atValue(new ATObject[] { AGSymbol.jAlloc("o")}), 525 new AGBegin(NATTable.of(methodBody)))); 526 } 527 528 529}