Explorar o código

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 %!s(int64=4) %!d(string=hai) anos
pai
achega
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": "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_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) {
 			strColumn = this.column.toString();
 		}
+		let text = null;
 		if(strColumn) {
-			return `${this.id}[${strLine}][${strColumn}]`;
+			text = `${this.id}[${strLine}][${strColumn}]`;
 		} 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 () {
-    let element = this.value[0];
+    const element = this.value[0];
     if (element instanceof ArrayLiteral) {
       return element.value[0].type;
     } else {
@@ -21,7 +21,7 @@ export class ArrayLiteral extends Literal {
   }
 
   get columns () {
-    let element = this.value[0];
+    const element = this.value[0];
     if (!(element instanceof ArrayLiteral)){
       return null;
     } else {
@@ -38,7 +38,7 @@ export class ArrayLiteral extends Literal {
   }
 
   validateType () {
-    let valid = true;
+    const valid = true;
     // const subtype = this.subtype;
     // if(this.columns !== null) {
     //   const len = this.lines;
@@ -65,7 +65,7 @@ export class ArrayLiteral extends Literal {
   }
 
   validateSize () {
-    let valid = true;
+    const valid = true;
     // if(this.columns !== null) {
     //   const equalityTest = this.value.map(v => v.value.length)
     //     .reduce((old, next) => {
@@ -86,6 +86,11 @@ export class ArrayLiteral extends Literal {
 
   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 () {
-    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 {
 
   constructor () {
-    this._sourceInfo = null;
+		this._sourceInfo = null;
+		this._parenthesis = false;
   }
 
   set sourceInfo (sourceInfo) {
@@ -11,4 +12,12 @@ export class Expression {
 	get 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());
 			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 op = this.op.value;
     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() {
-    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() {
-    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() {
-    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 () {
     const l = this.left.toString();
     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 () {
-    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.checkCloseParenthesis();
     const tokenB = this.getToken();
-    const sourceInfo = SourceInfo.createSourceInfoFromList(tokenA, tokenB);
     this.pos += 1;
-    exp.sourceInfo = sourceInfo;
+    exp.sourceInfo = SourceInfo.createSourceInfoFromList(tokenA, tokenB);
+    exp.parenthesis = true;
     return exp;
   }
 

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

@@ -477,5 +477,32 @@ export const ProcessorErrorFactory  = Object.freeze({
     // SourceInfo have to be valid...
     const context = [expected_num, source_info.line, exp, actual_num];
     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.loopTimers = [];
     this.output = null;
+    /**
+     * Stores the sourceInfo of every function call, command or expression
+     */
+    this.function_call_stack = [];
   }
 
   registerInput (input) {
@@ -263,6 +267,10 @@ export class IVProgProcessor {
     } else {
       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)
       .then(sto => {
         sto.destroy();
@@ -271,6 +279,7 @@ export class IVProgProcessor {
             LanguageDefinedFunction.getMainFunctionName() : func.name;
           return Promise.reject(ProcessorErrorFactory.function_no_return(funcName));
         } else {
+          this.function_call_stack.pop();
           return store;
         }
       });
@@ -711,6 +720,10 @@ export class IVProgProcessor {
     if(Types.VOID.isCompatible(func.returnType)) {
       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);
     return $newStore.then( sto => {
       if(sto.mode !== Modes.RETURN) {
@@ -718,6 +731,7 @@ export class IVProgProcessor {
       }
       const val = sto.applyStore('$');
       sto.destroy();
+      this.function_call_stack.pop();
       return Promise.resolve(val);
     });
   }
@@ -926,6 +940,9 @@ export class IVProgProcessor {
           return new StoreValue(resultType, result);
         }
         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))
             result = left.get().divToInt(right.get());
           else

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

@@ -6,6 +6,7 @@ import { MultiType } from '../../typeSystem/multiType';
 import { ArrayType } from '../../typeSystem/array_type';
 import { Modes } from '../modes';
 import { StoreValue } from '../store/value/store_value';
+import { ProcessorErrorFactory } from '../error/processorErrorFactory';
 
 /**
  * sin
@@ -79,12 +80,11 @@ export function createCosFun () {
 }
 
 export function createTanFun () {
-  const tanFun = (sto, _) => {
+  const tanFun = function (sto, _) {
     const x = sto.applyStore('x');
     const angle = x.get().mod(360);
     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 temp = new StoreValue(Types.REAL, result);
@@ -101,8 +101,11 @@ export function createTanFun () {
 }
 
 export function createSqrtFun () {
-  const sqrtFun = (sto, _) => {
+  const sqrtFun = function (sto, _) {
     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 temp = new StoreValue(Types.REAL, result);
     sto.insertStore("$", temp);
@@ -137,11 +140,10 @@ export function createPowFun () {
 }
 
 export function createLogFun () {
-  const logFun = (sto, _) => {
+  const logFun = function (sto, _) {
     const x = sto.applyStore('x');
     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 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 { Modes } from '../modes';
 import { StoreValue } from '../store/value/store_value';
+import { ProcessorErrorFactory } from '../error/processorErrorFactory';
 
 /*
 *  substring
@@ -79,12 +80,11 @@ export function createLowercaseFun () {
 }
 
 export function createrCharAtFun () {
-  const charAtFun = (sto, _) => {
+  const charAtFun = function (sto, _) {
     const str = sto.applyStore("str");
     const idx = sto.applyStore("index");
     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()));
     sto.insertStore("$", temp);

+ 3 - 2
js/util/config.js

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