Преглед на файлове

Implement language processor

*Execute every defined command

*Evaluate every defined expression

*Still missing system function calls
Lucas de Souza преди 5 години
родител
ревизия
d1c1ae117f

+ 22 - 22
js/ast/expressions/arrayLiteral.js

@@ -33,28 +33,28 @@ export class ArrayLiteral extends Literal {
   }
 
   validateType () {
-    let valid = true;
-    if(this.columns !== null) {
-      const len = this.columns;
-      const len2 = this.lines;
-      for (let i = len - 1; i >= 0; i--) {
-        for (let j = len2 - 1; j >= 0; j--) {
-          if(this.value[i].value[j].type !== this.subtype) {
-            valid = false;
-            break;
-          }
-        }
-      }
-    } else {
-      const len = this.lines;
-      for (var i = len - 1; i >= 0; i--) {
-        if(this.value[i].type !== this.subtype) {
-          valid = false;
-          break;
-        }
-      }
-    }
-    return valid;
+    // let valid = true;
+    // if(this.columns !== null) {
+    //   const len = this.columns;
+    //   const len2 = this.lines;
+    //   for (let i = len - 1; i >= 0; i--) {
+    //     for (let j = len2 - 1; j >= 0; j--) {
+    //       if(this.value[i].value[j].type !== this.subtype) {
+    //         valid = false;
+    //         break;
+    //       }
+    //     }
+    //   }
+    // } else {
+    //   const len = this.lines;
+    //   for (var i = len - 1; i >= 0; i--) {
+    //     if(this.value[i].type !== this.subtype) {
+    //       valid = false;
+    //       break;
+    //     }
+    //   }
+    // }
+    return true;//valid;
   }
 
   validateSize () {

+ 60 - 0
js/processor/compatibilityTable.js

@@ -0,0 +1,60 @@
+import { Types } from './../ast/types';
+import { Operators } from './../ast/operators';
+
+const InfixCompatibilityTable = Object.freeze([
+  /*Add*/[Types.INTEGER, Types.REAL, Types.STRING],
+  /*Sub*/[Types.INTEGER, Types.REAL],
+  /*Mult*/[Types.INTEGER, Types.REAL],
+  /*Div*/[Types.INTEGER, Types.REAL],
+  /*Mod*/[Types.INTEGER],
+  /*Gt*/[Types.INTEGER, Types.REAL],
+  /*Ge*/[Types.INTEGER, Types.REAL],
+  /*Lt*/[Types.INTEGER, Types.REAL],
+  /*Le*/[Types.INTEGER, Types.REAL],
+  /*Eq*/[Types.INTEGER, Types.REAL, Types.STRING, Types.BOOLEAN],
+  /*Neq*/[Types.INTEGER, Types.REAL, Types.STRING, Types.BOOLEAN],
+  /*And*/[Types.BOOLEAN],
+  /*Or*/[Types.BOOLEAN],
+  /*Not*/null,
+]);
+
+const UnaryCompatibilityTable = Object.freeze([
+  /*Add*/[Types.INTEGER, Types.REAL],
+  /*Sub*/[Types.INTEGER, Types.REAL],
+  /*Mult*/null,
+  /*Div*/null,
+  /*Mod*/null,
+  /*Gt*/null,
+  /*Ge*/null,
+  /*Lt*/null,
+  /*Le*/null,
+  /*Eq*/null,
+  /*Neq*/null,
+  /*And*/null,
+  /*Or*/null,
+  /*Not*/[Types.BOOLEAN],
+]);
+
+export function canApplyUnaryOp (op, a) {
+  const list = UnaryCompatibilityTable[op];
+  if (!!!list) {
+    return false;
+  }
+  const type = list.find(t => t === a.type);
+  if (!!!type) {
+    return false;
+  }
+  return true
+}
+
+export function canApplyInfixOp (op, a, b) {
+  const list = InfixCompatibilityTable[op];
+  if (!!!list) {
+    return false;
+  }
+  const type = list.find(t => t === a.type);
+  if (!!!type) {
+    return false;
+  }
+  return type === b.type;
+}

+ 5 - 0
js/processor/context.js

@@ -0,0 +1,5 @@
+export const Context = Object.freeze({
+  BASE: Symbol('context:base'),
+  BREAKABLE: Symbol('context:breakable'),
+  FUNCTION: Symbol('context:function')
+});

+ 563 - 0
js/processor/ivprogProcessor.js

@@ -0,0 +1,563 @@
+import { Store } from './store/store';
+import { StoreObject } from './store/storeObject';
+import { StoreObjectArray } from './store/storeObjectArray';
+import { Modes } from './modes';
+import { Context } from './context';
+import { Types } from './../ast/types';
+import { Operators } from './../ast/operators';
+import { canApplyInfixOp, canApplyUnaryOp } from './compatibilityTable';
+import * as Commands from './../ast/commands/';
+import * as Expressions from './../ast/expressions/';
+
+export class IVProgProcessor {
+
+  constructor(ast) {
+    this.ast = ast;
+    this.globalStore = new Store();
+    this.stores = [this.globalStore];
+    this.context = [Context.BASE];
+    this.input = null;
+    this.output = null;
+  }
+
+  registerInput (input) {
+    this.input = input;
+  }
+
+  registerOutput (output) {
+    this.output = output;
+  }
+
+  checkContext(context) {
+    return this.context[this.context.length] === context;
+  }
+
+  ignoreSwitchCases (store) {
+    if (store.mode === Modes.RETURN) {
+      return true;
+    } else if (store.mode === Modes.BREAK) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  interpretAST () {
+    this.initGlobal();
+    const mainFunc = this.findMainFunction();
+    if(mainFunc === null) {
+      // TODO: Better error message
+      throw new Error("Missing main funciton.");
+    }
+    return this.runFunction(mainFunc, [], this.globalStore);
+  }
+
+  initGlobal () {
+    if(!this.checkContext(Context.BASE)) {
+      throw new Error("!!!CRITICAL: Invalid call to initGlobal outside BASE context!!!");
+    }
+    this.ast.global.forEach(decl => {
+      this.executeCommand(this.globalStore, decl).then(sto => this.globalStore = sto);
+    });
+  }
+
+  findMainFunction () {
+    return this.ast.functions.find(v => v.isMain);
+  }
+
+  findFunction (name) {
+    const val = this.ast.functions.find( v => v.name === name);
+    if (!!!val) {
+      // TODO: better error message;
+      throw new Error(`Function ${name} is not defined.`);
+    }
+    return val;
+  }
+
+  runFunction (func, actualParameters, store) {
+    let funcStore = new Store();
+    funcStore.extendStore(this.globalStore);
+    const returnStoreObject = new StoreObject(func.returnType, null);
+    const funcName = func.isMain ? 'main' : func.name;
+    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.commands);
+    this.stores.pop();
+    this.context.pop();
+    return result;
+  }
+
+  associateParameters (formalList, actualList, callerStore, calleeStore) {
+    if (formalList.length != actualList.length) {
+      // 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) {
+          case 1: {
+            if (vl.lines > 0 && vl.columns === null
+              && vl.subtype === v.subtype) {
+              calleeStore.insertStore(v.id, vl);
+            } else {
+              // TODO: Better error message
+              throw new Error(`Parameter ${v.id} is not compatible with the value given.`);
+            }
+            break;
+          }
+          case 2: {
+            if (vl.lines > 0 && vl.columns > 0
+              && vl.subtype === v.subtype) {
+              calleeStore.insertStore(v.id, vl);
+            } else {
+              // TODO: Better error message
+              throw new Error(`Parameter ${v.id} is not compatible with the value given.`);
+            }
+            break;
+          }
+          case 0: {
+            if (vl.type !== v.type) {
+              // TODO: Better error message
+              throw new Error(`Parameter ${v.id} is not compatible with ${vl.type}.`);
+            } else {
+              calleeStore.insertStore(v.id, vl);
+            }
+          }
+        }
+      });
+    });
+    return calleeStore;
+  }
+
+  executeCommands (store, cmds) {
+    const auxExecCmdFun = (promise, cmd) => promise.then( sto => this.executeCommand(sto, cmd));
+    let breakLoop = false;
+    let $result = Promise.resolve(store);
+    for (let index = 0; index < cmds.length && !breakLoop; index++) {
+      const cmd = cmds[index];
+      $result = auxExecCmdFun($result, cmd);
+      $result.then(sto => {
+        if(sto.mode === Modes.RETURN) {
+          breakLoop = true;
+        } else if (this.checkContext(Context.BREAKABLE && 
+          sto.mode === Modes.BREAK)) {
+            breakLoop = true;
+        }
+        return sto;
+      });
+    }
+    return $result;
+  }
+
+  executeCommand (store, cmd) {
+
+    while (store.mode === Modes.PAUSE) {
+      continue;
+    }
+
+    if(store.mode === Modes.RETURN) {
+      return Promise.resolve(store);
+    } else if(this.checkContext(Context.BREAKABLE) && store.mode === Modes.BREAK) {
+      return Promise.resolve(store);
+    }
+
+    if (cmd instanceof Commands.Declaration) {
+      return this.executeDeclaration(store, cmd);
+    } else if (cmd instanceof Commands.Assign) {
+      return this.executeAssign(store, cmd);
+    } else if (cmd instanceof Commands.Break) {
+      return this.executeBreak(store, cmd);
+    } else if (cmd instanceof Commands.Return) {
+      return this.executeReturn(store, cmd);
+    } else if (cmd instanceof Commands.IfThenElse) {
+      return this.executeIfThenElse(store, cmd);
+    } else if (cmd instanceof Commands.While) {
+      return this.executeWhile(store, cmd);
+    } else if (cmd instanceof Commands.DoWhile) {
+      return this.executeDoWhile(store, cmd);
+    } else if (cmd instanceof Commands.For) {
+      return this.executeFor(store, cmd);
+    } else if (cmd instanceof Commands.Switch) {
+      return this.executeSwitch(store, cmd);
+    } else if (cmd instanceof Commands.FunctionCall) {
+      return this.executeFunctionCall(store, cmd);
+    } else {
+      throw new Error("!!!CRITICAL A unknown command was found!!!\n" + cmd);
+    }
+  }
+
+  executeFunctionCall (store, cmd) {
+    const func = this.findFunction(cmd.id);
+    this.runFunction(func, cmd.actualParameters, store);
+    return Promise.resolve(store);
+  }
+
+  executeSwitch (store, cmd) {
+    const auxCaseFun = (promise, switchExp, aCase) => {
+      return promise.then( result => {
+        const sto = result.sto;
+        if (this.ignoreSwitchCases(sto)) {
+          return Promise.resolve(result);
+        } else if (result.wasTrue || aCase.isDefault) {
+          const $newSto = this.executeCommand(result.sto,aCase.commands);
+          return $newSto.then(nSto => {
+            return Promise.resolve({wasTrue: true, sto: nSto});
+          });
+        } else {
+          const $value = this.evaluateExpression(sto,
+            new Expressions.InfixApp('==', switchExp, aCase.expression));
+          return $value.then(vl => {
+            if (vl.value) {
+              const $newSto = this.executeCommand(result.sto,aCase.commands);
+              return $newSto.then(nSto => {
+                return Promise.resolve({wasTrue: true, sto: nSto});
+              });
+            } else {
+              return Promise.resolve({wasTrue: false, sto: sto});
+            }
+          });
+        }
+      });
+    }
+
+    try {
+      this.context.push(Context.BREAKABLE);
+      let breakLoop = false;
+      const case0 = cmd.cases[0];
+      let $result = Promise.resolve({wasTrue: false, sto: store});
+      for (let index = 0; index < cmd.cases.length && !breakLoop; index++) {
+        const aCase = cmd.cases[index];
+        $result = auxCaseFun($result, cmd.expression, aCase);
+        $result.then( r => breakLoop = this.ignoreSwitchCases(r.sto));
+      }
+      this.context.pop();
+      return result.then(r => r.sto);
+    } catch (error) {
+      return Promise.reject(error);
+    }
+  }
+
+  executeFor (store, cmd) {
+    try {
+      //BEGIN for -> while rewrite
+      const initCmd = cmd.assignment;
+      const condition = cmd.condition;
+      const increment = cmd.increment;
+      const whileBlock = new Commands.CommandBlock([],
+        cmd.commands.concat(increment));
+      const forAsWhile = new Commands.While(condition, whileBlock);
+      //END for -> while rewrite
+      const newCmdList = [initCmd,forAsWhile];
+      return this.executeCommands(store, newCmdList);
+    } catch (error) {
+      return Promise.reject(error);
+    }
+  }
+
+  executeDoWhile (store, cmd) {
+    try {
+      this.context.push(Context.BREAKABLE);
+      const $newStore = this.executeCommands(store, cmd.commands);
+      return $newStore.then(sto => {
+        const $value = this.evaluateExpression(sto, cmd.expression);
+        return $value.then(vl => {
+          if (vl.type !== Types.BOOLEAN) {
+            // TODO: Better error message -- Inform line and column from token!!!!
+            // THIS IF SHOULD BE IN A SEMANTIC ANALYSER
+            return Promise.reject(new Error(`DoWhile expression must be of type boolean`));
+          }
+          if (vl.value) {
+            this.context.pop();
+            return this.executeCommand(sto, cmd);
+          } else {
+            this.context.pop();
+            return Promise.resolve(sto);
+          }
+        });
+      });
+    } catch (error) {
+      return Promise.reject(error)
+    }
+  }
+
+  executeWhile (store, cmd) {
+    try {
+      this.context.push(Context.BREAKABLE);
+      const $value = this.evaluateExpression(store, cmd.expression);
+      return $value.then(vl => {
+        if(vl.type === Types.BOOLEAN) {
+          if(vl.value) {
+            const $newStore = this.executeCommands(store, cmd.commands);
+            return $newStore.then(sto => {
+              this.context.pop();
+              return this.executeCommand(sto, cmd);
+            });
+          } else {
+            this.context.pop();
+            return Promise.resolve(store);
+          }
+        } else {
+          // TODO: Better error message -- Inform line and column from token!!!!
+          // THIS IF SHOULD BE IN A SEMANTIC ANALYSER
+          return Promise.reject(new Error(`Loop condition must be of type boolean`));
+        }
+      });
+    } catch (error) {
+      return Promise.reject(error);
+    }
+  }
+
+  executeIfThenElse (store, cmd) {
+    try {
+      const $value = this.evaluateExpression(cmd.condition);
+      return $value.then(vl => {
+        if(vl.type === Types.BOOLEAN) {
+          if(vl.value) {
+            return this.executeCommands(store, cmd.ifTrue);
+          } else {
+            return this.executeCommands(store, cmd.ifFalse);
+          }
+        } else {
+          // TODO: Better error message -- Inform line and column from token!!!!
+          // THIS IF SHOULD BE IN A SEMANTIC ANALYSER
+          return Promise.reject(new Error(`If expression must be of type boolean`));
+        }
+      });
+    } catch (error) {
+      return Promise.reject(error);
+    }
+  }
+
+  executeReturn (store, cmd) {
+    try {
+      const funcType = store.applyStore('$');
+      const $value = this.evaluateExpression(store, cmd.expression);
+      const funcName = store.applyStore('$name');
+      return $value.then(vl => {
+        if (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}.`));
+        } else {
+          store.updateStore('$', vl);
+          store.mode = Modes.RETURN;
+          return Promise.resolve(store);
+        }
+      });
+    } catch (error) {
+      return Promise.reject(error);
+    }
+  }
+
+  executeBreak (store, _) {
+    if(this.checkContext(Context.BREAKABLE)) {
+      store.mode = Modes.BREAK;
+      return Promise.resolve(store);
+    } else {
+      return Promise.reject(new Error("!!!CRITIAL: Break command outside Loop/Switch scope!!!"));
+    }
+  }
+
+  executeAssign (store, cmd) {
+    try {
+      const $value = this.evaluateExpression(store, cmd.expression);
+      return $value.then( vl => {
+        store.updateStore(cmd.id, vl) 
+        return store;
+      });
+    } catch (error) {
+      return Promise.reject(error);
+    }
+  }
+
+  executeDeclaration (store, cmd) {
+    try {
+      const $value = this.evaluateExpression(store, cmd.initial);
+      if(cmd instanceof Commands.ArrayDeclaration) {
+        const temp = new StoreObjectArray(decl.subtype, decl.lines, decl.columns, null, decl.isConst);
+        store.insertStore(decl.id, temp);
+        return $value.then(vl => {
+          store.updateStore(decl.id, vl)
+          return store;
+        });
+      } else {
+        const temp = new StoreObject(decl.type, null, decl.isConst);
+        store.insertStore(decl.id, temp);
+        return $value.then(vl => {
+          store.updateStore(decl.id, vl)
+          return store;
+        });
+      }
+    } catch (e) {
+      return Promise.reject(e);
+    }
+  }
+
+   evaluateExpression (store, exp) {
+
+    if (exp instanceof Expressions.UnaryApp) {
+      return this.evaluateUnaryApp(store, exp);
+    } else if (exp instanceof Expressions.InfixApp) {
+      return this.evaluateInfixApp(store, exp);
+    } else if (exp instanceof Expressions.ArrayAccess) {
+      return this.evaluateArrayAccess(store, exp);
+    } else if (exp instanceof Expressions.VariableLiteral) {
+      return this.evaluateVariableLiteral(store, exp);
+    } else if (exp instanceof Expressions.IntLiteral) {
+      return this.evaluateLiteral(store, exp);
+    } else if (exp instanceof Expressions.RealLiteral) {
+      return this.evaluateLiteral(store, exp);
+    } else if (exp instanceof Expressions.BoolLiteral) {
+      return this.evaluateLiteral(store, exp);
+    } else if (exp instanceof Expressions.StringLiteral) {
+      return this.evaluateLiteral(store, exp);
+    } else if (exp instanceof Expressions.ArrayLiteral) {
+      return this.evaluateArrayLiteral(store, exp);
+    } else if (exp instanceof Expressions.FunctionCall) {
+      return this.evaluateFunctionCall(store, exp);
+    }
+  }
+
+  evaluateFunctionCall (store, exp) {
+    const func = this.findFunction(exp.id);
+    if(func.returnType === Types.VOID) {
+      // TODO: better error message
+      return Promise.reject(new Error(`Function ${exp.id} cannot be used inside an expression`));
+    }
+    const $newStore = this.runFunction(func, exp.actualParameters, store);
+    return $newStore.then( sto => sto.applyStore('$'));
+  }
+
+  evaluateArrayLiteral (store, exp) {
+    if(exp.columns !== null) {
+      let column = [];
+      for (let i = 0; i < exp.lines; i++) {
+        const line = [];
+        for (let j = 0; j < exp.columns; j++) {
+          const $value = this.evaluateExpression(store, exp.value[i].value[j]);
+          $value.then(value => line.push(value));
+        }
+        const stoArray = new StoreObjectArray(line[0].type, line.length, null, line);
+        column.push(stoArray);
+      }
+      const arr = new StoreObjectArray(column[0].subtype, column.length, column[0].lines, column);
+      return Promise.resolve(arr);
+    } else {
+      let line = [];
+      for (let i = 0; i < exp.lines; i++) {
+        const $value = this.evaluateExpression(store, exp.value[i].value[j]);
+        $value.then(value => line.push(value));
+      }
+      const stoArray = new StoreObjectArray(line[0].type, line.length, null, line);
+      return Promise.resolve(stoArray);
+    }
+  }
+
+  evaluateLiteral (_, exp) {
+    return Promise.resolve(new StoreObject(exp.type, exp.value));
+  }
+
+  evaluateVariableLiteral (store, exp) {
+    try {
+      const val = store.applyStore(exp.id);
+      return Promise.resolve(val);
+    } catch (error) {
+      return Promise.reject(error);
+    }
+  }
+
+  evaluateArrayAccess (store, exp) {
+    const mustBeArray = store.applyStore(exp.id);
+    if (mustBeArray.type !== Types.ARRAY) {
+      // TODO: better error message
+      return Promise.reject(new Error(`${exp.id} is not of type array`));
+    }
+    if (exp.line >= mustBeArray.lines) {
+      // TODO: better error message
+      return Promise.reject(new Error(`${exp.id}: index out of bounds: ${exp.lines}`));
+    }
+    if (exp.column !== null && mustBeArray.columns === null ){
+      // TODO: better error message
+      return Promise.reject(new Error(`${exp.id}: index out of bounds: ${exp.column}`));
+    }
+    if(exp.column !== null && exp.column >= mustBeArray.columns) {
+      // TODO: better error message
+      return Promise.reject(new Error(`${exp.id}: index out of bounds: ${exp.column}`));
+    }
+
+    if (exp.column !== null) {
+      return Promise.resolve(mustBeArray.value[exp.line][exp.column]);
+    } else {
+      return Promise.resolve(mustBeArray.value[exp.line]);
+    }
+  }
+
+  evaluateUnaryApp (store, unaryApp) {
+    const $left = this.evaluateExpression(store, infixApp.left);
+    return $left.then( left => {
+      if (!canApplyUnaryOp(infixApp.op, left)) {
+        // TODO: better urgent error message
+        return Promise.reject(new Error(`Cannot use this op to ${left.type}`));
+      }
+      switch (unaryApp.op) {
+        case Operators.ADD:
+          return new StoreObject(left.type, +left.value);
+        case Operators.SUB:
+          return new StoreObject(left.type, -left.value);
+        case Operators.NOT:
+          return new StoreObject(left.type, !left.value);
+        default:
+        return Promise.reject(new Error('!!!Critical Invalid UnaryApp '+ unaryApp.op));
+      }
+    });
+  }
+
+  evaluateInfixApp (store, infixApp) {
+    const $left = this.evaluateExpression(store, infixApp.left);
+    const $right = this.evaluateExpression(store, infixApp.right);
+    return Promise.all([$left, $right]).then(values => {
+      const left = values[0];
+      const right = values[1];
+      if (!canApplyInfixOp(infixApp.op, left, right)) {
+        // TODO: better urgent error message
+        return Promise.reject(new Error(`Cannot use this op to ${left.type} and ${right.type}`));
+      }
+      switch (infixApp.op) {
+        case Operators.ADD:
+          return new StoreObject(left.type, left.value + right.value);
+        case Operators.SUB:
+          return new StoreObject(left.type, left.value - right.value);
+        case Operators.MULT:
+          return new StoreObject(left.type, left.value * right.value);
+        case Operators.DIV:
+          return new StoreObject(left.type, left.value / right.value);
+        case Operators.MOD:
+          return new StoreObject(left.type, left.value % right.value);
+        case Operators.GT:
+          return new StoreObject(Types.BOOLEAN, left.value > right.value);
+        case Operators.GE:
+          return new StoreObject(Types.BOOLEAN, left.value >= right.value);
+        case Operators.LT:
+          return new StoreObject(Types.BOOLEAN, left.value < right.value);
+        case Operators.LE:
+          return new StoreObject(Types.BOOLEAN, left.value <= right.value);
+        case Operators.EQ:
+          return new StoreObject(Types.BOOLEAN, left.value === right.value);
+        case Operators.NEQ:
+          return new StoreObject(Types.BOOLEAN, left.value !== right.value);
+        case Operators.AND:
+          return new StoreObject(Types.BOOLEAN, left.value && right.value);
+        case Operators.OR:
+          return new StoreObject(Types.BOOLEAN, left.value || right.value);
+        default:
+          return Promise.reject(new Error('!!!Critical Invalid InfixApp '+ infixApp.op));
+      }
+    });
+  }
+
+}

+ 6 - 0
js/processor/modes.js

@@ -0,0 +1,6 @@
+export const Modes = Object.freeze({
+  RETURN: Symbol('mode:return'),
+  BREAK: Symbol('mode:break'),
+  PAUSE: Symbol('mode:pause'),
+  RUN: Symbol('mode:run')
+});

+ 58 - 0
js/processor/store/store.js

@@ -0,0 +1,58 @@
+import { Modes } from './../modes';
+export class Store {
+
+  constructor() {
+    this.store = {};
+    this.nextStore = null;
+    this.mode = Modes.RUN; 
+  }
+
+  extendStore (nextStore) {
+    this.nextStore = nextStore;
+  }
+
+  applyStore (id) {
+    if(!this.store[id]) {
+      if (this.nextStore !== null) {
+        return this.nextStore.applyStore(id);
+      } else {
+        // TODO: better error message
+        throw new Error(`Variable ${id} not found.`);
+      }
+    }
+    return this.store[id];
+  }
+
+  updateStore (id, stoObj) {
+    if(!this.store[id]) {
+      if(this.extendStore !== null) {
+        return this.extendStore.updateStore(id, stoObj);
+      } else {
+        // TODO: better error message
+        throw new Error(`Variable ${id} not found.`);
+      }
+    } else {
+      const oldObj = this.applyStore(id);
+      if(oldObj.readOnly) {
+        // TODO: better error message
+        throw new Error("Cannot change value of a read only variable: " + id);
+      }
+      if(oldObj.isCompatible(stoObj)) {
+        this.store[id] = Object.freeze(stoObj);
+        return this;
+      } else {
+        // TODO: better error message
+        throw new Error(`${stoObj.type} is not compatible with the value given`);
+      }
+    }
+  }
+
+  insertStore (id, stoObj) {
+    if (this.store[id]) {
+      // TODO: better error message
+      throw new Error(`${id} is already defined`);
+    }
+    this.store[id] = stoObj;
+    return this;
+  }
+}

+ 15 - 0
js/processor/store/storeObject.js

@@ -0,0 +1,15 @@
+export class StoreObject {
+
+  constructor (type, value, readOnly) {
+    this.type = type;
+    this.value = value;
+    this.readOnly = readOnly;
+  }
+
+  isCompatible (another) {
+    if( another instanceof StoreObject) {
+      return this.type === another.type;
+    }
+    return false;
+  }
+}

+ 22 - 0
js/processor/store/storeObjectArray.js

@@ -0,0 +1,22 @@
+import { Types } from './../../ast/types';
+import { StoreObject } from './storeObject';
+export class StoreObjectArray extends StoreObject {
+
+  constructor (subtype, lines, columns, value, readOnly) {
+    super(Types.ARRAY, value, readOnly);
+    this.lines = lines;
+    this.columns = columns;
+    this.subtype = subtype;
+  }
+
+  isCompatible (another) {
+    if(another instanceof StoreObjectArray) {
+      if(this.lines === another.lines &&
+        this.columns === another.columns &&
+        this.subtype === another.subtype) {
+          return super.isCompatible(another);
+        }
+    }
+    return false;
+  }
+}