module Autograde; from Questions import all; from Tests import all; export SumLengths; operation GradeTest is inputs: t:TakenTest; outputs: t':GradedTest; description: (* Given a TakenTest, grade each question on the test, and return a GradedTest. *); precondition: (* * There are as many questions in the test as answers *) SumLengths(t.questions) = #t.answers; postcondition: (* * For each question on the test, grade that question. *) (forall (i:integer | i >= 1 and i <= #t.questions) forall (j:integer | j >= 1 and j <= #t.questions[i].qs) t'.s[SumLengths(t.questions[0:i]) + j] = GradeQuestion(t.questions[i].qs[j], t.answers[SumLengths(t.questions[0:i]) + j])) and (* * The grade on the test is a sum of all the scores. *) (t'.g = SumReals(t'.s)); end; operation GradeQuestion is inputs: q:Question, a:GivenAnswer; outputs: s:Score; precondition: (* * The answer is not empty *) not (a = nil); postcondition: (* * The question is graded according to its type *) (if (q?= 1 and i <= #q.ans) if (q.ans[i].at = a) then s = (q.pv * q.ans[i].ap)/100; end; function GradeCode is inputs: q:Coding, a:GivenAnswer; outputs: s:Score; postcondition: (* * The coding question is autograded based on its autograde * parameter - by program output, code text, or test script. *) (if (q.ans?co) then s = GradeCodeOutput(q, a)) (if (q.ans?pt) then s = GradeEssayDesc(q, a)) (if (q.ans?ts) then s = GradeCodeScript(q, a)); end; function GradeCodeOutput is inputs: q:Coding, a:GivenAnswer; outputs: s:Score; description: (* Runs the given answer code and compares the output to the correct output. *); precondition: (* * The given code compiles and runs without errors. *); postcondition: (* * Assign full credit if the code's output is the correct * output; assign no credit otherwise. *) if (q.ans.co = RunCode(a)) then s = q.pv else s = 0; end; function GradeCodeScript is inputs: q:Coding, a:GivenAnswer; outputs: s:Score; description: (* Runs through the grading script, and sums the total score for the question using the grading comments in the script. *); precondition: (* * The given code compiles and runs without errors. *); postcondition: (* * For each line of the grading script, check the output of * that line against the correct output, and if it is correct, * add the appropriate amount to the score. *) forall(i:integer | i >= 1 and i <= #q.ans.ts.lines) if (Execute(q.ans.ts.lines[i]) = q.ans.ts.result[i].at) then (s = s + (q.ans.ts.result[i].ap * q.pv)/100); end; function RunCode is inputs: c:string; outputs: o:string; description: (* Compiles & runs the given code, and returns the output. Implementation of this is outside the scope of pre & post conditions. *); end; function Execute is inputs: l:string; outputs: o:string; description: (* Given a line to execute in a shell, executes it and returns the output, if any. Implementation of this is outside the scope of pre & post conditions. *); end; function GradeEssay is inputs: q:Essay, a:GivenAnswer; outputs: s:Score; precondition: (* * The essay's answer is a FormattedObject object. *) a?= 1 and i < #q.ans) (if (ParseFitB(a)[i] = q.ans[i].at) then (s = s + (q.ans[i].ap * q.pv)/100)); end; function ParseFitB is inputs: up:GivenAnswer; outputs: p:GivenAnswer*; description: (* Parses the given Fill-in-the-blank answer to provide a number of answers to check. *); end; function GradeTF is inputs: q:TrueFalse, a:GivenAnswer; outputs: s:Score; description: (* Grades a true/false question by turning the GivenAnswer into a boolean and comparing it to the correct answer. *); postcondition: (* * If the student's answer is correct, assign full credit; * otherwise, assign no credit. *) if (q.ans = (a = "True")) then s = q.pv else s = 0; end; function GradeMatch is inputs: q:Matching, a:GivenAnswer; outputs: s:Score; description: (* Checks each answer the student provides against the correct answer and assigns a sum total score for the question. *); postcondition: (* * For each student answer, check the answer for that match * against the correct answer, and if it is correct, * add the appropriate amount to the score. *) forall(i:integer | i >= 1 and i < #q.ans) (if (ParseMatch(a)[i] = q.ans[i].at) then (s = s + (q.ans[i].ap * q.pv)/100)); end; function ParseMatch is inputs: up:GivenAnswer; outputs: p:GivenAnswer*; description: (* Parses the given Matching answer to provide a number of answers to check. *); end; function SumReals(r:real*) = if (#r = 0) then 0 else r[1] + SumReals(r[2:#r]); function SumLengths is inputs: qg:QuestionGroup*; outputs: i:integer; postcondition: if (#qg = 0) then i=0 else i=(#qg[1].qs + SumLengths(qg[2:#qg])); end; end Autograde;