Browse Source

RefNode; cascade receivers are RefNode's

Herbert Vojčík 9 years ago
parent
commit
3123d2096c

+ 130 - 0
src/Compiler-AST.js

@@ -554,6 +554,29 @@ messageSends: ["select:", "allNodes", "and:", "isNavigationNode", "inPosition:",
 }),
 $globals.Node);
 
+$core.addMethod(
+$core.method({
+selector: "nodeId",
+protocol: 'accessing',
+fn: function (){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return self._identityHash();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"nodeId",{},$globals.Node)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "nodeId\x0a\x09^ self identityHash",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["identityHash"]
+}),
+$globals.Node);
+
 $core.addMethod(
 $core.method({
 selector: "nodes",
@@ -1982,6 +2005,90 @@ $globals.MethodNode);
 
 
 
+$core.addClass('RefNode', $globals.Node, ['node'], 'Compiler-AST');
+//>>excludeStart("ide", pragmas.excludeIdeData);
+$globals.RefNode.comment="I reference other `node`, which may be used more times,\x0aeach time getting separate instance of me.\x0a\x0aI have my own `parent`, but delegate visitors to `node`.\x0a\x0aDuring semantic analysis, analyzer does `node shouldBeAliased: true`\x0abecause of the fact it may be used more times.";
+//>>excludeEnd("ide");
+$core.addMethod(
+$core.method({
+selector: "accept:",
+protocol: 'visiting',
+fn: function (aVisitor){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return $recv(aVisitor)._visitRefNode_(self);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"accept:",{aVisitor:aVisitor},$globals.RefNode)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aVisitor"],
+source: "accept: aVisitor\x0a\x09^ aVisitor visitRefNode: self",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["visitRefNode:"]
+}),
+$globals.RefNode);
+
+$core.addMethod(
+$core.method({
+selector: "isImmutable",
+protocol: 'testing',
+fn: function (){
+var self=this;
+return true;
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "isImmutable\x0a\x09^ true",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.RefNode);
+
+$core.addMethod(
+$core.method({
+selector: "node",
+protocol: 'accessing',
+fn: function (){
+var self=this;
+return self["@node"];
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "node\x0a\x09^ node",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.RefNode);
+
+$core.addMethod(
+$core.method({
+selector: "node:",
+protocol: 'accessing',
+fn: function (anObject){
+var self=this;
+self["@node"]=anObject;
+return self;
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anObject"],
+source: "node: anObject\x0a\x09node := anObject",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.RefNode);
+
+
+
 $core.addClass('ReturnNode', $globals.Node, ['scope'], 'Compiler-AST');
 //>>excludeStart("ide", pragmas.excludeIdeData);
 $globals.ReturnNode.comment="I represent an return node. At the AST level, there is not difference between a local return or non-local return.";
@@ -3468,6 +3575,29 @@ messageSends: ["visitAll:", "nodes"]
 }),
 $globals.NodeVisitor);
 
+$core.addMethod(
+$core.method({
+selector: "visitRefNode:",
+protocol: 'visiting',
+fn: function (aNode){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return self._visit_($recv(aNode)._node());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"visitRefNode:",{aNode:aNode},$globals.NodeVisitor)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aNode"],
+source: "visitRefNode: aNode\x0a\x09^ self visit: aNode node",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["visit:", "node"]
+}),
+$globals.NodeVisitor);
+
 $core.addMethod(
 $core.method({
 selector: "visitReturnNode:",

+ 42 - 0
src/Compiler-AST.st

@@ -52,6 +52,10 @@ navigationNodeAt: aPoint ifAbsent: aBlock
 		(b positionStart dist: aPoint) ]) first
 !
 
+nodeId
+	^ self identityHash
+!
+
 nodes
 	^ nodes ifNil: [ nodes := Array new ]
 !
@@ -460,6 +464,40 @@ accept: aVisitor
 	^ aVisitor visitMethodNode: self
 ! !
 
+Node subclass: #RefNode
+	instanceVariableNames: 'node'
+	package: 'Compiler-AST'!
+!RefNode commentStamp!
+I reference other `node`, which may be used more times,
+each time getting separate instance of me.
+
+I have my own `parent`, but delegate visitors to `node`.
+
+During semantic analysis, analyzer does `node shouldBeAliased: true`
+because of the fact it may be used more times.!
+
+!RefNode methodsFor: 'accessing'!
+
+node
+	^ node
+!
+
+node: anObject
+	node := anObject
+! !
+
+!RefNode methodsFor: 'testing'!
+
+isImmutable
+	^ true
+! !
+
+!RefNode methodsFor: 'visiting'!
+
+accept: aVisitor
+	^ aVisitor visitRefNode: self
+! !
+
 Node subclass: #ReturnNode
 	instanceVariableNames: 'scope'
 	package: 'Compiler-AST'!
@@ -825,6 +863,10 @@ visitNode: aNode
 	^ self visitAll: aNode nodes
 !
 
+visitRefNode: aNode
+	^ self visit: aNode node
+!
+
 visitReturnNode: aNode
 	^ self visitNode: aNode
 !

+ 120 - 50
src/Compiler-IR.js

@@ -4,7 +4,7 @@ $core.addPackage('Compiler-IR');
 $core.packages["Compiler-IR"].innerEval = function (expr) { return eval(expr); };
 $core.packages["Compiler-IR"].transport = {"type":"amd","amdNamespace":"amber_core"};
 
-$core.addClass('IRASTTranslator', $globals.NodeVisitor, ['source', 'theClass', 'method', 'sequence', 'nextAlias'], 'Compiler-IR');
+$core.addClass('IRASTTranslator', $globals.NodeVisitor, ['source', 'theClass', 'method', 'sequence', 'nextAlias', 'nodeAliases'], 'Compiler-IR');
 //>>excludeStart("ide", pragmas.excludeIdeData);
 $globals.IRASTTranslator.comment="I am the AST (abstract syntax tree) visitor responsible for building the intermediate representation graph.";
 //>>excludeEnd("ide");
@@ -21,7 +21,7 @@ function $IRAssignment(){return $globals.IRAssignment||(typeof IRAssignment=="un
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $1,$2,$3,$5,$4,$6,$7,$9,$8;
+var $1,$2,$3,$4,$6,$5,$7,$8,$10,$9,$receiver;
 $1=$recv(aNode)._isImmutable();
 if($core.assert($1)){
 $2=self._visit_(aNode);
@@ -30,33 +30,42 @@ $ctx1.sendIdx["visit:"]=1;
 //>>excludeEnd("ctx");
 return $2;
 };
-$3=$recv($IRVariable())._new();
+$3=self._nodeAliasFor_(aNode);
+if(($receiver = $3) == null || $receiver.isNil){
+$3;
+} else {
+var aliasVar;
+aliasVar=$receiver;
+return aliasVar;
+};
+$4=$recv($IRVariable())._new();
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["new"]=1;
 //>>excludeEnd("ctx");
-$5=$recv($AliasVar())._new();
+$6=$recv($AliasVar())._new();
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["new"]=2;
 //>>excludeEnd("ctx");
-$4=$recv($5)._name_("$".__comma(self._nextAlias()));
-$recv($3)._variable_($4);
-$6=$recv($3)._yourself();
+$5=$recv($6)._name_("$".__comma(self._nextAlias()));
+$recv($4)._variable_($5);
+$7=$recv($4)._yourself();
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["yourself"]=1;
 //>>excludeEnd("ctx");
-variable=$6;
-$7=self._sequence();
-$9=$recv($IRAssignment())._new();
-$recv($9)._add_(variable);
+variable=$7;
+self._node_aliased_(aNode,variable);
+$8=self._sequence();
+$10=$recv($IRAssignment())._new();
+$recv($10)._add_(variable);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["add:"]=2;
 //>>excludeEnd("ctx");
-$recv($9)._add_(self._visit_(aNode));
+$recv($10)._add_(self._visit_(aNode));
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["add:"]=3;
 //>>excludeEnd("ctx");
-$8=$recv($9)._yourself();
-$recv($7)._add_($8);
+$9=$recv($10)._yourself();
+$recv($8)._add_($9);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["add:"]=1;
 //>>excludeEnd("ctx");
@@ -68,10 +77,10 @@ return variable;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aNode"],
-source: "alias: aNode\x0a\x09| variable |\x0a\x0a\x09aNode isImmutable ifTrue: [ ^ self visit: aNode ].\x0a\x0a\x09variable := IRVariable new\x0a\x09\x09variable: (AliasVar new name: '$', self nextAlias);\x0a\x09\x09yourself.\x0a\x0a\x09self sequence add: (IRAssignment new\x0a\x09\x09add: variable;\x0a\x09\x09add: (self visit: aNode);\x0a\x09\x09yourself).\x0a\x0a\x09self method internalVariables add: variable.\x0a\x0a\x09^ variable",
+source: "alias: aNode\x0a\x09| variable |\x0a\x0a\x09aNode isImmutable ifTrue: [ ^ self visit: aNode ].\x0a\x09\x0a\x09(self nodeAliasFor: aNode) ifNotNil: [ :aliasVar | ^ aliasVar ].\x0a\x0a\x09variable := IRVariable new\x0a\x09\x09variable: (AliasVar new name: '$', self nextAlias);\x0a\x09\x09yourself.\x0a\x09self node: aNode aliased: variable.\x0a\x0a\x09self sequence add: (IRAssignment new\x0a\x09\x09add: variable;\x0a\x09\x09add: (self visit: aNode);\x0a\x09\x09yourself).\x0a\x0a\x09self method internalVariables add: variable.\x0a\x0a\x09^ variable",
 referencedClasses: ["IRVariable", "AliasVar", "IRAssignment"],
 //>>excludeEnd("ide");
-messageSends: ["ifTrue:", "isImmutable", "visit:", "variable:", "new", "name:", ",", "nextAlias", "yourself", "add:", "sequence", "internalVariables", "method"]
+messageSends: ["ifTrue:", "isImmutable", "visit:", "ifNotNil:", "nodeAliasFor:", "variable:", "new", "name:", ",", "nextAlias", "yourself", "node:aliased:", "add:", "sequence", "internalVariables", "method"]
 }),
 $globals.IRASTTranslator);
 
@@ -204,6 +213,70 @@ messageSends: ["ifNil:", "+", "asString"]
 }),
 $globals.IRASTTranslator);
 
+$core.addMethod(
+$core.method({
+selector: "node:aliased:",
+protocol: 'accessing',
+fn: function (aNode,anAliasVar){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1,$receiver;
+$1=self["@nodeAliases"];
+if(($receiver = $1) == null || $receiver.isNil){
+self["@nodeAliases"]=$globals.HashedCollection._newFromPairs_([]);
+self["@nodeAliases"];
+} else {
+$1;
+};
+$recv(self["@nodeAliases"])._at_put_($recv(aNode)._nodeId(),anAliasVar);
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"node:aliased:",{aNode:aNode,anAliasVar:anAliasVar},$globals.IRASTTranslator)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aNode", "anAliasVar"],
+source: "node: aNode aliased: anAliasVar\x0a\x09nodeAliases ifNil: [ nodeAliases := #{} ].\x0a\x09nodeAliases at: aNode nodeId put: anAliasVar",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["ifNil:", "at:put:", "nodeId"]
+}),
+$globals.IRASTTranslator);
+
+$core.addMethod(
+$core.method({
+selector: "nodeAliasFor:",
+protocol: 'accessing',
+fn: function (aNode){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1,$receiver;
+$1=self["@nodeAliases"];
+if(($receiver = $1) == null || $receiver.isNil){
+return nil;
+} else {
+return $recv(self["@nodeAliases"])._at_ifAbsent_($recv(aNode)._nodeId(),(function(){
+
+}));
+};
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"nodeAliasFor:",{aNode:aNode},$globals.IRASTTranslator)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aNode"],
+source: "nodeAliasFor: aNode\x0a\x09nodeAliases\x0a\x09\x09ifNil: [ ^ nil ]\x0a\x09\x09ifNotNil: [ ^ nodeAliases at: aNode nodeId ifAbsent: [] ]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["ifNil:ifNotNil:", "at:ifAbsent:", "nodeId"]
+}),
+$globals.IRASTTranslator);
+
 $core.addMethod(
 $core.method({
 selector: "sequence",
@@ -555,48 +628,22 @@ selector: "visitCascadeNode:",
 protocol: 'visiting',
 fn: function (aNode){
 var self=this;
-function $VariableNode(){return $globals.VariableNode||(typeof VariableNode=="undefined"?nil:VariableNode)}
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $1,$2,$4,$3;
-$1=$recv(aNode)._nodes();
+var $2,$1;
+$2=$recv(aNode)._nodes();
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["nodes"]=1;
 //>>excludeEnd("ctx");
-$recv($1)._inject_into_($recv(aNode)._receiver(),(function(previous,each){
-var receiver;
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-return $core.withContext(function($ctx2) {
-//>>excludeEnd("ctx");
-$2=$recv(previous)._isImmutable();
-if($core.assert($2)){
-receiver=previous;
-} else {
-var alias;
-alias=self._alias_(previous);
-alias;
-receiver=$recv($recv($VariableNode())._new())._binding_($recv(alias)._variable());
-};
-receiver;
-$recv(each)._receiver_(receiver);
-return receiver;
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx2) {$ctx2.fillBlock({previous:previous,each:each,receiver:receiver},$ctx1,1)});
-//>>excludeEnd("ctx");
-}));
-$4=$recv(aNode)._nodes();
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-$ctx1.sendIdx["nodes"]=2;
-//>>excludeEnd("ctx");
-$3=$recv($4)._allButLast();
-$recv($3)._do_((function(each){
+$1=$recv($2)._allButLast();
+$recv($1)._do_((function(each){
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx2) {
 //>>excludeEnd("ctx");
 return $recv(self._sequence())._add_(self._visit_(each));
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,4)});
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)});
 //>>excludeEnd("ctx");
 }));
 return self._visitOrAlias_($recv($recv(aNode)._nodes())._last());
@@ -606,10 +653,10 @@ return self._visitOrAlias_($recv($recv(aNode)._nodes())._last());
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aNode"],
-source: "visitCascadeNode: aNode\x0a\x09aNode nodes inject: aNode receiver into: [ :previous :each |\x0a\x09\x09| receiver |\x0a\x09\x09receiver := previous isImmutable \x0a\x09\x09\x09ifTrue: [ previous ]\x0a\x09\x09\x09ifFalse: [\x0a\x09\x09\x09\x09| alias |\x0a\x09\x09\x09\x09alias := self alias: previous.\x0a\x09\x09\x09\x09VariableNode new binding: alias variable ].\x0a\x09\x09each receiver: receiver.\x0a\x09\x09receiver ].\x0a\x0a\x09aNode nodes allButLast do: [ :each |\x0a\x09\x09self sequence add: (self visit: each) ].\x0a\x0a\x09^ self visitOrAlias: aNode nodes last",
-referencedClasses: ["VariableNode"],
+source: "visitCascadeNode: aNode\x0a\x09aNode nodes allButLast do: [ :each |\x0a\x09\x09self sequence add: (self visit: each) ].\x0a\x0a\x09^ self visitOrAlias: aNode nodes last",
+referencedClasses: [],
 //>>excludeEnd("ide");
-messageSends: ["inject:into:", "nodes", "receiver", "ifTrue:ifFalse:", "isImmutable", "alias:", "binding:", "new", "variable", "receiver:", "do:", "allButLast", "add:", "sequence", "visit:", "visitOrAlias:", "last"]
+messageSends: ["do:", "allButLast", "nodes", "add:", "sequence", "visit:", "visitOrAlias:", "last"]
 }),
 $globals.IRASTTranslator);
 
@@ -892,6 +939,29 @@ messageSends: ["ifTrue:ifFalse:", "shouldBeAliased", "alias:", "visit:"]
 }),
 $globals.IRASTTranslator);
 
+$core.addMethod(
+$core.method({
+selector: "visitRefNode:",
+protocol: 'visiting',
+fn: function (aNode){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return self._alias_($recv(aNode)._node());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"visitRefNode:",{aNode:aNode},$globals.IRASTTranslator)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aNode"],
+source: "visitRefNode: aNode\x0a\x09^ self alias: aNode node",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["alias:", "node"]
+}),
+$globals.IRASTTranslator);
+
 $core.addMethod(
 $core.method({
 selector: "visitReturnNode:",

+ 19 - 12
src/Compiler-IR.st

@@ -1,6 +1,6 @@
 Smalltalk createPackage: 'Compiler-IR'!
 NodeVisitor subclass: #IRASTTranslator
-	instanceVariableNames: 'source theClass method sequence nextAlias'
+	instanceVariableNames: 'source theClass method sequence nextAlias nodeAliases'
 	package: 'Compiler-IR'!
 !IRASTTranslator commentStamp!
 I am the AST (abstract syntax tree) visitor responsible for building the intermediate representation graph.!
@@ -21,6 +21,17 @@ nextAlias
 	^ nextAlias asString
 !
 
+node: aNode aliased: anAliasVar
+	nodeAliases ifNil: [ nodeAliases := #{} ].
+	nodeAliases at: aNode nodeId put: anAliasVar
+!
+
+nodeAliasFor: aNode
+	nodeAliases
+		ifNil: [ ^ nil ]
+		ifNotNil: [ ^ nodeAliases at: aNode nodeId ifAbsent: [] ]
+!
+
 sequence
 	^ sequence
 !
@@ -60,10 +71,13 @@ alias: aNode
 	| variable |
 
 	aNode isImmutable ifTrue: [ ^ self visit: aNode ].
+	
+	(self nodeAliasFor: aNode) ifNotNil: [ :aliasVar | ^ aliasVar ].
 
 	variable := IRVariable new
 		variable: (AliasVar new name: '$', self nextAlias);
 		yourself.
+	self node: aNode aliased: variable.
 
 	self sequence add: (IRAssignment new
 		add: variable;
@@ -138,17 +152,6 @@ visitBlockSequenceNode: aNode
 !
 
 visitCascadeNode: aNode
-	aNode nodes inject: aNode receiver into: [ :previous :each |
-		| receiver |
-		receiver := previous isImmutable 
-			ifTrue: [ previous ]
-			ifFalse: [
-				| alias |
-				alias := self alias: previous.
-				VariableNode new binding: alias variable ].
-		each receiver: receiver.
-		receiver ].
-
 	aNode nodes allButLast do: [ :each |
 		self sequence add: (self visit: each) ].
 
@@ -213,6 +216,10 @@ visitOrAlias: aNode
 		ifFalse: [ self visit: aNode ]
 !
 
+visitRefNode: aNode
+	^ self alias: aNode node
+!
+
 visitReturnNode: aNode
 	| return |
 	return := aNode nonLocalReturn

+ 131 - 2
src/Compiler-Interpreter.js

@@ -2139,6 +2139,57 @@ messageSends: ["ifEmpty:ifNotEmpty:", "nodes", "visit:", "first"]
 }),
 $globals.ASTEnterNode);
 
+$core.addMethod(
+$core.method({
+selector: "visitRefNode:",
+protocol: 'visiting',
+fn: function (aNode){
+var self=this;
+var ref;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+var $early={};
+try {
+ref=$recv(aNode)._node();
+$recv($recv(self._interpreter())._refResults())._at_ifPresent_ifAbsent_($recv(ref)._nodeId(),(function(){
+throw $early=[aNode];
+
+}),(function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$recv(ref)._parent_(aNode);
+$1=(
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.supercall = true, 
+//>>excludeEnd("ctx");
+($globals.ASTEnterNode.superclass||$boot.dnu).fn.prototype._visitRefNode_.apply($recv(self), [aNode]));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.supercall = false;
+//>>excludeEnd("ctx");;
+throw $early=[$1];
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)});
+//>>excludeEnd("ctx");
+}));
+return self;
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"visitRefNode:",{aNode:aNode,ref:ref},$globals.ASTEnterNode)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aNode"],
+source: "visitRefNode: aNode\x0a\x09| ref |\x0a\x09ref := aNode node.\x0a\x09self interpreter refResults at: ref nodeId\x0a\x09\x09ifPresent: [ ^ aNode ]\x0a\x09\x09ifAbsent: [ ref parent: aNode. ^ super visitRefNode: aNode ]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["node", "at:ifPresent:ifAbsent:", "refResults", "interpreter", "nodeId", "parent:", "visitRefNode:"]
+}),
+$globals.ASTEnterNode);
+
 
 $core.addMethod(
 $core.method({
@@ -2167,7 +2218,7 @@ messageSends: ["interpreter:", "new", "yourself"]
 $globals.ASTEnterNode.klass);
 
 
-$core.addClass('ASTInterpreter', $globals.NodeVisitor, ['node', 'context', 'stack', 'returnValue', 'returned', 'forceAtEnd'], 'Compiler-Interpreter');
+$core.addClass('ASTInterpreter', $globals.NodeVisitor, ['node', 'context', 'stack', 'returnValue', 'returned', 'forceAtEnd', 'refResults'], 'Compiler-Interpreter');
 //>>excludeStart("ide", pragmas.excludeIdeData);
 $globals.ASTInterpreter.comment="I visit an AST, interpreting (evaluating) nodes one after the other, using a small stack machine.\x0a\x0a## API\x0a\x0aWhile my instances should be used from within an `ASTDebugger`, which provides a more high level interface,\x0ayou can use methods from the `interpreting` protocol:\x0a\x0a- `#step` evaluates the current `node` only\x0a- `#stepOver` evaluates the AST from the current `node` up to the next stepping node (most likely the next send node)\x0a- `#proceed` evaluates eagerly the AST\x0a- `#restart` select the first node of the AST\x0a- `#skip` skips the current node, moving to the next one if any";
 //>>excludeEnd("ide");
@@ -2435,6 +2486,7 @@ $ctx1.supercall = true,
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.supercall = false;
 //>>excludeEnd("ctx");;
+self["@refResults"]=$globals.HashedCollection._newFromPairs_([]);
 self["@forceAtEnd"]=false;
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
@@ -2443,7 +2495,7 @@ return self;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "initialize\x0a\x09super initialize.\x0a\x0a\x09forceAtEnd := false",
+source: "initialize\x0a\x09super initialize.\x0a\x0a\x09refResults := #{}.\x0a\x09forceAtEnd := false",
 referencedClasses: [],
 //>>excludeEnd("ide");
 messageSends: ["initialize"]
@@ -2736,6 +2788,24 @@ messageSends: ["add:", "stack"]
 }),
 $globals.ASTInterpreter);
 
+$core.addMethod(
+$core.method({
+selector: "refResults",
+protocol: 'accessing',
+fn: function (){
+var self=this;
+return self["@refResults"];
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "refResults\x0a\x09^ refResults",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.ASTInterpreter);
+
 $core.addMethod(
 $core.method({
 selector: "restart",
@@ -3297,6 +3367,47 @@ messageSends: []
 }),
 $globals.ASTInterpreter);
 
+$core.addMethod(
+$core.method({
+selector: "visitRefNode:",
+protocol: 'visiting',
+fn: function (aNode){
+var self=this;
+var ref,refid;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+ref=$recv(aNode)._node();
+refid=$recv(ref)._nodeId();
+$1=self._refResults();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx["refResults"]=1;
+//>>excludeEnd("ctx");
+$recv($1)._at_ifAbsentPut_(refid,(function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return self._pop();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+self._push_($recv(self._refResults())._at_(refid));
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"visitRefNode:",{aNode:aNode,ref:ref,refid:refid},$globals.ASTInterpreter)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aNode"],
+source: "visitRefNode: aNode\x0a\x09| ref refid |\x0a\x09ref := aNode node.\x0a\x09refid := ref nodeId.\x0a\x09self refResults at: refid ifAbsentPut: [ self pop ].\x0a\x09self push: (self refResults at: refid)",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["node", "nodeId", "at:ifAbsentPut:", "refResults", "pop", "push:", "at:"]
+}),
+$globals.ASTInterpreter);
+
 $core.addMethod(
 $core.method({
 selector: "visitReturnNode:",
@@ -3935,6 +4046,24 @@ messageSends: ["at:ifAbsent:", "nodes", "+", "indexOf:"]
 }),
 $globals.Node);
 
+$core.addMethod(
+$core.method({
+selector: "nextSiblingNode:",
+protocol: '*Compiler-Interpreter',
+fn: function (aNode){
+var self=this;
+return nil;
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aNode"],
+source: "nextSiblingNode: aNode\x0a\x09\x22no siblings in ref node\x22\x0a\x09^ nil",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.RefNode);
+
 $core.addMethod(
 $core.method({
 selector: "isSteppingNode",

+ 29 - 1
src/Compiler-Interpreter.st

@@ -564,6 +564,14 @@ visitNode: aNode
 	aNode nodes
 		ifEmpty: [ ^ aNode ]
 		ifNotEmpty: [ :nodes | ^ self visit: nodes first ]
+!
+
+visitRefNode: aNode
+	| ref |
+	ref := aNode node.
+	self interpreter refResults at: ref nodeId
+		ifPresent: [ ^ aNode ]
+		ifAbsent: [ ref parent: aNode. ^ super visitRefNode: aNode ]
 ! !
 
 !ASTEnterNode class methodsFor: 'instance creation'!
@@ -575,7 +583,7 @@ on: anInterpreter
 ! !
 
 NodeVisitor subclass: #ASTInterpreter
-	instanceVariableNames: 'node context stack returnValue returned forceAtEnd'
+	instanceVariableNames: 'node context stack returnValue returned forceAtEnd refResults'
 	package: 'Compiler-Interpreter'!
 !ASTInterpreter commentStamp!
 I visit an AST, interpreting (evaluating) nodes one after the other, using a small stack machine.
@@ -611,6 +619,10 @@ node: aNode
 	node := aNode
 !
 
+refResults
+	^ refResults
+!
+
 result
 	^ self hasReturned 
 		ifTrue: [ self returnValue ] 
@@ -634,6 +646,7 @@ stack
 initialize
 	super initialize.
 
+	refResults := #{}.
 	forceAtEnd := false
 ! !
 
@@ -858,6 +871,14 @@ visitNode: aNode
 	"Do nothing by default. Especially, do not visit children recursively."
 !
 
+visitRefNode: aNode
+	| ref refid |
+	ref := aNode node.
+	refid := ref nodeId.
+	self refResults at: refid ifAbsentPut: [ self pop ].
+	self push: (self refResults at: refid)
+!
+
 visitReturnNode: aNode
 	returned := true.
 	self returnValue: self pop
@@ -1025,6 +1046,13 @@ nextSiblingNode: aNode
 		ifAbsent: [ ^ nil ]
 ! !
 
+!RefNode methodsFor: '*Compiler-Interpreter'!
+
+nextSiblingNode: aNode
+	"no siblings in ref node"
+	^ nil
+! !
+
 !SendNode methodsFor: '*Compiler-Interpreter'!
 
 isSteppingNode

+ 61 - 5
src/Compiler-Semantic.js

@@ -2266,10 +2266,33 @@ selector: "visitCascadeNode:",
 protocol: 'visiting',
 fn: function (aNode){
 var self=this;
+var recv;
+function $RefNode(){return $globals.RefNode||(typeof RefNode=="undefined"?nil:RefNode)}
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-$recv(aNode)._receiver_($recv($recv($recv(aNode)._nodes())._first())._receiver());
+var $2,$1,$3;
+$2=$recv(aNode)._nodes();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx["nodes"]=1;
+//>>excludeEnd("ctx");
+$1=$recv($2)._first();
+recv=$recv($1)._receiver();
+$recv($recv(aNode)._nodes())._do_((function(each){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$3=$recv($RefNode())._new();
+$recv($3)._node_(recv);
+return $recv(each)._receiver_($recv($3)._yourself());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["receiver:"]=1;
+//>>excludeEnd("ctx");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+$recv(aNode)._receiver_(recv);
 (
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.supercall = true, 
@@ -2280,15 +2303,15 @@ $ctx1.supercall = false;
 //>>excludeEnd("ctx");;
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"visitCascadeNode:",{aNode:aNode},$globals.SemanticAnalyzer)});
+}, function($ctx1) {$ctx1.fill(self,"visitCascadeNode:",{aNode:aNode,recv:recv},$globals.SemanticAnalyzer)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aNode"],
-source: "visitCascadeNode: aNode\x0a\x09aNode receiver: aNode nodes first receiver.\x0a\x09super visitCascadeNode: aNode",
-referencedClasses: [],
+source: "visitCascadeNode: aNode\x0a\x09| recv |\x0a\x09recv := aNode nodes first receiver.\x0a\x09aNode nodes do: [ :each | each receiver: (RefNode new node: recv; yourself) ].\x0a\x09aNode receiver: recv.\x0a\x09super visitCascadeNode: aNode",
+referencedClasses: ["RefNode"],
 //>>excludeEnd("ide");
-messageSends: ["receiver:", "receiver", "first", "nodes", "visitCascadeNode:"]
+messageSends: ["receiver", "first", "nodes", "do:", "receiver:", "node:", "new", "yourself", "visitCascadeNode:"]
 }),
 $globals.SemanticAnalyzer);
 
@@ -2351,6 +2374,39 @@ messageSends: ["pushScope:", "newMethodScope", "scope:", "node:", "do:", "allIns
 }),
 $globals.SemanticAnalyzer);
 
+$core.addMethod(
+$core.method({
+selector: "visitRefNode:",
+protocol: 'visiting',
+fn: function (aNode){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+$recv($recv(aNode)._node())._shouldBeAliased_(true);
+$1=(
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.supercall = true, 
+//>>excludeEnd("ctx");
+($globals.SemanticAnalyzer.superclass||$boot.dnu).fn.prototype._visitRefNode_.apply($recv(self), [aNode]));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.supercall = false;
+//>>excludeEnd("ctx");;
+return $1;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"visitRefNode:",{aNode:aNode},$globals.SemanticAnalyzer)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aNode"],
+source: "visitRefNode: aNode\x0a\x09aNode node shouldBeAliased: true.\x0a\x09^ super visitRefNode: aNode",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["shouldBeAliased:", "node", "visitRefNode:"]
+}),
+$globals.SemanticAnalyzer);
+
 $core.addMethod(
 $core.method({
 selector: "visitReturnNode:",

+ 9 - 1
src/Compiler-Semantic.st

@@ -550,7 +550,10 @@ visitBlockNode: aNode
 !
 
 visitCascadeNode: aNode
-	aNode receiver: aNode nodes first receiver.
+	| recv |
+	recv := aNode nodes first receiver.
+	aNode nodes do: [ :each | each receiver: (RefNode new node: recv; yourself) ].
+	aNode receiver: recv.
 	super visitCascadeNode: aNode
 !
 
@@ -573,6 +576,11 @@ visitMethodNode: aNode
 	self popScope
 !
 
+visitRefNode: aNode
+	aNode node shouldBeAliased: true.
+	^ super visitRefNode: aNode
+!
+
 visitReturnNode: aNode
 	aNode scope: currentScope.
 	currentScope isMethodScope