Browse Source

Implement bool, number and string output matching function

Lucas Mendonça 4 years ago
parent
commit
d659ff089b

+ 1 - 0
.eslintignore

@@ -0,0 +1 @@
+/js/visualUI/

+ 17 - 0
.eslintrc.json

@@ -0,0 +1,17 @@
+{
+    "env": {
+        "browser": true,
+        "es6": true
+    },
+    "extends": "eslint:recommended",
+    "globals": {
+        "Atomics": "readonly",
+        "SharedArrayBuffer": "readonly"
+    },
+    "parserOptions": {
+        "ecmaVersion": 2018,
+        "sourceType": "module"
+    },
+    "rules": {
+    }
+}

+ 4 - 6
js/assessment/ivprogAssessment.js

@@ -1,6 +1,5 @@
 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";
@@ -16,19 +15,18 @@ const StringTypes = line_i18n.StringTypes;
 
 export class IVProgAssessment {
 
-  constructor (textCode, testCases, domConsole) {
-    this.textCode = textCode;
+  constructor (ast_code, testCases, domConsole) {
+    this.ast_code = ast_code;
     this.testCases = testCases;
     this.domConsole = domConsole;
   }
 
   runTest () {
-    const outerRef = this
+    const outerRef = this;
     try {
-      const validTree = SemanticAnalyser.analyseFromSource(this.textCode);
       // loop test cases and show messages through domconsole
       const partialTests = this.testCases.map( (t, name) => {
-        return outerRef.partialEvaluateTestCase(new IVProgProcessor(validTree), t.input, t.output, name);
+        return outerRef.partialEvaluateTestCase(new IVProgProcessor(outerRef.ast_code), t.input, t.output, name);
       });
       const testResult = partialTests.reduce((acc, curr) => acc.then(curr), Promise.resolve(0));
       return testResult.then(function (total) {

+ 133 - 0
js/assessment/output_matching/output_matching.js

@@ -0,0 +1,133 @@
+import { Decimal } from 'decimal.js';
+import { InputAssessment } from "../../util/input_assessment";
+import { OutputTest } from "../../util/outputTest";
+import { Config } from "../../util/config";
+import { levenshteinDistance } from "../../util/utils";
+import * as TypeParser from "./../../typeSystem/parsers";
+import * as LocalizedStringsService from "../../services/localizedStringsService";
+import * as OutputResult from "./output_result";
+
+const LocalizedStrings = LocalizedStringsService.getInstance();
+
+export class OutputMatching {
+
+  static get NUM_REGEX () {
+    return /^[0-9]+(\.[0-9]+)?$/;
+  } 
+
+  static get NUM_IN_STRING_REGEX () {
+    return /[0-9]+(\.[0-9]+)?/g;
+  }
+
+  static get BOOLEAN_REGEX () {
+    const str = `^(${LocalizedStrings.getUI("logic_value_true")}|${LocalizedStrings.getUI("logic_value_true")})$`;
+    return new RegExp(str);
+  }
+
+  static get BOOLEAN_IN_STRING_REGEX () {
+    const str = `(${LocalizedStrings.getUI("logic_value_true")}|${LocalizedStrings.getUI("logic_value_true")})`;
+    return new RegExp(str, 'g');
+  }
+  
+  constructor (program, input_list, expected_output, test_name) {
+    this.program = program;
+    this.name = test_name;
+    this.input_list = input_list;
+    this.expected_output = expected_output;
+  }
+
+  eval () {
+    const outerThis = this;
+    const input = new InputAssessment(this.input_list);
+    const gen_output = new OutputTest();
+    program.registerInput(input);
+    program.registerOutput(gen_output);
+    const start_time = Date.now();
+    return this.program.interpretAST().then( sto => {
+      
+    }).catch(error => {
+
+    });
+  }
+
+  outputMatch (g_output, e_output) {
+    if(OutputMatching.NUM_REGEX.test(e_output)) {
+      const g_num = new Decimal(g_output);
+      const e_num = new Decimal(e_output);
+      this.checkNumbers(g_num, e_num);
+    } else if (OutputMatching.BOOLEAN_REGEX.test(e_output)) {
+      const g_bool = TypeParser.toBool(g_output);
+      const e_bool = TypeParser.toBool(e_output);
+      this.checkBoolean(g_bool, e_bool);
+    } else {
+      this.checkStrings(g_output, e_output);
+    }
+  }
+
+  checkNumbers (g_num, e_num) {
+    const decimalPlaces = Math.min(e_num.dp(), Config.decimalPlaces);
+    g_num = new Decimal(g_num.toFixed(decimalPlaces, Decimal.ROUND_FLOOR));
+    e_num = new Decimal(e_num.toFixed(decimalPlaces, Decimal.ROUND_FLOOR));
+    const result = g_num.eq(e_num);
+    const grade = result ? 1 : 0;
+    return OutputResult.createNumberResult(e_num, g_num, grade);
+  }
+
+  checkBoolean (g_bool, e_bool) {
+    const grade = g_bool == e_bool ? 1 : 0;
+    const g_bool_text = TypeParser.convertBoolToString(g_bool);
+    const e_bool_text = TypeParser.convertBoolToString(e_bool);
+    return OutputResult.createBoolResult(e_bool_text, g_bool_text, grade);
+  }
+
+  checkStrings (g_output, e_ouput) {
+    const assessmentList = []
+    let e_ouput_clean = e_ouput;
+    let g_output_clean = g_output;
+    if (OutputMatching.NUM_IN_STRING_REGEX.test(e_ouput)) {
+      const expected_numbers = e_ouput.match(OutputMatching.NUM_IN_STRING_REGEX);
+      const generated_numbers = g_output.match(OutputMatching.NUM_IN_STRING_REGEX);
+      const result = generated_numbers.map((val, i) => {
+        if(i >= expected_numbers.length) {
+          return OutputResult.createNumberResult(null, val, 0);
+        }
+        return this.checkNumbers(val, expected_numbers[i]);
+      }, this);
+      if(expected_numbers.length > generated_numbers.length) {
+        for(let i = generated_numbers.length; i < expected_numbers.length; ++i) {
+          result.push(OutputResult.createNumberResult(expected_numbers[i], null, 0));
+        }
+      }
+      e_ouput_clean = e_ouput_clean.replace(OutputMatching.NUM_IN_STRING_REGEX, '').trim();
+      g_output_clean = g_output_clean.replace(OutputMatching.NUM_IN_STRING_REGEX, '').trim();
+      const numberGrade = result.reduce((prev, r) => prev + r.grade, 0) / result.length;
+      assessmentList.push(numberGrade);
+    } 
+    if(OutputMatching.BOOLEAN_IN_STRING_REGEX.test(e_ouput)) {
+      const expected_bools = e_ouput.match(OutputMatching.BOOLEAN_IN_STRING_REGEX);
+      const generated_bools = g_output.match(OutputMatching.BOOLEAN_IN_STRING_REGEX);
+      const result = generated_bools.map((val, i) => {
+        if(i >= expected_bools.length) {
+          return OutputResult.createBoolResult(null, val, 0);
+        }
+        return this.checkBoolean(val, expected_bools[i]);
+      }, this);
+      if(expected_bools.length > generated_bools.length) {
+        for(let i = generated_bools.length; i < expected_bools.length; ++i) {
+          result.push(OutputResult.createBoolResult(expected_bools[i], null, 0));
+        }
+      }
+      e_ouput_clean = e_ouput_clean.replace(OutputMatching.NUM_IN_STRING_REGEX, '').trim();
+      g_output_clean = g_output_clean.replace(OutputMatching.NUM_IN_STRING_REGEX, '').trim();
+      const boolGrade = result.reduce((prev, r) => prev + r.grade, 0) / result.length;
+      assessmentList.push(boolGrade);
+    }
+    const dist = levenshteinDistance(g_output_clean, e_ouput_clean);
+    const gradeDiff = Math.max(0, e_ouput_clean.length - dist);
+    const assessment_size = assessmentList.length + 1;
+    const gradeAcc = assessmentList.reduce((prev, val) => prev + val/assessment_size, 0);
+    const finalGrade = 1 * (gradeDiff/assessment_size + gradeAcc);
+    return OutputResult.createStringResult(e_ouput, g_output, finalGrade);
+  }
+  
+}

+ 22 - 0
js/assessment/output_matching/output_result.js

@@ -0,0 +1,22 @@
+
+export function createNumberResult (expected, generated, grade) {
+  return new OutputMatchResult(expected, generated, grade, "number");
+}
+
+export function createBoolResult (expected, generated, grade) {
+  return new OutputMatchResult(expected, generated, grade, "bool");
+}
+
+export function createStringResult (expected, generated, grade) {
+  return new OutputMatchResult(expected, generated, grade, "string");
+}
+
+export class OutputMatchResult {
+
+  constructor (expected, generated, grade, type) {
+    this.expected = expected;
+    this.generated = generated;
+    this.grade = grade;
+    this.type = type;
+  }
+}

+ 2 - 2
js/typeSystem/parsers.js

@@ -14,8 +14,8 @@ export function toString (str) {
   value = value.replace(/\\t/g, "\t");
   value = value.replace(/\\n/g, "\n");
   value = value.replace(/\\r/g, "\r");
-  value = value.replace(/\\\"/g, "\"");
-  value = value.replace(/\\\'/g, "\'");
+  value = value.replace(/\\"/g, "\"");
+  value = value.replace(/\\'/g, '\'');
   value = value.replace(/\\\\/g, "\\");
   return value;
 }

+ 24 - 0
js/util/input_assessment.js

@@ -0,0 +1,24 @@
+import { Input } from './../io/input';
+import { LocalizedStrings } from '../services/localizedStringsService';
+
+export class InputAssessment extends Input {
+
+  constructor (input_list) {
+    super();
+    this.index = 0;
+    this.input_list = input_list.map((val) => {
+      return {"value": val, "read": false};
+    });
+  }
+
+  requestInput (callback) {
+    if(this.index < this.input_list.length) {
+      const input = this.input_list[this.index];
+      input.read = true;
+      this.index += 1;
+      callback(input.value);
+    } else {
+      throw new Error(LocalizedStrings.getError("exceeded_input_request"));
+    }
+  }
+}

+ 39 - 1
js/util/utils.js

@@ -153,7 +153,7 @@ function fillCache () {
 export function getCodeEditorModeConfig () {
   const blockList = ["RK_SWITCH", "RK_PROGRAM","RK_CASE","RK_DEFAULT","RK_FOR",
     "RK_FUNCTION","RK_DO","RK_WHILE","RK_IF","RK_ELSE"]
-  const keywordsList = [,"RK_CONST","RK_RETURN","RK_BREAK"];
+  const keywordsList = ["RK_CONST","RK_RETURN","RK_BREAK"];
   const typeList = ["RK_REAL","RK_VOID","RK_BOOLEAN","RK_STRING","RK_INTEGER"];
   const atomList = ["RK_FALSE", "RK_TRUE"];
 
@@ -215,3 +215,41 @@ export function getCodeEditorModeConfig () {
     blocks: blocks
   }
 }
+
+/**
+ * Source: https://gist.github.com/andrei-m/982927
+ * @param {string} a 
+ * @param {string} b 
+ */
+export function levenshteinDistance (a, b) {
+  if(a.length == 0) return b.length; 
+  if(b.length == 0) return a.length; 
+
+  const matrix = [];
+
+  // increment along the first column of each row
+  let i;
+  for(i = 0; i <= b.length; i++){
+    matrix[i] = [i];
+  }
+
+  // increment each column in the first row
+  let j;
+  for(j = 0; j <= a.length; j++){
+    matrix[0][j] = j;
+  }
+
+  // Fill in the rest of the matrix
+  for(i = 1; i <= b.length; i++){
+    for(j = 1; j <= a.length; j++){
+      if(b.charCodeAt(i-1) == a.charCodeAt(j-1)){
+        matrix[i][j] = matrix[i-1][j-1];
+      } else {
+        matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution
+                                Math.min(matrix[i][j-1] + 1, // insertion
+                                         matrix[i-1][j] + 1)); // deletion
+      }
+    }
+  }
+  return matrix[b.length][a.length];
+}

+ 22 - 13
js/visualUI/functions.js

@@ -750,8 +750,8 @@ export function initVisualUI () {
   });
 
   $('.assessment').on('click', () => {
-    runCodeAssessment();
     is_iassign = true;
+    runCodeAssessment();
   });
 
   $('.div_toggle_console').on('click', () => {
@@ -919,22 +919,31 @@ function runCodeAssessment () {
 
   toggleConsole(true);
 
-  // if(domConsole == null)
-  //   domConsole = new DOMConsole("#ivprog-term");
-  // $("#ivprog-term").slideDown(500);
-  const runner = new IVProgAssessment(strCode, _testCases, domConsole);
-  isRunning = true;
-  runner.runTest().then(grade => {
+  try {
+    const data = SemanticAnalyser.analyseFromSource(strCode);
+    isRunning = true;
+    const runner = new IVProgAssessment(data, _testCases, domConsole);
+    runner.runTest().then(grade => {
+      if (!is_iassign) {
+        parent.getEvaluationCallback(grade);
+      } else {
+        is_iassign = false;
+      }
+      isRunning = false;
+    }).catch( err => {
+      console.log(err);
+      isRunning = false;
+    });
+  } catch (error) {
+    isRunning = false;
+    domConsole.err(error.message);
+    console.log(error);
     if (!is_iassign) {
-      parent.getEvaluationCallback(grade);
+      parent.getEvaluationCallback(0);
     } else {
       is_iassign = false;
     }
-    isRunning = false;
-  }).catch( err => {
-    console.log(err);
-    isRunning = false;
-  });
+  }
   return 0;
 }
 

File diff suppressed because it is too large
+ 739 - 34
package-lock.json


+ 1 - 0
package.json

@@ -32,6 +32,7 @@
     "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",