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