Browse Source

First shot at an AST interpreter

Nicolas Petton 12 years ago
parent
commit
b362fee8f8
3 changed files with 692 additions and 0 deletions
  1. 247 0
      js/Compiler-Interpreter.deploy.js
  2. 333 0
      js/Compiler-Interpreter.js
  3. 112 0
      st/Compiler-Interpreter.st

+ 247 - 0
js/Compiler-Interpreter.deploy.js

@@ -0,0 +1,247 @@
+smalltalk.addPackage('Compiler-Interpreter', {});
+smalltalk.addClass('ASTBlockClosure', smalltalk.BlockClosure, ['astNode'], 'Compiler-Interpreter');
+smalltalk.addMethod(
+"_astNode",
+smalltalk.method({
+selector: "astNode",
+fn: function (){
+var self=this;
+return self["@astNode"];
+}
+}),
+smalltalk.ASTBlockClosure);
+
+smalltalk.addMethod(
+"_astNode_",
+smalltalk.method({
+selector: "astNode:",
+fn: function (aNode){
+var self=this;
+self["@astNode"]=aNode;
+return self}
+}),
+smalltalk.ASTBlockClosure);
+
+smalltalk.addMethod(
+"_value",
+smalltalk.method({
+selector: "value",
+fn: function (){
+var self=this;
+var $1;
+$1=smalltalk.send(smalltalk.send((smalltalk.ASTInterpreter || ASTInterpreter),"_current",[]),"_blockValue_",[self]);
+return $1;
+}
+}),
+smalltalk.ASTBlockClosure);
+
+
+
+smalltalk.addClass('ASTInterpreter', smalltalk.NodeVisitor, ['currentNode', 'context', 'shouldReturn'], 'Compiler-Interpreter');
+smalltalk.addMethod(
+"_blockValue_",
+smalltalk.method({
+selector: "blockValue:",
+fn: function (anASTBlockClosure){
+var self=this;
+var $1;
+$1=smalltalk.send(self,"_interpret_",[smalltalk.send(smalltalk.send(smalltalk.send(anASTBlockClosure,"_astNode",[]),"_nodes",[]),"_first",[])]);
+return $1;
+}
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_context",
+smalltalk.method({
+selector: "context",
+fn: function (){
+var self=this;
+return self["@context"];
+}
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_context_",
+smalltalk.method({
+selector: "context:",
+fn: function (aMethodContext){
+var self=this;
+self["@context"]=aMethodContext;
+return self}
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_initialize",
+smalltalk.method({
+selector: "initialize",
+fn: function (){
+var self=this;
+smalltalk.send(self,"_initialize",[],smalltalk.NodeVisitor);
+self["@shouldReturn"]=false;
+return self}
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_interpret_",
+smalltalk.method({
+selector: "interpret:",
+fn: function (aNode){
+var self=this;
+var $1;
+self["@shouldReturn"]=false;
+$1=smalltalk.send(self,"_interpretNode_",[aNode]);
+return $1;
+}
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_interpretNode_",
+smalltalk.method({
+selector: "interpretNode:",
+fn: function (aNode){
+var self=this;
+var $1;
+self["@currentNode"]=aNode;
+$1=smalltalk.send(self,"_visit_",[aNode]);
+return $1;
+}
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_send_to_arguments_",
+smalltalk.method({
+selector: "send:to:arguments:",
+fn: function (aSelector,anObject,aCollection){
+var self=this;
+var $1;
+$1=smalltalk.send(anObject,"_perform_withArguments_",[aSelector,aCollection]);
+return $1;
+}
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_visitBlockNode_",
+smalltalk.method({
+selector: "visitBlockNode:",
+fn: function (aNode){
+var self=this;
+var $2,$3,$1;
+$2=smalltalk.send((smalltalk.ASTBlockClosure || ASTBlockClosure),"_new",[]);
+smalltalk.send($2,"_astNode_",[aNode]);
+$3=smalltalk.send($2,"_yourself",[]);
+$1=$3;
+return $1;
+}
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_visitReturnNode_",
+smalltalk.method({
+selector: "visitReturnNode:",
+fn: function (aNode){
+var self=this;
+var $1;
+self["@shouldReturn"]=true;
+$1=smalltalk.send(self,"_interpret_",[smalltalk.send(smalltalk.send(aNode,"_nodes",[]),"_first",[])]);
+return $1;
+}
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_visitSendNode_",
+smalltalk.method({
+selector: "visitSendNode:",
+fn: function (aNode){
+var self=this;
+var $1;
+var receiver;
+var arguments;
+receiver=smalltalk.send(self,"_interpret_",[smalltalk.send(aNode,"_receiver",[])]);
+arguments=smalltalk.send(smalltalk.send(aNode,"_arguments",[]),"_collect_",[(function(each){
+return smalltalk.send(self,"_interpret_",[each]);
+})]);
+$1=smalltalk.send(self,"_send_to_arguments_",[smalltalk.send(aNode,"_selector",[]),receiver,arguments]);
+return $1;
+}
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_visitSequenceNode_",
+smalltalk.method({
+selector: "visitSequenceNode:",
+fn: function (aNode){
+var self=this;
+var $1;
+var $early={};
+try {
+smalltalk.send(smalltalk.send(smalltalk.send(aNode,"_nodes",[]),"_allButLast",[]),"_do_",[(function(each){
+var value;
+value=smalltalk.send(self,"_interpret_",[each]);
+value;
+if(smalltalk.assert(self["@shouldReturn"])){
+throw $early=[value];
+};
+})]);
+$1=smalltalk.send(self,"_interpret_",[smalltalk.send(smalltalk.send(aNode,"_nodes",[]),"_last",[])]);
+return $1;
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+}
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_visitValueNode_",
+smalltalk.method({
+selector: "visitValueNode:",
+fn: function (aNode){
+var self=this;
+var $1;
+$1=smalltalk.send(aNode,"_value",[]);
+return $1;
+}
+}),
+smalltalk.ASTInterpreter);
+
+
+smalltalk.ASTInterpreter.klass.iVarNames = ['current'];
+smalltalk.addMethod(
+"_current",
+smalltalk.method({
+selector: "current",
+fn: function (){
+var self=this;
+var $1;
+if(($receiver = self["@current"]) == nil || $receiver == undefined){
+self["@current"]=smalltalk.send(self,"_new",[],smalltalk.NodeVisitor.klass);
+$1=self["@current"];
+} else {
+$1=self["@current"];
+};
+return $1;
+}
+}),
+smalltalk.ASTInterpreter.klass);
+
+smalltalk.addMethod(
+"_new",
+smalltalk.method({
+selector: "new",
+fn: function (){
+var self=this;
+smalltalk.send(self,"_shouldNotImplement",[]);
+return self}
+}),
+smalltalk.ASTInterpreter.klass);
+
+

+ 333 - 0
js/Compiler-Interpreter.js

@@ -0,0 +1,333 @@
+smalltalk.addPackage('Compiler-Interpreter', {});
+smalltalk.addClass('ASTBlockClosure', smalltalk.BlockClosure, ['astNode'], 'Compiler-Interpreter');
+smalltalk.ASTBlockClosure.comment="ASTBlockClosure is polymorph with BlockClosure. \x0aAn ASTBlockClosure is used to interpret a BlockNode, and override all \x22primitive\x22 methods (#value and co)."
+smalltalk.addMethod(
+"_astNode",
+smalltalk.method({
+selector: "astNode",
+category: 'accessing',
+fn: function (){
+var self=this;
+return self["@astNode"];
+},
+args: [],
+source: "astNode\x0a\x09^ astNode",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.ASTBlockClosure);
+
+smalltalk.addMethod(
+"_astNode_",
+smalltalk.method({
+selector: "astNode:",
+category: 'accessing',
+fn: function (aNode){
+var self=this;
+self["@astNode"]=aNode;
+return self},
+args: ["aNode"],
+source: "astNode: aNode\x0a\x09astNode := aNode",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.ASTBlockClosure);
+
+smalltalk.addMethod(
+"_value",
+smalltalk.method({
+selector: "value",
+category: 'evaluating',
+fn: function (){
+var self=this;
+var $1;
+$1=smalltalk.send(smalltalk.send((smalltalk.ASTInterpreter || ASTInterpreter),"_current",[]),"_blockValue_",[self]);
+return $1;
+},
+args: [],
+source: "value\x0a\x09^ ASTInterpreter current blockValue: self",
+messageSends: ["blockValue:", "current"],
+referencedClasses: ["ASTInterpreter"]
+}),
+smalltalk.ASTBlockClosure);
+
+
+
+smalltalk.addClass('ASTInterpreter', smalltalk.NodeVisitor, ['currentNode', 'context', 'shouldReturn'], 'Compiler-Interpreter');
+smalltalk.addMethod(
+"_blockValue_",
+smalltalk.method({
+selector: "blockValue:",
+category: 'interpreting',
+fn: function (anASTBlockClosure){
+var self=this;
+var $1;
+$1=smalltalk.send(self,"_interpret_",[smalltalk.send(smalltalk.send(smalltalk.send(anASTBlockClosure,"_astNode",[]),"_nodes",[]),"_first",[])]);
+return $1;
+},
+args: ["anASTBlockClosure"],
+source: "blockValue: anASTBlockClosure\x0a\x09^ self interpret: anASTBlockClosure astNode nodes first",
+messageSends: ["interpret:", "first", "nodes", "astNode"],
+referencedClasses: []
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_context",
+smalltalk.method({
+selector: "context",
+category: 'accessing',
+fn: function (){
+var self=this;
+return self["@context"];
+},
+args: [],
+source: "context\x0a\x09^ context",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_context_",
+smalltalk.method({
+selector: "context:",
+category: 'accessing',
+fn: function (aMethodContext){
+var self=this;
+self["@context"]=aMethodContext;
+return self},
+args: ["aMethodContext"],
+source: "context: aMethodContext\x0a\x09context := aMethodContext",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_initialize",
+smalltalk.method({
+selector: "initialize",
+category: 'initialization',
+fn: function (){
+var self=this;
+smalltalk.send(self,"_initialize",[],smalltalk.NodeVisitor);
+self["@shouldReturn"]=false;
+return self},
+args: [],
+source: "initialize\x0a\x09super initialize.\x0a    shouldReturn := false",
+messageSends: ["initialize"],
+referencedClasses: []
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_interpret_",
+smalltalk.method({
+selector: "interpret:",
+category: 'interpreting',
+fn: function (aNode){
+var self=this;
+var $1;
+self["@shouldReturn"]=false;
+$1=smalltalk.send(self,"_interpretNode_",[aNode]);
+return $1;
+},
+args: ["aNode"],
+source: "interpret: aNode\x0a\x09shouldReturn := false.\x0a    ^ self interpretNode: aNode",
+messageSends: ["interpretNode:"],
+referencedClasses: []
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_interpretNode_",
+smalltalk.method({
+selector: "interpretNode:",
+category: 'interpreting',
+fn: function (aNode){
+var self=this;
+var $1;
+self["@currentNode"]=aNode;
+$1=smalltalk.send(self,"_visit_",[aNode]);
+return $1;
+},
+args: ["aNode"],
+source: "interpretNode: aNode\x0a\x09currentNode := aNode.\x0a    ^ self visit: aNode",
+messageSends: ["visit:"],
+referencedClasses: []
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_send_to_arguments_",
+smalltalk.method({
+selector: "send:to:arguments:",
+category: 'interpreting',
+fn: function (aSelector,anObject,aCollection){
+var self=this;
+var $1;
+$1=smalltalk.send(anObject,"_perform_withArguments_",[aSelector,aCollection]);
+return $1;
+},
+args: ["aSelector", "anObject", "aCollection"],
+source: "send: aSelector to: anObject arguments: aCollection\x0a\x09^ anObject perform: aSelector withArguments: aCollection",
+messageSends: ["perform:withArguments:"],
+referencedClasses: []
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_visitBlockNode_",
+smalltalk.method({
+selector: "visitBlockNode:",
+category: 'visiting',
+fn: function (aNode){
+var self=this;
+var $2,$3,$1;
+$2=smalltalk.send((smalltalk.ASTBlockClosure || ASTBlockClosure),"_new",[]);
+smalltalk.send($2,"_astNode_",[aNode]);
+$3=smalltalk.send($2,"_yourself",[]);
+$1=$3;
+return $1;
+},
+args: ["aNode"],
+source: "visitBlockNode: aNode\x0a\x09^ ASTBlockClosure new\x0a    \x09astNode: aNode;\x0a        yourself",
+messageSends: ["astNode:", "new", "yourself"],
+referencedClasses: ["ASTBlockClosure"]
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_visitReturnNode_",
+smalltalk.method({
+selector: "visitReturnNode:",
+category: 'visiting',
+fn: function (aNode){
+var self=this;
+var $1;
+self["@shouldReturn"]=true;
+$1=smalltalk.send(self,"_interpret_",[smalltalk.send(smalltalk.send(aNode,"_nodes",[]),"_first",[])]);
+return $1;
+},
+args: ["aNode"],
+source: "visitReturnNode: aNode\x0a\x09shouldReturn := true.\x0a    ^ self interpret: aNode nodes first",
+messageSends: ["interpret:", "first", "nodes"],
+referencedClasses: []
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_visitSendNode_",
+smalltalk.method({
+selector: "visitSendNode:",
+category: 'visiting',
+fn: function (aNode){
+var self=this;
+var $1;
+var receiver;
+var arguments;
+receiver=smalltalk.send(self,"_interpret_",[smalltalk.send(aNode,"_receiver",[])]);
+arguments=smalltalk.send(smalltalk.send(aNode,"_arguments",[]),"_collect_",[(function(each){
+return smalltalk.send(self,"_interpret_",[each]);
+})]);
+$1=smalltalk.send(self,"_send_to_arguments_",[smalltalk.send(aNode,"_selector",[]),receiver,arguments]);
+return $1;
+},
+args: ["aNode"],
+source: "visitSendNode: aNode\x0a\x09\x22TODO: Handle super sends\x22\x0a\x09| receiver arguments |\x0a    \x0a    receiver := self interpret: aNode receiver.\x0a    arguments := aNode arguments collect: [ :each |\x0a\x09\x09self interpret: each ].\x0a    \x0a    ^ self send: aNode selector to: receiver arguments: arguments",
+messageSends: ["interpret:", "receiver", "collect:", "arguments", "send:to:arguments:", "selector"],
+referencedClasses: []
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_visitSequenceNode_",
+smalltalk.method({
+selector: "visitSequenceNode:",
+category: 'visiting',
+fn: function (aNode){
+var self=this;
+var $1;
+var $early={};
+try {
+smalltalk.send(smalltalk.send(smalltalk.send(aNode,"_nodes",[]),"_allButLast",[]),"_do_",[(function(each){
+var value;
+value=smalltalk.send(self,"_interpret_",[each]);
+value;
+if(smalltalk.assert(self["@shouldReturn"])){
+throw $early=[value];
+};
+})]);
+$1=smalltalk.send(self,"_interpret_",[smalltalk.send(smalltalk.send(aNode,"_nodes",[]),"_last",[])]);
+return $1;
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+},
+args: ["aNode"],
+source: "visitSequenceNode: aNode\x0a\x09aNode nodes allButLast do: [ :each | | value |\x0a        value := self interpret: each.\x0a\x09\x09shouldReturn ifTrue: [ ^ value ] ].\x0a    ^ self interpret: aNode nodes last",
+messageSends: ["do:", "interpret:", "ifTrue:", "allButLast", "nodes", "last"],
+referencedClasses: []
+}),
+smalltalk.ASTInterpreter);
+
+smalltalk.addMethod(
+"_visitValueNode_",
+smalltalk.method({
+selector: "visitValueNode:",
+category: 'visiting',
+fn: function (aNode){
+var self=this;
+var $1;
+$1=smalltalk.send(aNode,"_value",[]);
+return $1;
+},
+args: ["aNode"],
+source: "visitValueNode: aNode\x0a\x09^ aNode value",
+messageSends: ["value"],
+referencedClasses: []
+}),
+smalltalk.ASTInterpreter);
+
+
+smalltalk.ASTInterpreter.klass.iVarNames = ['current'];
+smalltalk.addMethod(
+"_current",
+smalltalk.method({
+selector: "current",
+category: 'instance creation',
+fn: function (){
+var self=this;
+var $1;
+if(($receiver = self["@current"]) == nil || $receiver == undefined){
+self["@current"]=smalltalk.send(self,"_new",[],smalltalk.NodeVisitor.klass);
+$1=self["@current"];
+} else {
+$1=self["@current"];
+};
+return $1;
+},
+args: [],
+source: "current\x0a\x09^ current ifNil: [ current := super new ]",
+messageSends: ["ifNil:", "new"],
+referencedClasses: []
+}),
+smalltalk.ASTInterpreter.klass);
+
+smalltalk.addMethod(
+"_new",
+smalltalk.method({
+selector: "new",
+category: 'instance creation',
+fn: function (){
+var self=this;
+smalltalk.send(self,"_shouldNotImplement",[]);
+return self},
+args: [],
+source: "new\x0a\x09self shouldNotImplement",
+messageSends: ["shouldNotImplement"],
+referencedClasses: []
+}),
+smalltalk.ASTInterpreter.klass);
+
+

+ 112 - 0
st/Compiler-Interpreter.st

@@ -0,0 +1,112 @@
+Smalltalk current createPackage: 'Compiler-Interpreter' properties: #{}!
+BlockClosure subclass: #ASTBlockClosure
+	instanceVariableNames: 'astNode'
+	package: 'Compiler-Interpreter'!
+!ASTBlockClosure commentStamp!
+ASTBlockClosure is polymorph with BlockClosure. 
+An ASTBlockClosure is used to interpret a BlockNode, and override all "primitive" methods (#value and co).!
+
+!ASTBlockClosure methodsFor: 'accessing'!
+
+astNode
+	^ astNode
+!
+
+astNode: aNode
+	astNode := aNode
+! !
+
+!ASTBlockClosure methodsFor: 'evaluating'!
+
+value
+	^ ASTInterpreter current blockValue: self
+! !
+
+NodeVisitor subclass: #ASTInterpreter
+	instanceVariableNames: 'currentNode context shouldReturn'
+	package: 'Compiler-Interpreter'!
+
+!ASTInterpreter methodsFor: 'accessing'!
+
+context
+	^ context
+!
+
+context: aMethodContext
+	context := aMethodContext
+! !
+
+!ASTInterpreter methodsFor: 'initialization'!
+
+initialize
+	super initialize.
+    shouldReturn := false
+! !
+
+!ASTInterpreter methodsFor: 'interpreting'!
+
+blockValue: anASTBlockClosure
+	^ self interpret: anASTBlockClosure astNode nodes first
+!
+
+interpret: aNode
+	shouldReturn := false.
+    ^ self interpretNode: aNode
+!
+
+interpretNode: aNode
+	currentNode := aNode.
+    ^ self visit: aNode
+!
+
+send: aSelector to: anObject arguments: aCollection
+	^ anObject perform: aSelector withArguments: aCollection
+! !
+
+!ASTInterpreter methodsFor: 'visiting'!
+
+visitBlockNode: aNode
+	^ ASTBlockClosure new
+    	astNode: aNode;
+        yourself
+!
+
+visitReturnNode: aNode
+	shouldReturn := true.
+    ^ self interpret: aNode nodes first
+!
+
+visitSendNode: aNode
+	"TODO: Handle super sends"
+	| receiver arguments |
+    
+    receiver := self interpret: aNode receiver.
+    arguments := aNode arguments collect: [ :each |
+		self interpret: each ].
+    
+    ^ self send: aNode selector to: receiver arguments: arguments
+!
+
+visitSequenceNode: aNode
+	aNode nodes allButLast do: [ :each | | value |
+        value := self interpret: each.
+		shouldReturn ifTrue: [ ^ value ] ].
+    ^ self interpret: aNode nodes last
+!
+
+visitValueNode: aNode
+	^ aNode value
+! !
+
+ASTInterpreter class instanceVariableNames: 'current'!
+
+!ASTInterpreter class methodsFor: 'instance creation'!
+
+current
+	^ current ifNil: [ current := super new ]
+!
+
+new
+	self shouldNotImplement
+! !
+