/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. */
  28. package edu.vub.at.objects.natives;
  29. import edu.vub.at.AmbientTalkTest;
  30. import edu.vub.at.eval.Evaluator;
  31. import edu.vub.at.exceptions.InterpreterException;
  32. import edu.vub.at.exceptions.XIllegalOperation;
  33. import edu.vub.at.exceptions.XSelectorNotFound;
  34. import edu.vub.at.exceptions.XUndefinedSlot;
  35. import edu.vub.at.objects.ATContext;
  36. import edu.vub.at.objects.ATMethod;
  37. import edu.vub.at.objects.ATObject;
  38. import edu.vub.at.objects.grammar.ATStatement;
  39. import edu.vub.at.objects.grammar.ATSymbol;
  40. import edu.vub.at.objects.natives.grammar.AGBegin;
  41. import edu.vub.at.objects.natives.grammar.AGDefFunction;
  42. import edu.vub.at.objects.natives.grammar.AGSelf;
  43. import edu.vub.at.objects.natives.grammar.AGSymbol;
  44. /**
  45. * AmbientTalk/2 is a dually scoped programming language, providing access to both the lexical
  46. * scope methods and objects are defined in, as well as a dynamic scope which follows the
  47. * parent chain of an object. Moreover, the language features the notion of closures and methods
  48. * which have important semantic differences, and a few additional concepts.
  49. *
  50. * This test suite documents the proper binding semantics for variables, self and super inside
  51. * methods and closures.
  52. *
  53. * @author smostinc
  54. */
  55. public class NATObjectClosureTest extends AmbientTalkTest {
  56. /**
  57. * This class is a special statement class used to test the correct scoping of method
  58. * invocation from the java level, rather than by executing ambienttalk code directly.
  59. * It is to be instantiated with the expected values and then passed into a method.
  60. */
  61. private class AGScopeTest extends NATByCopy implements ATStatement {
  62. private ATObject scope_;
  63. private ATObject self_;
  64. private ATObject super_;
  65. public AGScopeTest(ATObject scope, ATObject self, ATObject zuper) {
  66. scope_ = scope;
  67. self_ = self;
  68. super_ = zuper;
  69. }
  70. public ATObject meta_eval(ATContext ctx) throws InterpreterException {
  71. // SCOPE-test
  72. // Is the current callframe lexically connected to the expected scope
  73. ATObject lexEnv = ctx.base_lexicalScope();
  74. while (lexEnv != scope_) {
  75. if(lexEnv == OBJNil._INSTANCE_) {
  76. fail("Lexical scope not found");
  77. break;
  78. }
  79. lexEnv = lexEnv.impl_lexicalParent();
  80. }
  81. // SELF-tests
  82. // Is the current value of self consistent with our expectations
  83. assertEquals(self_, ctx.base_self());
  84. // Is the expected value of self accessible through the pseudovariable
  85. assertEquals(self_, AGSelf._INSTANCE_.meta_eval(ctx));
  86. // SUPER-tests
  87. // Is the current value of super consistent with our expectations
  88. assertEquals(super_, ctx.base_lexicalScope().impl_call(NATObject._SUPER_NAME_, NATTable.EMPTY));
  89. return this;
  90. }
  91. public ATStatement asStatement() {
  92. return this;
  93. }
  94. public ATMethod transformToMethodNamed(ATSymbol name) throws InterpreterException {
  95. return new NATMethod(
  96. name,
  97. NATTable.EMPTY,
  98. new AGBegin(NATTable.atValue(new ATObject[] { this })));
  99. }
  100. public NATText meta_print() throws InterpreterException {
  101. return NATText.atValue("scopetest");
  102. }
  103. }
  104. public static void main(String[] args) {
  105. junit.swingui.TestRunner.run(NATObjectClosureTest.class);
  106. }
  107. /**
  108. * When defining an object, the programmer can choose to create either a method or a closure.
  109. * In the context of an object which is not part of a dynamic hierarchy, there is no scoping
  110. * difference between both solutions: both prefer the definitions in the object to the outer
  111. * lexical scope, and in this particular case, their self and super bindings are identical.
  112. *
  113. * Note that the next test illustrates the esential difference between closures (who capture
  114. * self and super) and methods (who leave them late bound).
  115. */
  116. public void testOrphanObjectScope() {
  117. evalAndReturn(
  118. "def scope := \"outer\"; \n" +
  119. "def orphan := object: {" +
  120. " def scope := \"inner\";" +
  121. " def method() { \n" +
  122. " if: !(scope == \"inner\") then: { fail() }; \n" +
  123. " if: (self == nil) then: { fail() }; \n" +
  124. " if: !(super == nil) then: { fail() }; \n" +
  125. " }; \n" +
  126. " def closure := { \n" +
  127. " if: !(scope == \"inner\") then: { fail() }; \n" +
  128. " if: (self == nil) then: { fail() }; \n" +
  129. " if: !(super == nil) then: { fail() }; \n" +
  130. " }; \n" +
  131. "};" +
  132. "orphan.method(); \n" +
  133. "orphan.closure(); \n");
  134. }
  135. /**
  136. * When defining an object, the programmer can choose to create either a method or a closure.
  137. * The fundamental difference between both is the way they treat self and super. A closure
  138. * will create bindings for these variables upon creation, whereas a method leaves them late
  139. * bound. This implies that the binding for self in a closure is never late bound, as this
  140. * test illustrates. Note that super is statically bound in both cases.
  141. */
  142. public void testParentObjectScope() {
  143. evalAndReturn(
  144. "def scope := \"outer\"; \n" +
  145. "def parent := object: { \n" +
  146. " def scope := \"parent\"; \n" +
  147. " def parent := self; \n" +
  148. " def method() { \n" +
  149. " if: (self.scope == \"parent\") then: { fail() }; \n" +
  150. " if: (self == parent) then: { fail() }; \n" +
  151. // without prefix : use lexical binding
  152. " if: !(scope == \"parent\") then: { fail() }; \n" +
  153. " if: !(super == nil) then: { fail() }; \n" +
  154. " }; \n" +
  155. " def closure := { \n" +
  156. " if: !(self.scope == \"parent\") then: { fail() }; \n" +
  157. " if: !(self == parent) then: { fail() }; \n" +
  158. // without prefix : use lexical binding
  159. " if: !(scope == \"parent\") then: { fail() }; \n" +
  160. " if: !(super == nil) then: { fail() }; \n" +
  161. " }; \n" +
  162. "}; \n" +
  163. "def child := extend: parent with: { \n" +
  164. " def scope := \"child\"; \n" +
  165. "}; \n" +
  166. "child.method(); \n" +
  167. "child.closure(); \n");
  168. }
  169. /**
  170. * Closures are created when defining a function inside an object's method as well. This
  171. * test illustrates that these closures also capture their self and super bindings at
  172. * creation time. In this case, note that the closure is created only when the method
  173. * is executed, yielding behaviour somewhat similar to late binding.
  174. */
  175. public void testNestedClosureScope() {
  176. evalAndReturn(
  177. "def scope := \"outer\"; \n" +
  178. "def parent := object: { \n" +
  179. " def scope := \"parent\"; \n" +
  180. " def method(invoker) { \n" +
  181. " def scope := \"method\"; \n" +
  182. " def nestedClosure() { \n" +
  183. " if: !(self.scope == invoker.scope) then: { fail() }; \n" +
  184. " if: !(self == invoker) then: { fail() }; \n" +
  185. // without prefix : use lexical binding
  186. " if: !(scope == \"method\") then: { fail() }; \n" +
  187. " if: !(super == nil) then: { fail() }; \n" +
  188. " }; \n" +
  189. // return the first class closure
  190. " &nestedClosure; \n" +
  191. " }; \n" +
  192. "}; \n" +
  193. "def child := extend: parent with: { \n" +
  194. " def scope := \"child\"; \n" +
  195. "}; \n" +
  196. "parent.method(parent)(); \n" +
  197. "child.method(child)(); \n");
  198. }
  199. /**
  200. * When objects are lexically nested, the nested object has lexical access to the methods
  201. * of the enclosing object. This test method illustrates that such a lexical invocation
  202. * is equivalent to a direct invocation on the enclosing object, with respect to the
  203. * bindings for the self and super variables.
  204. *
  205. * Design Principle : through lexical access the self binding cannot be set to objects
  206. * which are not part of the dynamic object chain.
  207. */
  208. public void testInvokeLexicallyVisibleMethod() {
  209. evalAndReturn(
  210. "def outer := object: { \n" +
  211. " def scope := \"outer\"; \n" +
  212. " def outer := self; \n" +
  213. " def method() { \n" +
  214. " if: !(scope == \"outer\") then: { fail() }; \n" +
  215. " if: !(self == outer) then: { fail() }; \n" +
  216. " if: !(super == nil) then: { fail() }; \n" +
  217. " }; \n" +
  218. " def inner := object: { \n" +
  219. " def test() { \n" +
  220. " method(); \n" +
  221. " }; \n" +
  222. " }; \n" +
  223. "}; \n" +
  224. "outer.inner.test(); \n");
  225. }
  226. /**
  227. * The previous test illustrated that it is possible to perform a lexical invocation
  228. * of methods in an enclosing object. This test tries to perform a similar feat, yet
  229. * calls a method which is not defined by the enclosing object, but by its parent.
  230. * This invocation will fail.
  231. *
  232. * Design Principle : lexical invocation is strictly limited to methods and closures
  233. * which are lexically visible.
  234. *
  235. */
  236. public void testLexicallyInvokeInheritedMethod() {
  237. evalAndTestException(
  238. "def outer := object: { \n" +
  239. " def scope := \"outer\"; \n" +
  240. " def outer := self; \n" +
  241. " def method() { \n" +
  242. " if: !(scope == \"outer\") then: { fail() }; \n" +
  243. " if: !(self == outer) then: { fail() }; \n" +
  244. " if: !(super == nil) then: { fail() }; \n" +
  245. " }; \n" +
  246. "}; \n" +
  247. "def outerChild := extend: outer with: { \n" +
  248. " def inner := object: { \n" +
  249. " def test() { \n" +
  250. " method(); \n" +
  251. " }; \n" +
  252. " }; \n" +
  253. "}; \n" +
  254. "outerChild.inner.test(); \n",
  255. XUndefinedSlot.class);
  256. }
  257. /**
  258. * AmbientTalk introduces first class delegation using the ^ symbol. This feature ensures
  259. * that the self of a message is late bound. This test illustrates the late binding of self
  260. * for delegated invocations, as opposed to ordinary invocations.
  261. */
  262. public void testDelegatedMethodScope() {
  263. evalAndReturn(
  264. "def scope := \"outer\"; \n" +
  265. "def parent := object: { \n" +
  266. " def scope := \"parent\"; \n" +
  267. " def method(invoker) { \n" +
  268. " if: !(self.scope == invoker.scope) then: { fail() }; \n" +
  269. " if: !(self == invoker) then: { fail() }; \n" +
  270. // without prefix : use lexical binding
  271. " if: !(scope == \"parent\") then: { fail() }; \n" +
  272. " if: !(super == nil) then: { fail() }; \n" +
  273. " }; \n" +
  274. "}; \n" +
  275. "def child := extend: parent with: { \n" +
  276. " def scope := \"child\"; \n" +
  277. " def test() { \n" +
  278. " super.method( super ); \n" +
  279. " super^method( self ); \n" +
  280. " }; \n" +
  281. "}; \n" +
  282. "child.test(); \n");
  283. }
  284. /**
  285. * Methods can be added to an object using external method definitions. These definitions
  286. * provide access to the dynamic parent chain of objects, as any normal method would. The
  287. * difference is that the external method has access to its own lexical scope and not to the
  288. * one of the object it is being inserted to.
  289. *
  290. * Design Principle: self and super in an external methods are confined to the dynamich
  291. * inheritance chain of the object they are being inserted to, and are subject to the
  292. * same constraints as those bindings in internal methods.
  293. * Design Principle: Methods (including external ones) have unqualified access to their
  294. * surrounding lexical scope, and to this scope only.
  295. */
  296. public void testExternalMethodScope() {
  297. evalAndReturn(
  298. "def scope := \"outer\"; \n" +
  299. "def parent := object: { \n" +
  300. " def scope := \"parent\"; \n" +
  301. "}; \n" +
  302. "def child := extend: parent with: { \n" +
  303. " def scope := \"child\"; \n" +
  304. "}; \n" +
  305. // isolates have no scope transfer
  306. "def extender := isolate: {" +
  307. " def scope := \"extender\"; \n" +
  308. " def extend(object) {" +
  309. " def object.method(invoker) { \n" +
  310. " if: !(self.scope == invoker.scope) then: { fail() }; \n" +
  311. " if: !(self == invoker) then: { fail() }; \n" +
  312. // without prefix : use lexical binding
  313. " if: !(scope == \"extender\") then: { fail() }; \n" +
  314. " if: !(super == object.super) then: { fail() }; \n" +
  315. " }; \n" +
  316. " }; \n" +
  317. "}; \n" +
  318. "extender.extend(parent); \n" +
  319. "child.method(child); \n" +
  320. "extender.extend(child); \n" +
  321. "child.method(child); \n");
  322. }
  323. /**
  324. * Isolates are objects which have no access to variables in their lexical scope (hence
  325. * their name). This kind of object can thus be safely passed by copy to another actor.
  326. * This test method illustrates that isolates have no access to their lexical but that
  327. * they can access copies of outlying variables using ad hoc syntax.
  328. */
  329. public void testIsolateScope() {
  330. evalAndReturn(
  331. "def invisible := \"can't see me\"; \n" +
  332. "def copiedVar := 42; \n" +
  333. "def test := isolate: { | copiedVar | \n" +
  334. " def testLexicalVariableCopy() { \n" +
  335. " copiedVar; \n" +
  336. " }; \n" +
  337. " def attemptLexicalVisiblity() { \n" +
  338. " invisible; \n" +
  339. " }; \n" +
  340. "}; \n" +
  341. // as the variables are copied, subsequent assignments are not observed
  342. "copiedVar := 23; \n" +
  343. "if: !(test.testLexicalVariableCopy() == 42) then: { fail(); }; \n");
  344. // attempting to use a variable that was not copied will fail
  345. evalAndTestException(
  346. "test.attemptLexicalVisiblity()",
  347. XUndefinedSlot.class);
  348. }
  349. /**
  350. * Since external definitions inherently are equipped access to their lexical scope,
  351. * and isolates are prohibited access to any form of lexical scope so that they can
  352. * be copied between actors, these mechanisms are irreconcilable.
  353. *
  354. * Design Principle: isolates have a sealed scope which can not be extended from the
  355. * outside by means of external method definitions.
  356. */
  357. public void testExternalDefinitionOnIsolates() {
  358. evalAndTestException(
  359. "def i := isolate: { nil }; \n" +
  360. "def i.method() { nil }; \n",
  361. XIllegalOperation.class);
  362. }
  363. /**
  364. * NATIVE TEST: Tests the validity of the various scope pointers in a context object when
  365. * applying a method defined in and invoked upon an orphan object.
  366. *
  367. * - covers meta_invoke & meta_select for method lookup
  368. * - covers closure creation in meta_select
  369. * - covers context initialisation at closure creation
  370. * - covers closure application
  371. */
  372. public void testMethodInvocation() {
  373. try {
  374. ATObject object = new NATObject(ctx_.base_lexicalScope());
  375. AGScopeTest expectedValues =
  376. new AGScopeTest(object, object, object.base_super());
  377. ATSymbol scopeTest = AGSymbol.jAlloc("scopeTest");
  378. object.meta_addMethod(expectedValues.transformToMethodNamed(scopeTest));
  379. object.meta_invoke(object, scopeTest, NATTable.EMPTY);
  380. } catch (InterpreterException e) {
  381. e.printStackTrace();
  382. fail();
  383. }
  384. }
  385. /**
  386. * NATIVE TEST: Tests the validity of the various scope pointers in a context object when
  387. * applying a method in a simple hierarchy of objects.
  388. *
  389. * - covers meta_invoke & meta_select for method lookup with dynamic chains
  390. * - covers proper self semantics at closure creation
  391. * - covers super semantics during method application
  392. */
  393. public void testDelegatedMethodInvocation() {
  394. try {
  395. NATObject parent = new NATObject(ctx_.base_lexicalScope());
  396. NATObject child = new NATObject(parent, ctx_.base_lexicalScope(), NATObject._IS_A_);
  397. AGScopeTest lateBoundSelfTest = new AGScopeTest(parent, child, parent.base_super());
  398. AGScopeTest superSemanticsTest = new AGScopeTest(child, child, child.base_super());
  399. ATSymbol lateBoundSelf = AGSymbol.alloc(NATText.atValue("lateBoundSelf"));
  400. ATSymbol superSemantics = AGSymbol.alloc(NATText.atValue("superSemantics"));
  401. parent.meta_addMethod(lateBoundSelfTest.transformToMethodNamed(lateBoundSelf));
  402. child.meta_addMethod(superSemanticsTest.transformToMethodNamed(superSemantics));
  403. child.meta_invoke(child, lateBoundSelf, NATTable.EMPTY);
  404. child.meta_invoke(child, superSemantics, NATTable.EMPTY);
  405. } catch (InterpreterException e) {
  406. e.printStackTrace();
  407. fail();
  408. }
  409. }
  410. /**
  411. * NATIVE TEST: Makes a simple extension of an orphan object using a closure. Tests the
  412. * correct scoping of methods with objects created using meta_extend
  413. *
  414. * - covers meta_extend for object extension.
  415. * - covers method definition using AGDefMethod
  416. */
  417. public void testExtend() {
  418. try {
  419. NATObject parent = new NATObject(ctx_.base_lexicalScope());
  420. ATSymbol superSemantics = AGSymbol.alloc(NATText.atValue("superSemantics"));
  421. AGScopeTest superSemanticsTest = new AGScopeTest(null, null, null);
  422. // We explicitly need to write out the construction of this object extension
  423. // extend: parent with: { def superSemantics() { #superSemanticsTest } };
  424. ATObject child = OBJLexicalRoot._INSTANCE_.base_extend_with_(parent,
  425. new NATClosure(
  426. new NATMethod(AGSymbol.alloc(NATText.atValue("lambda")), NATTable.EMPTY,
  427. new AGBegin(NATTable.atValue(new ATObject[] {
  428. new AGDefFunction(superSemantics, NATTable.EMPTY,
  429. new AGBegin(
  430. NATTable.atValue(new ATObject[] { superSemanticsTest })))}))),
  431. ctx_.base_lexicalScope(),
  432. ctx_.base_lexicalScope()));
  433. superSemanticsTest.scope_ = child;
  434. superSemanticsTest.self_ = child;
  435. superSemanticsTest.super_ = child.base_super();
  436. ATSymbol lateBoundSelf = AGSymbol.alloc(NATText.atValue("lateBoundSelf"));
  437. AGScopeTest lateBoundSelfTest = new AGScopeTest(parent, child, parent.base_super());
  438. parent.meta_addMethod(lateBoundSelfTest.transformToMethodNamed(lateBoundSelf));
  439. child.meta_invoke(child, lateBoundSelf, NATTable.EMPTY);
  440. child.meta_invoke(child, superSemantics, NATTable.EMPTY);
  441. } catch (InterpreterException e) {
  442. e.printStackTrace();
  443. fail();
  444. }
  445. }
  446. /**
  447. * NATIVE TEST: Tests whether the definition of an external method refers to the correct
  448. * bindings for:
  449. *
  450. * - lexically accessed variables
  451. * - the value of 'self'
  452. * - the value of 'super'
  453. */
  454. public void testExternalMethodBindings() throws InterpreterException {
  455. /*
  456. * Test code:
  457. *
  458. * def hostParent := object: { nil }
  459. * def host := extend: hostParent with: { nil }
  460. * def extender := object: {
  461. * def extend(o) {
  462. * def x := 5;
  463. * def o.m() { assert(lex==extender); assert(self==host); assert(super==hostParent) }
  464. * }
  465. * }
  466. */
  467. ATObject hostParent = new NATObject();
  468. ATObject host = new NATObject(hostParent, Evaluator.getGlobalLexicalScope(), NATObject._IS_A_);
  469. ATObject extender = new NATObject();
  470. ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("scopetest"), new AGScopeTest(extender, host, hostParent));
  471. ATObject methodBody = evalAndReturn("`{def o.m() { #scopetest }}");
  472. extender.meta_addMethod(new NATMethod(AGSymbol.jAlloc("extend"),
  473. NATTable.atValue(new ATObject[] { AGSymbol.jAlloc("o")}),
  474. new AGBegin(NATTable.of(methodBody))));
  475. }
  476. }