--- eval/BaseGrader.php	(revision 0)
+++ eval/BaseGrader.php	(working copy)
@@ -0,0 +1,373 @@
+<?php
+
+require_once(IA_ROOT_DIR."common/task.php");
+
+abstract class BaseGrader {
+    protected $task, $tparams, $job;
+    protected $result, $testResults;
+
+    public function __construct($task, $tparams, $job) {
+        $this->task = $task;
+        $this->tparams = $tparams;
+        $this->job = $job;
+    }
+
+    /**
+     * Compiles custom evaluator and interactive program if needed.
+     * The evaluator is used when multiple correct solutions can be outputted,
+     * in which case simply comparing files is not enough.
+     * The interactive program is used in 'interactive' tasks. This program is
+     * run in parallel with the user's program and comunication between them
+     * is implemented through pipes.
+     *
+     * @return boolean          true on success, false on error
+     */
+    protected function compileEvaluators() {
+        $evals = array(
+            'evaluator' => 'evaluatorul problemei',
+            'interact' => 'programul interactiv',
+        );
+        foreach ($evals as $eval_type => $eval_desc) {
+            if (!getattr($this->task, $eval_type)) {
+                continue;
+            }
+
+            $source_file = IA_ROOT_DIR . 'eval/temp/' . $this->task[$eval_type];
+            if (!copy_grader_file($this->task, $this->task[$eval_type],
+                                  $source_file)) {
+                log_print("Task $eval_type not found");
+                $this->result = array(
+                    'score' => 0,
+                    'message' => 'Eroare in setarile problemei',
+                    'log' => ("Lipseste $eval_desc.\n" .
+                              "Ar trebui sa existe un atasament ".
+                              "'grader_{$this->task[$eval_type]}' " .
+                              "la pagina cu enuntul problemei"),
+                );
+                return false;
+            }
+
+            $compiler_messages = '';
+            if (!compile_file($source_file, $eval_type, $compiler_messages)) {
+                log_print("Task $eval_type compile error");
+                $this->result = array(
+                    'score' => 0,
+                    'message' => "Eroare de compilare in $eval_desc",
+                    'log' => ("Eroare de compilare in $eval_desc:\n" .
+                              $compiler_messages),
+                );
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Processes the user submission. For classic and interactive tasks, this
+     * means compiling the user's source file.
+     *
+     * @return boolean          true on success, false on error
+     */
+    protected function processUserSubmission() {
+        $source_file = 'user.' . $this->job['compiler_id'];
+        if (file_put_contents($source_file,
+                              $this->job['file_contents']) === false) {
+            log_print('User program could not be written to disk');
+            $this->result = array(
+                'score' => 0,
+                'message' => 'Eroare de sistem',
+                'log' => 'Contacteaza un administrator',
+            );
+            return false;
+        }
+
+        $compiler_messages = '';
+        if (!compile_file($source_file, 'user', $compiler_messages)) {
+            log_print('User program compile error');
+            log_print($compiler_messages);
+            $this->result = array(
+                'score' => 0,
+                'message' => 'Eroare de compilare',
+                'log' => "Eroare de compilare:\n" . $compiler_messages,
+            );
+            return false;
+        }
+        $this->result['log'] = "Compilare:\n" . $compiler_messages . "\n";
+        return true;
+    }
+
+    /**
+     * Perform any necessary actions before running test cases.
+     * These include compiling evaluators or interactive programs and
+     * compiling user source files.
+     *
+     * @return boolean          true on success, false on error
+     */
+    protected function preTestCases() {
+        $this->result = array(
+            'score' => 0,
+            'message' => 'Evaluare incompleta',
+            'log' => '',
+        );
+
+        // Clean temporary directory and chdir to it
+        $ret = clean_dir(IA_ROOT_DIR . 'eval/temp/');
+        log_assert($ret, "Can't clean to temp dir.");
+        $ret = chdir(IA_ROOT_DIR . 'eval/temp/');
+        log_assert($ret, "Can't chdir to temp dir.");
+
+        // Compile all source files
+        if (!$this->compileEvaluators()) {
+            return false;
+        }
+        if (!$this->processUserSubmission()) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Perform any necessary actions after running all test cases.
+     *
+     * @return boolean          true on success, false on error
+     */
+    protected function postTestCases() {
+        $this->result['message'] = 'Evaluare completa';
+        if ($this->result['score'] < 0 ||
+            $this->result['score'] > IA_JUDGE_MAX_SCORE) {
+            $this->result = array(
+                'score' => 0,
+                'message' => 'Eroare in evaluatorul problemei',
+                'log' => 'Evaluatorul a returnat un scor invalid.',
+            );
+            return false;
+        }
+        return true;
+    }
+
+    protected function getInFile($jaildir) {
+        return $jaildir . $this->task['id'] . '.in';
+    }
+
+    protected function getOutFile($jaildir) {
+        return $jaildir . $this->task['id'] . '.out';
+    }
+
+    protected function getOkFile($jaildir) {
+        return $jaildir . $this->task['id'] . '.ok';
+    }
+
+    protected function getUserFile($jaildir, $testno) {
+        // FIXME: jrun does not work with absolute paths because of chroot
+        return 'user_' . $this->job['id'] . '_' . $testno;
+    }
+
+    /**
+     * Evaluates the contestant's output on a particular test case.
+     *
+     * @param  int     $testno
+     * @param  string  $jaildir
+     * @return bool                  true on success, false on failure
+     */
+    protected function testCaseJudgeOutputs($testno, $jaildir) {
+        $test_result = &$this->testResults[$testno];
+        $outfile = $this->getOutFile($jaildir);
+        $okfile = $this->getOkFile($jaildir);
+
+        // Copy ok file, if used.
+        if ($this->task['use_ok_files']) {
+            if (!copy_grader_file($this->task, 'test' . $testno . '.ok',
+                                  $okfile)) {
+                log_print("Test $testno: .ok file not found");
+                $this->result = array(
+                    'score' => 0,
+                    'message' => 'Eroare in testele problemei',
+                    'log' => ("Lipseste fisierul .ok al testului $testno.\n" .
+                              "Ar trebui sa existe un atasament".
+                              " 'grader_test$testno.ok' ".
+                              "la pagina cu enuntul problemei"),
+                );
+                return false;
+            }
+        }
+
+        if (!$this->task['evaluator']) {
+            // Diff grading, trivial.
+            $test_result['grader_time'] = null;
+            $test_result['grader_mem'] = null;
+            if (is_readable($outfile)) {
+                $diff_output = shell_exec("diff -qBbEa $outfile $okfile");
+                if ($diff_output == '') {
+                    log_print("Test $testno: Diff eval: Files identical");
+                    $test_result['message'] = 'OK';
+                    $test_result['score'] = 100 / $this->task['test_count'];
+                } else {
+                    log_print("Test $testno: Diff eval: Files differ");
+                    $test_result['message'] = 'Incorect';
+                    $test_result['score'] = 0;
+                }
+            } else {
+                log_print("Test $testno: Diff eval: output missing");
+                $test_result['message'] = 'Fisier de iesire lipsa';
+                $test_result['score'] = 0;
+            }
+            return true;
+        }
+
+        // Custom grader.
+        $ret = copy(IA_ROOT_DIR . 'eval/temp/evaluator',
+                    $jaildir . 'evaluator');
+        log_assert($ret, "Failed copying custom grader");
+        $ret = chmod('evaluator', 0555);
+        log_assert("Failed to chmod a+x user program");
+
+        // Run task eval, and check result
+        $jrunres = jail_run('evaluator', $jaildir,
+                            IA_JUDGE_TASK_EVAL_TIMELIMIT,
+                            IA_JUDGE_TASK_EVAL_MEMLIMIT,
+                            true);
+        log_assert($jrunres['result'] != 'ERROR', "Error in jrun");
+
+        // Task eval is not allowed to fail.
+        if ($jrunres['result'] == 'FAIL') {
+            log_print("Test $testno: Task eval failed");
+            $this->result = array(
+                'score' => 0,
+                'message' => 'Eroare in evaluatorul problemei',
+                'log' => ("A aparut o eroare in rularea evaluatorului ".
+                          "pe testul $testno: {$jrunres['message']}".
+                          ": timp {$jrunres['time']}ms".
+                          ": mem {$jrunres['memory']}kb"),
+            );
+            return false;
+        }
+
+        // Get score.
+        $jrunres['stdout'] = trim($jrunres['stdout']);
+        if ($jrunres['stdout'] === '' ||
+            !is_whole_number($jrunres['stdout'])) {
+            log_print("Test $testno: Task eval score broken or empty");
+            $this->result = array(
+                'score' => 0,
+                'message' => 'Eroare in evaluatorul problemei',
+                'log' => ("Evaluatorul nu a returnat un numar la stdout ".
+                          "pe testul $testno (se ignora spatii, newline, etc)"),
+            );
+            return false;
+        }
+
+        $test_result['grader_time'] = $jrunres['time'];
+        $test_result['grader_mem'] = $jrunres['memory'];
+        $test_result['score'] = (int)$jrunres['stdout'];
+        if ($test_result['score'] < 0 ||
+            $test_result['score'] > IA_JUDGE_MAX_SCORE) {
+            log_print("Test $testno: Invalid score returned by evaluator");
+            $this->result = array(
+                'score' => 0,
+                'message' => 'Eroare in evaluatorul problemei',
+                'log' => 'Evaluatorul a returnat un scor invalid.',
+            );
+            return false;
+        }
+
+        // Get message.
+        $message = $jrunres['stderr'];
+        $match = array();
+        if (!preg_match("/^([^\n]*)/i", $message, $match)) {
+            log_error("Broken preg_match???");
+        }
+        $message = $match[1];
+        if (strpos($message, "\n") ||
+            strlen($message) > IA_JUDGE_MAX_EVAL_MESSAGE) {
+            log_print("Test $testno: Task eval message broken");
+            $this->result = array(
+                'score' => 0,
+                'message' => 'Eroare in evaluatorul problemei',
+                'log' =>
+                    ('Evaluatorul a returnat un mesaj mai lung de ' .
+                     IA_JUDGE_MAX_EVAL_MESSAGE . 'de caractere la stdout'),
+            );
+            return false;
+        }
+        $test_result['message'] = $message;
+
+        // Log.
+        log_print("Test $testno: Eval gave {$test_result['score']} points " .
+                  "and said {$test_result['message']}");
+        return true;
+    }
+
+    /**
+     * Judges the user's submission on one test case. Depinding on task type,
+     * it should copy the necessary input and user files, compile and run any
+     * source files and call testCaseJudgeOutputs() afterwards
+     *
+     * @param  int     $testno
+     * @param  string  $jaildir
+     * @return bool                  true on success, false on failure
+     */
+    abstract protected function testCaseJudge($testno, $jaildir);
+
+    /**
+     * Grade the submission and return the result
+     *
+     * @return array            Array containing 'score', 'message' and 'log'
+     *                          fields
+     */
+    public function grade() {
+        if (!$this->preTestCases()) {
+            return $this->result;
+        }
+
+        // Running tests.
+        $this->testResults = array();
+        $test_score = array();
+
+        $test_groups = task_get_testgroups($this->task);
+        $group_idx = 0;
+        foreach ($test_groups as $group) {
+            $group_idx++;
+            foreach ($group as $testno) {
+                if (IA_JUDGE_KEEP_JAILS) {
+                    $jaildir = (IA_ROOT_DIR . 'eval/jail/' .
+                                $this->job['id'] . '-' . $testno . '/');
+                } else {
+                    $jaildir = IA_ROOT_DIR . "eval/jail/";
+                }
+
+                // Clean and chdir to jail dir.
+                $ret = clean_dir($jaildir);
+                log_assert($ret, "Can't clean jail dir.");
+                $ret = chdir($jaildir);
+                log_assert($ret, "Can't chdir to jail dir.");
+
+                if (!$this->testCaseJudge($testno, $jaildir)) {
+                    return $this->result;
+                }
+                $test_result = &$this->testResults[$testno];
+                job_test_update($this->job['id'], $testno, $group_idx,
+                                $test_result['test_time'],
+                                $test_result['test_mem'],
+                                $test_result['grader_time'],
+                                $test_result['grader_mem'],
+                                $test_result['score'], $test_result['message']);
+                $test_score[$testno] = $test_result['score'];
+            }
+
+            $solved_group = true;
+            $group_score = 0;
+            foreach ($group as $testno) {
+                if ($test_score[$testno] == 0) {
+                    $solved_group = false;
+                }
+                $group_score += $test_score[$testno];
+            }
+            if ($solved_group) {
+                $this->result['score'] += $group_score;
+            }
+        }
+
+        $this->postTestCases();
+        return $this->result;
+    }
+}
Index: eval/ClassicGrader.php
new file mode 100644
===================================================================
--- eval/ClassicGrader.php	(revision 0)
+++ eval/ClassicGrader.php	(working copy)
@@ -0,0 +1,91 @@
+<?php
+
+require_once(IA_ROOT_DIR . 'eval/BaseGrader.php');
+
+class ClassicGrader extends BaseGrader {
+    protected function testCaseJudge($testno, $jaildir) {
+        $this->testResults[$testno] = array();
+        $test_result = &$this->testResults[$testno];
+        $infile = $this->getInFile($jaildir);
+        $userfile = $this->getUserFile($jaildir, $testno);
+
+        // HACK: Capture run-time stderr for Python jobs.
+        //
+        // WARNING! This is a security hole! Users may dump input tests
+        // on stderr and see them in the monitor page. Currently, only
+        // admins are allowed to submit Python.
+        //
+        // TODO: Come up with a safe way of reporting run-time errors
+        // for Python scripts.
+        $capture_std = ('py' == $this->job['compiler_id']);
+
+        // Download input file.
+        if (!copy_grader_file($this->task, 'test' . $testno . '.in',
+                              $infile)) {
+            log_print("Test $testno: input not found");
+            $this->result = array(
+                'score' => 0,
+                'message' => 'Eroare in teste',
+                'log' => ("Lipseste intrarea testului $testno.\n" .
+                          "Ar trebui sa existe un atasament".
+                          " 'grader_test$testno.in' ".
+                          "la pagina cu enuntul problemei"),
+            );
+            return false;
+        }
+
+        $ret = copy(IA_ROOT_DIR.'eval/temp/user', $userfile);
+        log_assert($ret, "Failed copying user program");
+        $ret = chmod($userfile, 0555);
+        log_assert("Failed to chmod a+x user program");
+
+        // Run user program on a test case.
+        $timelimit = $this->tparams['timelimit'] * 1000;
+        $memlimit = $this->tparams['memlimit'];
+        // Adjust time and memory limit for Python jobs.
+        if ('py' == $this->job['compiler_id']) {
+            $timelimit *= 4.0;
+            $memlimit *= 2.0;
+        }
+
+        $jrunres = jail_run($userfile, $jaildir,
+                            (int)$timelimit,
+                            (int)$memlimit,
+                            $capture_std);
+        log_assert($jrunres['result'] != 'ERROR',
+                   'Error in jrun: ' . $jrunres['message']);
+        if ($jrunres['result'] == 'FAIL') {
+            log_print("Test $testno: User program failed: " .
+                      $jrunres['message'] . ' ' . $jrunres['time'] .
+                      'ms ' . $jrunres['memory'] . 'kb');
+            if ($capture_std) {
+                // TODO: Come up with a safe way of reporting run-time
+                // errors for Python scripts.
+                $message = ($jrunres['message'].": "
+                            .substr($jrunres['stderr'],
+                                    -min(100, strlen($jrunres['stderr']))));
+            } else {
+                $message = $jrunres['message'];
+            }
+            $test_result = array(
+                'test_time' => $jrunres['time'],
+                'test_mem' => $jrunres['memory'],
+                'grader_time' => null,
+                'grader_mem' => null,
+                'score' => 0,
+                'message' => $message,
+            );
+            // User program failed on this test. Bye bye.
+            return true;
+        }
+
+        $test_result = array(
+            'test_time' => $jrunres['time'],
+            'test_mem' => $jrunres['memory'],
+        );
+        if (!$this->testCaseJudgeOutputs($testno, $jaildir)) {
+            return false;
+        }
+        return true;
+    }
+}
Index: eval/classic_grader.php
deleted file mode 100644
===================================================================
--- eval/classic_grader.php	(revision 1182)
+++ /dev/null	(working copy)
@@ -1,283 +0,0 @@
-<?php
-
-require_once(IA_ROOT_DIR."common/task.php");
-
-// Grades a classic task.
-function classic_task_grade_job($task, $tparams, $job) {
-    $result = array(
-            'score' => 0,
-            'message' => 'Evaluare incompleta',
-            'log' => ''
-            );
-    log_assert_valid(task_validate($task));
-    log_assert_valid(task_validate_parameters($task['type'], $tparams));
-
-    // Clean temp dir and chdir
-    $ret = clean_dir(IA_ROOT_DIR.'eval/temp/');
-    log_assert($ret, "Can't clean to temp dir.");
-    $ret = chdir(IA_ROOT_DIR.'eval/temp/');
-    log_assert($ret, "Can't chdir to temp dir.");
-
-    // Compile custom evaluator.
-    // Don't send system error, send custom message
-    if ($task['evaluator']) {
-        if (!copy_grader_file($task, $task['evaluator'],
-                    IA_ROOT_DIR.'eval/temp/'.$task['evaluator'])) {
-            log_print('Task eval not found');
-            $result['message'] = 'Eroare in setarile problemei';
-            $result['log'] = "Lipseste evaluatorul problemei.\n";
-            $result['log'] .= "Ar trebui sa existe un atasament".
-                " 'grader_{$task['evaluator']}' ".
-                "la pagina cu enuntul problemei";
-            return $result;
-        }
-
-        if (!compile_file($task['evaluator'], 'eval', $compiler_messages)) {
-            log_print('Task eval compile error');
-            $result['message'] = 'Eroare de compilare in evaluator';
-            $result['log'] = "Eroare de compilare in evaluator:\n" . $compiler_messages;
-            return $result;
-        }
-    } else {
-        //log_print("Unique output, no evaluator!");
-    }
-
-    // Compile user source.
-    $ret = @file_put_contents("user." . $job['compiler_id'], $job['file_contents']);
-    //log_assert($ret, "Failed putting user file on disk");
-    if (!compile_file("user." . $job['compiler_id'], 'user', $compiler_messages)) {
-        log_print('User program compile error');
-        log_print($compiler_messages);
-        $result['message'] = "Eroare de compilare";
-        $result['log'] = "Eroare de compilare:\n" . $compiler_messages;
-        return $result;
-    } else {
-        $result['log'] = "Compilare:\n" . $compiler_messages . "\n";
-    }
-
-    // HACK: Capture run-time stderr for Python jobs.
-    //
-    // WARNING! This is a security hole! Users may dump input tests
-    // on stderr and see them in the monitor page. Currently, only
-    // admins are allowed to submit Python.
-    //
-    // TODO: Come up with a safe way of reporting run-time errors
-    // for Python scripts.
-    $capture_std = ('py' == $job['compiler_id']);
-
-    // Adjust time and memory limit for Python jobs.
-    if ('py' == $job['compiler_id']) {
-        $factor_memlimit = 2.0;
-        $factor_timelimit = 12.0;
-    } else {
-        $factor_memlimit = 1.0;
-        $factor_timelimit = 1.0;
-    }
-
-    // Running tests.
-    $test_score = array();
-    for ($testno = 1; $testno <= $task['test_count']; ++$testno) {
-        $test_score[$testno] = 0;
-    }
-    $test_groups = task_get_testgroups($task);
-    $group_idx = 0;
-    foreach ($test_groups as $group) {
-        $group_idx++;
-        foreach ($group as $testno) {
-            if (IA_JUDGE_KEEP_JAILS) {
-                $jaildir = IA_ROOT_DIR . "eval/jail/{$job['id']}-{$testno}/";
-            } else {
-                $jaildir = IA_ROOT_DIR . "eval/jail/";
-            }
-            $infile = $jaildir.$task['id'].'.in';
-            $outfile = $jaildir.$task['id'].'.out';
-            $okfile = $jaildir.$task['id'].'.ok';
-
-            $userfile = "user_{$job['id']}_{$testno}";
-
-            // Clean and chdir to jail dir.
-            $ret = clean_dir($jaildir);
-            log_assert($ret, "Can't clean jail dir.");
-            $ret = chdir($jaildir);
-            log_assert($ret, "Can't chdir to jail dir.");
-
-            // Download grader file.
-            if (!copy_grader_file($task, 'test' . $testno . '.in', $infile)) {
-                log_print("Test $testno: input not found");
-                $result['message'] = 'Eroare in teste';
-                $test_score[$testno] = 0;
-                $result['log'] = "Lipseste intrarea testului $testno.\n";
-                $result['log'] .= "Ar trebui sa existe un atasament".
-                    " 'grader_test$testno.in' ".
-                    "la pagina cu enuntul problemei";
-                return $result;
-            }
-
-            $ret = copy(IA_ROOT_DIR.'eval/temp/user', $jaildir . $userfile);
-            log_assert($ret, "Failed copying user program");
-            $ret = chmod($userfile, 0555);
-            log_assert("Failed to chmod a+x user program");
-
-            // Run user program.
-            $jrunres = jail_run($userfile, $jaildir,
-                        (int)($tparams['timelimit'] * 1000 * $factor_timelimit),
-                        (int)($tparams['memlimit'] * $factor_memlimit),
-                        $capture_std);
-            log_assert($jrunres['result'] != 'ERROR',
-                        "Error in jrun: ".$jrunres['message']);
-            if ($jrunres['result'] == 'FAIL') {
-                log_print("Test $testno: User program failed: {$jrunres['message']} ".
-                        "{$jrunres['time']}ms {$jrunres['memory']}kb");
-                if ($capture_std) {
-                    // TODO: Come up with a safe way of reporting run-time
-                    // errors for Python scripts.
-                    $job_message = $jrunres['message'].": "
-                            .substr($jrunres['stderr'],
-                                    -min(100, strlen($jrunres['stderr'])));
-                } else {
-                    $job_message = $jrunres['message'];
-                }
-                job_test_update($job['id'], $testno, $group_idx, $jrunres['time'], $jrunres['memory'], 
-                        null, null, 0, $job_message);
-                // User program failed on this test. Bye bye.
-                continue;
-            }
-
-            $test_time = $jrunres['time'];
-            $test_mem = $jrunres['memory'];
-            $test_msg = $jrunres['message'];
-            $grader_time = null;
-            $grader_mem = null;
-
-            // Copy ok file, if used.
-            if ($task['use_ok_files']) {
-                if (!copy_grader_file($task , 'test' . $testno . '.ok', $okfile)) {
-                    log_print("Test $testno: .ok file not found");
-                    $result['message'] = 'Eroare in teste';
-                    $test_score[$testno] = 0;
-                    $result['log'] = "Lipseste fisierul .ok al testului $testno.\n";
-                    $result['log'] .= "Ar trebui sa existe un atasament".
-                        " 'grader_test$testno.ok' ".
-                        "la pagina cu enuntul problemei";
-                    return $result;
-                }
-            }
-
-            if (!$task['evaluator']) {
-                // Diff grading, trivial.
-                if (is_readable($outfile)) {
-                    $diff_output = shell_exec("diff -qBbEa $outfile $okfile");
-                    if ($diff_output == '') {
-                        log_print("Test $testno: Diff eval: Files identical"); 
-                        $score = 100 / $task['test_count'];
-                        $test_msg = "OK";
-                        $test_score[$testno] = $score;
-                    } else {
-                        log_print("Test $testno: Diff eval: Files differ"); 
-                        $test_msg = "Incorect";
-                        $test_score[$testno] = 0;
-                    }
-                } else {
-                    log_print("Test $testno: Diff eval: output missing"); 
-                    $test_msg = "Fisier de iesire lipsa: 0 puncte";
-                    $test_score[$testno] = 0;
-                }
-            } else {
-                // Custom grader.
-                $ret = copy(IA_ROOT_DIR . 'eval/temp/eval', $jaildir . 'eval');
-                log_assert($ret, "Failed copying custom grader");
-                $ret = chmod('eval', 0555);
-                log_assert("Failed to chmod a+x user program");
-
-                // Run task eval, and check result
-                $jrunres = jail_run('eval', $jaildir,
-                        IA_JUDGE_TASK_EVAL_TIMELIMIT,
-                        IA_JUDGE_TASK_EVAL_MEMLIMIT,
-                        true);
-                log_assert($jrunres['result'] != 'ERROR', "Error in jrun");
-
-                // Task eval is not allowed to fail.
-                if ($jrunres['result'] == 'FAIL') {
-                    log_print("Test $testno: Task eval failed");
-                    $result['message'] = 'Eroare in evaluator';
-                    $test_score[$testno] = 0;
-                    $result['log'] = "A aparut o eroare in rularea evaluatorului ".
-                        "pe testul $testno: {$jrunres['message']}".
-                        ": timp {$jrunres['time']}ms".
-                        ": mem {$jrunres['memory']}kb";
-                    return $result;
-                }
-
-                // Get score.
-                $jrunres['stdout'] = trim($jrunres['stdout']);
-                if ($jrunres['stdout'] === '') {
-                    log_warn("Grader didn't return stdout, assuming 0 points");
-                    $score = 0;
-                } else if (!is_whole_number($jrunres['stdout'])) {
-                    log_print("Test $testno: Task eval score broken");
-                    $result['message'] = 'Eroare in evaluator';
-                    $test_score[$testno] = 0;
-                    $result['log'] = "Evaluatorul nu a returnat un numar la stdout ".
-                        "pe testul $testno (se ignora spatii, newline, etc)";
-                    return $result;
-                } else {
-                    $score = (int)$jrunres['stdout'];
-                    if ($score > IA_JUDGE_MAX_SCORE) {
-                        log_print("Test $testno: Task eval score too high");
-                        $result['message'] = 'Eroare in evaluator';
-                        $test_score[$testno] = 0;
-                        $result['log'] = "Evaluatorul a returnat un scor prea mare.";
-                        return $result;
-                    }
-                }
-
-                // Get message.
-                $message = $jrunres['stderr'];
-                if (!preg_match("/^([^\n]*)/i", $message, $match)) {
-                    log_error("Broken preg_match???");
-                }
-                $message = $match[1];
-                if (strpos($message, "\n") || strlen($message) > 100) {
-                    log_print("Test $testno: Task eval message broken");
-                    $result['message'] = 'Eroare in evaluator';
-                    $test_score[$testno] = 0;
-                    $result['log'] = "Evaluatorul a returnat un mesaj mai lung de 100 ".
-                        "de caractere la stdout in testul $testno";
-                    return $result;
-                }
-
-                // log.
-                log_print("Test $testno: Eval gave $score points and said $message");
-                $test_score[$testno] = $score;
-                $test_msg = $message;
-                $grader_time = $jrunres['time'];
-                $grader_mem = $jrunres['memory'];
-            }
-            job_test_update($job['id'], $testno, $group_idx, $test_time, $test_mem, 
-                    $grader_time, $grader_mem, $test_score[$testno], $test_msg);
-        }
-
-        $solved_group = true;
-        $group_score = 0;
-        foreach ($group as $testno) {
-            if ($test_score[$testno] == 0) {
-                $solved_group = false;
-            }
-            $group_score += $test_score[$testno];
-        }
-        if ($solved_group) {
-            $result['score'] += $group_score;
-        }
-    }
-
-    $result['message'] = 'Evaluare completa';
-    if ($result['score'] > IA_JUDGE_MAX_SCORE) {
-        $result['message'] = 'Eroare in evaluator';
-        $result['score'] = 0;
-        $result['log'] .= "\n\nEvaluatorul a returnat un scor prea mare.";
-    }
-
-    return $result;
-}
-
-?>
Index: eval/config.php.sample
===================================================================
--- eval/config.php.sample	(revision 1182)
+++ eval/config.php.sample	(working copy)
@@ -28,9 +28,12 @@ define("IA_JUDGE_TASK_EVAL_TIMELIMIT", 5000);
 // Memory limit for graders.
 define("IA_JUDGE_TASK_EVAL_MEMLIMIT", 64000);
 
-// Memory limit for graders.
+// Maximum score per task.
 define("IA_JUDGE_MAX_SCORE", 100);
 
+// Maximum length allowed for a evaluator's feedback message
+define("IA_JUDGE_MAX_EVAL_MESSAGE", 100);
+
 // If true then keep all jails forever.
 // This is useful in finding judge bugs.
 define("IA_JUDGE_KEEP_JAILS", true);
Index: eval/eval
===================================================================
--- eval/eval	(revision 1182)
+++ eval/eval	(working copy)
@@ -16,7 +16,7 @@ require_once(IA_ROOT_DIR.'common/db/user.php');
 
 require_once(IA_ROOT_DIR.'eval/utilities.php');
 require_once(IA_ROOT_DIR.'eval/download.php');
-require_once(IA_ROOT_DIR.'eval/classic_grader.php');
+require_once(IA_ROOT_DIR.'eval/ClassicGrader.php');
 
 error_reporting(0xFFFF);
 
@@ -37,19 +37,23 @@ function job_handle($job) {
     // Get task
     $task = task_get($job['task_id']);
     log_assert(!is_null($task), "Nu am gasit task-ul " . $job['task_id']);
+    log_assert_valid(task_validate($task));
 
     // Get task parameters.
     $task_parameters = task_get_parameters($job['task_id']);
     log_assert(!is_null($task_parameters),
             "Nu am putut paramtetrii task-ul " . $job['task_id']);
+    log_assert_valid(task_validate_parameters($task['type'],
+                                              $task_parameters));
 
     // Make the grader and execute it.
     log_print("Job #{$job['id']} task {$task['id']} round {$job['round_id']} type {$task['type']}");
     if ($task['type'] == 'classic') {
-        $job_result = classic_task_grade_job($task, $task_parameters, $job);
+        $grader = new ClassicGrader($task, $task_parameters, $job);
     } else {
         log_error("Nu stiu sa evaluez task-uri de tip ".$task['type']);
     }
+    $job_result = $grader->grade();
 
     $submit_count = 0;
     /**
Index: scripts/hphp-install.sh
===================================================================
--- scripts/hphp-install.sh	(revision 1182)
+++ scripts/hphp-install.sh	(working copy)
@@ -43,7 +43,7 @@ sudo apt-get install -y cmake \
                         libboost-dev \
                         libboost-system-dev \
                         libboost-program-options-dev \
-                        libboost-filesystem-dev
+                        libboost-filesystem-dev \
                         libmysqlclient-dev \
                         libmemcached-dev \
                         libpcre3-dev \
@@ -60,10 +60,10 @@ sudo apt-get install -y cmake \
                         libcap-dev \
                         binutils-dev \
                         libzip-dev
-# sudo yum install boost-devel flex bison re2c mysql-devel libxml2-devel
-# libmcrypt-devel libicu-devel openssl-devel binutils-devel libcap-devel
-# gd-devel zlib-devel tbb-devel pcre-devel expat-devel libmemcached-devel
-# bzip2-devel readline-devel openldap-devel oniguruma-devel uw-imap-devel
+# sudo yum install boost-devel flex bison re2c mysql-devel libxml2-devel \
+# libmcrypt-devel libicu-devel openssl-devel binutils-devel libcap-devel \
+# gd-devel zlib-devel tbb-devel pcre-devel expat-devel libmemcached-devel \
+# bzip2-devel readline-devel openldap-devel oniguruma-devel uw-imap-devel \
 # pam-devel
 
 # Build HPHP
