|  | @@ -1,14 +1,18 @@
 | 
	
		
			
				|  |  | -import { IVProgParser } from "./../ast/ivprogParser";
 | 
	
		
			
				|  |  | +import { Decimal } from 'decimal.js';
 | 
	
		
			
				|  |  | +import line_i18n from 'line-i18n'
 | 
	
		
			
				|  |  |  import { SemanticAnalyser } from "./../processor/semantic/semanticAnalyser";
 | 
	
		
			
				|  |  |  import { IVProgProcessor } from "./../processor/ivprogProcessor";
 | 
	
		
			
				|  |  |  import { InputTest } from "./../util/inputTest";
 | 
	
		
			
				|  |  |  import { OutputTest } from "./../util/outputTest";
 | 
	
		
			
				|  |  | +import { DOMConsole} from "./../io/domConsole";
 | 
	
		
			
				|  |  |  import * as LocalizedStringsService from "../services/localizedStringsService";
 | 
	
		
			
				|  |  | -import { Decimal } from 'decimal.js';
 | 
	
		
			
				|  |  |  import { Config } from "../util/config";
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  const LocalizedStrings = LocalizedStringsService.getInstance();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +const StringTypes = line_i18n.StringTypes;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  export class IVProgAssessment {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    constructor (textCode, testCases, domConsole) {
 | 
	
	
		
			
				|  | @@ -19,29 +23,30 @@ export class IVProgAssessment {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    runTest () {
 | 
	
		
			
				|  |  |      try {
 | 
	
		
			
				|  |  | -      // try and show error messages through domconsole
 | 
	
		
			
				|  |  | -      const parser = IVProgParser.createParser(this.textCode);
 | 
	
		
			
				|  |  | -      const semantic = new SemanticAnalyser(parser.parseTree());
 | 
	
		
			
				|  |  | -      const validTree = semantic.analyseTree();
 | 
	
		
			
				|  |  | +      const validTree = SemanticAnalyser.analyseFromSource(this.textCode);
 | 
	
		
			
				|  |  |        // loop test cases and show messages through domconsole
 | 
	
		
			
				|  |  |        const partialTests = this.testCases.map( (t, name) => {
 | 
	
		
			
				|  |  |          return this.partialEvaluateTestCase(new IVProgProcessor(validTree), t.input, t.output, name);
 | 
	
		
			
				|  |  |        });
 | 
	
		
			
				|  |  |        const testResult = partialTests.reduce((acc, curr) => acc.then(curr), Promise.resolve(0));
 | 
	
		
			
				|  |  | -      return testResult.then(total => Promise.resolve(total / this.testCases.length))
 | 
	
		
			
				|  |  | -        .catch(err => {
 | 
	
		
			
				|  |  | -          this.domConsole.err("Erro durante a execução do programa");// try and show error messages through domconsole
 | 
	
		
			
				|  |  | +      return testResult.then(total => {
 | 
	
		
			
				|  |  | +        const grade = total / this.testCases.length;
 | 
	
		
			
				|  |  | +        const channel = grade == 1 ? DOMConsole.INFO : DOMConsole.ERR;
 | 
	
		
			
				|  |  | +        this.writeToConsole(channel, StringTypes.MESSAGE, "test_suite_grade", grade * 100);
 | 
	
		
			
				|  |  | +        return Promise.resolve(grade)
 | 
	
		
			
				|  |  | +      }).catch(err => {
 | 
	
		
			
				|  |  | +          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) {
 | 
	
		
			
				|  |  | -      this.domConsole.err("Erro durante a execução do programa");// try and show error messages through domconsole
 | 
	
		
			
				|  |  | +      this.domConsole.err("Erro inesperado durante a execução do programa");// try and show error messages through domconsole
 | 
	
		
			
				|  |  |        this.domConsole.err(error.message);
 | 
	
		
			
				|  |  |        return Promise.resolve(0);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  evaluateTestCase (prog, inputList, outputList, name, accumulator) {
 | 
	
		
			
				|  |  | +  evaluateTestCase (prog, inputList, expectedOutputs, name, accumulator) {
 | 
	
		
			
				|  |  |      const outerThis = this;
 | 
	
		
			
				|  |  |      const input = new InputTest(inputList);
 | 
	
		
			
				|  |  |      const output = new OutputTest();
 | 
	
	
		
			
				|  | @@ -52,28 +57,33 @@ export class IVProgAssessment {
 | 
	
		
			
				|  |  |        const millis = Date.now() - startTime;
 | 
	
		
			
				|  |  |        if (input.inputList.length !== input.index) {
 | 
	
		
			
				|  |  |          outerThis.showErrorMessage('test_case_few_reads', name+1);
 | 
	
		
			
				|  |  | -        outerThis.showMessage('test_case_duration', millis);
 | 
	
		
			
				|  |  | -        return Promise.resolve(accumulator + 1 * (input.index/inputList.length));
 | 
	
		
			
				|  |  | -      } else if (output.list.length < outputList.length) {
 | 
	
		
			
				|  |  | -        outerThis.showErrorMessage('test_case_failed', name + 1, inputList.join(','),
 | 
	
		
			
				|  |  | -          outputList.join(','), output.list.join(','));
 | 
	
		
			
				|  |  | -        outerThis.showMessage('test_case_duration', millis);
 | 
	
		
			
				|  |  | -        return Promise.resolve(accumulator + 1 * (output.list.length/outputList.length));
 | 
	
		
			
				|  |  | -      } else if (output.list.length > outputList.length) {
 | 
	
		
			
				|  |  | +        outerThis.showInfoMessage('test_case_duration', millis);
 | 
	
		
			
				|  |  | +        return Promise.resolve(accumulator + (input.index/inputList.length));
 | 
	
		
			
				|  |  | +      } else if (output.list.length != expectedOutputs.length) {
 | 
	
		
			
				|  |  |          outerThis.showErrorMessage('test_case_failed', name + 1, inputList.join(','),
 | 
	
		
			
				|  |  | -          outputList.join(','), output.list.join(','));
 | 
	
		
			
				|  |  | -        outerThis.showMessage('test_case_duration', millis);
 | 
	
		
			
				|  |  | -        return Promise.resolve(accumulator + 1 * (outputList.length/output.list.length));
 | 
	
		
			
				|  |  | +          expectedOutputs.join(','), output.list.join(','));
 | 
	
		
			
				|  |  | +        outerThis.showInfoMessage('test_case_duration', millis);
 | 
	
		
			
				|  |  | +        // must check for a partial match of the generated output
 | 
	
		
			
				|  |  | +        const numMatchedOutputs = output.list.reduce((acc, actualOutput, index) => {
 | 
	
		
			
				|  |  | +          if(outerThis.checkOutputValues(actualOutput, expectedOutputs[index])) {
 | 
	
		
			
				|  |  | +            return acc + 1;
 | 
	
		
			
				|  |  | +          } else {
 | 
	
		
			
				|  |  | +            return acc;
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +        }, 0);
 | 
	
		
			
				|  |  | +        const maxLength = Math.max(expectedOutputs.length, output.list.length);
 | 
	
		
			
				|  |  | +        return Promise.resolve(accumulator + (numMatchedOutputs/maxLength));
 | 
	
		
			
				|  |  |        } else {
 | 
	
		
			
				|  |  | -        const isOk = outerThis.checkOutput(output.list, outputList);
 | 
	
		
			
				|  |  | +        const isOk = outerThis.checkOutputLists(output.list, expectedOutputs);
 | 
	
		
			
				|  |  |          if(!isOk) {
 | 
	
		
			
				|  |  | +          console.log("not ok.");
 | 
	
		
			
				|  |  |            outerThis.showErrorMessage('test_case_failed', name + 1, inputList.join(','),
 | 
	
		
			
				|  |  | -            outputList.join(','), output.list.join(','));
 | 
	
		
			
				|  |  | -          outerThis.showMessage('test_case_duration', millis);
 | 
	
		
			
				|  |  | +            expectedOutputs.join(','), output.list.join(','));
 | 
	
		
			
				|  |  | +          outerThis.showInfoMessage('test_case_duration', millis);
 | 
	
		
			
				|  |  |            return Promise.resolve(accumulator);
 | 
	
		
			
				|  |  |          } else {
 | 
	
		
			
				|  |  | -          outerThis.showMessage('test_case_success', name + 1);
 | 
	
		
			
				|  |  | -          outerThis.showMessage('test_case_duration', millis);
 | 
	
		
			
				|  |  | +          outerThis.showInfoMessage('test_case_success', name + 1);
 | 
	
		
			
				|  |  | +          outerThis.showInfoMessage('test_case_duration', millis);
 | 
	
		
			
				|  |  |            return Promise.resolve(accumulator + 1);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |        }
 | 
	
	
		
			
				|  | @@ -83,42 +93,69 @@ export class IVProgAssessment {
 | 
	
		
			
				|  |  |      });
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  partialEvaluateTestCase (prog, inputList, outputList, name) {
 | 
	
		
			
				|  |  | -    return this.evaluateTestCase.bind(this, prog, inputList, outputList, name);
 | 
	
		
			
				|  |  | +  partialEvaluateTestCase (prog, inputList, expectedOutputs, name) {
 | 
	
		
			
				|  |  | +    return this.evaluateTestCase.bind(this, prog, inputList, expectedOutputs, name);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  checkOutput (aList, bList) {
 | 
	
		
			
				|  |  | -    for (let i = 0; i < aList.length; i++) {
 | 
	
		
			
				|  |  | -      const outValue = aList[i];
 | 
	
		
			
				|  |  | -      let castNumberA = parseFloat(outValue);
 | 
	
		
			
				|  |  | -      if(!Number.isNaN(castNumberA)) {
 | 
	
		
			
				|  |  | -        let castNumberB = parseFloat(bList[i]);
 | 
	
		
			
				|  |  | -        if(Number.isNaN(castNumberB)) {
 | 
	
		
			
				|  |  | -          return false;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        castNumberA = new Decimal(castNumberA);
 | 
	
		
			
				|  |  | -        castNumberB = new Decimal(castNumberB);
 | 
	
		
			
				|  |  | -        const decimalPlaces = Math.min(castNumberB.dp(), Config.decimalPlaces);
 | 
	
		
			
				|  |  | -        Decimal.set({ rounding: Decimal.ROUND_FLOOR});
 | 
	
		
			
				|  |  | -        castNumberA = new Decimal(castNumberA.toFixed(decimalPlaces));
 | 
	
		
			
				|  |  | -        castNumberB = new Decimal(castNumberB.toFixed(decimalPlaces));
 | 
	
		
			
				|  |  | -        const aEqualsB = castNumberA.eq(castNumberB);
 | 
	
		
			
				|  |  | -        Decimal.set({ rounding: Decimal.ROUND_HALF_UP});
 | 
	
		
			
				|  |  | -        if (!aEqualsB) {
 | 
	
		
			
				|  |  | -          return false;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -      } else if(outValue != bList[i]) {
 | 
	
		
			
				|  |  | +  checkOutputLists (actualOutputs, expectedOutputs) {
 | 
	
		
			
				|  |  | +    for (let i = 0; i < actualOutputs.length; i++) {
 | 
	
		
			
				|  |  | +      const outValue = actualOutputs[i];
 | 
	
		
			
				|  |  | +      const expectedValue = expectedOutputs[i];
 | 
	
		
			
				|  |  | +      if(!this.checkOutputValues(outValue, expectedValue)) {
 | 
	
		
			
				|  |  |          return false;
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      return true;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +  checkOutputValues (actualValue, expectedValue) {
 | 
	
		
			
				|  |  | +    let castNumberA = parseFloat(actualValue);
 | 
	
		
			
				|  |  | +    if(!Number.isNaN(castNumberA)) {
 | 
	
		
			
				|  |  | +      let castNumberB = parseFloat(expectedValue);
 | 
	
		
			
				|  |  | +      if(Number.isNaN(castNumberB)) {
 | 
	
		
			
				|  |  | +        return false;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      castNumberA = new Decimal(castNumberA);
 | 
	
		
			
				|  |  | +      castNumberB = new Decimal(castNumberB);
 | 
	
		
			
				|  |  | +      const decimalPlaces = Math.min(castNumberB.dp(), Config.decimalPlaces);
 | 
	
		
			
				|  |  | +      Decimal.set({ rounding: Decimal.ROUND_FLOOR});
 | 
	
		
			
				|  |  | +      castNumberA = new Decimal(castNumberA.toFixed(decimalPlaces));
 | 
	
		
			
				|  |  | +      castNumberB = new Decimal(castNumberB.toFixed(decimalPlaces));
 | 
	
		
			
				|  |  | +      const aEqualsB = castNumberA.eq(castNumberB);
 | 
	
		
			
				|  |  | +      Decimal.set({ rounding: Decimal.ROUND_HALF_UP});
 | 
	
		
			
				|  |  | +      if (!aEqualsB) {
 | 
	
		
			
				|  |  | +        return false;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    } else if(actualValue != expectedValue) {
 | 
	
		
			
				|  |  | +      return false;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return true;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    showErrorMessage (errorID, ...args) {
 | 
	
		
			
				|  |  |      this.domConsole.err(LocalizedStrings.getError(errorID, args));
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  showMessage (msgID, ...args) {
 | 
	
		
			
				|  |  | +  showInfoMessage (msgID, ...args) {
 | 
	
		
			
				|  |  |      this.domConsole.info(LocalizedStrings.getMessage(msgID, args));
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  writeToConsole (channel, msgType, msgID, ...args) {
 | 
	
		
			
				|  |  | +    let msg = LocalizedStrings.getString(msgID, msgType);
 | 
	
		
			
				|  |  | +    msg = LocalizedStrings.processString(msg, args);
 | 
	
		
			
				|  |  | +    switch(channel) {
 | 
	
		
			
				|  |  | +      case DOMConsole.ERR: {
 | 
	
		
			
				|  |  | +        this.domConsole.err(msg);
 | 
	
		
			
				|  |  | +        break;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      case DOMConsole.INFO: {
 | 
	
		
			
				|  |  | +        this.domConsole.info(msg);
 | 
	
		
			
				|  |  | +        break;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      case DOMConsole.USER: {
 | 
	
		
			
				|  |  | +        this.domConsole.write(msg);
 | 
	
		
			
				|  |  | +        break;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  }
 |