瀏覽代碼

Merge branch 'improv-execution' of LInE/ivprog into master

Lucas de Souza 5 年之前
父節點
當前提交
3f1ab88a54

+ 5 - 0
i18n/csv.d.ts

@@ -0,0 +1,5 @@
+declare module "*.csv" {
+  const content: {[id:string]:any};
+  export default content;
+}
+

+ 2 - 1
i18n/error.csv

@@ -132,4 +132,5 @@ exceeded_recursive_calls,Erro na execução da linha $0: Número de chamadas rec
 invalid_for_variable,Erro na linha $0: A variavel $1 utilizada no comando repita_para deve ser do tipo inteiro.,Error at line $0: The variable $1 used in the repeat_for command must be of type int.,
 invalid_for_from,Erro na linha $0: O valor $1 passado para o parâmetro 'de' do comando repita_para deve ser do tipo inteiro.,Error at line $0: The value $1 passed to the parameter 'from' of the repeat_for command must be of type int.,
 invalid_for_to,Erro na linha $0: O valor $1 passado para o parâmetro 'para' do comando repita_para deve ser do tipo inteiro.,Error at line $0: The value $1 passed to the parameter 'to' of the repeat_for command must be of type int.,
-invalid_for_pass,Erro na linha $0: O valor $1 passado para o parâmetro 'passo' do comando repita_para deve ser do tipo inteiro.,Error at line $0: The value $1 passed to the parameter 'pass' of the repeat_for command must be of type int.,
+invalid_for_pass,Erro na linha $0: O valor $1 passado para o parâmetro 'passo' do comando repita_para deve ser do tipo inteiro.,Error at line $0: The value $1 passed to the parameter 'pass' of the repeat_for command must be of type int.,
+exceed_max_instructions,Número de instruções excedeu o limite definido. Certifique-se que seu código não possui laços infinitos ou muitas chamadas de funções recursivas.,The number of instructions executed by your program exceed the defined limit. Check your code for infinite loops or excessive recursive functions calls.,

+ 0 - 14
i18n/index.js

@@ -1,14 +0,0 @@
-import Messages from './message.csv';
-import UI from './ui.csv';
-import Errors from './error.csv';
-
-const i18n_data = {};
-for(const key in UI) {
-  const data = {};
-  data['error'] = Errors[key];
-  data['message'] = Messages[key];
-  data['ui'] = UI[key];
-  i18n_data[key] = data;
-}
-
-export default i18n_data;

+ 21 - 0
i18n/index.ts

@@ -0,0 +1,21 @@
+/// <reference path="csv.d.ts" />
+import * as Messages from './message.csv';
+import * as UI from './ui.csv';
+import * as Errors from './error.csv';
+
+type Dict =  {[id:string]:any};
+
+const MessagesObj = Messages as Dict;
+const UIObj = UI as Dict;
+const ErrorsObj = Errors as Dict;
+const i18n_data = {} as Dict;
+
+for(const key in UIObj) {
+  const data = {} as Dict;
+  data['error'] = ErrorsObj[key];
+  data['message'] = MessagesObj[key];
+  data['ui'] = UIObj[key];
+  i18n_data[key] = data;
+}
+
+export default i18n_data;

+ 3 - 1
i18n/message.csv

@@ -7,4 +7,6 @@ assessment-empty-expected-tooltip,A saída gerada foi além do esperado,The gene
 assessment-empty-generated-tooltip,O programa não gerou saídas suficientes,The program did not generated enough outputs," "
 testcase_autogen_unused_input,O caso de teste $0 possui mais entradas do que as leituras feitas no programa.,The test case $0 has more inputs than output than the number of reads present in the algorithm.," "
 testcase_autogen_empty,O caso de teste $0 não gerou qualquer saída.,The test case $0 did not generate any output.," "
-success_execution,Programa executado com sucesso!,Program executed successfully!,
+success_execution,Programa executado com sucesso!,Program executed successfully!,
+aborted_execution,A execução do programa foi interrompida!,Program execution was aborted!,
+unexpected_execution_error,Erro inesperado durante a execução do programa.,Unexpected error during program execution.,

+ 5 - 5
i18n/ui.csv

@@ -20,8 +20,8 @@ program,programa,program,
 type_text,cadeia,string,
 textvar_default_value,texto,text,
 type_boolean,logico,bool,
-logic_value_true,1,1,
-logic_value_false,0,0,
+logic_value_true,verdadeiro,true,
+logic_value_false,falso,false,
 variable,Variável,Variable,
 command,Comando,Command,
 new_parameter,parametro,parameter,
@@ -45,7 +45,7 @@ vector_info_string,vetor de $0,vector of $0,
 text_attribution,Atribuição,Assignment,
 text_if,se,if,
 text_break,pare,break,
-text_else,senao,eles,
+text_else,senao,else,
 text_for,repita_para,repeat_for,
 text_code_while,repita_enquanto,repeat_while,
 text_code_do,repita,repeat,
@@ -112,7 +112,7 @@ var_menu_select_function,Selecione uma função,Select a function,
 expression_menu_select,Construir uma expressão lógica,Construct a logic expression,
 inform_valid_content,Informe o conteúdo!,Provide some data,
 inform_valid_expression,Construa uma expressão lógica!,Construct a logic expression,
-tooltip_terminal_clear,Limpa o terminal removendo todos os textos,Clears the terminal by removing all text,
+tooltip_terminal_clear,Limpa o terminal removendo todos os textos já escritos e os pendentes.,Clears the terminal by removing all wrriten and pending text.,
 tooltip_terminal_show,Exibe o terminal caso esteja escondido,Shows the terminal if it is hidden,
 tooltip_terminal_hide,Esconde o terminal caso não esteja escondido,Hides the terminal if it is not on display,
 text_ivprog_version,Versão,Version,
@@ -136,4 +136,4 @@ text_code_for_pass,passo,pass,
 text_for_from,de,from,
 text_for_to,até,to,
 text_for_pass,passo,pass,
-text_relational_expression,Relacionais,Relational,
+text_relational_expression,Relacionais,Relational,

+ 22 - 13
js/assessment/ivprogAssessment.js

@@ -3,63 +3,72 @@ import { IVProgProcessor } from "./../processor/ivprogProcessor";
 import { DOMConsole} from "./../io/domConsole";
 import * as LocalizedStringsService from "../services/localizedStringsService";
 import { OutputMatching } from './output_matching/output_matching';
+import { Config } from '../util/config';
 
 
 const LocalizedStrings = LocalizedStringsService.getInstance();
 
 const StringTypes = line_i18n.StringTypes;
 
+const AssessmentConfig = {
+  max_instruction_count: 350250,
+  suspend_threshold: 200
+}
+
 export class IVProgAssessment {
 
   constructor (ast_code, testCases, domConsole) {
     this.ast_code = ast_code;
     this.testCases = testCases;
     this.domConsole = domConsole;
+    this.old_config = JSON.parse(JSON.stringify(Config));
+    Config.setConfig(AssessmentConfig);
   }
 
   runTest () {
-    const outerRef = this;
     try {
       // loop test cases and show messages through domconsole
       const partialTests = this.testCases.map( (t, name) => {
-        return new OutputMatching(new IVProgProcessor(outerRef.ast_code), t.input, t.output, name);
+        return new OutputMatching(new IVProgProcessor(this.ast_code), t.input, t.output, name);
       });
       const testResult = partialTests.map(om => om.eval());
       return Promise.all(testResult).then(results => {
         let grade = 0;
-        for(let i = 0; i < results.length; ++i) {
+        for(let i = 0; i < results.length; i += 1) {
           const result = results[i];
           grade += result.grade;
           if(result.grade == 1) {
-            outerRef.writeToConsole(DOMConsole.INFO, StringTypes.MESSAGE,'test_case_success',
+            this.writeToConsole(DOMConsole.INFO, StringTypes.MESSAGE,'test_case_success',
               result.name + 1, result.generateOutput());
           } else if (result.status == 1) {
-            outerRef.writeToConsole(DOMConsole.ERR, StringTypes.ERROR,'test_case_failed_exception',
+            this.writeToConsole(DOMConsole.ERR, StringTypes.ERROR,'test_case_failed_exception',
               result.name + 1, result.error_msg, result.generateOutput());
           } else {
-            outerRef.writeToConsole(DOMConsole.ERR, StringTypes.ERROR,'test_case_failed',
+            this.writeToConsole(DOMConsole.ERR, StringTypes.ERROR,'test_case_failed',
               result.name + 1, result.generateOutput());
           }
         }
         grade /= results.length;
         const channel = grade == 1 ? DOMConsole.INFO : DOMConsole.ERR;
-        outerRef.writeToConsole(channel, StringTypes.MESSAGE, "test_suite_grade", (grade * 100).toFixed(2));
+        this.writeToConsole(channel, StringTypes.MESSAGE, "test_suite_grade", (grade * 100).toFixed(2));
         return grade;
       });
       // return testResult.then(function (total) {
-      //   const grade = total / outerRef.testCases.length;
+      //   const grade = total / this.testCases.length;
       //   const channel = grade == 1 ? DOMConsole.INFO : DOMConsole.ERR;
-      //   outerRef.writeToConsole(channel, StringTypes.MESSAGE, "test_suite_grade", (grade * 100).toFixed(2));
+      //   this.writeToConsole(channel, StringTypes.MESSAGE, "test_suite_grade", (grade * 100).toFixed(2));
       //   return Promise.resolve(grade)
       // }).catch(err => {
-      //   outerRef.domConsole.err("Erro inesperado durante o cálculo da nota.");// try and show error messages through domconsole
-      //   outerRef.domConsole.err(err.message);
+      //   this.domConsole.err("Erro inesperado durante o cálculo da nota.");// try and show error messages through domconsole
+      //   this.domConsole.err(err.message);
       //   return Promise.resolve(0);
       // });
     } catch (error) {
-      outerRef.domConsole.err("Erro inesperado durante a execução do programa");// try and show error messages through domconsole
-      outerRef.domConsole.err(error.message);
+      this.showErrorMessage(DOMConsole.ERR, StringTypes.MESSAGE, "unexpected_execution_error");
+      this.domConsole.err(error.message);
       return Promise.resolve(0);
+    } finally {
+      Config.setConfig(this.old_config);
     }
   }
 

+ 4 - 3
js/assessment/output_matching/output_matching.js

@@ -82,14 +82,14 @@ export class OutputMatching {
   outputMatch (g_output, e_output) {
     if(OutputMatching.NUM_REGEX.test(e_output)) {
       if(!OutputMatching.NUM_REGEX.test(g_output)) {
-        return OutputResult.createNumberResult(e_output, g_output, 0);
+        return this.checkStrings(g_output, e_output);
       }
       const g_num = new Decimal(g_output);
       const e_num = new Decimal(e_output);
       return this.checkNumbers(g_num, e_num);
     } else if (OutputMatching.BOOLEAN_REGEX.test(e_output)) {
       if (!OutputMatching.BOOLEAN_REGEX.test(g_output)) {
-        return OutputResult.createBoolResult(e_output, g_output, 0);
+        return this.checkStrings(g_output, e_output)
       }
       const g_bool = TypeParser.toBool(g_output);
       const e_bool = TypeParser.toBool(e_output);
@@ -162,7 +162,8 @@ export class OutputMatching {
       assessmentList.push(boolGrade);
     }
     const dist = levenshteinDistance(g_output_clean, e_output_clean);
-    const gradeDiff = Math.max(0, e_output_clean.length - dist)/e_output_clean.length;
+    let gradeDiff = Math.max(0, e_output_clean.length - dist)/e_output_clean.length;
+    gradeDiff = Number.isNaN(gradeDiff) ? 0 : gradeDiff;
     const assessment_size = assessmentList.length + 1;
     const gradeAcc = assessmentList.reduce((prev, val) => prev + val/assessment_size, 0);
     const finalGrade = 1 * (gradeDiff/assessment_size + gradeAcc);

+ 32 - 4
js/iassign-integration-functions.js

@@ -161,7 +161,11 @@ function getiLMContent () {
 function prepareActivityToEdit (ilm_cont) {
   //var content = JSON.parse(ilm_cont.split('\n::algorithm::')[0]);
   // Ver arquivo js/util/iassignHelpers.js
-  var content = ivprogCore.prepareActivityToStudentHelper(ilm_cont);
+  var content = ivprogCore.prepareActivityToStudentHelper(ilm_cont).getOrElse(null);
+  if(!content) {
+    showInvalidData();
+    return;
+  }
   var testCases = ivprogCore.getTestCases();
 
   settingsProgrammingTypes = content.settingsProgrammingType;
@@ -244,7 +248,11 @@ function includePreviousAlgorithm () {
 
 function prepareActivityToStudent (ilm_cont) {
     // Ver arquivo js/util/iassignHelpers.js
-    var content = ivprogCore.prepareActivityToStudentHelper(ilm_cont);
+    var content = ivprogCore.prepareActivityToStudentHelper(ilm_cont).getOrElse(null);
+    if(!content) {
+      showInvalidData();
+      return;
+    }
     // Casos de testes agora são delegados ao tratamento apropriado pela função acima
     // var testCases = content.testcases;
     settingsProgrammingTypes = content.settingsProgrammingType;
@@ -723,9 +731,14 @@ function full_screen() {
 }
 
 function teacherAutoEval (data) {
+  previousContent = data;
   $.get(iLMparameters.iLM_PARAM_TeacherAutoEval, function (originalData) {
     // Ver arquivo js/util/iassignHelpers.js
-    var content = ivprogCore.prepareActivityToStudentHelper(data);
+    var content = ivprogCore.prepareActivityToStudentHelper(data).getOrElse(null);
+    if(!content) {
+      showInvalidData();
+      return;
+    }
     // Casos de testes agora são delegados ao tratamento apropriado pela função acima
     // var testCases = content.testcases;
     settingsProgrammingTypes = content.settingsProgrammingType; 
@@ -737,7 +750,7 @@ function teacherAutoEval (data) {
     if (content.algorithmInIlm != null) {
       algorithm_in_ilm = content.algorithmInIlm;
       parsePreviousAlgorithm();
-      ivprogCore.autoEval(originalData, parent.getEvaluationCallback);
+      ivprogCore.autoEval(originalData, displayGrade);
     }
 
     ivprogTextualOrVisual();
@@ -746,4 +759,19 @@ function teacherAutoEval (data) {
       blockAllEditingOptions();
     }
   });
+}
+
+function displayGrade(grade) {
+  alert(grade);
+}
+
+function showInvalidData () {
+  $('.ui.height_100.add_accordion').dimmer({
+    closable: false
+  });
+  $('.dimmer_content_message h3').html(LocalizedStrings.getUI('text_message_error_activity_file'));
+  $('.dimmer_content_message button').text(LocalizedStrings.getUI('text_message_error_activity_reload'));
+  $('.dimmer_content_message').css('display', 'block');
+  $('.ui.height_100.add_accordion').dimmer('add content', '.dimmer_content_message');
+  $('.ui.height_100.add_accordion').dimmer('show');
 }

+ 50 - 14
js/io/domConsole.js

@@ -62,7 +62,8 @@ export class DOMConsole {
     this.setup();
     this.inputListeners = [];
     this.hideInput();
-    this.was_waiting_input = false;
+    this.pending_writes = [];
+    this.last_clear = -1;
   }
 
   setup () {
@@ -139,12 +140,11 @@ export class DOMConsole {
       return;
     }
     this.input.focus();
-    const that = this;
-    this.cursorInterval = window.setInterval(function() {
-      if (that.cursorRef.style.visibility === 'visible') {
-        that.cursorRef.style.visibility = 'hidden';
+    this.cursorInterval = window.setInterval(() => {
+      if (this.cursorRef.style.visibility === 'visible') {
+        this.cursorRef.style.visibility = 'hidden';
       } else {
-        that.cursorRef.style.visibility = 'visible';
+        this.cursorRef.style.visibility = 'visible';
       }
     }, 500);
   }
@@ -182,23 +182,37 @@ export class DOMConsole {
   }
 
   _appendText (text, type) {
-    setTimeout(() => {
+    const write_time = Date.now();
+    const pending_write = setTimeout(() => {
+      this.pending_writes.shift();
+      if(this.last_clear >= write_time) {
+        return;
+      }
       const divClass = this.getClassForType(type);
       const textDiv = document.createElement('div');
       textDiv.classList.add(divClass);
       textDiv.innerHTML = this.getOutputText(text);
       this.termDiv.insertBefore(textDiv, this.inputDiv);
       this.scrollTerm();
-    }, 50)
+    }, 5);
+    this.pending_writes.push(pending_write);
   }
 
   _appendUserInput (text) {
-    const divClass = this.getClassForType(DOMConsole.INPUT);
-    const textDiv = document.createElement('div');
-    textDiv.innerHTML = this.getUserInputText(text);
-    textDiv.classList.add(divClass);
-    this.termDiv.insertBefore(textDiv, this.inputDiv);
-    this.scrollTerm();
+    const write_time = Date.now();
+    const pending_write = setTimeout(() => {
+      this.pending_writes.shift();
+      if(this.last_clear >= write_time) {
+        return;
+      }
+      const divClass = this.getClassForType(DOMConsole.INPUT);
+      const textDiv = document.createElement('div');
+      textDiv.innerHTML = this.getUserInputText(text);
+      textDiv.classList.add(divClass);
+      this.termDiv.insertBefore(textDiv, this.inputDiv);
+      this.scrollTerm();
+    }, 5);
+    this.pending_writes.push(pending_write);
   }
 
   getOutputText (text) {
@@ -320,7 +334,16 @@ export class DOMConsole {
     });
   }
 
+  clearPendingWrites () {
+    this.last_clear = Date.now();
+    for(const id in this.pending_writes) {
+      clearTimeout(id);
+    }
+  }
+
   clear () {
+    this.clearPendingWrites();
+    this.pending_writes = [];
     while(this.inputDiv.parentElement.childNodes.length > 1) {
       this.inputDiv.parentElement.removeChild(this.inputDiv.parentElement.firstChild);
     }
@@ -348,4 +371,17 @@ export class DOMConsole {
   scheduleNotify () {
     this.idleInterval = window.setInterval(this.notifyIdle.bind(this), Config.idle_input_interval);
   }
+
+  cancelPendingInputRequests () {
+    this.inputListeners.forEach(resolve => resolve(''));
+    this.inputListeners.splice(0, this.inputListeners.length);
+    if(this.idleInterval != null) {
+      clearInterval(this.idleInterval);
+      this.idleInterval = null;
+    }
+    this.input.value = '';
+    this.inputSpan.innerHTML = '';
+    this.hideInput();
+    this.anyKey = false;
+  }
 }

+ 6 - 3
js/io/domInput.js

@@ -33,9 +33,12 @@ export class DOMInput extends Input{
   }
 
   notifyInput (text) {
-    this.listeners.forEach(resolve => {
-      resolve(text);
-    })
+    this.listeners.forEach(resolve => resolve(text));
+    this.listeners.splice(0, this.listeners.length);
+  }
+
+  cancelPendingInputRequests () {
+    this.listeners.forEach(resolve => resolve(''));
     this.listeners.splice(0, this.listeners.length);
   }
 

+ 4 - 0
js/io/input.js

@@ -3,4 +3,8 @@ export class Input {
   requestInput (callback) {
     throw new Error("Must be implemented");
   }
+
+  cancelPendingInputRequests () {
+    throw new Error("Must be implemented");
+  }
 }

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

@@ -102,6 +102,9 @@ export const ProcessorErrorFactory  = Object.freeze({
     const context = [exp];
     return createSemanticError("loop_condition_type", context);
   },
+  /**
+   * @deprecated 01/10/2019
+   */
   endless_loop_full: (sourceInfo) => {
     if(sourceInfo) {
       const context = [sourceInfo.line];
@@ -493,6 +496,9 @@ export const ProcessorErrorFactory  = Object.freeze({
   negative_sqrt_value: (source_info) => {
     return createRuntimeError("negative_sqrt_value",[source_info.line]);
   },
+  /**
+   * @deprecated 01/10/2019
+   */
   exceeded_recursive_calls: (source_info) => {
     const context = [source_info.line];
     return createRuntimeError("exceeded_recursive_calls", context);
@@ -512,5 +518,8 @@ export const ProcessorErrorFactory  = Object.freeze({
   invalid_for_pass: (exp, source_info) => {
     const context = [source_info.line, exp];
     return createSemanticError("invalid_for_pass", context);
+  },
+  exceed_max_instructions: () => {
+    return createRuntimeError('exceed_max_instructions');
   }
 });

+ 118 - 101
js/processor/ivprogProcessor.js

@@ -18,17 +18,10 @@ import { StoreValueRef } from './store/value/store_value_ref';
 import { ArrayStoreValue } from './store/value/array_store_value';
 import { ArrayStoreValueRef } from './store/value/array_store_value_ref';
 import { StoreValueAddress } from './store/value/store_value_address';
+import { LocalizedStrings } from '../services/localizedStringsService';
 
 export class IVProgProcessor {
 
-  static get LOOP_TIMEOUT () {
-    return Config.loopTimeout;
-  }
-
-  static set LOOP_TIMEOUT (ms) {
-    Config.setConfig({loopTimeout: ms});
-  }
-
   static get MAIN_INTERNAL_ID () {
     return "$main";
   }
@@ -40,12 +33,14 @@ export class IVProgProcessor {
     this.context = [Context.BASE];
     this.input = null;
     this.forceKill = false;
-    this.loopTimers = [];
     this.output = null;
+    this.mode = Modes.RUN;
     /**
      * Stores the sourceInfo of every function call, command or expression
      */
     this.function_call_stack = [];
+    this.instruction_count = 0;
+    this.function_call_count = 0;
   }
 
   registerInput (input) {
@@ -86,6 +81,8 @@ export class IVProgProcessor {
     this.globalStore = new Store("$global");
     this.stores = [this.globalStore];
     this.context = [Context.BASE];
+    this.instruction_count = 0;
+    this.mode = Modes.RUN;
   }
 
   interpretAST () {
@@ -134,17 +131,24 @@ export class IVProgProcessor {
     const funcName = func.isMain ? IVProgProcessor.MAIN_INTERNAL_ID : func.name;
     const funcStore = new Store(funcName);
     funcStore.extendStore(this.globalStore);
-    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;
-        });
+    return new Promise((resolve, reject) => {
+      const run_lambda = () => {
+        const newFuncStore$ = this.associateParameters(func.formalParameters, actualParameters, store, funcStore);
+        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;
+            });
+        }).then(resolve)
+        .catch(reject);
+      }
+      run_lambda();
     });
+    
   }
 
   associateParameters (formal_params, effective_params, caller_store, callee_store) {
@@ -220,42 +224,57 @@ export class IVProgProcessor {
   }
 
   executeCommand (store, cmd) {
-    if(this.forceKill) {
-      return Promise.reject("FORCED_KILL!");
-    } else if (store.mode === Modes.PAUSE) {
-      return Promise.resolve(this.executeCommand(store, cmd));
-    } else 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.ArrayIndexAssign) {
-      return this.executeArrayIndexAssign(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.RepeatUntil) {
-      return this.executeRepeatUntil(store, cmd);
-    } else if (cmd instanceof Commands.While) {
-      return this.executeWhile(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 Expressions.FunctionCall) {
-      return this.executeFunctionCall(store, cmd);     
-    } else if (cmd instanceof Commands.SysCall) {
-      return this.executeSysCall(store, cmd);
-    } else {
-      return Promise.reject(ProcessorErrorFactory.unknown_command(cmd.sourceInfo))
-    }
+    this.instruction_count += 1;
+    return new Promise((resolve, reject) => {
+      const command_lambda = () => {
+        if(this.instruction_count >= Config.max_instruction_count) {
+          return reject(ProcessorErrorFactory.exceed_max_instructions());
+        } else if(this.forceKill) {
+          return reject("FORCED_KILL!");
+        } else if (store.mode === Modes.PAUSE) {
+          return resolve(this.executeCommand(store, cmd));
+        } else if(store.mode === Modes.RETURN) {
+          return resolve(store);
+        } else if(this.checkContext(Context.BREAKABLE) && store.mode === Modes.BREAK) {
+          return resolve(store);
+        } else if (this.mode === Modes.ABORT) {
+          return reject(LocalizedStrings.getMessage('aborted_execution'));
+        }
+        if (cmd instanceof Commands.Declaration) {
+          return resolve(this.executeDeclaration(store, cmd));
+        } else if (cmd instanceof Commands.ArrayIndexAssign) {
+          return resolve(this.executeArrayIndexAssign(store, cmd));
+        } else if (cmd instanceof Commands.Assign) {
+          return resolve(this.executeAssign(store, cmd));
+        } else if (cmd instanceof Commands.Break) {
+          return resolve(this.executeBreak(store, cmd));
+        } else if (cmd instanceof Commands.Return) {
+          return resolve(this.executeReturn(store, cmd));
+        } else if (cmd instanceof Commands.IfThenElse) {
+          return resolve(this.executeIfThenElse(store, cmd));
+        } else if (cmd instanceof Commands.RepeatUntil) {
+          return resolve(this.executeRepeatUntil(store, cmd));
+        } else if (cmd instanceof Commands.While) {
+          return resolve(this.executeWhile(store, cmd));
+        } else if (cmd instanceof Commands.For) {
+          return resolve(this.executeFor(store, cmd));
+        } else if (cmd instanceof Commands.Switch) {
+          return resolve(this.executeSwitch(store, cmd));
+        } else if (cmd instanceof Expressions.FunctionCall) {
+          return resolve(this.executeFunctionCall(store, cmd));     
+        } else if (cmd instanceof Commands.SysCall) {
+          return resolve(this.executeSysCall(store, cmd));
+        } else {
+          return reject(ProcessorErrorFactory.unknown_command(cmd.sourceInfo))
+        }
+      };
+      if(this.instruction_count % Config.suspend_threshold == 0) {
+        //every 100th command should briefly delay its execution in order to allow the browser to process other things
+        setTimeout(command_lambda, 5);
+      } else {
+        command_lambda();
+      }
+    })
   }
 
   executeSysCall (store, cmd) {
@@ -270,9 +289,9 @@ export class IVProgProcessor {
     } else {
       func = this.findFunction(cmd.id);
     }
-    if(this.function_call_stack.length >= Config.max_call_stack) {
-      return Promise.reject(ProcessorErrorFactory.exceeded_recursive_calls(cmd.sourceInfo));
-    }
+    // if(this.function_call_stack.length >= Config.max_call_stack) {
+    //   return Promise.reject(ProcessorErrorFactory.exceeded_recursive_calls(cmd.sourceInfo));
+    // }
     this.function_call_stack.push(cmd.sourceInfo);
     return this.runFunction(func, cmd.actualParameters, store)
       .then(sto => {
@@ -364,14 +383,12 @@ export class IVProgProcessor {
 
   executeRepeatUntil (store, cmd) {
     try {
-      this.loopTimers.push(Date.now());
       this.context.push(Context.BREAKABLE);
       const $newStore = this.executeCommands(store, cmd.commands);
       return $newStore.then(sto => {
         if(sto.mode === Modes.BREAK) {
           this.context.pop();
           sto.mode = Modes.RUN;
-          this.loopTimers.pop();
           return sto;
         }
         const $value = this.evaluateExpression(sto, cmd.expression);
@@ -381,17 +398,9 @@ export class IVProgProcessor {
           }
           if (!vl.get()) {
             this.context.pop();
-            if(this.loopTimers.length > 0) {
-              const time = this.loopTimers[0];
-              if(Date.now() - time >= IVProgProcessor.LOOP_TIMEOUT) {
-                this.forceKill = true;
-                return Promise.reject(ProcessorErrorFactory.endless_loop_full(cmd.sourceInfo));
-              }
-            }
             return this.executeCommand(sto, cmd);
           } else {
             this.context.pop();
-            this.loopTimers.pop();
             return sto;
           }
         })
@@ -403,7 +412,6 @@ export class IVProgProcessor {
 
   executeWhile (store, cmd) {
     try {
-      this.loopTimers.push(Date.now());
       this.context.push(Context.BREAKABLE);
       const $value = this.evaluateExpression(store, cmd.expression);
       return $value.then(vl => {
@@ -413,22 +421,13 @@ export class IVProgProcessor {
             return $newStore.then(sto => {
               this.context.pop();
               if (sto.mode === Modes.BREAK) {
-                this.loopTimers.pop();
                 sto.mode = Modes.RUN;
                 return sto;
               }
-              if (this.loopTimers.length > 0) {
-                const time = this.loopTimers[0];
-                if(Date.now() - time >= IVProgProcessor.LOOP_TIMEOUT) {
-                  this.forceKill = true;
-                  return Promise.reject(ProcessorErrorFactory.endless_loop_full(cmd.sourceInfo));
-                }
-              }
               return this.executeCommand(sto, cmd);
             });
           } else {
             this.context.pop();
-            this.loopTimers.pop();
             return store;
           }
         } else {
@@ -470,7 +469,7 @@ export class IVProgProcessor {
     try {
       const funcName = store.name === IVProgProcessor.MAIN_INTERNAL_ID ? 
         LanguageDefinedFunction.getMainFunctionName() : store.name;
-      console.log(funcName,  store.name === IVProgProcessor.MAIN_INTERNAL_ID);
+      // console.log(funcName,  store.name === IVProgProcessor.MAIN_INTERNAL_ID);
       const func = this.findFunction(store.name);
       const funcType = func.returnType;
       const $value = this.evaluateExpression(store, cmd.expression);
@@ -714,28 +713,46 @@ export class IVProgProcessor {
   }
 
   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 Promise.reject(new Error("Internal Error: The system should not eval an array literal."))
-    } else if (exp instanceof Expressions.FunctionCall) {
-      return this.evaluateFunctionCall(store, exp);
-    }
-    return Promise.resolve(null);
+    this.instruction_count += 1;
+    return new Promise((resolve, reject) => {
+      const expression_lambda = () => {
+        if (this.mode === Modes.ABORT) {
+          return reject(LocalizedStrings.getMessage('aborted_execution'));
+        }
+        if(this.instruction_count >= Config.max_instruction_count) {
+          return reject(new Error("Número de instruções excedeu o limite definido. Verifique se seu código não possui laços infinitos ou muitas chamadas de funções recursivas."))
+        }
+        if (exp instanceof Expressions.UnaryApp) {
+          return resolve(this.evaluateUnaryApp(store, exp));
+        } else if (exp instanceof Expressions.InfixApp) {
+          return resolve(this.evaluateInfixApp(store, exp));
+        } else if (exp instanceof Expressions.ArrayAccess) {
+          return resolve(this.evaluateArrayAccess(store, exp));
+        } else if (exp instanceof Expressions.VariableLiteral) {
+          return resolve(this.evaluateVariableLiteral(store, exp));
+        } else if (exp instanceof Expressions.IntLiteral) {
+          return resolve(this.evaluateLiteral(store, exp));
+        } else if (exp instanceof Expressions.RealLiteral) {
+          return resolve(this.evaluateLiteral(store, exp));
+        } else if (exp instanceof Expressions.BoolLiteral) {
+          return resolve(this.evaluateLiteral(store, exp));
+        } else if (exp instanceof Expressions.StringLiteral) {
+          return resolve(this.evaluateLiteral(store, exp));
+        } else if (exp instanceof Expressions.ArrayLiteral) {
+          return reject(new Error("Internal Error: The system should not eval an array literal."))
+        } else if (exp instanceof Expressions.FunctionCall) {
+          return resolve(this.evaluateFunctionCall(store, exp));
+        }
+        return resolve(null);
+      };
+      if(this.instruction_count % Config.suspend_threshold == 0) {
+        //every 100th command should briefly delay its execution in order to allow the browser to process other things
+        setTimeout(expression_lambda, 5);
+      } else {
+        expression_lambda();
+      }
+    });
+    
   }
 
   evaluateFunctionCall (store, exp) {

+ 4 - 1
js/processor/lib/io.js

@@ -45,6 +45,10 @@ export function createInputFun () {
           return Promise.reject(new Error("!!!!Critical error: Unknown type in readFunction!!!!"));
         }
       } catch (_) {
+        if(this.mode == Modes.ABORT) {
+          store.mode = Modes.RETURN;
+          return Promise.resolve(store);
+        }
         const stringInfo = typeToConvert.stringInfo()[0]
         const realObject = store.getStoreObject("p1");
         if (realObject.getReferenceDimension() > 0) {
@@ -56,7 +60,6 @@ export function createInputFun () {
         const error = ProcessorErrorFactory.invalid_read_type(text, stringInfo.type, stringInfo.dim, realObject.getRefObj(), this.function_call_stack.pop());
         return Promise.reject(error);
       }
-      this.loopTimers.splice(0, this.loopTimers.length)
       const stoValue = new StoreValue(type, result);
       store.updateStore('p1', stoValue);
       store.mode = Modes.RETURN;

+ 2 - 1
js/processor/modes.ts

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

+ 3 - 2
js/util/config.js

@@ -1,13 +1,14 @@
 class ConfigObject {
 
   constructor () {
-    this.loopTimeout = 5000;
     this.decimalPlaces = 8;
     this.intConvertRoundMode = 2;
     this.default_lang = 'pt';
     this.enable_type_casting = true;
     this.idle_input_interval = 5000;
-    this.max_call_stack = 100;
+    this.suspend_threshold = 100;
+    // this.max_instruction_count = 350250; - automated evaluation limit
+    this.max_instruction_count = Number.MAX_SAFE_INTEGER;
   }
 
   setConfig (opts) {

+ 50 - 47
js/util/iassignHelpers.js

@@ -3,7 +3,8 @@ import { generate } from "../visualUI/code_generator";
 import { IVProgAssessment } from "../assessment/ivprogAssessment";
 import { TestConsole } from "./testConsole";
 import { parseLogs } from "./../services/userLog";
-import { LocalizedStrings } from './../services/localizedStringsService';
+import { SemanticAnalyser } from "../processor/semantic/semanticAnalyser";
+import { Maybe } from "./maybe";
 
 function parseActivityData (data) {
   let algorithm_in_ilm = null;
@@ -19,56 +20,45 @@ function parseActivityData (data) {
     content = JSON.parse(data.split('\n::algorithm::')[0]);
     content['algorithm_in_ilm'] = algorithm_in_ilm;
   } catch (e) {
-    $('.ui.height_100.add_accordion').dimmer({
-      closable: false
-    });
-    $('.dimmer_content_message h3').html(LocalizedStrings.getUI('text_message_error_activity_file'));
-    $('.dimmer_content_message button').text(LocalizedStrings.getUI('text_message_error_activity_reload'));
-    $('.dimmer_content_message').css('display', 'block');
-    $('.ui.height_100.add_accordion').dimmer('add content', '.dimmer_content_message');
-    $('.ui.height_100.add_accordion').dimmer('show');
     console.error(e);
+    return Maybe.none();
   }
-  return content;
+  return Maybe.some(content);
 }
 
 export function prepareActivityToStudentHelper (ilm_cont) {
-  const content = parseActivityData(ilm_cont);
-  const testCases = content.testcases;
-  setTestCases(testCases);
+  const maybe_content = parseActivityData(ilm_cont);
+  return maybe_content.map(content => {
+    const testCases = content.testcases;
+    setTestCases(testCases);
 
-  let prog_type = null;
-  if (content.settings_programming_type) {
-    prog_type = content.settings_programming_type[0].value;
-  } else {
-    prog_type = "visual";
-  }
+    let prog_type = null;
+    if (content.settings_programming_type) {
+      prog_type = content.settings_programming_type[0].value;
+    } else {
+      prog_type = "visual";
+    }
 
-  return {
-    settingsProgrammingType: prog_type,
-    settingsDataTypes: content.settings_data_types,
-    settingsCommands: content.settings_commands,
-    settingsFunctions: content.settings_functions,
-    algorithmInIlm: content.algorithm_in_ilm,
-    settingsFilter: content.settings_filter
-  }
+    return {
+      settingsProgrammingType: prog_type,
+      settingsDataTypes: content.settings_data_types,
+      settingsCommands: content.settings_commands,
+      settingsFunctions: content.settings_functions,
+      algorithmInIlm: content.algorithm_in_ilm,
+      settingsFilter: content.settings_filter
+    }
+  });
 }
 
-export function autoEval (originalData, callback) {
-  const code = generate();
-  const original = parseActivityData(originalData);
-  if (code == null) {
-    return callback(-1);
-  } else {
-    if (!compareTestcases(original.testcases, getTestCases())) {
-      return callback(-2);
+function compareArray (a, b) {
+  for (let i = 0; i < a.length; ++i) {
+    const elementA = a[i];
+    const elementB = b[i];
+    if (elementA != elementB) {
+      return false;
     }
-    const autoAssessment = new IVProgAssessment(code, getTestCases(), new TestConsole([]));
-    autoAssessment.runTest().then( grade => callback(grade)).catch(err => {
-      console.log(err);
-      callback(0);
-    });
   }
+  return true;
 }
 
 function compareTestcases (original, student) {
@@ -85,14 +75,27 @@ function compareTestcases (original, student) {
       return false;
     }
   }
+  return true;
 }
 
-function compareArray (a, b) {
-  for (let i = 0; i < a.length; ++i) {
-    const elementA = a[i];
-    const elementB = b[i];
-    if (elementA != elementB) {
-      return false;
+export function autoEval (originalData, callback) {
+  const code = generate();
+  const original = parseActivityData(originalData).getOrElse(undefined);
+  if(original == null) {
+    alert("iAssign did not provide the original activity data!");
+    return callback(null);
+  }
+  if (code == null) {
+    return callback(-1); // @FeedbackConvention Casos de teste vazios
+  } else {
+    if (!compareTestcases(original.testcases, getTestCases())) {
+      return callback(-2); // @FeedbackConvention Casos de teste alterados pelo aluno
     }
+    const ast_code = SemanticAnalyser.analyseFromSource(code);
+    const autoAssessment = new IVProgAssessment(ast_code, getTestCases(), new TestConsole([]));
+    autoAssessment.runTest().then( grade => callback(grade)).catch(err => {
+      console.log(err);
+      callback(0);
+    });
   }
-}
+}

+ 81 - 11
js/visualUI/functions.js

@@ -17,6 +17,7 @@ import { registerUserEvent, ActionTypes } from "./../services/userLog";
 import VersionInfo from './../../.ima_version.json';
 import * as TextEditor from "./text_editor";
 import { isValidIdentifier } from "./../util/utils";
+import { Modes } from '../processor/modes';
 
 var counter_new_functions = 0;
 var counter_new_parameters = 0;
@@ -24,6 +25,7 @@ var ivprog_version = VersionInfo.version;
 
 const globalChangeListeners = [];
 const functionsChangeListeners = [];
+let proc = null;
 let domConsole = null;
 let _testCases = [];
 let isRunning = false;
@@ -737,10 +739,16 @@ export function initVisualUI () {
     GlobalsManagement.addGlobal(program, true);
   });
 
-  $('.run_button').on('click', () => {
+  $('#run_button').on('click', function () {
+    showStopButton();
     runCode();
   });
 
+  $('#stop_button').on('click', function () {
+    showRunButton();
+    stopExecution();
+  });
+
   $('.visual_coding_button').on('click', () => {
     toggleVisualCoding();
   });
@@ -970,24 +978,56 @@ function runCode () {
   //$("#ivprog-term").slideDown(500);
   try {
     const data = SemanticAnalyser.analyseFromSource(strCode);
-    const proc = new IVProgProcessor(data);
+    proc = new IVProgProcessor(data);
     proc.registerInput(domConsole);
     proc.registerOutput(domConsole);
     $("#ivprog-term").addClass('ivprog-term-active');
     isRunning = true;
     proc.interpretAST().then( _ => {
-      domConsole.info(LocalizedStrings.getMessage("success_execution"));
-      $("#ivprog-term").removeClass('ivprog-term-active');
-      isRunning = false;
+      scheduleCall(() => {
+        if(domConsole.pending_writes.length == 0) {
+          if(proc.mode === Modes.ABORT) {
+            domConsole.info(LocalizedStrings.getMessage("aborted_execution"));  
+          } else {
+            domConsole.info(LocalizedStrings.getMessage("success_execution"));
+          }
+          $("#ivprog-term").removeClass('ivprog-term-active');
+          isRunning = false;
+          proc = null;
+          showRunButton();
+          return true;
+        }
+        return false;
+      },100);
     }).catch(err => {
-      domConsole.err(err.message);
-      $("#ivprog-term").removeClass('ivprog-term-active');
-      isRunning = false;
+      scheduleCall(() => {
+        if(domConsole.pending_writes.length == 0) {
+          if(err instanceof Error) {
+            domConsole.err(err.message);
+          } else {
+            domConsole.err(err);
+          }
+          $("#ivprog-term").removeClass('ivprog-term-active');
+          isRunning = false;
+          proc = null;
+          showRunButton();
+          return true;
+        }
+        return false;
+      },100);
     }) 
   } catch (error) {
-    isRunning = false;
-    domConsole.err(error.message);
-    console.log(error);
+    scheduleCall(() => {
+      if(domConsole.pending_writes.length == 0) {
+        isRunning = false;
+        proc = null;
+        showRunButton();
+        domConsole.err(error.message);
+        console.log(error);
+        return true;
+      }
+      return false;
+    },100);
   }
   
 }
@@ -1412,4 +1452,34 @@ export function removeGlobalListener (index) {
 
 export function removeFunctionListener (index) {
   functionsChangeListeners.splice(index);
+}
+
+function scheduleCall (callback, time) {
+  const scheduled_call = () => {
+    const id = setInterval(() => {
+      if(callback()) {
+        clearInterval(id);
+      }
+    }, time)
+  }
+  scheduled_call();
+}
+
+function showRunButton () {
+  document.getElementById('run_button').style.display = 'inline';
+  document.getElementById('stop_button').style.display = 'none';
+}
+
+function showStopButton () {
+  document.getElementById('run_button').style.display = 'none';
+  document.getElementById('stop_button').style.display = 'inline';
+}
+
+function stopExecution () {
+  domConsole.clearPendingWrites();
+  domConsole.cancelPendingInputRequests();
+  if(!isRunning) {
+    return;
+  }
+  proc.mode = Modes.ABORT;
 }

File diff suppressed because it is too large
+ 1882 - 1344
package-lock.json


+ 20 - 20
package.json

@@ -26,35 +26,35 @@
   },
   "homepage": "https://git.lcalion.com/ivprog#readme",
   "devDependencies": {
-    "@babel/core": "^7.4.3",
-    "@babel/preset-env": "^7.4.3",
-    "@typescript-eslint/eslint-plugin": "^2.0.0",
-    "@typescript-eslint/parser": "^2.0.0",
+    "@babel/core": "^7.6.2",
+    "@babel/preset-env": "^7.6.2",
+    "@typescript-eslint/eslint-plugin": "^2.3.2",
+    "@typescript-eslint/parser": "^2.3.2",
     "antlr4-webpack-loader": "^0.1.1",
-    "babel-loader": "^8.0.5",
-    "clean-webpack-plugin": "^2.0.1",
-    "copy-webpack-plugin": "^5.0.2",
-    "eslint": "^6.1.0",
-    "html-webpack-plugin": "^4.0.0-beta.5",
-    "jasmine-core": "^3.4.0",
-    "karma": "^4.1.0",
+    "babel-loader": "^8.0.6",
+    "clean-webpack-plugin": "^2.0.2",
+    "copy-webpack-plugin": "^5.0.4",
+    "eslint": "^6.5.1",
+    "html-webpack-plugin": "^4.0.0-beta.8",
+    "jasmine-core": "^3.5.0",
+    "karma": "^4.3.0",
     "karma-chrome-launcher": "^2.2.0",
     "karma-jasmine": "^2.0.1",
     "karma-mocha-reporter": "^2.2.5",
     "karma-webpack": "^3.0.5",
-    "puppeteer-core": "^1.7.0",
-    "ts-loader": "^5.4.3",
-    "typescript": "^3.4.5",
-    "webpack": "^4.30.0",
-    "webpack-cli": "^3.3.1"
+    "puppeteer-core": "^1.20.0",
+    "ts-loader": "^5.4.5",
+    "typescript": "^3.6.3",
+    "webpack": "^4.41.0",
+    "webpack-cli": "^3.3.9"
   },
   "dependencies": {
     "antlr4": "^4.7.2",
-    "codemirror": "^5.48.0",
-    "csv-parser": "^2.3.0",
-    "decimal.js": "^10.1.1",
+    "codemirror": "^5.49.0",
+    "csv-parser": "^2.3.1",
+    "decimal.js": "^10.2.0",
     "line-i18n": "git+http://200.144.254.107/git/LInE/line-i18n.git",
     "melanke-watchjs": "^1.5.0",
-    "server": "^1.0.18"
+    "server": "^1.0.19"
   }
 }

+ 4 - 1
templates/index.html

@@ -52,9 +52,12 @@
           <a class="item redo_button disabled">
             <i class="redo icon"></i>
           </a>
-          <a class="item run_button">
+          <a id="run_button" class="item run_button">
             <i class="play icon"></i>
           </a>
+          <a id="stop_button" class="item stop_button" style="display: none">
+            <i class="stop icon"></i>
+          </a>
           <a class="item assessment assessment_button">
             <i class="check icon"></i>
           </a>