PageRenderTime 47ms CodeModel.GetById 7ms app.highlight 33ms RepoModel.GetById 1ms app.codeStats 1ms

/interpreter/tags/at2dist130208/test/edu/vub/at/objects/natives/NATObjectClosureTest.java

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