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