Browse Source

Merge branch 'up' into branching-sends

# Conflicts:
#	src/Compiler-AST.js
#	src/Compiler-AST.st
Herbert Vojčík 8 years ago
parent
commit
146a2baeb2

+ 107 - 0
src/Compiler-AST.js

@@ -1921,6 +1921,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('QuasiSendNode', $globals.Node, ['receiver'], 'Compiler-AST');
 //>>excludeStart("ide", pragmas.excludeIdeData);
 $globals.QuasiSendNode.comment="I am a node that has a receiver.\x0a\x0aMy subclasses are `SendNode`, `CascadeNode` and `BranchSendNode`.";
@@ -3593,6 +3677,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:",

+ 38 - 0
src/Compiler-AST.st

@@ -436,6 +436,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: #QuasiSendNode
 	instanceVariableNames: 'receiver'
 	package: 'Compiler-AST'!
@@ -865,6 +899,10 @@ visitNode: aNode
 	^ self visitAll: aNode nodes
 !
 
+visitRefNode: aNode
+	^ self visit: aNode node
+!
+
 visitReturnNode: aNode
 	^ self visitNode: aNode
 !

+ 31 - 34
src/Compiler-IR.js

@@ -660,48 +660,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());
@@ -711,10 +685,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);
 
@@ -997,6 +971,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:",

+ 4 - 11
src/Compiler-IR.st

@@ -159,17 +159,6 @@ visitBranchSendNode: 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) ].
 
@@ -234,6 +223,10 @@ visitOrAlias: aNode
 		ifFalse: [ self visit: aNode ]
 !
 
+visitRefNode: aNode
+	^ self alias: aNode node
+!
+
 visitReturnNode: aNode
 	| return |
 	return := aNode nonLocalReturn

+ 134 - 6
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:",
@@ -3345,7 +3456,7 @@ $ctx2.sendIdx["pop"]=1;
 }, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)});
 //>>excludeEnd("ctx");
 }));
-receiver=self._peek();
+receiver=self._pop();
 message=self._messageFromSendNode_arguments_(aNode,$recv(args)._reversed());
 result=self._sendMessage_to_superSend_(message,receiver,$recv(aNode)._superSend());
 $1=$recv($recv(aNode)._isCascadeSendNode())._and_((function(){
@@ -3358,7 +3469,6 @@ return $recv($recv(aNode)._isLastChild())._not();
 //>>excludeEnd("ctx");
 }));
 if(!$core.assert($1)){
-self._pop();
 self._push_(result);
 };
 return self;
@@ -3368,10 +3478,10 @@ return self;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aNode"],
-source: "visitSendNode: aNode\x0a\x09| receiver args message result |\x0a\x09\x0a\x09args := aNode arguments collect: [ :each | self pop ].\x0a\x09receiver := self peek.\x0a\x09\x0a\x09message := self\x0a\x09\x09messageFromSendNode: aNode\x0a\x09\x09arguments: args reversed.\x0a\x09\x0a\x09result := self sendMessage: message to: receiver superSend: aNode superSend.\x0a\x09\x0a\x09\x22For cascade sends, push the reciever if the send is not the last one\x22\x0a\x09(aNode isCascadeSendNode and: [ aNode isLastChild not ])\x0a\x09\x09ifFalse: [ self pop; push: result ]",
+source: "visitSendNode: aNode\x0a\x09| receiver args message result |\x0a\x09\x0a\x09args := aNode arguments collect: [ :each | self pop ].\x0a\x09receiver := self pop.\x0a\x09\x0a\x09message := self\x0a\x09\x09messageFromSendNode: aNode\x0a\x09\x09arguments: args reversed.\x0a\x09\x0a\x09result := self sendMessage: message to: receiver superSend: aNode superSend.\x0a\x09\x0a\x09\x22For cascade sends, push the reciever if the send is not the last one\x22\x0a\x09(aNode isCascadeSendNode and: [ aNode isLastChild not ])\x0a\x09\x09ifFalse: [ self push: result ]",
 referencedClasses: [],
 //>>excludeEnd("ide");
-messageSends: ["collect:", "arguments", "pop", "peek", "messageFromSendNode:arguments:", "reversed", "sendMessage:to:superSend:", "superSend", "ifFalse:", "and:", "isCascadeSendNode", "not", "isLastChild", "push:"]
+messageSends: ["collect:", "arguments", "pop", "messageFromSendNode:arguments:", "reversed", "sendMessage:to:superSend:", "superSend", "ifFalse:", "and:", "isCascadeSendNode", "not", "isLastChild", "push:"]
 }),
 $globals.ASTInterpreter);
 
@@ -3935,6 +4045,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",

+ 31 - 3
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
@@ -867,7 +888,7 @@ visitSendNode: aNode
 	| receiver args message result |
 	
 	args := aNode arguments collect: [ :each | self pop ].
-	receiver := self peek.
+	receiver := self pop.
 	
 	message := self
 		messageFromSendNode: aNode
@@ -877,7 +898,7 @@ visitSendNode: aNode
 	
 	"For cascade sends, push the reciever if the send is not the last one"
 	(aNode isCascadeSendNode and: [ aNode isLastChild not ])
-		ifFalse: [ self pop; push: result ]
+		ifFalse: [ self push: result ]
 !
 
 visitSequenceNode: aNode
@@ -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

+ 106 - 6
src/Compiler-Semantic.js

@@ -1730,7 +1730,7 @@ $globals.UnknownVar);
 
 
 
-$core.addClass('SemanticAnalyzer', $globals.NodeVisitor, ['currentScope', 'blockIndex', 'thePackage', 'theClass', 'classReferences', 'messageSends'], 'Compiler-Semantic');
+$core.addClass('SemanticAnalyzer', $globals.NodeVisitor, ['currentScope', 'blockIndex', 'thePackage', 'theClass', 'classReferences', 'messageSends', 'visitedRefIds'], 'Compiler-Semantic');
 //>>excludeStart("ide", pragmas.excludeIdeData);
 $globals.SemanticAnalyzer.comment="I semantically analyze the abstract syntax tree and annotate it with informations such as non local returns and variable scopes.";
 //>>excludeEnd("ide");
@@ -1844,6 +1844,39 @@ messageSends: ["value", "ifTrue:ifFalse:", "and:", "not", "includes:", "globalJs
 }),
 $globals.SemanticAnalyzer);
 
+$core.addMethod(
+$core.method({
+selector: "initialize",
+protocol: 'initialization',
+fn: function (){
+var self=this;
+function $Set(){return $globals.Set||(typeof Set=="undefined"?nil:Set)}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+(
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.supercall = true, 
+//>>excludeEnd("ctx");
+($globals.SemanticAnalyzer.superclass||$boot.dnu).fn.prototype._initialize.apply($recv(self), []));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.supercall = false;
+//>>excludeEnd("ctx");;
+self["@visitedRefIds"]=$recv($Set())._new();
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"initialize",{},$globals.SemanticAnalyzer)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "initialize\x0a\x09super initialize.\x0a\x0a\x09visitedRefIds := Set new",
+referencedClasses: ["Set"],
+//>>excludeEnd("ide");
+messageSends: ["initialize", "new"]
+}),
+$globals.SemanticAnalyzer);
+
 $core.addMethod(
 $core.method({
 selector: "isVariableUndefined:inPackage:",
@@ -2298,10 +2331,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, 
@@ -2312,15 +2368,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);
 
@@ -2383,6 +2439,50 @@ messageSends: ["pushScope:", "newMethodScope", "scope:", "node:", "do:", "allIns
 }),
 $globals.SemanticAnalyzer);
 
+$core.addMethod(
+$core.method({
+selector: "visitRefNode:",
+protocol: 'visiting',
+fn: function (aNode){
+var self=this;
+var ref,aux;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1,$2;
+ref=$recv(aNode)._node();
+aux=$recv(self["@visitedRefIds"])._size();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx["size"]=1;
+//>>excludeEnd("ctx");
+$recv(self["@visitedRefIds"])._add_($recv(ref)._nodeId());
+$1=$recv(aux).__lt($recv(self["@visitedRefIds"])._size());
+if($core.assert($1)){
+$recv(ref)._shouldBeAliased_(true);
+$2=(
+//>>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 $2;
+};
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"visitRefNode:",{aNode:aNode,ref:ref,aux:aux},$globals.SemanticAnalyzer)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aNode"],
+source: "visitRefNode: aNode\x0a\x09| ref aux |\x0a\x09ref := aNode node.\x0a\x09aux := visitedRefIds size.\x0a\x09visitedRefIds add: ref nodeId.\x0a\x09aux < visitedRefIds size \x22added; not visited yet\x22 ifTrue: [\x0a\x09\x09ref shouldBeAliased: true.\x0a\x09\x09^ super visitRefNode: aNode ]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["node", "size", "add:", "nodeId", "ifTrue:", "<", "shouldBeAliased:", "visitRefNode:"]
+}),
+$globals.SemanticAnalyzer);
+
 $core.addMethod(
 $core.method({
 selector: "visitReturnNode:",

+ 23 - 2
src/Compiler-Semantic.st

@@ -410,7 +410,7 @@ isUnknownVar
 ! !
 
 NodeVisitor subclass: #SemanticAnalyzer
-	instanceVariableNames: 'currentScope blockIndex thePackage theClass classReferences messageSends'
+	instanceVariableNames: 'currentScope blockIndex thePackage theClass classReferences messageSends visitedRefIds'
 	package: 'Compiler-Semantic'!
 !SemanticAnalyzer commentStamp!
 I semantically analyze the abstract syntax tree and annotate it with informations such as non local returns and variable scopes.!
@@ -487,6 +487,14 @@ newScopeOfClass: aLexicalScopeClass
 		yourself
 ! !
 
+!SemanticAnalyzer methodsFor: 'initialization'!
+
+initialize
+	super initialize.
+
+	visitedRefIds := Set new
+! !
+
 !SemanticAnalyzer methodsFor: 'private'!
 
 nextBlockIndex
@@ -555,7 +563,10 @@ visitBranchSendNode: 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
 !
 
@@ -578,6 +589,16 @@ visitMethodNode: aNode
 	self popScope
 !
 
+visitRefNode: aNode
+	| ref aux |
+	ref := aNode node.
+	aux := visitedRefIds size.
+	visitedRefIds add: ref nodeId.
+	aux < visitedRefIds size "added; not visited yet" ifTrue: [
+		ref shouldBeAliased: true.
+		^ super visitRefNode: aNode ]
+!
+
 visitReturnNode: aNode
 	aNode scope: currentScope.
 	currentScope isMethodScope

+ 24 - 0
src/Compiler-Tests.js

@@ -939,6 +939,30 @@ messageSends: ["should:return:"]
 }),
 $globals.CodeGeneratorTest);
 
+$core.addMethod(
+$core.method({
+selector: "testInnerCascades",
+protocol: 'tests',
+fn: function (){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+self._should_return_("foo ^ Array new add: (3 squared; negated); add: (4 negated; squared); yourself",[(-3), (16)]);
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testInnerCascades",{},$globals.CodeGeneratorTest)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testInnerCascades\x0a\x09\x0a\x09self should: 'foo ^ Array new add: (3 squared; negated); add: (4 negated; squared); yourself' return: #(-3 16)",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["should:return:"]
+}),
+$globals.CodeGeneratorTest);
+
 $core.addMethod(
 $core.method({
 selector: "testInnerTemporalDependentElementsOrdered",

+ 5 - 0
src/Compiler-Tests.st

@@ -255,6 +255,11 @@ testGlobalVar
 	self should: 'foo ^ NonExistingVar' return: nil
 !
 
+testInnerCascades
+	
+	self should: 'foo ^ Array new add: (3 squared; negated); add: (4 negated; squared); yourself' return: #(-3 16)
+!
+
 testInnerTemporalDependentElementsOrdered
 	self should: 'foo
 	| x |

+ 8 - 10
src/Kernel-Tests.js

@@ -5301,7 +5301,7 @@ var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $2,$3,$4,$1,$5,$7,$8,$9,$6,$10,$12,$11;
+var $2,$3,$4,$1,$5,$7,$8,$9,$6,$10,$11;
 (
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.supercall = true, 
@@ -5360,13 +5360,12 @@ self._assert_equals_($6,$10);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["assert:equals:"]=2;
 //>>excludeEnd("ctx");
-$12=self._collectionWithNewValue();
+$11=self._collectionWithNewValue();
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["collectionWithNewValue"]=3;
 //>>excludeEnd("ctx");
-$recv($12)._addAll_(self._collection());
-$11=$recv($12)._yourself();
-self._assert_equals_($11,self._collectionWithNewValue());
+$recv($11)._addAll_(self._collection());
+self._assert_equals_($recv($11)._yourself(),self._collectionWithNewValue());
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx1) {$ctx1.fill(self,"testAddAll",{},$globals.AssociativeCollectionTest)});
@@ -9168,7 +9167,7 @@ var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $2,$3,$4,$1,$5,$7,$8,$9,$6,$10,$12,$11;
+var $2,$3,$4,$1,$5,$7,$8,$9,$6,$10,$11;
 (
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.supercall = true, 
@@ -9227,13 +9226,12 @@ self._assert_equals_($6,$10);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["assert:equals:"]=2;
 //>>excludeEnd("ctx");
-$12=self._collectionWithNewValue();
+$11=self._collectionWithNewValue();
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["collectionWithNewValue"]=3;
 //>>excludeEnd("ctx");
-$recv($12)._addAll_(self._collection());
-$11=$recv($12)._yourself();
-self._assert_equals_($11,self._collectionWithNewValue());
+$recv($11)._addAll_(self._collection());
+self._assert_equals_($recv($11)._yourself(),self._collectionWithNewValue());
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx1) {$ctx1.fill(self,"testAddAll",{},$globals.SetTest)});