Browse Source

imported variables are read-only (according to language report)

Vladislav Folts 12 năm trước cách đây
mục cha
commit
76663bc48d
5 tập tin đã thay đổi với 78 bổ sung29 xóa
  1. 5 1
      src/context.js
  2. 1 1
      src/procedure.js
  3. 9 2
      src/scope.js
  4. 10 1
      src/type.js
  5. 53 24
      test/test_unit.js

+ 5 - 1
src/context.js

@@ -1657,7 +1657,11 @@ exports.ModuleDeclaration = ChainedContext.extend({
         else
             throw new Errors.Error("original module name '" + this.__name + "' expected, got '" + id + "'" );
     },
-    findModule: function(name){return this.parent().findModule(name);},
+    findModule: function(name){
+        if (name == this.__name)
+            throw new Errors.Error("module '" + this.__name + "' cannot import itself");
+        return this.parent().findModule(name);
+    },
     handleImport: function(modules){
         var gen = this.codeGenerator();
         gen.write("var " + this.__name + " = function " + "(");

+ 1 - 1
src/procedure.js

@@ -97,7 +97,7 @@ var ProcCallGenerator = Class.extend({
             if (info instanceof Type.Const)
                 throw new Errors.Error("constant cannot be used as VAR parameter");
             if (info.isReadOnly())
-                throw new Errors.Error("read-only variable cannot be used as VAR parameter");
+                throw new Errors.Error(info.idType() + " cannot be used as VAR parameter");
         }
         return new CheckArgumentResult(arg.type, arg.isVar, castOperation);
     },

+ 9 - 2
src/scope.js

@@ -69,10 +69,17 @@ var ProcedureScope = Scope.extend({
 var CompiledModule = Type.Module.extend({
     init: function Scope$CompiledModule(id){
         Type.Module.prototype.init.call(this, id);
-        this.__exports = undefined;
+        this.__exports = {};
     },
     defineExports: function(exports){
-        this.__exports = exports;
+        for(var id in exports){
+            var symbol = exports[id];
+            if (symbol.isVariable())
+                symbol = new Symbol.Symbol(
+                    id,
+                    new Type.ExportedVariable(symbol.info()));
+            this.__exports[id] = symbol;
+        }
     },  
     findSymbol: function(id){
         var s = this.__exports[id];

+ 10 - 1
src/type.js

@@ -151,7 +151,7 @@ exports.Const = Id.extend({
     value: function(){return this.__value;}
 });
 
-exports.Variable = Id.extend({
+var Variable = Id.extend({
     init: function Variable(type, isVar, isReadOnly){
         Id.prototype.init.bind(this)();
         this.__type = type;
@@ -164,6 +164,13 @@ exports.Variable = Id.extend({
     isReadOnly: function(){return this.__isReadOnly;}
 });
 
+var ExportedVariable = Variable.extend({
+    init: function ExportedVariable(variable){
+        Variable.prototype.init.call(this, variable.type(), variable.isVar(), true);
+    },
+    idType: function(){return "imported variable";},
+});
+
 exports.Procedure = BasicType.extend({
     init: function Procedure(name){
         BasicType.prototype.init.call(this, name, "null");
@@ -179,6 +186,8 @@ var Module = Id.extend({
     name: function(){return this.__name;}
 });
 
+exports.Variable = Variable;
+exports.ExportedVariable = ExportedVariable;
 exports.Module = Module;
 exports.Type = Type;
 exports.TypeId = TypeId;

+ 53 - 24
test/test_unit.js

@@ -53,7 +53,7 @@ function parseUsingGrammar(grammar, s, cxFactory){
 
 function setup(run){
     return {
-        parse: function(s){
+        expectOK: function(s){
             function handleError(e){throw new TestError(s + "\n\t" + e);}
 
             if (!runAndHandleErrors(run, s, handleError))
@@ -109,7 +109,7 @@ function testWithSetup(setup, pass, fail){
         var test = setup();
         var i;
         for(i = 0; i < pass.length; ++i)
-            test.parse(pass[i]);
+            test.expectOK(pass[i]);
     
         if (fail)
             for(i = 0; i < fail.length; ++i){
@@ -133,6 +133,20 @@ function testWithGrammar(grammar, pass, fail){
         fail);
 }
 
+function testWithModule(src, pass, fail){
+    return testWithSetup(
+        function(){
+            var imported = oc.compileModule(new Stream(src));
+            var module = imported.symbol().info();
+            return setup(function(s){
+                oc.compileModule(new Stream(s),
+                                 undefined,
+                                 function(){return module;});
+            });},
+        pass,
+        fail);
+}
+
 var testSuite = {
 "comment": testWithGrammar(
     Grammar.expression,
@@ -1142,37 +1156,52 @@ var testSuite = {
           "cannot export from within procedure: variable 'i'"]
          )
     ),
-"IMPORT": testWithGrammar(
+"import JS": testWithGrammar(
     Grammar.module,
     pass("MODULE m; IMPORT JS; END m.",
          "MODULE m; IMPORT JS; BEGIN JS.alert(\"test\") END m.",
-         "MODULE m; IMPORT JS; BEGIN JS.console.info(123) END m.",
-         "MODULE m; IMPORT J := JS; END m.",
-         "MODULE m; IMPORT J := JS; BEGIN J.alert(\"test\") END m."),
+         "MODULE m; IMPORT JS; BEGIN JS.console.info(123) END m."
+         )
+    ),
+"import unknown module": testWithGrammar(
+    Grammar.module,
+    pass(),
     fail(["MODULE m; IMPORT unknown; END m.", "module(s) not found: unknown"],
-         ["MODULE m; IMPORT unknown1, unknown2; END m.", "module(s) not found: unknown1, unknown2"],
-         ["MODULE m; IMPORT u1 := unknown1, unknown2; END m.", "module(s) not found: unknown1, unknown2"],
+         ["MODULE m; IMPORT unknown1, unknown2; END m.", "module(s) not found: unknown1, unknown2"]
+         )
+    ),
+"self import is failed": testWithGrammar(
+    Grammar.module,
+    pass(),
+    fail(["MODULE test; IMPORT test; END test.", "module 'test' cannot import itself"])
+    ),
+"import aliases": testWithGrammar(
+    Grammar.module,
+    pass("MODULE m; IMPORT J := JS; END m.",
+         "MODULE m; IMPORT J := JS; BEGIN J.alert(\"test\") END m."),
+    fail(["MODULE m; IMPORT u1 := unknown1, unknown2; END m.", "module(s) not found: unknown1, unknown2"],
          ["MODULE m; IMPORT a1 := m1, a2 := m1; END m.", "module already imported: 'm1'"],
          ["MODULE m; IMPORT a1 := u1, a1 := u2; END m.", "duplicated alias: 'a1'"],
          ["MODULE m; IMPORT J := JS; BEGIN JS.alert(\"test\") END m.", "undeclared identifier: 'JS'"]
          )
     ),
-//"imported variables are read-only": function(){
-"imported identifiers": testWithSetup(
-        function(){
-            var imported = oc.compileModule(new Stream("MODULE test; END test."));
-            var module = imported.symbol().info();
-            return setup(function(s){
-                oc.compileModule(new Stream(s),
-                                 undefined,
-                                 function(){return module;});
-            });},
-        pass(),
-        fail(["MODULE m; IMPORT test; BEGIN test.p(); END m.",
-              "identifier 'p' is not exported by module 'test'"],
-             ["MODULE m; IMPORT t := test; BEGIN t.p(); END m.",
-              "identifier 'p' is not exported by module 'test'"]
-            ))
+"imported module without exports": testWithModule(
+    "MODULE test; END test.",
+    pass("MODULE m; IMPORT test; END m."),
+    fail(["MODULE m; IMPORT test; BEGIN test.p(); END m.",
+          "identifier 'p' is not exported by module 'test'"],
+         ["MODULE m; IMPORT t := test; BEGIN t.p(); END m.",
+          "identifier 'p' is not exported by module 'test'"]
+        )),
+"imported variables are read-only": testWithModule(
+    "MODULE test; VAR i*: INTEGER; END test.",
+    pass("MODULE m; IMPORT test; PROCEDURE p(i: INTEGER); END p; BEGIN p(test.i); END m."),
+    fail(["MODULE m; IMPORT test; BEGIN test.i := 123; END m.",
+          "cannot assign to imported variable"],
+         ["MODULE m; IMPORT test; PROCEDURE p(VAR i: INTEGER); END p; BEGIN p(test.i); END m.",
+          "imported variable cannot be used as VAR parameter"]
+        )
+    )
 };
 
 Test.run(testSuite);