7 Commits 3669389473 ... 9a35214ee6

Author SHA1 Message Date
  Herby Vojčík 9a35214ee6 UnknownVar => ExternallyKnownVar 4 years ago
  Herby Vojčík 89bdb83fec Refactor: Put responsibility where it's due. 4 years ago
  Herby Vojčík 976ee5a571 Actually check for existence of a variable. 4 years ago
  Herby Vojčík a3d5721c6d SemanticAnalyzer >> isVariableKnown:inPackage: 4 years ago
  Herby Vojčík 24d2c49031 Cosmetics. 4 years ago
  Herby Vojčík 0148d48c5e Remove ivar that was never read. 4 years ago
  Herby Vojčík 56a8f25284 aScope bindingFor: is never used with a string. 4 years ago

+ 17 - 2
lang/API-CHANGES.txt

@@ -1,6 +1,7 @@
 0.24.1:
 
 * Deprecate Behavior >> javascriptConstructor(:)
+* UnknownVar => ExternallyKnownVar
 
 + Behavior >>
   + alternateConstructorViaSelector:
@@ -10,6 +11,12 @@
   + javaScriptConstructor:
 + BlockClosure >>
   + tryIfTrue:catch:
++ ExternallyKnownVar >>
+  + isExternallyKnownVar
++ SemanticAnalyzer >>
+  + isVariableKnown:inPackage:
++ ScopeVar >>
+  + isExternallyKnownVar
 + SequenceableCollection >>
   + copyWithFirst:
 + SmalltalkImage >>
@@ -26,12 +33,20 @@
 - JavaScriptError >>
   - shouldBeStubbed
   - wrap
-- MethodContext
+- MethodContext >>
   - stubHere
   - stubToAtMost:
-- SendNode
+- MethodLexicalScope >>
+  - unknownVariables
+- ScopeVar >>
+  - isUnknownVar
+- SemanticAnalyzer >>
+  - isVariableUndefined:inPackage:
+- SendNode >>
   - shouldBeInlined
   - shouldBeInlined:
+- UnknownVar >>
+  - isUnknownVar
 - amber/boot api >>
   - addElement(arraySet, el)
   - removeElement(arraySet, el)

+ 3 - 3
lang/src/Compiler-Interpreter.js

@@ -3404,11 +3404,11 @@ selector: "visitVariableNode:",
 protocol: "visiting",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aNode"],
-source: "visitVariableNode: aNode\x0a\x09aNode binding isUnknownVar ifTrue: [\x0a\x09\x09^ self push: (Platform globals at: aNode value ifAbsent: [ self error: 'Unknown variable' ]) ].\x0a\x09\x09\x0a\x09self push: (aNode binding isInstanceVar\x0a\x09\x09ifTrue: [ self context receiver instVarAt: aNode value ]\x0a\x09\x09ifFalse: [ self context \x0a\x09\x09\x09localAt: (aNode binding isSuper ifTrue: [ 'self' ] ifFalse: [ aNode value ])\x0a\x09\x09\x09ifAbsent: [\x0a\x09\x09\x09\x09aNode value isCapitalized\x0a\x09\x09\x09\x09\x09ifTrue: [\x0a\x09\x09\x09\x09\x09\x09Smalltalk globals \x0a\x09\x09\x09\x09\x09\x09\x09at: aNode value \x0a\x09\x09\x09\x09\x09\x09\x09ifAbsent: [ Platform globals at: aNode value ] ] ] ])",
+source: "visitVariableNode: aNode\x0a\x09aNode binding isExternallyKnownVar ifTrue: [\x0a\x09\x09^ self push: (Platform globals at: aNode value ifAbsent: [ self error: 'Unknown variable' ]) ].\x0a\x09\x09\x0a\x09self push: (aNode binding isInstanceVar\x0a\x09\x09ifTrue: [ self context receiver instVarAt: aNode value ]\x0a\x09\x09ifFalse: [ self context \x0a\x09\x09\x09localAt: (aNode binding isSuper ifTrue: [ 'self' ] ifFalse: [ aNode value ])\x0a\x09\x09\x09ifAbsent: [\x0a\x09\x09\x09\x09aNode value isCapitalized\x0a\x09\x09\x09\x09\x09ifTrue: [\x0a\x09\x09\x09\x09\x09\x09Smalltalk globals \x0a\x09\x09\x09\x09\x09\x09\x09at: aNode value \x0a\x09\x09\x09\x09\x09\x09\x09ifAbsent: [ Platform globals at: aNode value ] ] ] ])",
 referencedClasses: ["Platform", "Smalltalk"],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["ifTrue:", "isUnknownVar", "binding", "push:", "at:ifAbsent:", "globals", "value", "error:", "ifTrue:ifFalse:", "isInstanceVar", "instVarAt:", "receiver", "context", "localAt:ifAbsent:", "isSuper", "isCapitalized", "at:"]
+messageSends: ["ifTrue:", "isExternallyKnownVar", "binding", "push:", "at:ifAbsent:", "globals", "value", "error:", "ifTrue:ifFalse:", "isInstanceVar", "instVarAt:", "receiver", "context", "localAt:ifAbsent:", "isSuper", "isCapitalized", "at:"]
 }, function ($methodClass){ return function (aNode){
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
@@ -3419,7 +3419,7 @@ $2=$recv(aNode)._binding();
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["binding"]=1;
 //>>excludeEnd("ctx");
-$1=$recv($2)._isUnknownVar();
+$1=$recv($2)._isExternallyKnownVar();
 if($core.assert($1)){
 $5=$recv($globals.Platform)._globals();
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);

+ 1 - 1
lang/src/Compiler-Interpreter.st

@@ -887,7 +887,7 @@ visitValueNode: aNode
 !
 
 visitVariableNode: aNode
-	aNode binding isUnknownVar ifTrue: [
+	aNode binding isExternallyKnownVar ifTrue: [
 		^ self push: (Platform globals at: aNode value ifAbsent: [ self error: 'Unknown variable' ]) ].
 		
 	self push: (aNode binding isInstanceVar

+ 136 - 177
lang/src/Compiler-Semantic.js

@@ -154,37 +154,29 @@ $core.method({
 selector: "bindingFor:",
 protocol: "accessing",
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["aStringOrNode"],
-source: "bindingFor: aStringOrNode\x0a\x09^ self pseudoVars at: aStringOrNode value ifAbsent: [\x0a\x09\x09self args at: aStringOrNode value ifAbsent: [\x0a\x09\x09\x09self temps at: aStringOrNode value ifAbsent: [ nil ]]]",
+args: ["aNode"],
+source: "bindingFor: aNode\x0a\x09| identifier |\x0a\x09identifier := aNode value.\x0a\x09^ self pseudoVars at: identifier ifAbsent: [\x0a\x09\x09self args at: identifier ifAbsent: [\x0a\x09\x09\x09self temps at: identifier ifAbsent: [ nil ]]]",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["at:ifAbsent:", "pseudoVars", "value", "args", "temps"]
-}, function ($methodClass){ return function (aStringOrNode){
+messageSends: ["value", "at:ifAbsent:", "pseudoVars", "args", "temps"]
+}, function ($methodClass){ return function (aNode){
 var self=this,$self=this;
+var identifier;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $2,$3,$4,$5,$1;
-$2=$self._pseudoVars();
-$3=$recv(aStringOrNode)._value();
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-$ctx1.sendIdx["value"]=1;
-//>>excludeEnd("ctx");
-$1=$recv($2)._at_ifAbsent_($3,(function(){
+var $1;
+identifier=$recv(aNode)._value();
+$1=$recv($self._pseudoVars())._at_ifAbsent_(identifier,(function(){
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx2) {
 //>>excludeEnd("ctx");
-$4=$self._args();
-$5=$recv(aStringOrNode)._value();
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-$ctx2.sendIdx["value"]=2;
-//>>excludeEnd("ctx");
-return $recv($4)._at_ifAbsent_($5,(function(){
+return $recv($self._args())._at_ifAbsent_(identifier,(function(){
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx3) {
 //>>excludeEnd("ctx");
-return $recv($self._temps())._at_ifAbsent_($recv(aStringOrNode)._value(),(function(){
+return $recv($self._temps())._at_ifAbsent_(identifier,(function(){
 return nil;
 
 }));
@@ -204,7 +196,7 @@ $ctx1.sendIdx["at:ifAbsent:"]=1;
 //>>excludeEnd("ctx");
 return $1;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"bindingFor:",{aStringOrNode:aStringOrNode})});
+}, function($ctx1) {$ctx1.fill(self,"bindingFor:",{aNode:aNode,identifier:identifier})});
 //>>excludeEnd("ctx");
 }; }),
 $globals.LexicalScope);
@@ -650,7 +642,7 @@ $globals.LexicalScope);
 
 
 
-$core.addClass("MethodLexicalScope", $globals.LexicalScope, ["iVars", "pseudoVars", "unknownVariables", "localReturn", "nonLocalReturns"], "Compiler-Semantic");
+$core.addClass("MethodLexicalScope", $globals.LexicalScope, ["iVars", "pseudoVars", "localReturn", "nonLocalReturns"], "Compiler-Semantic");
 //>>excludeStart("ide", pragmas.excludeIdeData);
 $globals.MethodLexicalScope.comment="I represent a method scope.";
 //>>excludeEnd("ide");
@@ -1056,36 +1048,6 @@ return self;
 }; }),
 $globals.MethodLexicalScope);
 
-$core.addMethod(
-$core.method({
-selector: "unknownVariables",
-protocol: "accessing",
-//>>excludeStart("ide", pragmas.excludeIdeData);
-args: [],
-source: "unknownVariables\x0a\x09^ unknownVariables ifNil: [ unknownVariables := OrderedCollection new ]",
-referencedClasses: ["OrderedCollection"],
-//>>excludeEnd("ide");
-pragmas: [],
-messageSends: ["ifNil:", "new"]
-}, function ($methodClass){ return function (){
-var self=this,$self=this;
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-return $core.withContext(function($ctx1) {
-//>>excludeEnd("ctx");
-var $1,$receiver;
-$1=$self.unknownVariables;
-if(($receiver = $1) == null || $receiver.a$nil){
-$self.unknownVariables=$recv($globals.OrderedCollection)._new();
-return $self.unknownVariables;
-} else {
-return $1;
-}
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"unknownVariables",{})});
-//>>excludeEnd("ctx");
-}; }),
-$globals.MethodLexicalScope);
-
 
 
 $core.addClass("ScopeVar", $globals.Object, ["scope", "name"], "Compiler-Semantic");
@@ -1153,11 +1115,11 @@ $globals.ScopeVar);
 
 $core.addMethod(
 $core.method({
-selector: "isImmutable",
+selector: "isExternallyKnownVar",
 protocol: "testing",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "isImmutable\x0a\x09^ false",
+source: "isExternallyKnownVar\x0a\x09^ false",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
@@ -1171,11 +1133,11 @@ $globals.ScopeVar);
 
 $core.addMethod(
 $core.method({
-selector: "isInstanceVar",
+selector: "isImmutable",
 protocol: "testing",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "isInstanceVar\x0a\x09^ false",
+source: "isImmutable\x0a\x09^ false",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
@@ -1189,11 +1151,11 @@ $globals.ScopeVar);
 
 $core.addMethod(
 $core.method({
-selector: "isPseudoVar",
+selector: "isInstanceVar",
 protocol: "testing",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "isPseudoVar\x0a\x09^ false",
+source: "isInstanceVar\x0a\x09^ false",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
@@ -1207,11 +1169,11 @@ $globals.ScopeVar);
 
 $core.addMethod(
 $core.method({
-selector: "isSelf",
+selector: "isPseudoVar",
 protocol: "testing",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "isSelf\x0a\x09^ false",
+source: "isPseudoVar\x0a\x09^ false",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
@@ -1225,11 +1187,11 @@ $globals.ScopeVar);
 
 $core.addMethod(
 $core.method({
-selector: "isSuper",
+selector: "isSelf",
 protocol: "testing",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "isSuper\x0a\x09^ false",
+source: "isSelf\x0a\x09^ false",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
@@ -1243,11 +1205,11 @@ $globals.ScopeVar);
 
 $core.addMethod(
 $core.method({
-selector: "isTempVar",
+selector: "isSuper",
 protocol: "testing",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "isTempVar\x0a\x09^ false",
+source: "isSuper\x0a\x09^ false",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
@@ -1261,11 +1223,11 @@ $globals.ScopeVar);
 
 $core.addMethod(
 $core.method({
-selector: "isUnknownVar",
+selector: "isTempVar",
 protocol: "testing",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "isUnknownVar\x0a\x09^ false",
+source: "isTempVar\x0a\x09^ false",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
@@ -1585,6 +1547,30 @@ $globals.ClassRefVar);
 
 
 
+$core.addClass("ExternallyKnownVar", $globals.ScopeVar, [], "Compiler-Semantic");
+//>>excludeStart("ide", pragmas.excludeIdeData);
+$globals.ExternallyKnownVar.comment="I am a variable known externally (not in method scope).";
+//>>excludeEnd("ide");
+$core.addMethod(
+$core.method({
+selector: "isExternallyKnownVar",
+protocol: "testing",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "isExternallyKnownVar\x0a\x09^ true",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: []
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+return true;
+
+}; }),
+$globals.ExternallyKnownVar);
+
+
+
 $core.addClass("InstanceVar", $globals.ScopeVar, [], "Compiler-Semantic");
 //>>excludeStart("ide", pragmas.excludeIdeData);
 $globals.InstanceVar.comment="I am an instance variable of a method or block.";
@@ -1881,34 +1867,66 @@ $globals.TempVar);
 
 
 
-$core.addClass("UnknownVar", $globals.ScopeVar, [], "Compiler-Semantic");
+$core.addClass("SemanticAnalyzer", $globals.NodeVisitor, ["currentScope", "blockIndex", "thePackage", "theClass", "classReferences", "messageSends"], "Compiler-Semantic");
 //>>excludeStart("ide", pragmas.excludeIdeData);
-$globals.UnknownVar.comment="I am an unknown variable. Amber uses unknown variables as JavaScript globals";
+$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");
 $core.addMethod(
 $core.method({
-selector: "isUnknownVar",
-protocol: "testing",
+selector: "bindUnscopedVariable:",
+protocol: "private",
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: [],
-source: "isUnknownVar\x0a\x09^ true",
-referencedClasses: [],
+args: ["aString"],
+source: "bindUnscopedVariable: aString\x0a\x09aString isCapitalized ifTrue: [ \x22Capital letter variables might be globals.\x22\x0a\x09\x09self classReferences add: aString.\x0a\x09\x09^ ClassRefVar new name: aString; yourself ].\x0a\x0a\x09\x22Throw an error if the variable is undeclared in the global JS scope (i.e. window).\x0a\x09We allow all variables listed by Smalltalk>>#globalJsVariables.\x0a\x09This list includes: `window`, `document`,  `process` and `global`\x0a\x09for nodejs and browser environments.\x0a\x09\x0a\x09This is only to make sure compilation works on both browser-based and nodejs environments.\x0a\x09The ideal solution would be to use a pragma instead\x22\x0a\x0a\x09((Smalltalk globalJsVariables includes: aString)\x0a\x09\x09or: [ self isVariableKnown: aString inPackage: self thePackage ]) ifTrue: [\x0a\x09\x09\x09^ ExternallyKnownVar new name: aString; yourself ].\x0a\x0a\x09self errorUnknownVariable: aString",
+referencedClasses: ["ClassRefVar", "Smalltalk", "ExternallyKnownVar"],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: []
-}, function ($methodClass){ return function (){
+messageSends: ["ifTrue:", "isCapitalized", "add:", "classReferences", "name:", "new", "yourself", "or:", "includes:", "globalJsVariables", "isVariableKnown:inPackage:", "thePackage", "errorUnknownVariable:"]
+}, function ($methodClass){ return function (aString){
 var self=this,$self=this;
-return true;
-
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1,$2,$3,$4,$5;
+$1=$recv(aString)._isCapitalized();
+if($core.assert($1)){
+$recv($self._classReferences())._add_(aString);
+$2=$recv($globals.ClassRefVar)._new();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx["new"]=1;
+//>>excludeEnd("ctx");
+$recv($2)._name_(aString);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx["name:"]=1;
+//>>excludeEnd("ctx");
+$3=$recv($2)._yourself();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx["yourself"]=1;
+//>>excludeEnd("ctx");
+return $3;
+}
+$4=$recv($recv($recv($globals.Smalltalk)._globalJsVariables())._includes_(aString))._or_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._isVariableKnown_inPackage_(aString,$self._thePackage());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)});
+//>>excludeEnd("ctx");
+}));
+if($core.assert($4)){
+$5=$recv($globals.ExternallyKnownVar)._new();
+$recv($5)._name_(aString);
+return $recv($5)._yourself();
+}
+$self._errorUnknownVariable_(aString);
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"bindUnscopedVariable:",{aString:aString})});
+//>>excludeEnd("ctx");
 }; }),
-$globals.UnknownVar);
-
-
+$globals.SemanticAnalyzer);
 
-$core.addClass("SemanticAnalyzer", $globals.NodeVisitor, ["currentScope", "blockIndex", "thePackage", "theClass", "classReferences", "messageSends"], "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");
 $core.addMethod(
 $core.method({
 selector: "classReferences",
@@ -1971,84 +1989,61 @@ $core.method({
 selector: "errorUnknownVariable:",
 protocol: "error handling",
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["aNode"],
-source: "errorUnknownVariable: aNode\x0a\x09\x22Throw an error if the variable is undeclared in the global JS scope (i.e. window).\x0a\x09We allow all variables listed by Smalltalk>>#globalJsVariables.\x0a\x09This list includes: `window`, `document`,  `process` and `global`\x0a\x09for nodejs and browser environments.\x0a\x09\x0a\x09This is only to make sure compilation works on both browser-based and nodejs environments.\x0a\x09The ideal solution would be to use a pragma instead\x22\x0a\x0a\x09| identifier |\x0a\x09identifier := aNode value.\x0a\x09\x0a\x09((Smalltalk globalJsVariables includes: identifier) not\x0a\x09\x09and: [ self isVariableUndefined: identifier inPackage: self thePackage ])\x0a\x09\x09\x09ifTrue: [\x0a\x09\x09\x09\x09UnknownVariableError new\x0a\x09\x09\x09\x09\x09variableName: aNode value;\x0a\x09\x09\x09\x09\x09signal ]\x0a\x09\x09\x09ifFalse: [\x0a\x09\x09\x09\x09currentScope methodScope unknownVariables add: aNode value ]",
-referencedClasses: ["Smalltalk", "UnknownVariableError"],
+args: ["aString"],
+source: "errorUnknownVariable: aString\x0a\x09UnknownVariableError new\x0a\x09\x09variableName: aString;\x0a\x09\x09signal",
+referencedClasses: ["UnknownVariableError"],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["value", "ifTrue:ifFalse:", "and:", "not", "includes:", "globalJsVariables", "isVariableUndefined:inPackage:", "thePackage", "variableName:", "new", "signal", "add:", "unknownVariables", "methodScope"]
-}, function ($methodClass){ return function (aNode){
+messageSends: ["variableName:", "new", "signal"]
+}, function ($methodClass){ return function (aString){
 var self=this,$self=this;
-var identifier;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $1,$2,$3;
-identifier=$recv(aNode)._value();
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-$ctx1.sendIdx["value"]=1;
-//>>excludeEnd("ctx");
-$1=$recv($recv($recv($recv($globals.Smalltalk)._globalJsVariables())._includes_(identifier))._not())._and_((function(){
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-return $core.withContext(function($ctx2) {
-//>>excludeEnd("ctx");
-return $self._isVariableUndefined_inPackage_(identifier,$self._thePackage());
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)});
-//>>excludeEnd("ctx");
-}));
-if($core.assert($1)){
-$2=$recv($globals.UnknownVariableError)._new();
-$3=$recv(aNode)._value();
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-$ctx1.sendIdx["value"]=2;
-//>>excludeEnd("ctx");
-$recv($2)._variableName_($3);
-$recv($2)._signal();
-} else {
-$recv($recv($recv($self.currentScope)._methodScope())._unknownVariables())._add_($recv(aNode)._value());
-}
+var $1;
+$1=$recv($globals.UnknownVariableError)._new();
+$recv($1)._variableName_(aString);
+$recv($1)._signal();
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"errorUnknownVariable:",{aNode:aNode,identifier:identifier})});
+}, function($ctx1) {$ctx1.fill(self,"errorUnknownVariable:",{aString:aString})});
 //>>excludeEnd("ctx");
 }; }),
 $globals.SemanticAnalyzer);
 
 $core.addMethod(
 $core.method({
-selector: "isVariableUndefined:inPackage:",
+selector: "isVariableKnown:inPackage:",
 protocol: "testing",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aString", "aPackage"],
-source: "isVariableUndefined: aString inPackage: aPackage\x0a\x09aPackage ifNotNil: [\x0a\x09\x09| packageKnownVars |\x0a\x09\x09packageKnownVars := (aPackage imports\x0a\x09\x09\x09reject: #isString)\x0a\x09\x09\x09collect: #key.\x0a\x09\x09(packageKnownVars includes: aString) ifTrue: [ ^ false ]].\x0a\x09^ Compiler eval: 'typeof ', aString, ' === \x22undefined\x22'",
+source: "isVariableKnown: aString inPackage: aPackage\x0a\x09^ Compiler new\x0a\x09\x09eval: 'typeof(', aString, ')!== \x22undefined\x22||(function(){try{return(', aString, ',true)}catch(_){return false}})()'\x0a\x09\x09forPackage: aPackage",
 referencedClasses: ["Compiler"],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["ifNotNil:", "collect:", "reject:", "imports", "ifTrue:", "includes:", "eval:", ","]
+messageSends: ["eval:forPackage:", "new", ","]
 }, function ($methodClass){ return function (aString,aPackage){
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $1,$2,$receiver;
-if(($receiver = aPackage) == null || $receiver.a$nil){
-aPackage;
-} else {
-var packageKnownVars;
-packageKnownVars=$recv($recv($recv(aPackage)._imports())._reject_("isString"))._collect_("key");
-$1=$recv(packageKnownVars)._includes_(aString);
-if($core.assert($1)){
-return false;
-}
-}
-$2=$recv("typeof ".__comma(aString)).__comma(" === \x22undefined\x22");
+var $1,$4,$3,$2;
+$1=$recv($globals.Compiler)._new();
+$4=$recv("typeof(".__comma(aString)).__comma(")!== \x22undefined\x22||(function(){try{return(");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx[","]=3;
+//>>excludeEnd("ctx");
+$3=$recv($4).__comma(aString);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx[","]=2;
+//>>excludeEnd("ctx");
+$2=$recv($3).__comma(",true)}catch(_){return false}})()");
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx[","]=1;
 //>>excludeEnd("ctx");
-return $recv($globals.Compiler)._eval_($2);
+return $recv($1)._eval_forPackage_($2,aPackage);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"isVariableUndefined:inPackage:",{aString:aString,aPackage:aPackage})});
+}, function($ctx1) {$ctx1.fill(self,"isVariableKnown:inPackage:",{aString:aString,aPackage:aPackage})});
 //>>excludeEnd("ctx");
 }; }),
 $globals.SemanticAnalyzer);
@@ -2645,63 +2640,27 @@ selector: "visitVariableNode:",
 protocol: "visiting",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aNode"],
-source: "visitVariableNode: aNode\x0a\x09\x22Bind a ScopeVar to aNode by doing a lookup in the current scope.\x0a\x09If no ScopeVar is found, bind a UnknowVar and throw an error.\x22\x0a\x0a\x09| binding |\x0a\x09binding := currentScope lookupVariable: aNode.\x0a\x09\x0a\x09binding ifNil: [\x0a\x09\x09aNode value isCapitalized\x0a\x09\x09\x09ifTrue: [ \x22Capital letter variables might be globals.\x22\x0a\x09\x09\x09\x09binding := ClassRefVar new name: aNode value; yourself.\x0a\x09\x09\x09\x09self classReferences add: aNode value]\x0a\x09\x09\x09ifFalse: [\x0a\x09\x09\x09\x09self errorUnknownVariable: aNode.\x0a\x09\x09\x09\x09binding := UnknownVar new name: aNode value; yourself ] ].\x0a\x09\x09\x0a\x09aNode binding: binding.",
-referencedClasses: ["ClassRefVar", "UnknownVar"],
+source: "visitVariableNode: aNode\x0a\x09\x22Bind a ScopeVar to aNode by doing a lookup in the current scope.\x0a\x09If no var is found in scope, represent an externally known variable or throw an error.\x22\x0a\x0a\x09aNode binding:\x0a\x09\x09((currentScope lookupVariable: aNode) ifNil: [ self bindUnscopedVariable: aNode value ])",
+referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["lookupVariable:", "ifNil:", "ifTrue:ifFalse:", "isCapitalized", "value", "name:", "new", "yourself", "add:", "classReferences", "errorUnknownVariable:", "binding:"]
+messageSends: ["binding:", "ifNil:", "lookupVariable:", "bindUnscopedVariable:", "value"]
 }, function ($methodClass){ return function (aNode){
 var self=this,$self=this;
-var binding;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $1,$3,$2,$4,$5,$6,$7,$8,$receiver;
-binding=$recv($self.currentScope)._lookupVariable_(aNode);
-$1=binding;
-if(($receiver = $1) == null || $receiver.a$nil){
-$3=$recv(aNode)._value();
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-$ctx1.sendIdx["value"]=1;
-//>>excludeEnd("ctx");
-$2=$recv($3)._isCapitalized();
-if($core.assert($2)){
-$4=$recv($globals.ClassRefVar)._new();
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-$ctx1.sendIdx["new"]=1;
-//>>excludeEnd("ctx");
-$5=$recv(aNode)._value();
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-$ctx1.sendIdx["value"]=2;
-//>>excludeEnd("ctx");
-$recv($4)._name_($5);
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-$ctx1.sendIdx["name:"]=1;
-//>>excludeEnd("ctx");
-binding=$recv($4)._yourself();
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-$ctx1.sendIdx["yourself"]=1;
-//>>excludeEnd("ctx");
-$6=$self._classReferences();
-$7=$recv(aNode)._value();
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-$ctx1.sendIdx["value"]=3;
-//>>excludeEnd("ctx");
-$recv($6)._add_($7);
-} else {
-$self._errorUnknownVariable_(aNode);
-$8=$recv($globals.UnknownVar)._new();
-$recv($8)._name_($recv(aNode)._value());
-binding=$recv($8)._yourself();
-binding;
-}
+var $2,$1,$receiver;
+$2=$recv($self.currentScope)._lookupVariable_(aNode);
+if(($receiver = $2) == null || $receiver.a$nil){
+$1=$self._bindUnscopedVariable_($recv(aNode)._value());
 } else {
-$1;
+$1=$2;
 }
-$recv(aNode)._binding_(binding);
+$recv(aNode)._binding_($1);
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"visitVariableNode:",{aNode:aNode,binding:binding})});
+}, function($ctx1) {$ctx1.fill(self,"visitVariableNode:",{aNode:aNode})});
 //>>excludeEnd("ctx");
 }; }),
 $globals.SemanticAnalyzer);

+ 54 - 67
lang/src/Compiler-Semantic.st

@@ -23,10 +23,12 @@ args
 	^ args ifNil: [ args := Dictionary new ]
 !
 
-bindingFor: aStringOrNode
-	^ self pseudoVars at: aStringOrNode value ifAbsent: [
-		self args at: aStringOrNode value ifAbsent: [
-			self temps at: aStringOrNode value ifAbsent: [ nil ]]]
+bindingFor: aNode
+	| identifier |
+	identifier := aNode value.
+	^ self pseudoVars at: identifier ifAbsent: [
+		self args at: identifier ifAbsent: [
+			self temps at: identifier ifAbsent: [ nil ]]]
 !
 
 blockIndex
@@ -124,7 +126,7 @@ isMethodScope
 ! !
 
 LexicalScope subclass: #MethodLexicalScope
-	slots: {#iVars. #pseudoVars. #unknownVariables. #localReturn. #nonLocalReturns}
+	slots: {#iVars. #pseudoVars. #localReturn. #nonLocalReturns}
 	package: 'Compiler-Semantic'!
 !MethodLexicalScope commentStamp!
 I represent a method scope.!
@@ -168,10 +170,6 @@ pseudoVars
 				scope: self methodScope;
 				yourself) ] ].
 	^ pseudoVars
-!
-
-unknownVariables
-	^ unknownVariables ifNil: [ unknownVariables := OrderedCollection new ]
 ! !
 
 !MethodLexicalScope methodsFor: 'adding'!
@@ -246,6 +244,10 @@ isClassRefVar
 	^ false
 !
 
+isExternallyKnownVar
+	^ false
+!
+
 isImmutable
 	^ false
 !
@@ -270,10 +272,6 @@ isTempVar
 	^ false
 !
 
-isUnknownVar
-	^ false
-!
-
 validateAssignment
 	(self isArgVar or: [ self isPseudoVar ]) ifTrue: [
 		InvalidAssignmentError new
@@ -349,6 +347,18 @@ isImmutable
 	^ true
 ! !
 
+ScopeVar subclass: #ExternallyKnownVar
+	slots: {}
+	package: 'Compiler-Semantic'!
+!ExternallyKnownVar commentStamp!
+I am a variable known externally (not in method scope).!
+
+!ExternallyKnownVar methodsFor: 'testing'!
+
+isExternallyKnownVar
+	^ true
+! !
+
 ScopeVar subclass: #InstanceVar
 	slots: {}
 	package: 'Compiler-Semantic'!
@@ -454,18 +464,6 @@ isTempVar
 	^ true
 ! !
 
-ScopeVar subclass: #UnknownVar
-	slots: {}
-	package: 'Compiler-Semantic'!
-!UnknownVar commentStamp!
-I am an unknown variable. Amber uses unknown variables as JavaScript globals!
-
-!UnknownVar methodsFor: 'testing'!
-
-isUnknownVar
-	^ true
-! !
-
 NodeVisitor subclass: #SemanticAnalyzer
 	slots: {#currentScope. #blockIndex. #thePackage. #theClass. #classReferences. #messageSends}
 	package: 'Compiler-Semantic'!
@@ -506,26 +504,10 @@ errorShadowingVariable: aString
 		signal
 !
 
-errorUnknownVariable: aNode
-	"Throw an error if the variable is undeclared in the global JS scope (i.e. window).
-	We allow all variables listed by Smalltalk>>#globalJsVariables.
-	This list includes: `window`, `document`,  `process` and `global`
-	for nodejs and browser environments.
-	
-	This is only to make sure compilation works on both browser-based and nodejs environments.
-	The ideal solution would be to use a pragma instead"
-
-	| identifier |
-	identifier := aNode value.
-	
-	((Smalltalk globalJsVariables includes: identifier) not
-		and: [ self isVariableUndefined: identifier inPackage: self thePackage ])
-			ifTrue: [
-				UnknownVariableError new
-					variableName: aNode value;
-					signal ]
-			ifFalse: [
-				currentScope methodScope unknownVariables add: aNode value ]
+errorUnknownVariable: aString
+	UnknownVariableError new
+		variableName: aString;
+		signal
 ! !
 
 !SemanticAnalyzer methodsFor: 'factory'!
@@ -546,6 +528,26 @@ newScopeOfClass: aLexicalScopeClass
 
 !SemanticAnalyzer methodsFor: 'private'!
 
+bindUnscopedVariable: aString
+	aString isCapitalized ifTrue: [ "Capital letter variables might be globals."
+		self classReferences add: aString.
+		^ ClassRefVar new name: aString; yourself ].
+
+	"Throw an error if the variable is undeclared in the global JS scope (i.e. window).
+	We allow all variables listed by Smalltalk>>#globalJsVariables.
+	This list includes: `window`, `document`,  `process` and `global`
+	for nodejs and browser environments.
+	
+	This is only to make sure compilation works on both browser-based and nodejs environments.
+	The ideal solution would be to use a pragma instead"
+
+	((Smalltalk globalJsVariables includes: aString)
+		or: [ self isVariableKnown: aString inPackage: self thePackage ]) ifTrue: [
+			^ ExternallyKnownVar new name: aString; yourself ].
+
+	self errorUnknownVariable: aString
+!
+
 nextBlockIndex
 	blockIndex ifNil: [ blockIndex := 0 ].
 	
@@ -574,14 +576,10 @@ validateVariableScope: aString
 
 !SemanticAnalyzer methodsFor: 'testing'!
 
-isVariableUndefined: aString inPackage: aPackage
-	aPackage ifNotNil: [
-		| packageKnownVars |
-		packageKnownVars := (aPackage imports
-			reject: #isString)
-			collect: #key.
-		(packageKnownVars includes: aString) ifTrue: [ ^ false ]].
-	^ Compiler eval: 'typeof ', aString, ' === "undefined"'
+isVariableKnown: aString inPackage: aPackage
+	^ Compiler new
+		eval: 'typeof(', aString, ')!!== "undefined"||(function(){try{return(', aString, ',true)}catch(_){return false}})()'
+		forPackage: aPackage
 ! !
 
 !SemanticAnalyzer methodsFor: 'visiting'!
@@ -659,21 +657,10 @@ visitSequenceNode: aNode
 
 visitVariableNode: aNode
 	"Bind a ScopeVar to aNode by doing a lookup in the current scope.
-	If no ScopeVar is found, bind a UnknowVar and throw an error."
+	If no var is found in scope, represent an externally known variable or throw an error."
 
-	| binding |
-	binding := currentScope lookupVariable: aNode.
-	
-	binding ifNil: [
-		aNode value isCapitalized
-			ifTrue: [ "Capital letter variables might be globals."
-				binding := ClassRefVar new name: aNode value; yourself.
-				self classReferences add: aNode value]
-			ifFalse: [
-				self errorUnknownVariable: aNode.
-				binding := UnknownVar new name: aNode value; yourself ] ].
-		
-	aNode binding: binding.
+	aNode binding:
+		((currentScope lookupVariable: aNode) ifNil: [ self bindUnscopedVariable: aNode value ])
 ! !
 
 !SemanticAnalyzer class methodsFor: 'instance creation'!