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