Kaynağa Gözat

Fix #6 and other bugs

-Fix a bug where recursive functions call would have an invalid state
-Fix a bug where IfThenElse commands was accounting for a IfThenElse chain
-Implement test cases for the bugs above and others
Lucas de Souza 5 yıl önce
ebeveyn
işleme
0265d07baa

+ 1 - 1
.vscode/launch.json

@@ -8,7 +8,7 @@
             "type": "node",
             "request": "launch",
             "name": "Launch Program",
-            "program": "${workspaceFolder}/js/main.js"
+            "program": "${workspaceFolder}/build/ivprog.bundle.js"
         }
     ]
 }

+ 89 - 73
js/processor/ivprogProcessor.js

@@ -84,15 +84,17 @@ export class IVProgProcessor {
     const funcNameStoreObject = new StoreObject(Types.STRING, funcName, true);
     funcStore.insertStore('$', returnStoreObject);
     funcStore.insertStore('$name', funcNameStoreObject);
-    funcStore = this.associateParameters(func.formalParameters, actualParameters, store, funcStore);
-    this.context.push(Context.FUNCTION);
-    this.stores.push(funcStore);
-    const result = this.executeCommands(funcStore, func.variablesDeclarations).then(sto => {
-      return this.executeCommands(sto, func.commands)
+    const newFuncStore$ = this.associateParameters(func.formalParameters, actualParameters, store, funcStore);
+    return newFuncStore$.then(sto => {
+      this.context.push(Context.FUNCTION);
+      this.stores.push(sto);
+      return this.executeCommands(sto, func.variablesDeclarations)
+        .then(stoWithVars => this.executeCommands(stoWithVars, func.commands)).then(finalSto => {
+          this.stores.pop();
+          this.context.pop();
+          return finalSto;
+        });
     });
-    this.stores.pop();
-    this.context.pop();
-    return result;
   }
 
   associateParameters (formalList, actualList, callerStore, calleeStore) {
@@ -100,65 +102,77 @@ export class IVProgProcessor {
       // TODO: Better error message
       throw new Error("Numbers of parameters doesn't match");
     }
-    formalList.forEach((v, i) => {
-      this.evaluateExpression(callerStore, actualList[i])
-      .then(vl => {
-        switch (v.dimensions) {
+    const promises$ = actualList.map(actualParameter => this.evaluateExpression(callerStore, actualParameter));
+    return Promise.all(promises$).then(values => {
+      for (let i = 0; i < values.length; i++) {
+        const stoObj = values[i];
+        const formalParameter = formalList[i];
+        switch (formalParameter.dimensions) {
           case 1: {
-            if (vl.lines > 0 && vl.columns === null
-              && vl.subtype === v.type) {
-              if(v.byRef && !vl.inStore) {
+            if (stoObj.lines > 0 && stoObj.columns === null
+              && stoObj.subtype === formalParameter.type) {
+
+              if(formalParameter.byRef && !stoObj.inStore) {
                 throw new Error('You must inform a variable as parameter');
               }
-              if(v.byRef) {
-                const ref = new StoreObjectRef(vl.id, callerStore);
-                calleeStore.insertStore(v.id, ref);
+
+              if(formalParameter.byRef) {
+                const ref = new StoreObjectRef(stoObj.id, callerStore);
+                calleeStore.insertStore(formalParameter.id, ref);
               } else {
-                calleeStore.insertStore(v.id, vl);
+                calleeStore.insertStore(formalParameter.id, stoObj);
               }
+
             } else {
               // TODO: Better error message
-              throw new Error(`Parameter ${v.id} is not compatible with the value given.`);
+              throw new Error(`Parameter ${formalParameter.id} is not compatible with the value given.`);
             }
             break;
           }
           case 2: {
-            if (vl.lines > 0 && vl.columns > 0
-              && vl.subtype === v.type) {
-              if(v.byRef && !vl.inStore) {
+            if (stoObj.lines > 0 && stoObj.columns > 0
+              && stoObj.subtype === formalParameter.type) {
+
+              if(formalParameter.byRef && !stoObj.inStore) {
                 throw new Error('You must inform a variable as parameter');
               }
-              if(v.byRef) {
-                const ref = new StoreObjectRef(vl.id, callerStore);
-                calleeStore.insertStore(v.id, ref);
+
+              if(formalParameter.byRef) {
+                const ref = new StoreObjectRef(stoObj.id, callerStore);
+                calleeStore.insertStore(formalParameter.id, ref);
               } else {
-                calleeStore.insertStore(v.id, vl);
+                calleeStore.insertStore(formalParameter.id, stoObj);
               }
+
             } else {
               // TODO: Better error message
-              throw new Error(`Parameter ${v.id} is not compatible with the value given.`);
+              throw new Error(`Parameter ${formalParameter.id} is not compatible with the value given.`);
             }
             break;
           }
           case 0: {
-            if(v.byRef && !vl.inStore) {
+            if(formalParameter.byRef && !stoObj.inStore) {
+
               throw new Error('You must inform a variable as parameter');
-            } else if (v.type !== Types.ALL && vl.type !== v.type) {
+            } else if (formalParameter.type !== Types.ALL && stoObj.type !== formalParameter.type) {
+
               // TODO: Better error message
-              throw new Error(`Parameter ${v.id} is not compatible with ${vl.type}.`);
+              throw new Error(`Parameter ${formalParameter.id} is not compatible with ${stoObj.type}.`);
             } else {
-              if(v.byRef) {
-                const ref = new StoreObjectRef(vl.id, callerStore);
-                calleeStore.insertStore(v.id, ref);
+
+              if(formalParameter.byRef) {
+                const ref = new StoreObjectRef(stoObj.id, callerStore);
+                calleeStore.insertStore(formalParameter.id, ref);
               } else {
-                calleeStore.insertStore(v.id, vl);
+                calleeStore.insertStore(formalParameter.id, stoObj);
               }
+
             }
           }
         }
-      });
+      }
+      return calleeStore;
     });
-    return calleeStore;
   }
 
   executeCommands (store, cmds) {
@@ -222,46 +236,37 @@ export class IVProgProcessor {
 
   executeSysCall (store, cmd) {
     if (cmd.id === NAMES.WRITE) {
-      this.runWriteFunction(store)
+      return this.runWriteFunction(store)
+    } else if (cmd.id === NAMES.READ) {
+      return this.runReadFunction(store);
     }
   }
 
   runWriteFunction (store) {
     const val = store.applyStore('p1');
-    this.output.appendText(val.val);
+    this.output.sendOutput(val.val);
     return Promise.resolve(store);
   }
 
   runReadFunction (store) {
-    const typeToConvert = store.applyStore('p1').type;
-    const listenInput = (text) => {
+    const request = new Promise((resolve, _) => {
+      this.input.requestInput(resolve);
+    });
+    return request.then(text => {
+      const typeToConvert = store.applyStore('p1').type;
+      let stoObj = null;
       if (typeToConvert === Types.INTEGER) {
         const val = toInt(text);
-        return new StoreObject(Types.INTEGER, val);
+        stoObj = new StoreObject(Types.INTEGER, val);
       } else if (typeToConvert === Types.REAL) {
-        return new StoreObject(Types.REAL, parseFloat(text));
+        stoObj = new StoreObject(Types.REAL, parseFloat(text));
       } else if (typeToConvert === Types.BOOLEAN) {
-        
+        stoObj = new StoreObject(Types.BOOLEAN, true);
       } else if (typeToConvert === Types.STRING) {
-        return new StoreObject(Types.STRING, text);
-      }
-    }
-
-    return Promise.resolve(store).then( sto => {
-      let received = false;
-      const notify = (text) => {
-        received = true;
-        const result = listenInput(text);
-        sto.updateStore('p1', result);
+        stoObj = new StoreObject(Types.STRING, text);
       }
-      const obj = {notify: notify.bind(this)}
-      this.input.registerListener(obj);
-      this.input.requestInput();
-      while(!received) {
-        continue;
-      }
-      this.input.removeListener(obj);
-      return sto;
+      store.updateStore('p1', stoObj);
+      return Promise.resolve(store);
     });
   }
 
@@ -401,13 +406,19 @@ export class IVProgProcessor {
 
   executeIfThenElse (store, cmd) {
     try {
-      const $value = this.evaluateExpression(cmd.condition);
+      const $value = this.evaluateExpression(store, cmd.condition);
       return $value.then(vl => {
         if(vl.type === Types.BOOLEAN) {
           if(vl.value) {
-            return this.executeCommands(store, cmd.ifTrue);
+            return this.executeCommands(store, cmd.ifTrue.commands);
+          } else if( cmd.ifFalse !== null){
+            if(cmd.ifFalse instanceof Commands.IfThenElse) {
+              return this.executeCommand(store, cmd.ifFalse);
+            } else {
+              return this.executeCommands(store, cmd.ifFalse.commands);
+            }
           } else {
-            return this.executeCommands(store, cmd.ifFalse);
+            return Promise.resolve(store);
           }
         } else {
           // TODO: Better error message -- Inform line and column from token!!!!
@@ -426,7 +437,12 @@ export class IVProgProcessor {
       const $value = this.evaluateExpression(store, cmd.expression);
       const funcName = store.applyStore('$name');
       return $value.then(vl => {
-        if (funcType.type !== vl.type) {
+
+        if(vl === null && funcType === Types.VOID) {
+          return Promise.resolve(store);
+        }
+
+        if (vl === null || funcType.type !== vl.type) {
           // TODO: Better error message -- Inform line and column from token!!!!
           // THIS IF SHOULD BE IN A SEMANTIC ANALYSER
           return Promise.reject(new Error(`Function ${funcName.value} must return ${funcType.type} instead of ${vl.type}.`));
@@ -489,10 +505,10 @@ export class IVProgProcessor {
           const value = values[2];
           const temp = new StoreObjectArray(cmd.subtype, line, column, null, cmd.isConst);
           store.insertStore(cmd.id, temp);
-          if(value !== null)
-            return  store.updateStore(cmd.id, value);
-          else
-            return store;
+          if(value !== null) {
+            store.updateStore(cmd.id, value);
+          }
+          return store;
         });
         
       } else {
@@ -500,9 +516,8 @@ export class IVProgProcessor {
         store.insertStore(cmd.id, temp);
         return $value.then(vl => {
           if (vl !== null)
-            return store.updateStore(cmd.id, vl)
-          else
-            return store;
+            store.updateStore(cmd.id, vl)
+          return store;
         });
       }
     } catch (e) {
@@ -533,6 +548,7 @@ export class IVProgProcessor {
     } else if (exp instanceof Expressions.FunctionCall) {
       return this.evaluateFunctionCall(store, exp);
     }
+    console.log('null exp');
     return Promise.resolve(null);
   }
 

+ 3 - 2
js/processor/store/store.js

@@ -29,8 +29,9 @@ export class Store {
 
   updateStore (id, stoObj) {
     if(!this.store[id]) {
-      if(this.extendStore !== null) {
-        return this.extendStore.updateStore(id, stoObj);
+      if(this.nextStore !== null) {
+        this.nextStore.updateStore(id, stoObj);
+        return this;
       } else {
         // TODO: better error message
         throw new Error(`Variable ${id} not found.`);

+ 27 - 0
tests/test24.spec.js

@@ -0,0 +1,27 @@
+import Lexers from './../grammar/';
+import { IVProgParser } from './../js/ast/ivprogParser';
+import { IVProgProcessor} from './../js/processor/ivprogProcessor'
+
+describe('Command Do...While', function () {
+
+  let input = `programa {
+
+    funcao inicio() {
+      inteiro a = 0
+      faca {
+        a = a + 1
+      } enquanto(a < 5)
+    }
+  }`;
+
+  const lexer = Lexers['pt_br'];
+
+  it(`should result in a valid state`, function (done) {
+    const parser = new IVProgParser(input, lexer);
+    const exec = new IVProgProcessor(parser.parseTree());
+    exec.interpretAST().then(sto => {
+      expect(sto.applyStore('a').value).toEqual(5);
+      done();
+    }).catch( err => done(err));
+  });
+});

+ 37 - 0
tests/test33.spec.js

@@ -0,0 +1,37 @@
+import Lexers from './../grammar/';
+import { IVProgParser } from './../js/ast/ivprogParser';
+import { IVProgProcessor} from './../js/processor/ivprogProcessor'
+
+describe('A non-const global variable', function () {
+
+  let input = `programa {
+
+    inteiro a = 5
+
+    funcao inicio() {
+      escolha (a) {
+        caso 0:
+          a = a + 1
+          pare
+        caso 1:
+          a = a + 2
+        caso 2:
+          a = a * 2
+          pare
+        caso contrario:
+          a = 5 + 8
+      }
+    }
+  }`;
+
+  const lexer = Lexers['pt_br'];
+
+  it(`should be modifiable inside a function`, function (done) {
+    const parser = new IVProgParser(input, lexer);
+    const exec = new IVProgProcessor(parser.parseTree());
+    exec.interpretAST().then(sto => {
+      expect(sto.applyStore('a').value).toEqual(13);
+      done();
+    }).catch( err => done(err));
+  });
+});

+ 31 - 0
tests/test34.spec.js

@@ -0,0 +1,31 @@
+import Lexers from './../grammar/';
+import { IVProgParser } from './../js/ast/ivprogParser';
+import { IVProgProcessor} from './../js/processor/ivprogProcessor'
+
+describe('IfThenElse command ', function () {
+
+  let input = `programa {
+
+    funcao inicio() {
+      inteiro a = 0
+      se (a > 2) {
+        a = 5
+      } senao se (a < 2) {
+        a = 10
+      } senao {
+        a = -1
+      }
+    }
+  }`;
+
+  const lexer = Lexers['pt_br'];
+
+  it(`should produce a valid state`, function (done) {
+    const parser = new IVProgParser(input, lexer);
+    const exec = new IVProgProcessor(parser.parseTree());
+    exec.interpretAST().then(sto => {
+      expect(sto.applyStore('a').value).toEqual(10);
+      done();
+    }).catch( err => done(err));
+  });
+});

+ 34 - 0
tests/test35.spec.js

@@ -0,0 +1,34 @@
+import Lexers from './../grammar/';
+import { IVProgParser } from './../js/ast/ivprogParser';
+import { IVProgProcessor} from './../js/processor/ivprogProcessor'
+
+describe('A recursive call', function () {
+
+  let input = `programa {
+
+    funcao inicio() {
+      inteiro a = fib(3)
+    }
+
+    funcao inteiro fib(inteiro n) {
+      se (n <= 0 ) {
+        retorne 0
+      } senao se (n == 1) {
+        retorne 1
+      } senao {
+        retorne fib(n-1) + fib(n-2)
+      }
+    }
+  }`;
+
+  const lexer = Lexers['pt_br'];
+
+  it(`should produce a valid state`, function (done) {
+    const parser = new IVProgParser(input, lexer);
+    const exec = new IVProgProcessor(parser.parseTree());
+    exec.interpretAST().then(sto => {
+      expect(sto.applyStore('a').value).toEqual(2);
+      done();
+    }).catch( err => done(err));
+  });
+});