/interpreter/tags/reactive-pattern-matching/test/edu/vub/at/objects/natives/NATObjectClosureTest.java

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