PageRenderTime 55ms CodeModel.GetById 9ms app.highlight 40ms RepoModel.GetById 1ms app.codeStats 0ms

/interpreter/tags/at2-build270707/test/edu/vub/at/objects/natives/NATObjectClosureTest.java

http://ambienttalk.googlecode.com/
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}