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