PageRenderTime 38ms CodeModel.GetById 19ms app.highlight 14ms RepoModel.GetById 1ms app.codeStats 0ms

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

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