Browse Source

Added the necessary fields for the Assistant

victor_passos 2 years ago
parent
commit
3fc2653bf4
1 changed files with 242 additions and 295 deletions
  1. 242 295
      js/assessment/output_matching/assessment_result.js

+ 242 - 295
js/assessment/output_matching/assessment_result.js

@@ -1,332 +1,279 @@
-import StringDiff from "./../../util/string_diff";
-import { LocalizedStrings } from "./../../services/localizedStringsService";
+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 { OutputAssessmentResult } from "./assessment_result";
+import * as TypeParser from "./../../typeSystem/parsers";
+import * as LocalizedStringsService from "../../services/localizedStringsService";
+import * as OutputResult from "./output_result";
 
 
-export class OutputAssessmentResult {
-  static get PAGE_TEMPLATE () {
-    return `<!DOCTYPE html>
-    <html>
-      <head>
-        <meta http-equiv='content-type' content='text/html; charset=UTF-8'>
-        <link rel='stylesheet' href='css/ivprog-assessment.css' type='text/css'/>
-      </head>
-    <body>
-      :assessment-result:
-    </body>
-    </html>`;
-  }
-
-  static get DETAIL_TEMPLATE () {
-    return `<div class='details-body'>
-        <div class='details-header'>
-          <h2>:test-name:</h2>
-          <p>:time-label:: <span>:time:ms</span></p>
-          <p>:grade-label:: <span>:grade:%</span></p>
-        </div>
-        <div>
-          <h3>:input-label:</h3>
-          <ul>
-            <li>:input-list:</li>
-          </ul>
-        </div>
-        <div>
-          <h3>:output-label:</h3>
-          :output-result:
-        </div>
-      </div>`;
-  }
-
-  static get ASSISTANT_TEMPLATE () {
-    return `<div class='details-body'>
-        <div class='assistant-div-left'>
-          <h1>Desempenho</h1>
-          <p style='padding-left:0.5rem;'>:performance-text:</p>
-        </div>
-        <div class='assistant-div-right'>
-          <h1>Sugestões</h1>
-          <p style='padding-left:0.5rem;'>:suggestion-text:</p>
-        </div>
-      </div>`;
-  }
-
-  static get OUPUT_TABLE_TEMPLATE () {
-    return `<div class='detaisl-div-table'>
-        <table class='assessment-output-table'>
-          <tr>
-            <th>:expected-label:</th>
-            <th>:generated-label:</th>
-            <th>:result-label:</th>
-          </tr>
-          :results:
-        </table>
-      </div>`;
-  }
+const LocalizedStrings = LocalizedStringsService.getInstance();
 
 
-  static get OUTPUT_TEMPLATE () {
-    return `<tr><td class=':class-expected:'>$0</td>
-            <td class=':class-generated:'>$1</td>
-            <td class=':class-result:'>$2</td></tr>`;
+export class OutputMatching {
+  static get NUM_REGEX () {
+    return /^[+-]?([0-9]+([.][0-9]*)?(e[+-]?[0-9]+)?)$/;
   }
   }
 
 
-  static get EMPTY_OUTPUT_TEMPLATE () {
-    return `<div class='assessment-popup'><img class='assessment-empty-output' src='img/empty.svg'>
-      <span class='assessment-popuptext'>$0</span></div>`;
+  static get NUM_IN_STRING_REGEX () {
+    return /[+-]?([0-9]+([.][0-9]*)?(e[+-]?[0-9]+)?)/g;
   }
   }
 
 
-  static get FAILED_TEMPLATE () {
-    return `<p class='assessment-failed-execution'><span class='assessment-failed-case'>✗</span>$0</p>`;
+  static get BOOLEAN_REGEX () {
+    const str = `^(${LocalizedStrings.getUI(
+      "logic_value_true"
+    )}|${LocalizedStrings.getUI("logic_value_false")})$`;
+    return new RegExp(str);
   }
   }
 
 
-  static get INPUT_INFO_TEMPLATE () {
-    return `<span class='$0'>$1</span>`;
+  static get BOOLEAN_IN_STRING_REGEX () {
+    const str = `(${LocalizedStrings.getUI(
+      "logic_value_true"
+    )}|${LocalizedStrings.getUI("logic_value_false")})`;
+    return new RegExp(str, "g");
   }
   }
 
 
-  // Status code - it is not grade related!
-  // 0 - Succesful execution
-  // 1 - failed execution
-  constructor (
-    name,
-    status,
-    inputs,
-    result,
-    store,
-    time,
-    tags,
-    error_msg = "",
-    error_id = ""
-  ) {
-    this.name = name;
-    this.status = status;
-    this.inputs = inputs;
-    this.results = result;
-    this.store = store;
-    this.time = time;
-    this.error_msg = error_msg;
-    this.tags = tags;
-    this.error_id = error_id;
+  constructor (program, input_list, expected_output, test_name, tag = null) {
+    this.program = program;
+    this.name = test_name;
+    this.input_list = input_list;
+    this.expected_output = expected_output;
+    this.tag = tag;
   }
   }
 
 
-  get grade () {
-    if (this.results == null) {
-      return 0;
-    }
-    return (
-      this.results.reduce((prev, val) => prev + val.grade, 0) /
-      this.results.length
-    );
+  eval () {
+    const input = new InputAssessment(this.input_list);
+    const gen_output = new OutputTest();
+    this.program.registerInput(input);
+    this.program.registerOutput(gen_output);
+    const start_time = Date.now();
+    return this.program
+      .interpretAST()
+      .then((sto) => {
+        const final_time = Date.now() - start_time;
+        if (input.isInputAvailable()) {
+          const error = this.getErrorMessage(
+            "test_case_few_reads",
+            this.name + 1
+          );
+          return new OutputAssessmentResult(
+            this.name,
+            1,
+            input.input_list,
+            null,
+            sto,
+            final_time,
+            this.tag,
+            error,
+            error.id
+          );
+        }
+        const result = gen_output.list.map((g_out, i) => {
+          if (i >= this.expected_output.length) {
+            return new OutputResult.OutputMatchResult(
+              null,
+              g_out,
+              0,
+              this.getPotentialOutputType(g_out)
+            );
+          }
+          return this.outputMatch(g_out, this.expected_output[i]);
+        }, this);
+        if (this.expected_output.length > gen_output.list.length) {
+          console.log(
+            "Saída insuficientes!",
+            this.expected_output.length,
+            gen_output.list.length
+          );
+          for (
+            let i = gen_output.list.length;
+            i < this.expected_output.length;
+            ++i
+          ) {
+            const e_out = this.expected_output[i];
+            result.push(
+              new OutputResult.OutputMatchResult(
+                e_out,
+                null,
+                0,
+                this.getPotentialOutputType(e_out)
+              )
+            );
+          }
+        } else if (
+          this.expected_output.length == 0 &&
+          this.expected_output.length == gen_output.list.length
+        ) {
+          // no output expected....
+          result.push(new OutputResult.OutputMatchResult("", "", 1, "string"));
+        }
+        return new OutputAssessmentResult(
+          this.name,
+          0,
+          input.input_list,
+          result,
+          sto,
+          final_time,
+          this.tag
+        );
+      })
+      .catch((error) => {
+        return new OutputAssessmentResult(
+          this.name,
+          1,
+          input.input_list,
+          null,
+          null,
+          null,
+          this.tags,
+          this.getErrorMessage(
+            "test_case_exception",
+            this.name + 1,
+            error.message
+          ),
+          error.id
+        );
+      });
   }
   }
 
 
-  prepareResults () {
-    let template = OutputAssessmentResult.DETAIL_TEMPLATE;
-    const grade = (this.grade * 100).toFixed(2);
-    const time = this.time || "-";
-    template = template.replace(
-      ":test-name:",
-      LocalizedStrings.getUI("assessment-detail-title", [this.name + 1])
-    );
-    template = template.replace(
-      ":time-label:",
-      LocalizedStrings.getUI("assessment-detail-time-label")
-    );
-    template = template.replace(":time:", time);
-    template = template.replace(
-      ":grade-label:",
-      LocalizedStrings.getUI("assessment-detail-grade-label")
-    );
-    template = template.replace(":grade:", grade);
-    const input_spans = this.prepareInputList(this.inputs);
-    template = template.replace(
-      ":input-label:",
-      LocalizedStrings.getUI("assessment-detail-input-label")
-    );
-    template = template.replace(":input-list:", input_spans);
-    template = template.replace(
-      ":output-label:",
-      LocalizedStrings.getUI("assessment-detail-output-label")
-    );
-    if (this.status == 0) {
-      const output_rows = this.results.map((result) => {
-        if (result.type == "string") {
-          return this.formatString(result);
-        } else if (result.type == "number") {
-          return this.formatNumber(result);
-        } else {
-          return this.formatBool(result);
-        }
-      }, this);
-      template = template.replace(
-        ":output-result:",
-        this.prepareOutputTable(output_rows)
-      );
+  getPotentialOutputType (output) {
+    if (OutputMatching.NUM_REGEX.test(output)) {
+      return "number";
+    } else if (OutputMatching.BOOLEAN_REGEX.test(output)) {
+      return "bool";
     } else {
     } else {
-      let failed_text = OutputAssessmentResult.FAILED_TEMPLATE;
-      failed_text = failed_text.replace("$0", this.error_msg);
-      template = template.replace(":output-result:", failed_text);
+      return "string";
     }
     }
-    return template;
   }
   }
 
 
-  prepareInputList (input_list) {
-    const list = input_list.map((input) => {
-      let template = OutputAssessmentResult.INPUT_INFO_TEMPLATE;
-      template = template.replace("$1", input.value);
-      if (input.read) {
-        template = template.replace("$0", "assessment-input-read");
-      } else {
-        template = template.replace("$0", "assessment-input-unread");
+  outputMatch (g_output, e_output) {
+    if (OutputMatching.NUM_REGEX.test(e_output)) {
+      if (!OutputMatching.NUM_REGEX.test(g_output)) {
+        return this.checkStrings(g_output, e_output);
       }
       }
-      return template;
-    }, this);
-    return list.join(LocalizedStrings.getUI("text_join_assessment_outputs"));
-  }
-
-  prepareOutputTable (output_rows) {
-    let template = OutputAssessmentResult.OUPUT_TABLE_TEMPLATE;
-    template = template.replace(
-      ":expected-label:",
-      LocalizedStrings.getUI("assessment-detail-expected-label")
-    );
-    template = template.replace(
-      ":generated-label:",
-      LocalizedStrings.getUI("assessment-detail-generated-label")
-    );
-    template = template.replace(
-      ":result-label:",
-      LocalizedStrings.getUI("assessment-detail-result-label")
-    );
-    template = template.replace(":results:", output_rows.join(""));
-    return template;
-  }
-
-  generateOutput () {
-    const assessment_result = this.prepareResults();
-    let page = OutputAssessmentResult.PAGE_TEMPLATE;
-    page = page.replace(":assessment-result:", assessment_result);
-    page = page.replace(/(\r|\n|\t)/gm, "").replace(/> *</g, "><");
-    return page;
-  }
-
-  generateAssistantOutput (performanceText,suggestionText) {
-    let template = OutputAssessmentResult.ASSISTANT_TEMPLATE;
-    template = template.replace(":performance-text:",performanceText);
-    template = template.replace(":suggestion-text:",suggestionText);
-    let page = OutputAssessmentResult.PAGE_TEMPLATE;
-    page = page.replace(":assessment-result:", template);
-    page = page.replace(/(\r|\n|\t)/gm, "").replace(/> *</g, "><");
-    return page;
+      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 this.checkStrings(g_output, e_output);
+      }
+      const g_bool = TypeParser.toBool(g_output);
+      const e_bool = TypeParser.toBool(e_output);
+      return this.checkBoolean(g_bool, e_bool);
+    } else {
+      return this.checkStrings(g_output, e_output);
+    }
   }
   }
 
 
-  formatNumber (result) {
-    const result_class =
-      result.grade == 1
-        ? "assessment-number-result"
-        : "assessment-number-result-failed";
-    const template = this.formatOutput(
-      "assessment-number-expected",
-      "assessment-number-generated",
-      result_class,
-      result
+  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.toNumber(),
+      g_num.toNumber(),
+      grade
     );
     );
-    return template;
   }
   }
 
 
-  formatBool (result) {
-    const result_class =
-      result.grade == 1
-        ? "assessment-bool-result"
-        : "assessment-bool-result-failed";
-    const template = this.formatOutput(
-      "assessment-bool-expected",
-      "assessment-bool-generated",
-      result_class,
-      result
-    );
-    return template;
+  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);
   }
   }
 
 
-  formatOutput (expected_class, generated_class, result_class, result) {
-    let template = OutputAssessmentResult.OUTPUT_TEMPLATE;
-    template = template.replace(":class-expected:", expected_class);
-    template = template.replace(":class-generated:", generated_class);
-    template = template.replace(":class-result:", result_class);
-    let expected_tmpl = result.expected;
-    let generated_tmpl = result.generated;
-    if (expected_tmpl == null) {
-      expected_tmpl = OutputAssessmentResult.EMPTY_OUTPUT_TEMPLATE.replace(
-        "$0",
-        LocalizedStrings.getMessage("assessment-empty-expected-tooltip")
+  checkStrings (g_output, e_ouput) {
+    const assessmentList = [];
+    let e_output_clean = e_ouput.trim();
+    let g_output_clean = g_output.trim();
+    if (OutputMatching.NUM_IN_STRING_REGEX.test(e_ouput)) {
+      const expected_numbers = e_ouput.match(
+        OutputMatching.NUM_IN_STRING_REGEX
       );
       );
-    } else if (generated_tmpl == null) {
-      generated_tmpl = OutputAssessmentResult.EMPTY_OUTPUT_TEMPLATE.replace(
-        "$0",
-        LocalizedStrings.getMessage("assessment-empty-generated-tooltip")
-      );
-    }
-    template = template.replace("$0", expected_tmpl);
-    template = template.replace("$1", generated_tmpl);
-    const final_result = result.grade == 1 ? "✓" : "✗";
-    template = template.replace("$2", final_result);
-    return template;
-  }
-
-  formatString (result) {
-    const expected_class = "assessment-string-expected";
-    const generated_class = "assessment-string-generated";
-    //const result_class = 'assessment-string-result';
-
-    let template = OutputAssessmentResult.OUTPUT_TEMPLATE;
-    template = template.replace(":class-expected:", expected_class);
-    template = template.replace(":class-generated:", generated_class);
-    //template = template.replace(":class-result:", result_class);
-
-    const g_string = result.generated || "";
-    const e_string = result.expected || "";
-    // console.log("generated: ", g_string,"expected: ", e_string);
-    let g_string_tmpl = g_string;
-    let e_string_tmpl = e_string;
-    if (result.generated == null) {
-      g_string_tmpl = OutputAssessmentResult.EMPTY_OUTPUT_TEMPLATE.replace(
-        "$0",
-        LocalizedStrings.getMessage("assessment-empty-generated-tooltip")
+      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);
+        }
+        const g_val = new Decimal(val);
+        const e_val = new Decimal(expected_numbers[i]);
+        return this.checkNumbers(g_val, e_val);
+      }, 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_output_clean = e_output_clean.replace(
+        OutputMatching.NUM_IN_STRING_REGEX,
+        ""
       );
       );
-    } else if (result.expected == null) {
-      e_string_tmpl = OutputAssessmentResult.EMPTY_OUTPUT_TEMPLATE.replace(
-        "$0",
-        LocalizedStrings.getMessage("assessment-empty-expected-tooltip")
+      g_output_clean = g_output_clean.replace(
+        OutputMatching.NUM_IN_STRING_REGEX,
+        ""
       );
       );
+      const numberGrade =
+        result.reduce((prev, r) => prev + r.grade, 0) / result.length;
+      assessmentList.push(numberGrade);
     }
     }
-    template = template.replace("$0", e_string_tmpl);
-    template = template.replace("$1", g_string_tmpl);
-    if (result.grade == 1) {
-      template = template.replace("$2", "✓");
-      template = template.replace(":class-result:", "assessment-string-result");
-    } else {
-      const diff = StringDiff(g_string, e_string);
-      // console.log(diff);
-      const diff_vec = diff.map(
-        (part) => this.getDiffStringStyle(part[1], part[0]),
-        this
+    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);
+        }
+        const g_bool = TypeParser.toBool(val);
+        const e_bool = TypeParser.toBool(expected_bools[i]);
+        return this.checkBoolean(g_bool, e_bool);
+      }, 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_output_clean = e_output_clean.replace(
+        OutputMatching.BOOLEAN_IN_STRING_REGEX,
+        ""
       );
       );
-      const diff_string = diff_vec.reduce((prev, actual) => prev + actual, "");
-      template = template.replace(
-        "$2",
-        "<span class='assessment-failed-case'>✗</span>" + diff_string
+      g_output_clean = g_output_clean.replace(
+        OutputMatching.BOOLEAN_IN_STRING_REGEX,
+        ""
       );
       );
-      template = template.replace(":class-result:", "assessment-string-diff");
+      const boolGrade =
+        result.reduce((prev, r) => prev + r.grade, 0) / result.length;
+      assessmentList.push(boolGrade);
     }
     }
-    return template;
+    const dist = levenshteinDistance(g_output_clean, e_output_clean);
+    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);
+    return OutputResult.createStringResult(e_ouput, g_output, finalGrade);
   }
   }
 
 
-  getDiffStringStyle (text, action) {
-    const template = "<span class='$0'>$1</span>";
-    // Fix missing whitespace when its a single element
-    text = text.replace(/\s/g, "&#160;");
-    switch (action) {
-      case StringDiff.INSERT:
-        return template.replace("$0", "stringdiff-insert").replace("$1", text);
-      case StringDiff.DELETE:
-        return template.replace("$0", "stringdiff-delete").replace("$1", text);
-      case StringDiff.EQUAL:
-        return template.replace("$0", "stringdiff-equal").replace("$1", text);
-    }
+  getErrorMessage (errorID, ...args) {
+    return LocalizedStrings.getError(errorID, args);
   }
   }
 }
 }