Browse Source

Implement function call stack property of IVProgProcessor class to track functions calls, mainly recursive calls

Implement language defined functions error messages using the function call stack source info data
Lucas de Souza 5 years ago
parent
commit
d117362f99

+ 7 - 1
i18n/pt/error.json

@@ -121,5 +121,11 @@
   "invalid_matrix_index_assign_full": "Erro na linha $0: A posição $1 da matriz $2 aceita apenas vetores de tamanho $3, mas $4 tem tamanho $5",
   "invalid_matrix_index_assign_full": "Erro na linha $0: A posição $1 da matriz $2 aceita apenas vetores de tamanho $3, mas $4 tem tamanho $5",
   "invalid_matrix_index_assign": "A linha $0 da matriz $1 aceita apenas vetores de tamanho $2, mas $3 tem tamanho $4",
   "invalid_matrix_index_assign": "A linha $0 da matriz $1 aceita apenas vetores de tamanho $2, mas $3 tem tamanho $4",
   "invalid_number_elements_vector": "Esperava-se por $0 elementos na linha $1 mas a expressão $2 possui $3 elementos.",
   "invalid_number_elements_vector": "Esperava-se por $0 elementos na linha $1 mas a expressão $2 possui $3 elementos.",
-  "invalid_number_lines_matrix": "Esperava-se por uma matriz com $0 linhas na linha $1 mas a expressão $2 possui $3 linhas"
+  "invalid_number_lines_matrix": "Esperava-se por uma matriz com $0 linhas na linha $1 mas a expressão $2 possui $3 linhas",
+  "divsion_by_zero_full": "Erro na linha $0: A expressão $1 resulta em uma divisão por 0.",
+  "divsion_by_zero": "A expressão $0 resulta em uma divisão por 0.",
+  "undefined_tanget_value": "Erro na linha $0: A tangente de $1° não é indefinida.",
+  "negative_log_value": "Erro na linha $0: Não se pode calcular o log de um valor negativo.",
+  "invalid_string_index": "Erro na linha $0 durante a execução da função $1: $2 é um índice inválido para a cadeia de texto $3. Os valores válidos vão de 0 à $4",
+  "negative_sqrt_value": "Erro na linha $0: Não é permitido calcular a raiz quadrada de um número negativo."
 }
 }

+ 8 - 2
js/ast/expressions/arrayAccess.js

@@ -15,10 +15,16 @@ export class ArrayAccess extends Expression {
 		if(this.column) {
 		if(this.column) {
 			strColumn = this.column.toString();
 			strColumn = this.column.toString();
 		}
 		}
+		let text = null;
 		if(strColumn) {
 		if(strColumn) {
-			return `${this.id}[${strLine}][${strColumn}]`;
+			text = `${this.id}[${strLine}][${strColumn}]`;
 		} else {
 		} else {
-			return `${this.id}[${strLine}]`;
+			text = `${this.id}[${strLine}]`;
+		}
+		if(this.parenthesis ){
+			return `(${text})`
+		} else {
+			return text;
 		}
 		}
 	}
 	}
 }
 }

+ 10 - 5
js/ast/expressions/arrayLiteral.js

@@ -8,7 +8,7 @@ export class ArrayLiteral extends Literal {
   }
   }
 
 
   get subtype () {
   get subtype () {
-    let element = this.value[0];
+    const element = this.value[0];
     if (element instanceof ArrayLiteral) {
     if (element instanceof ArrayLiteral) {
       return element.value[0].type;
       return element.value[0].type;
     } else {
     } else {
@@ -21,7 +21,7 @@ export class ArrayLiteral extends Literal {
   }
   }
 
 
   get columns () {
   get columns () {
-    let element = this.value[0];
+    const element = this.value[0];
     if (!(element instanceof ArrayLiteral)){
     if (!(element instanceof ArrayLiteral)){
       return null;
       return null;
     } else {
     } else {
@@ -38,7 +38,7 @@ export class ArrayLiteral extends Literal {
   }
   }
 
 
   validateType () {
   validateType () {
-    let valid = true;
+    const valid = true;
     // const subtype = this.subtype;
     // const subtype = this.subtype;
     // if(this.columns !== null) {
     // if(this.columns !== null) {
     //   const len = this.lines;
     //   const len = this.lines;
@@ -65,7 +65,7 @@ export class ArrayLiteral extends Literal {
   }
   }
 
 
   validateSize () {
   validateSize () {
-    let valid = true;
+    const valid = true;
     // if(this.columns !== null) {
     // if(this.columns !== null) {
     //   const equalityTest = this.value.map(v => v.value.length)
     //   const equalityTest = this.value.map(v => v.value.length)
     //     .reduce((old, next) => {
     //     .reduce((old, next) => {
@@ -86,6 +86,11 @@ export class ArrayLiteral extends Literal {
 
 
   toString () {
   toString () {
     const strList = this.value.map(arrayLiteral => arrayLiteral.toString());
     const strList = this.value.map(arrayLiteral => arrayLiteral.toString());
-    return "{" + strList.join(',') + "}";
+    const text = "{" + strList.join(',') + "}";
+    if(this.parenthesis) {
+      return `(${text})`;
+    } else {
+      return text;
+    }
   }
   }
 }
 }

+ 6 - 1
js/ast/expressions/boolLiteral.js

@@ -10,6 +10,11 @@ export class BoolLiteral extends Literal {
   }
   }
 
 
   toString () {
   toString () {
-    return convertBoolToString(this.value);
+    const text = convertBoolToString(this.value);
+    if(this.parenthesis) {
+      return `(${text})`
+    } else {
+      return text;
+    }
   }
   }
 }
 }

+ 10 - 1
js/ast/expressions/expression.js

@@ -1,7 +1,8 @@
 export class Expression {
 export class Expression {
 
 
   constructor () {
   constructor () {
-    this._sourceInfo = null;
+		this._sourceInfo = null;
+		this._parenthesis = false;
   }
   }
 
 
   set sourceInfo (sourceInfo) {
   set sourceInfo (sourceInfo) {
@@ -11,4 +12,12 @@ export class Expression {
 	get sourceInfo () {
 	get sourceInfo () {
 		return this._sourceInfo;
 		return this._sourceInfo;
 	}
 	}
+
+	set parenthesis (flag) {
+		this._parenthesis = flag;
+	}
+
+	get parenthesis () {
+		return this._parenthesis;
+	}
 }
 }

+ 5 - 1
js/ast/expressions/functionCall.js

@@ -31,6 +31,10 @@ export class FunctionCall extends Expression {
 			const strParams = this.actualParameters.map(v => v.toString());
 			const strParams = this.actualParameters.map(v => v.toString());
 			params = "(" + strParams.join(",") + ")";
 			params = "(" + strParams.join(",") + ")";
 		}
 		}
-		return name + params;
+		if(this.parenthesis) {
+			return `(${name + params})`;
+		} else {
+			return name + params;
+		}
 	}
 	}
 }
 }

+ 5 - 1
js/ast/expressions/infixApp.js

@@ -13,6 +13,10 @@ export class InfixApp extends Expression {
     const l = this.left.toString();
     const l = this.left.toString();
     const op = this.op.value;
     const op = this.op.value;
     const r = this.right.toString();
     const r = this.right.toString();
-    return l + op + r;
+    if(this.parenthesis) {
+      return  `(${l + op + r})`;
+    } else {
+      return l + op + r;
+    }
   }
   }
 }
 }

+ 6 - 1
js/ast/expressions/intLiteral.js

@@ -10,6 +10,11 @@ export class IntLiteral extends Literal {
   }
   }
 
 
   toString() {
   toString() {
-    return convertToString(this.value, this.type);
+    const text = convertToString(this.value, this.type);
+    if(this.parenthesis) {
+      return `(${text})`;
+    } else {
+      return text;
+    }
   }
   }
 }
 }

+ 6 - 1
js/ast/expressions/realLiteral.js

@@ -10,6 +10,11 @@ export class RealLiteral extends Literal {
   }
   }
 
 
   toString() {
   toString() {
-    return convertToString(this.value, this.type);
+    const text = convertToString(this.value, this.type);
+    if (this.parenthesis) {
+      return `(${text})`;
+    } else {
+      return text;
+    }
   }
   }
 }
 }

+ 6 - 1
js/ast/expressions/stringLiteral.js

@@ -9,6 +9,11 @@ export class StringLiteral extends Literal {
   }
   }
 
 
   toString() {
   toString() {
-    return '"' + this.value + '"';
+    const text = '"' + this.value + '"';
+    if(this.parenthesis) {
+      return `(${text})`;
+    } else {
+      return text;
+    }
   }
   }
 }
 }

+ 5 - 1
js/ast/expressions/unaryApp.js

@@ -9,6 +9,10 @@ export class UnaryApp extends InfixApp {
   toString () {
   toString () {
     const l = this.left.toString();
     const l = this.left.toString();
     const op = this.op.value;
     const op = this.op.value;
-    return op + l;
+    if(this.parenthesis) {
+      return `(${op + l})`;
+    } else {
+      return op + l;
+    }
   }
   }
 }
 }

+ 5 - 1
js/ast/expressions/variableLiteral.js

@@ -9,6 +9,10 @@ export class VariableLiteral extends Literal {
   }
   }
 
 
   toString () {
   toString () {
-    return this.id;
+    if(this.parenthesis) {
+      return `(${this.id})`;
+    } else {
+      return this.id;
+    }
   }
   }
 }
 }

+ 2 - 2
js/ast/ivprogParser.js

@@ -1285,9 +1285,9 @@ export class IVProgParser {
     this.consumeNewLines();
     this.consumeNewLines();
     this.checkCloseParenthesis();
     this.checkCloseParenthesis();
     const tokenB = this.getToken();
     const tokenB = this.getToken();
-    const sourceInfo = SourceInfo.createSourceInfoFromList(tokenA, tokenB);
     this.pos += 1;
     this.pos += 1;
-    exp.sourceInfo = sourceInfo;
+    exp.sourceInfo = SourceInfo.createSourceInfoFromList(tokenA, tokenB);
+    exp.parenthesis = true;
     return exp;
     return exp;
   }
   }
 
 

+ 27 - 0
js/processor/error/processorErrorFactory.js

@@ -477,5 +477,32 @@ export const ProcessorErrorFactory  = Object.freeze({
     // SourceInfo have to be valid...
     // SourceInfo have to be valid...
     const context = [expected_num, source_info.line, exp, actual_num];
     const context = [expected_num, source_info.line, exp, actual_num];
     return createRuntimeError("invalid_number_lines_matrix", context);
     return createRuntimeError("invalid_number_lines_matrix", context);
+  },
+  divsion_by_zero_full: (exp, source_info) => {
+    if(source_info) {
+      const context = [source_info.line, exp];
+      return createRuntimeError("divsion_by_zero_full", context);
+    } else {
+      return ProcessorErrorFactory.divsion_by_zero(exp);
+    }
+  },
+  divsion_by_zero: (exp) => {
+    const context = [exp];
+    return createRuntimeError("divsion_by_zero", context);
+  },
+  undefined_tanget_value: (value, source_info) => {
+    const context = [source_info.line, value];
+    return createRuntimeError("undefined_tanget_value", context);
+  },
+  negative_log_value: (source_info) => {
+    return createRuntimeError("negative_log_value",[source_info.line]);
+  },
+  invalid_string_index: (index, str, source_info) => {
+    const local_fun_name = LanguageDefinedFunction.getLocalName("$charAt");
+    const context = [source_info.line, local_fun_name, index, str, str.length - 1];
+    return createRuntimeError("invalid_string_index", context);
+  },
+  negative_sqrt_value: (source_info) => {
+    return createRuntimeError("negative_sqrt_value",[source_info.line]);
   }
   }
 });
 });

+ 17 - 0
js/processor/ivprogProcessor.js

@@ -42,6 +42,10 @@ export class IVProgProcessor {
     this.forceKill = false;
     this.forceKill = false;
     this.loopTimers = [];
     this.loopTimers = [];
     this.output = null;
     this.output = null;
+    /**
+     * Stores the sourceInfo of every function call, command or expression
+     */
+    this.function_call_stack = [];
   }
   }
 
 
   registerInput (input) {
   registerInput (input) {
@@ -263,6 +267,10 @@ export class IVProgProcessor {
     } else {
     } else {
       func = this.findFunction(cmd.id);
       func = this.findFunction(cmd.id);
     }
     }
+    if(this.function_call_stack.length >= Config.max_call_stack) {
+      return Promise.reject(new Error("Número de chamadas recursivas execedeu o limite máximo definido!"));
+    }
+    this.function_call_stack.push(cmd.sourceInfo);
     return this.runFunction(func, cmd.actualParameters, store)
     return this.runFunction(func, cmd.actualParameters, store)
       .then(sto => {
       .then(sto => {
         sto.destroy();
         sto.destroy();
@@ -271,6 +279,7 @@ export class IVProgProcessor {
             LanguageDefinedFunction.getMainFunctionName() : func.name;
             LanguageDefinedFunction.getMainFunctionName() : func.name;
           return Promise.reject(ProcessorErrorFactory.function_no_return(funcName));
           return Promise.reject(ProcessorErrorFactory.function_no_return(funcName));
         } else {
         } else {
+          this.function_call_stack.pop();
           return store;
           return store;
         }
         }
       });
       });
@@ -711,6 +720,10 @@ export class IVProgProcessor {
     if(Types.VOID.isCompatible(func.returnType)) {
     if(Types.VOID.isCompatible(func.returnType)) {
       return Promise.reject(ProcessorErrorFactory.void_in_expression_full(exp.id, exp.sourceInfo));
       return Promise.reject(ProcessorErrorFactory.void_in_expression_full(exp.id, exp.sourceInfo));
     }
     }
+    if(this.function_call_stack.length >= Config.max_call_stack) {
+      return Promise.reject(new Error("Número de chamadas recursivas execedeu o limite máximo definido!"));
+    }
+    this.function_call_stack.push(exp.sourceInfo);
     const $newStore = this.runFunction(func, exp.actualParameters, store);
     const $newStore = this.runFunction(func, exp.actualParameters, store);
     return $newStore.then( sto => {
     return $newStore.then( sto => {
       if(sto.mode !== Modes.RETURN) {
       if(sto.mode !== Modes.RETURN) {
@@ -718,6 +731,7 @@ export class IVProgProcessor {
       }
       }
       const val = sto.applyStore('$');
       const val = sto.applyStore('$');
       sto.destroy();
       sto.destroy();
+      this.function_call_stack.pop();
       return Promise.resolve(val);
       return Promise.resolve(val);
     });
     });
   }
   }
@@ -926,6 +940,9 @@ export class IVProgProcessor {
           return new StoreValue(resultType, result);
           return new StoreValue(resultType, result);
         }
         }
         case Operators.DIV.ord: {
         case Operators.DIV.ord: {
+          if(right.get() == 0) {
+            return Promise.reject(ProcessorErrorFactory.divsion_by_zero_full(infixApp.toString(), infixApp.sourceInfo));
+          }
           if (Types.INTEGER.isCompatible(resultType))
           if (Types.INTEGER.isCompatible(resultType))
             result = left.get().divToInt(right.get());
             result = left.get().divToInt(right.get());
           else
           else

+ 9 - 7
js/processor/lib/math.js

@@ -6,6 +6,7 @@ import { MultiType } from '../../typeSystem/multiType';
 import { ArrayType } from '../../typeSystem/array_type';
 import { ArrayType } from '../../typeSystem/array_type';
 import { Modes } from '../modes';
 import { Modes } from '../modes';
 import { StoreValue } from '../store/value/store_value';
 import { StoreValue } from '../store/value/store_value';
+import { ProcessorErrorFactory } from '../error/processorErrorFactory';
 
 
 /**
 /**
  * sin
  * sin
@@ -79,12 +80,11 @@ export function createCosFun () {
 }
 }
 
 
 export function createTanFun () {
 export function createTanFun () {
-  const tanFun = (sto, _) => {
+  const tanFun = function (sto, _) {
     const x = sto.applyStore('x');
     const x = sto.applyStore('x');
     const angle = x.get().mod(360);
     const angle = x.get().mod(360);
     if(angle.eq(90) || angle.eq(270)) {
     if(angle.eq(90) || angle.eq(270)) {
-      // TODO better error message
-      return Promise.reject("Tangent of "+x.get().toNumber()+"° is undefined.");
+      return Promise.reject(ProcessorErrorFactory.undefined_tanget_value(x.get().toNumber(), this.function_call_stack.pop()));
     }
     }
     const result = Decimal.tan(convertToRadians(angle));
     const result = Decimal.tan(convertToRadians(angle));
     const temp = new StoreValue(Types.REAL, result);
     const temp = new StoreValue(Types.REAL, result);
@@ -101,8 +101,11 @@ export function createTanFun () {
 }
 }
 
 
 export function createSqrtFun () {
 export function createSqrtFun () {
-  const sqrtFun = (sto, _) => {
+  const sqrtFun = function (sto, _) {
     const x = sto.applyStore('x');
     const x = sto.applyStore('x');
+    if(x.get().isNeg()) {
+      return Promise.reject(ProcessorErrorFactory.negative_sqrt_value(this.function_call_stack.pop()));
+    }
     const result = x.get().sqrt();
     const result = x.get().sqrt();
     const temp = new StoreValue(Types.REAL, result);
     const temp = new StoreValue(Types.REAL, result);
     sto.insertStore("$", temp);
     sto.insertStore("$", temp);
@@ -137,11 +140,10 @@ export function createPowFun () {
 }
 }
 
 
 export function createLogFun () {
 export function createLogFun () {
-  const logFun = (sto, _) => {
+  const logFun = function (sto, _) {
     const x = sto.applyStore('x');
     const x = sto.applyStore('x');
     if (x.get().isNegative()) {
     if (x.get().isNegative()) {
-      // TODO better error message
-      return Promise.reject(new Error("the value passed to log function cannot be negative"));
+      return Promise.reject(ProcessorErrorFactory.negative_log_value(this.function_call_stack.pop()));
     }
     }
     const result = Decimal.log10(x.get());
     const result = Decimal.log10(x.get());
     const temp = new StoreValue(Types.REAL, result);
     const temp = new StoreValue(Types.REAL, result);

+ 3 - 3
js/processor/lib/strings.js

@@ -3,6 +3,7 @@ import { Types } from './../../typeSystem/types';
 import { toInt } from "./../../typeSystem/parsers";
 import { toInt } from "./../../typeSystem/parsers";
 import { Modes } from '../modes';
 import { Modes } from '../modes';
 import { StoreValue } from '../store/value/store_value';
 import { StoreValue } from '../store/value/store_value';
+import { ProcessorErrorFactory } from '../error/processorErrorFactory';
 
 
 /*
 /*
 *  substring
 *  substring
@@ -79,12 +80,11 @@ export function createLowercaseFun () {
 }
 }
 
 
 export function createrCharAtFun () {
 export function createrCharAtFun () {
-  const charAtFun = (sto, _) => {
+  const charAtFun = function (sto, _) {
     const str = sto.applyStore("str");
     const str = sto.applyStore("str");
     const idx = sto.applyStore("index");
     const idx = sto.applyStore("index");
     if (idx.get().toNumber() < 0 || idx.get().toNumber() >= str.get().length) {
     if (idx.get().toNumber() < 0 || idx.get().toNumber() >= str.get().length) {
-      // TODO better error message
-      return Promise.reject(new Error("invalid string position"));
+      return Promise.reject(ProcessorErrorFactory.invalid_string_index(idx.get().toNumber(), str.get(), this.function_call_stack.pop()));
     }
     }
     const temp = new StoreValue(Types.STRING, str.get().charAt(idx.get().toNumber()));
     const temp = new StoreValue(Types.STRING, str.get().charAt(idx.get().toNumber()));
     sto.insertStore("$", temp);
     sto.insertStore("$", temp);

+ 3 - 2
js/util/config.js

@@ -7,15 +7,16 @@ class ConfigObject {
     this.default_lang = 'pt';
     this.default_lang = 'pt';
     this.enable_type_casting = true;
     this.enable_type_casting = true;
     this.idle_input_interval = 5000;
     this.idle_input_interval = 5000;
+    this.max_call_stack = 100;
   }
   }
 
 
   setConfig (opts) {
   setConfig (opts) {
     for (const key in opts) {
     for (const key in opts) {
-      if(this.hasOwnProperty(key)){
+      if(Object.prototype.hasOwnProperty.call(this, key)){
         this[key] = opts[key];
         this[key] = opts[key];
       }
       }
     }
     }
   }
   }
 }
 }
-let config = new ConfigObject();
+const config = new ConfigObject();
 export const Config = config;
 export const Config = config;