Browse Source

Fixes #910

- Adds AISemanticAnalyzer which is context aware
- Use the new class in Environment >> interpret:inContext:
Nicolas Petton 10 years ago
parent
commit
58e8971cce

+ 112 - 7
src/Compiler-Interpreter.js

@@ -1,4 +1,4 @@
-define("amber_core/Compiler-Interpreter", ["amber_vm/smalltalk", "amber_vm/nil", "amber_vm/_st", "amber_vm/globals", "amber_core/Kernel-Methods", "amber_core/Kernel-Objects", "amber_core/Compiler-Core", "amber_core/Kernel-Exceptions", "amber_core/Compiler-AST"], function(smalltalk,nil,_st, globals){
+define("amber_core/Compiler-Interpreter", ["amber_vm/smalltalk", "amber_vm/nil", "amber_vm/_st", "amber_vm/globals", "amber_core/Kernel-Methods", "amber_core/Compiler-Semantic", "amber_core/Kernel-Objects", "amber_core/Compiler-Core", "amber_core/Kernel-Exceptions", "amber_core/Compiler-AST"], function(smalltalk,nil,_st, globals){
 smalltalk.addPackage('Compiler-Interpreter');
 smalltalk.packages["Compiler-Interpreter"].transport = {"type":"amd","amdNamespace":"amber_core"};
 
@@ -246,13 +246,16 @@ return smalltalk.withContext(function($ctx1) {
 var $1;
 $1=_st(_st(self._ast())._arguments())._collect_((function(each){
 return smalltalk.withContext(function($ctx2) {
-return self._localAt_(each);
+return self._localAt_ifAbsent_(each,(function(){
+return smalltalk.withContext(function($ctx3) {
+return self._error_("Argument not in context");
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2,2)})}));
 }, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)})}));
 return $1;
 }, function($ctx1) {$ctx1.fill(self,"arguments",{},globals.AIContext)})},
 args: [],
-source: "arguments\x0a\x09^ self ast arguments collect: [ :each |\x0a\x09\x09self localAt: each ]",
-messageSends: ["collect:", "arguments", "ast", "localAt:"],
+source: "arguments\x0a\x09^ self ast arguments collect: [ :each |\x0a\x09\x09self localAt: each ifAbsent: [ self error: 'Argument not in context' ] ]",
+messageSends: ["collect:", "arguments", "ast", "localAt:ifAbsent:", "error:"],
 referencedClasses: []
 }),
 globals.AIContext);
@@ -581,14 +584,17 @@ return $2;
 } else {
 var context;
 context=$receiver;
-return _st(context)._localAt_(aString);
+return _st(context)._localAt_ifAbsent_(aString,(function(){
+return smalltalk.withContext(function($ctx3) {
+return self._error_("Variable missing");
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2,3)})}));
 };
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)})}));
 return $1;
 }, function($ctx1) {$ctx1.fill(self,"localAt:",{aString:aString},globals.AIContext)})},
 args: ["aString"],
-source: "localAt: aString\x0a\x09\x22Lookup the local value up to the method context\x22\x0a\x0a\x09^ self locals at: aString ifAbsent: [ \x0a\x09\x09self outerContext ifNotNil: [ :context | \x0a\x09\x09\x09context localAt: aString ] ]",
-messageSends: ["at:ifAbsent:", "locals", "ifNotNil:", "outerContext", "localAt:"],
+source: "localAt: aString\x0a\x09\x22Lookup the local value up to the method context\x22\x0a\x0a\x09^ self locals at: aString ifAbsent: [ \x0a\x09\x09self outerContext ifNotNil: [ :context | \x0a\x09\x09\x09context localAt: aString ifAbsent: [ \x0a\x09\x09\x09\x09self error: 'Variable missing' ] ] ]",
+messageSends: ["at:ifAbsent:", "locals", "ifNotNil:", "outerContext", "localAt:ifAbsent:", "error:"],
 referencedClasses: []
 }),
 globals.AIContext);
@@ -935,6 +941,105 @@ referencedClasses: []
 globals.AIContext.klass);
 
 
+smalltalk.addClass('AISemanticAnalyzer', globals.SemanticAnalyzer, ['context'], 'Compiler-Interpreter');
+globals.AISemanticAnalyzer.comment="I perform the same semantic analysis than `SemanticAnalyzer`, with the difference that provided an `AIContext` context, variables are bound with the context variables.";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "context",
+protocol: 'accessing',
+fn: function (){
+var self=this;
+var $1;
+$1=self["@context"];
+return $1;
+},
+args: [],
+source: "context\x0a\x09^ context",
+messageSends: [],
+referencedClasses: []
+}),
+globals.AISemanticAnalyzer);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "context:",
+protocol: 'accessing',
+fn: function (anAIContext){
+var self=this;
+self["@context"]=anAIContext;
+return self},
+args: ["anAIContext"],
+source: "context: anAIContext\x0a\x09context := anAIContext",
+messageSends: [],
+referencedClasses: []
+}),
+globals.AISemanticAnalyzer);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "visitVariableNode:",
+protocol: 'visiting',
+fn: function (aNode){
+var self=this;
+function $ASTContextVar(){return globals.ASTContextVar||(typeof ASTContextVar=="undefined"?nil:ASTContextVar)}
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+var $early={};
+try {
+_st(self._context())._localAt_ifAbsent_(_st(aNode)._value(),(function(){
+return smalltalk.withContext(function($ctx2) {
+$1=globals.AISemanticAnalyzer.superclass.fn.prototype._visitVariableNode_.apply(_st(self), [aNode]);
+throw $early=[$1];
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)})}));
+_st(aNode)._binding_(_st($ASTContextVar())._new());
+return self}
+catch(e) {if(e===$early)return e[0]; throw e}
+}, function($ctx1) {$ctx1.fill(self,"visitVariableNode:",{aNode:aNode},globals.AISemanticAnalyzer)})},
+args: ["aNode"],
+source: "visitVariableNode: aNode\x0a\x09self context \x0a\x09\x09localAt: aNode value \x0a\x09\x09ifAbsent: [ ^ super visitVariableNode: aNode ].\x0a\x0a\x09aNode binding: ASTContextVar new",
+messageSends: ["localAt:ifAbsent:", "context", "value", "visitVariableNode:", "binding:", "new"],
+referencedClasses: ["ASTContextVar"]
+}),
+globals.AISemanticAnalyzer);
+
+
+
+smalltalk.addClass('ASTContextVar', globals.ScopeVar, ['context'], 'Compiler-Interpreter');
+globals.ASTContextVar.comment="I am a variable defined in a `context`.";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "context",
+protocol: 'accessing',
+fn: function (){
+var self=this;
+var $1;
+$1=self["@context"];
+return $1;
+},
+args: [],
+source: "context\x0a\x09^ context",
+messageSends: [],
+referencedClasses: []
+}),
+globals.ASTContextVar);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "context:",
+protocol: 'accessing',
+fn: function (anObject){
+var self=this;
+self["@context"]=anObject;
+return self},
+args: ["anObject"],
+source: "context: anObject\x0a\x09context := anObject",
+messageSends: [],
+referencedClasses: []
+}),
+globals.ASTContextVar);
+
+
+
 smalltalk.addClass('ASTDebugger', globals.Object, ['interpreter', 'context'], 'Compiler-Interpreter');
 globals.ASTDebugger.comment="I am a stepping debugger interface for Amber code.\x0aI internally use an instance of `ASTInterpreter` to actually step through node and interpret them.\x0a\x0aMy instances are created from an `AIContext` with `ASTDebugger class >> context:`.\x0aThey hold an `AIContext` instance internally, recursive copy of the `MethodContext`.\x0a\x0a## API\x0a\x0aUse the methods of the `'stepping'` protocol to do stepping.";
 smalltalk.addMethod(

+ 45 - 2
src/Compiler-Interpreter.st

@@ -134,7 +134,8 @@ localAt: aString
 
 	^ self locals at: aString ifAbsent: [ 
 		self outerContext ifNotNil: [ :context | 
-			context localAt: aString ] ]
+			context localAt: aString ifAbsent: [ 
+				self error: 'Variable missing' ] ] ]
 !
 
 localAt: aString ifAbsent: aBlock
@@ -252,7 +253,7 @@ initializeLocals
 
 arguments
 	^ self ast arguments collect: [ :each |
-		self localAt: each ]
+		self localAt: each ifAbsent: [ self error: 'Argument not in context' ] ]
 !
 
 ast
@@ -307,6 +308,48 @@ fromMethodContext: aMethodContext
 		yourself
 ! !
 
+SemanticAnalyzer subclass: #AISemanticAnalyzer
+	instanceVariableNames: 'context'
+	package: 'Compiler-Interpreter'!
+!AISemanticAnalyzer commentStamp!
+I perform the same semantic analysis than `SemanticAnalyzer`, with the difference that provided an `AIContext` context, variables are bound with the context variables.!
+
+!AISemanticAnalyzer methodsFor: 'accessing'!
+
+context
+	^ context
+!
+
+context: anAIContext
+	context := anAIContext
+! !
+
+!AISemanticAnalyzer methodsFor: 'visiting'!
+
+visitVariableNode: aNode
+	self context 
+		localAt: aNode value 
+		ifAbsent: [ ^ super visitVariableNode: aNode ].
+
+	aNode binding: ASTContextVar new
+! !
+
+ScopeVar subclass: #ASTContextVar
+	instanceVariableNames: 'context'
+	package: 'Compiler-Interpreter'!
+!ASTContextVar commentStamp!
+I am a variable defined in a `context`.!
+
+!ASTContextVar methodsFor: 'accessing'!
+
+context
+	^ context
+!
+
+context: anObject
+	context := anObject
+! !
+
 Object subclass: #ASTDebugger
 	instanceVariableNames: 'interpreter context'
 	package: 'Compiler-Interpreter'!

+ 56 - 0
src/Compiler-Tests.js

@@ -1857,4 +1857,60 @@ referencedClasses: ["Smalltalk"]
 globals.SemanticAnalyzerTest);
 
 
+
+smalltalk.addClass('AISemanticAnalyzerTest', globals.SemanticAnalyzerTest, [], 'Compiler-Tests');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "setUp",
+protocol: 'running',
+fn: function (){
+var self=this;
+function $AISemanticAnalyzer(){return globals.AISemanticAnalyzer||(typeof AISemanticAnalyzer=="undefined"?nil:AISemanticAnalyzer)}
+function $Object(){return globals.Object||(typeof Object=="undefined"?nil:Object)}
+function $AIContext(){return globals.AIContext||(typeof AIContext=="undefined"?nil:AIContext)}
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$4,$5,$3,$6;
+$1=_st($AISemanticAnalyzer())._on_($Object());
+$2=$1;
+$4=_st($AIContext())._new();
+_st($4)._localAt_put_("local",(3));
+$5=_st($4)._yourself();
+$ctx1.sendIdx["yourself"]=1;
+$3=$5;
+_st($2)._context_($3);
+$6=_st($1)._yourself();
+self["@analyzer"]=$6;
+return self}, function($ctx1) {$ctx1.fill(self,"setUp",{},globals.AISemanticAnalyzerTest)})},
+args: [],
+source: "setUp\x0a\x09analyzer := (AISemanticAnalyzer on: Object)\x0a\x09\x09context: (AIContext new\x0a\x09\x09\x09localAt: 'local' put: 3;\x0a\x09\x09\x09yourself);\x0a\x09\x09yourself",
+messageSends: ["context:", "on:", "localAt:put:", "new", "yourself"],
+referencedClasses: ["AISemanticAnalyzer", "Object", "AIContext"]
+}),
+globals.AISemanticAnalyzerTest);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "testContextVariables",
+protocol: 'tests',
+fn: function (){
+var self=this;
+var src,ast;
+function $Smalltalk(){return globals.Smalltalk||(typeof Smalltalk=="undefined"?nil:Smalltalk)}
+function $UnknownVariableError(){return globals.UnknownVariableError||(typeof UnknownVariableError=="undefined"?nil:UnknownVariableError)}
+return smalltalk.withContext(function($ctx1) { 
+src="foo | a | local + a";
+ast=_st($Smalltalk())._parse_(src);
+self._shouldnt_raise_((function(){
+return smalltalk.withContext(function($ctx2) {
+return _st(self["@analyzer"])._visit_(ast);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)})}),$UnknownVariableError());
+return self}, function($ctx1) {$ctx1.fill(self,"testContextVariables",{src:src,ast:ast},globals.AISemanticAnalyzerTest)})},
+args: [],
+source: "testContextVariables\x0a\x09| src ast |\x0a\x09\x0a\x09src := 'foo | a | local + a'.\x0a\x09ast := Smalltalk parse: src.\x0a\x0a\x09self shouldnt: [ analyzer visit: ast ] raise: UnknownVariableError",
+messageSends: ["parse:", "shouldnt:raise:", "visit:"],
+referencedClasses: ["Smalltalk", "UnknownVariableError"]
+}),
+globals.AISemanticAnalyzerTest);
+
+
 });

+ 25 - 0
src/Compiler-Tests.st

@@ -708,3 +708,28 @@ testVariablesLookup
 	self assert: ast nodes first nodes last nodes first nodes first left binding scope == ast nodes first nodes last scope.
 ! !
 
+SemanticAnalyzerTest subclass: #AISemanticAnalyzerTest
+	instanceVariableNames: ''
+	package: 'Compiler-Tests'!
+
+!AISemanticAnalyzerTest methodsFor: 'running'!
+
+setUp
+	analyzer := (AISemanticAnalyzer on: Object)
+		context: (AIContext new
+			localAt: 'local' put: 3;
+			yourself);
+		yourself
+! !
+
+!AISemanticAnalyzerTest methodsFor: 'tests'!
+
+testContextVariables
+	| src ast |
+	
+	src := 'foo | a | local + a'.
+	ast := Smalltalk parse: src.
+
+	self shouldnt: [ analyzer visit: ast ] raise: UnknownVariableError
+! !
+

+ 10 - 8
src/Kernel-Infrastructure.js

@@ -594,9 +594,9 @@ var self=this;
 var compiler,ast;
 function $Compiler(){return globals.Compiler||(typeof Compiler=="undefined"?nil:Compiler)}
 function $Error(){return globals.Error||(typeof Error=="undefined"?nil:Error)}
-function $SemanticAnalyzer(){return globals.SemanticAnalyzer||(typeof SemanticAnalyzer=="undefined"?nil:SemanticAnalyzer)}
+function $AISemanticAnalyzer(){return globals.AISemanticAnalyzer||(typeof AISemanticAnalyzer=="undefined"?nil:AISemanticAnalyzer)}
 return smalltalk.withContext(function($ctx1) { 
-var $1,$2;
+var $1,$2,$3,$4;
 var $early={};
 try {
 compiler=_st($Compiler())._new();
@@ -609,16 +609,18 @@ return smalltalk.withContext(function($ctx2) {
 $1=self._alert_(_st(ex)._messageText());
 throw $early=[$1];
 }, function($ctx2) {$ctx2.fillBlock({ex:ex},$ctx1,2)})}));
-_st(_st($SemanticAnalyzer())._on_(_st(_st(anAIContext)._receiver())._class()))._visit_(ast);
-$2=_st(anAIContext)._evaluateNode_(ast);
-return $2;
+$2=_st($AISemanticAnalyzer())._on_(_st(_st(anAIContext)._receiver())._class());
+_st($2)._context_(anAIContext);
+$3=_st($2)._visit_(ast);
+$4=_st(anAIContext)._evaluateNode_(ast);
+return $4;
 }
 catch(e) {if(e===$early)return e[0]; throw e}
 }, function($ctx1) {$ctx1.fill(self,"interpret:inContext:",{aString:aString,anAIContext:anAIContext,compiler:compiler,ast:ast},globals.Environment)})},
 args: ["aString", "anAIContext"],
-source: "interpret: aString inContext: anAIContext\x0a\x09\x22Similar to #eval:on:, with the following differences:\x0a\x09- instead of compiling and running `aString`, `aString` is interpreted using an `ASTInterpreter`\x0a\x09- instead of evaluating against a receiver, evaluate in the context of `anAIContext`\x22\x0a\x0a\x09| compiler ast |\x0a\x09compiler := Compiler new.\x0a\x09[ ast := compiler parseExpression: aString ] on: Error do: [ :ex |\x0a\x09\x09^ self alert: ex messageText ].\x0a\x09(SemanticAnalyzer on: anAIContext receiver class)\x0a\x09\x09visit: ast.\x0a\x09^ anAIContext evaluateNode: ast",
-messageSends: ["new", "on:do:", "parseExpression:", "alert:", "messageText", "visit:", "on:", "class", "receiver", "evaluateNode:"],
-referencedClasses: ["Compiler", "Error", "SemanticAnalyzer"]
+source: "interpret: aString inContext: anAIContext\x0a\x09\x22Similar to #eval:on:, with the following differences:\x0a\x09- instead of compiling and running `aString`, `aString` is interpreted using an `ASTInterpreter`\x0a\x09- instead of evaluating against a receiver, evaluate in the context of `anAIContext`\x22\x0a\x0a\x09| compiler ast |\x0a\x09compiler := Compiler new.\x0a\x09[ ast := compiler parseExpression: aString ] on: Error do: [ :ex |\x0a\x09\x09^ self alert: ex messageText ].\x0a\x09(AISemanticAnalyzer on: anAIContext receiver class)\x0a\x09\x09context: anAIContext;\x0a\x09\x09visit: ast.\x0a\x09^ anAIContext evaluateNode: ast",
+messageSends: ["new", "on:do:", "parseExpression:", "alert:", "messageText", "context:", "on:", "class", "receiver", "visit:", "evaluateNode:"],
+referencedClasses: ["Compiler", "Error", "AISemanticAnalyzer"]
 }),
 globals.Environment);
 

+ 2 - 1
src/Kernel-Infrastructure.st

@@ -264,7 +264,8 @@ interpret: aString inContext: anAIContext
 	compiler := Compiler new.
 	[ ast := compiler parseExpression: aString ] on: Error do: [ :ex |
 		^ self alert: ex messageText ].
-	(SemanticAnalyzer on: anAIContext receiver class)
+	(AISemanticAnalyzer on: anAIContext receiver class)
+		context: anAIContext;
 		visit: ast.
 	^ anAIContext evaluateNode: ast
 ! !