PageRenderTime 75ms CodeModel.GetById 21ms app.highlight 46ms RepoModel.GetById 1ms app.codeStats 1ms

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

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