Browse Source

Fix #20

-Refactor ivprogParser to handle the array index assignment syntax

-Refactor the ivprogProcessor to properly execute the array index assignment

-Implement a naive semantic analysis for array index assignment

-Implement ArrayIndexAssign command
Lucas de Souza 6 years ago
parent
commit
b4de92b7d1

+ 10 - 0
js/ast/commands/arrayAssign.js

@@ -0,0 +1,10 @@
+import { Assign } from './assign';
+
+export class ArrayIndexAssign extends Assign {
+
+  constructor (id, lineExpression, columnExpression, expression) {
+    super(id, expression);
+    this.line = lineExpression;
+    this.column = columnExpression;
+  }
+}

+ 2 - 0
js/ast/commands/index.js

@@ -1,6 +1,7 @@
 import { Break } from './break';
 import { Return } from './return';
 import { Assign } from './assign';
+import { ArrayIndexAssign } from './arrayAssign';
 import { Declaration } from './declaration';
 import { ArrayDeclaration } from './arrayDeclaration';
 import { While } from './while';
@@ -19,6 +20,7 @@ export {
   Break,
   Return,
   Assign,
+  ArrayIndexAssign,
   Declaration,
   ArrayDeclaration,
   While,

+ 30 - 2
js/ast/ivprogParser.js

@@ -743,15 +743,43 @@ export class IVProgParser {
     const refToken = this.getToken();
     const isID = refToken.type === this.lexerClass.ID;
     const id = this.parseMaybeLibID();
+    if(this.checkOpenBrace(true)) {
+      this.pos++;
+      let lineExpression = null;
+      let columnExpression = null;
+      this.consumeNewLines();
+      lineExpression = this.parseExpression()
+      this.consumeNewLines();
+      this.checkCloseBrace();
+      this.pos++;
+      if (this.checkOpenBrace(true)) {
+        this.pos++
+        this.consumeNewLines();
+        columnExpression = this.parseExpression();
+        this.consumeNewLines();
+        this.checkCloseBrace();
+        this.pos++;
+      }
+      const equalToken = this.getToken();
+      if (equalToken.type !== this.lexerClass.EQUAL) {
+        throw SyntaxErrorFactory.token_missing_one('=', equalToken);
+      }
+      this.pos++;
+      const exp = this.parseExpressionOR();
+      this.checkEOS();
+      this.pos++;
+      const cmd = new Commands.ArrayIndexAssign(id, lineExpression, columnExpression, exp);
+      cmd.sourceInfo = SourceInfo.createSourceInfo(equalToken);
+      return cmd;
+    }
     const equalOrParenthesis = this.getToken();
     if (isID && equalOrParenthesis.type === this.lexerClass.EQUAL) {
-      const sourceInfo = SourceInfo.createSourceInfo(this.getToken());
       this.pos++
       const exp = this.parseExpressionOR();
       this.checkEOS();
       this.pos++;
       const cmd = new Commands.Assign(id, exp);
-      cmd.sourceInfo = sourceInfo;
+      cmd.sourceInfo = SourceInfo.createSourceInfo(equalOrParenthesis);
       return cmd;
     } else if (equalOrParenthesis.type === this.lexerClass.OPEN_PARENTHESIS) {
       const funcCall = this.parseFunctionCallCommand(id);

+ 68 - 0
js/processor/ivprogProcessor.js

@@ -206,6 +206,8 @@ export class IVProgProcessor {
 
     if (cmd instanceof Commands.Declaration) {
       return this.executeDeclaration(store, cmd);
+    } else if (cmd instanceof Commands.ArrayIndexAssign) {
+      return this.executeArrayIndexAssign(store, cmd);
     } else if (cmd instanceof Commands.Assign) {
       return this.executeAssign(store, cmd);
     } else if (cmd instanceof Commands.Break) {
@@ -447,6 +449,72 @@ export class IVProgProcessor {
     }
   }
 
+  executeArrayIndexAssign (store, cmd) {
+    return new Promise((resolve, reject) => {
+      const mustBeArray = store.applyStore(cmd.id);
+      if(mustBeArray.type !== Types.ARRAY) {
+        reject(new Error(cmd.id + " is not a vector/matrix"));
+        return;
+      }
+      const line$ = this.evaluateExpression(store, cmd.line);
+      const column$ = this.evaluateExpression(store, cmd.column);
+      const value$ =  this.evaluateExpression(store, cmd.expression);
+      Promise.all([line$, column$, value$]).then(results => {
+        const lineSO = results[0];
+        if(lineSO.type !== Types.INTEGER) {
+          // TODO: better error message
+          //SHOULD NOT BE HERE. IT MUST HAVE A SEMANTIC ANALYSIS
+          reject(new Error("Array dimension must be of type int"));
+          return;
+        }
+        const line = lineSO.number;
+        const columnSO = results[1];
+        let column = null
+        if (columnSO !== null) {
+          if(columnSO.type !== Types.INTEGER) {
+            // TODO: better error message
+            //SHOULD NOT BE HERE. IT MUST HAVE A SEMANTIC ANALYSIS
+            reject(new Error("Array dimension must be of type int"));
+            return;
+          }
+          column = columnSO.number;
+        }
+        const value = results[2];
+        if (line >= mustBeArray.lines) {
+          // TODO: better error message
+          return Promise.reject(new Error(`${exp.id}: index out of bounds: ${lines}`));
+        }
+        if (column !== null && mustBeArray.columns === null ){
+          // TODO: better error message
+          return Promise.reject(new Error(`${exp.id}: index out of bounds: ${column}`));
+        }
+        if(column !== null && column >= mustBeArray.columns) {
+          // TODO: better error message
+          return Promise.reject(new Error(`${exp.id}: index out of bounds: ${column}`));
+        }
+
+        const newArray = Object.assign(new StoreObjectArray(null,null,null), mustBeArray);
+        if (column !== null) {
+         if (value.type === Types.ARRAY) {
+           reject(new Error("Invalid operation. This must be a value: line "+cmd.sourceInfo.line));
+           return;
+         }
+         newArray.value[line].value[column] = value;
+         store.updateStore(cmd.id, newArray);
+        } else {
+         if(mustBeArray.columns !== null && value.type !== Types.ARRAY) {
+          reject(new Error("Invalid operation. This must be a vector: line "+cmd.sourceInfo.line));
+          return;
+         }
+         store.updateStore(cmd.id, newArray);
+        }
+        resolve(store);
+      }).catch(err => reject(err));
+    });
+    const $value = this.evaluateExpression(store, cmd.expression);
+    return $value.then( vl => store.updateStore(cmd.id, vl));
+  }
+
   executeDeclaration (store, cmd) {
     try {
       const $value = this.evaluateExpression(store, cmd.initial);

+ 28 - 1
js/processor/semantic/semanticAnalyser.js

@@ -1,7 +1,7 @@
 import { ProcessorErrorFactory } from './../error/processorErrorFactory';
 import { LanguageDefinedFunction } from './../definedFunctions';
 import { LanguageService } from './../../services/languageService';
-import { ArrayDeclaration, While, For, Switch, Case, Declaration, Assign, Break, IfThenElse, Return } from '../../ast/commands';
+import { ArrayDeclaration, While, For, Switch, Case, Declaration, Assign, Break, IfThenElse, Return, ArrayIndexAssign } from '../../ast/commands';
 import { InfixApp, UnaryApp, FunctionCall, IntLiteral, RealLiteral, StringLiteral, BoolLiteral, VariableLiteral, ArrayLiteral, ArrayAccess } from '../../ast/expressions';
 import { Literal } from '../../ast/expressions/literal';
 import { resultTypeAfterInfixOp, resultTypeAfterUnaryOp } from '../compatibilityTable';
@@ -287,6 +287,33 @@ export class SemanticAnalyser {
       }
       return result && hasDefault;
 
+    } else if (cmd instanceof ArrayIndexAssign) {
+      const typeInfo = this.findSymbol(cmd.id, this.symbolMap);
+      if(!typeInfo.subtype) {
+        throw new Error(cmd.id + " is not an array.");
+      }
+      const exp = cmd.expression;
+      const lineExp = cmd.line;
+      const lineType = this.evaluateExpressionType(lineExp);
+      if (lineType !== Types.INTEGER) {
+        throw new Error("array dimension must be of type int");
+      }
+      const columnExp = cmd.column;
+      if (typeInfo.columns === null && columnExp !== null) {
+        throw new Error(cmd.id + " is not a matrix");
+      } else if (columnExp !== null) {
+        const columnType = this.evaluateExpressionType(columnExp);
+        if (columnType !== Types.INTEGER) {
+          throw new Error("array dimension must be of type int");
+        }
+      }
+      // exp can be a arrayLiteral, a single value exp or an array access
+      if(exp instanceof ArrayLiteral) {
+        this.evaluateArrayLiteral(typeInfo.lines, (columnExp ? typeInfo.columns : null), typeInfo.subtype, exp);
+      } else {
+        // cannot properly evaluate since type system is poorly constructed
+      }
+      return optional;
     } else if (cmd instanceof Assign) {
       const typeInfo = this.findSymbol(cmd.id, this.symbolMap);
       const exp = cmd.expression;

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

@@ -52,8 +52,10 @@ export class Store {
         this.store[id] = Object.freeze(stoObj);
         return this;
       } else {
+        const oldType = oldObj.subtype ? oldObj.subtype.value : oldObj.type.value;
+        const stoType = stoObj.subtype ? stoObj.subtype.value : stoObj.type.value;
         // TODO: better error message
-        throw new Error(`${oldObj.type} is not compatible with the value given`);
+        throw new Error(`${oldType} is not compatible with type ${stoType} given`);
       }
     }
   }

+ 1 - 1
js/processor/store/storeObjectArray.js

@@ -3,7 +3,7 @@ import { StoreObject } from './storeObject';
 
 export class StoreObjectArray extends StoreObject {
 
-  constructor (subtype, lines, columns, value, readOnly) {
+  constructor (subtype, lines, columns, value = null, readOnly = false) {
     super(Types.ARRAY, value, readOnly);
     this._lines = lines;
     this._columns = columns;

+ 31 - 0
tests/test58.spec.js

@@ -0,0 +1,31 @@
+import { IVProgParser } from './../js/ast/ivprogParser';
+import { SemanticAnalyser } from './../js/processor/semantic/semanticAnalyser';
+import { LanguageService } from '../js/services/languageService';
+import { OutputTest } from '../js/util/outputTest';
+import { IVProgProcessor } from '../js/processor/ivprogProcessor';
+
+describe('Assign to an array index', function () {
+
+  const code = `programa {
+
+    funcao inicio() {
+      inteiro a[2] = {0,0}
+      a[0] = 1
+    }
+  }`;
+
+  localStorage.setItem('ivprog.lang', 'pt');
+
+  const lexer = LanguageService.getCurrentLexer();
+  const out = new OutputTest();
+
+  it(`should not throw an exception`, function (done) {
+    const parser = new IVProgParser(code, lexer);
+    const sem = new SemanticAnalyser(parser.parseTree());
+    const exec = new IVProgProcessor(sem.analyseTree());
+    exec.registerOutput(out);
+    exec.interpretAST().then(_ => {
+      done();
+    }).catch(err => done(err));
+  });
+});

+ 31 - 0
tests/test59.spec.js

@@ -0,0 +1,31 @@
+import { IVProgParser } from './../js/ast/ivprogParser';
+import { SemanticAnalyser } from './../js/processor/semantic/semanticAnalyser';
+import { LanguageService } from '../js/services/languageService';
+import { OutputTest } from '../js/util/outputTest';
+import { IVProgProcessor } from '../js/processor/ivprogProcessor';
+
+describe('Assigning a single value to only a dimension of a matrix', function () {
+
+  const code = `programa {
+
+    funcao inicio() {
+      inteiro a[2][2] = {{0,0},{0,0}}
+      a[0] = 1
+    }
+  }`;
+
+  localStorage.setItem('ivprog.lang', 'pt');
+
+  const lexer = LanguageService.getCurrentLexer();
+  const out = new OutputTest();
+
+  it(`should throw an exception`, function (done) {
+    const parser = new IVProgParser(code, lexer);
+    const sem = new SemanticAnalyser(parser.parseTree());
+    const exec = new IVProgProcessor(sem.analyseTree());
+    exec.registerOutput(out);
+    exec.interpretAST().then(_ => {
+      done("No exception thrown.");
+    }).catch(err => done());
+  });
+});