123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- <?php
- class SourceReader {
- private $source;
- private $pos = 0;
- private $size;
- function __construct ($source) {
- $this->source = str_replace(array("\r\n","\r"), "\n", $source);
- $this->size = strlen($this->source);
- }
- function remaining () {
- return $this->size - $this->pos;
- }
- function consume ($count = 1) {
- $val = '';
- for($i = 0; $i < $count; $i++) {
- if ($this->eof()) throw new Exception('End of file reached during read.', 801);
- $val = $val . $this->source[$this->pos];
- $this->pos++;
- }
- return $val;
- }
- function consumeUntil ($char, $inclusive = false, $stopAtEOF = false) {
- $skipped = '';
- while($this->source[$this->pos] !== $char) {
- if ($this->eof()) {
- if ($stopAtEOF) return $skipped;
- throw new Exception('End of file reached during read.', 801);
- }
- $skipped = $skipped . $this->source[$this->pos];
- $this->pos++;
- }
- if ($inclusive) {
- $skipped = $skipped . $char;
- $this->pos++;
- }
- return $skipped;
- }
- function consumeWhile ($char) {
- $skipped = '';
- while($this->source[$this->pos] === $char) {
- if ($this->eof()) throw new Exception('End of file reached during read.', 801);
- $skipped = $skipped . $this->source[$this->pos];
- $this->pos++;
- }
- return $skipped;
- }
- function eof () {
- return $this->pos >= $this->size;
- }
- function getChar () {
- return $this->source[$this->pos];
- }
- function lookAhead ($steps) {
- if (($this->pos + $steps) > $this->size) {
- throw new Exception('End of file reached during read.', 801);
- }
- $result = "";
- $i = $this->pos;
- for (; $i < ($this->pos + $steps); $i++) {
- $result = $result . $this->source[$i];
- }
- return $result;
- }
- function lookFoward ($pos) {
- if ($pos + $this->pos > $this->size) {
- throw new Exception('End of file reached during read.', 801);
- }
- return $this->source[$pos + $this->pos];
- }
- function lookBack ($steps) {
- if (($this->pos - $steps) < 0) {
- throw new Exception('End of file reached during read.', 801);
- }
- $result = "";
- $i = $this->pos;
- for (; $i > ($this->pos - $steps); $i--) {
- $result = $result . $this->source[$i];
- }
- return $result;
- }
- }
- class Parser {
- private $reader;
- private $line = 1;
- function __construct ($reader) {
- $this->reader = $reader;
- }
- function skipBlanklines () {
- while (in_array($this->reader->getChar(), array(" ", "\n"))) {
- $this->reader->consumeWhile(' ');
- if ($this->reader->getChar() === "\n") {
- $this->line++;
- $this->reader->consume();
- }
- }
- }
- function parse () {
- $numbers = array();
- $questions = array();
- try {
- while (!$this->reader->eof()) {
- $this->skipBlanklines();
- if($this->reader->eof()) break;
- $char = $this->reader->getChar();
- if ($char === '#') {
- // skip comment
- $this->reader->consumeUntil("\n", true);
- $this->line++;
- continue;
- }
- $char = $this->reader->consume();
- if ($char === ':') {
- $foward = $this->reader->getChar();
- if($foward !== ':') {
- $actual = $char.$foward;
- throw new Exception("Error na linha $this->line. ${actual} não é uma expressão válida, talvez você quisesse '::' (quebra de página )?", 800);
- }
- $this->reader->consumeUntil("\n", true);
- $this->line++;
- $pagebreak = new stdClass;
- $pagebreak->type = 'PAGEBREAK';
- $questions[] = $pagebreak;
- continue;
- }
- if (is_numeric($char)) {
- $foward = $this->reader->getChar();
- if (is_numeric($foward)) {
- // two digits
- $char = $char . $foward;
- $this->reader->consume();
- $foward = $this->reader->getChar();
- }
- if ($foward !== '.') {
- throw new Exception("Error na linha $this->line. O número da questão deve ser seguido de um .(ponto).", 800);
- }
- if (in_array($char, $numbers)) {
- throw new Exception('Error na linha '.($this->line).'. O número '.$char.' já foi utilizado anteriormente.', 802);
- }
- $numbers[] = $char;
- // read '.'
- $this->reader->consume();
- $questions[] = $this->parseQuestion($char);
- } else {
- throw new Exception('Error na linha '.($this->line).'. Você deve informar uma questão nessa linha', 800);
- }
- }
- } catch (Exception $e) {
- $message = $e->getMessage();
- // echo $e->getTraceAsString();
- throw new Exception ("Erro de processamento: $message Total de linhas processadas: $this->line", 800);
- }
- if (count($questions) == 0) {
- throw new Exception('Error na linha '.($this->line).'. Questionário deve ter ao menos uma questão.', 806);
- }
- return $questions;
- }
- function parseQuestion ($number) {
- $question = new stdClass();
- $question->number = 'q'.$number;
- // params
- $this->reader->consumeWhile(' ');
- if ($this->reader->getChar() !== '[') {
- throw new Exception('Error na linha '.($this->line).'. Toda questão deve ter seus parâmetros (M, D, S, I e *) definidos entre [ ].', 800);
- }
- $this->reader->consume();
- $params = $this->reader->consumeUntil("]");
- $params = str_replace(' ', '', $params);
- $validTypes = ['M','D','S', 'I'];
- if (strlen($params) === 0) {
- throw new Exception('Error na linha '.($this->line).'. Questões devem ter um parâmetro de tipo: M, D, I ou S.', 803);
- }
- $found = false;
- foreach ($validTypes as $type) {
- if (strpos($params, $type) !== FALSE) {
- if ($found) {
- throw new Exception('Error na linha '.($this->line).'. Questões devem ter apenas um parâmetro de tipo: M, D, I ou S.', 803);
- }
- $question->type = $type;
- $found = true;
- }
- }
- if (!$found) {
- throw new Exception('Error na linha '.($this->line).'. Questões devem ter um parâmetro de tipo: M, D, I ou S.', 803);
- }
- $params = str_replace($validTypes, '', $params);
- if(strlen($params) > 0) {
- if (strpos($params, '*') !== FALSE) {
- $question->required = true;
- $params = str_replace('*', '', $params);
- } else {
- throw new Exception('Error na linha '.($this->line).'. Parâmetro desconhecido:'.$params.'. Os parâmetros válidos são: M, D, S, I e *.', 800);
- }
- }
- if(strlen($params) > 0) {
- throw new Exception('Error na linha '.($this->line).'. Parâmetro desconhecido:'.$params.'. Os parâmetros válidos são: M, D, S, I e *.', 800);
- }
- $this->reader->consume();
- $this->reader->consumeWhile(' ');
- $question->text = trim($this->reader->consumeUntil("\n", false, true));
- if (strlen($question->text) === 0) {
- throw new Exception('Error na linha '.($this->line).'. Questões devem ter um texto associado!', 800);
- }
- if (!$this->reader->eof()) {
- $this->reader->consume();
- $this->line++;
- }
- // echo $question->type;
- // echo '<br/>';
- if (in_array($question->type, array('M','S'))) {
- $this->parseChoices($question);
- if (count($question->choices) < 2) {
- throw new Exception('Error na linha '.($this->line).'. A questão '.$question->number.' é de múltipla escolha e deve ter pelo menos duas alternativas.', 805);
- }
- }
- return $question;
- }
- function parseChoices ($question) {
- $question->choices = array();
- $letters = array();
- while (!$this->reader->eof()) {
- $this->skipBlanklines();
- if($this->reader->eof()){
- // blank lines at the end of file
- continue;
- }
- $char = $this->reader->getChar();
- $isPoint = $this->reader->lookFoward(1);
- if ($char === '#') {
- $this->reader->consumeUntil("\n", true);
- $this->line++;
- continue;
- } else if ($char === ':' || is_numeric($char)) {
- break;
- } else if ($isPoint !== '.') {
- // must be CONDICAO
- $cond = "CONDICAO:";
- $actual = $this->reader->lookAhead(strlen($cond));
- if(strcmp($cond, $actual) !== 0) {
- throw new Exception("Error na linha ".($this->line).". A expressão ".$actual." é desconhecida, o único valor válido nesse contexto seria a expressão 'CONDICAO:'.", 800);
- }
- // TODO parse conditionals
- $this->reader->consumeUntil("\n", true);
- $this->line++;
- break;
- }
- // echo 'Choice:'.$char;
- // echo '<br/>';
- if (in_array($char, $letters)) {
- throw new Exception('Error na linha '.($this->line).'. A letra '.$char.' já foi utilizada anteriormente nesta mesma questão.', 804);
- } else if (!preg_match('/[A-Z]/', $char)) {
- throw new Exception('Error na linha '.($this->line).'. As alternativas das questões devem ser letras maiúsculas A-Z.', 800);
- } else if ($isPoint !== '.') {
- throw new Exception('Error na linha '.($this->line).'. A letra da alternativa deve ser seguida de um .(ponto)!', 800);
- }
- $choice = new stdClass();
- $choice->value = $char;
- $this->reader->consumeUntil(".", true);
- $this->reader->consumeWhile(' ');
- // text can be last line of valid text
- $choice->text = trim($this->reader->consumeUntil("\n", false, true));
- if (strlen($choice->text) === 0) {
- throw new Exception('Error na linha '.($this->line).'. Alternativas devem ter um texto associado!', 800);
- }
- if(!$this->reader->eof()) {
- $this->reader->consume();
- $this->line++;
- }
- $question->choices[] = $choice;
- }
- }
- }
|