src/parser/SyntaxError.php
<?php
/**
* Quack Compiler and toolkit
* Copyright (C) 2015-2017 Quack and CONTRIBUTORS
*
* This file is part of Quack.
*
* Quack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Quack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Quack. If not, see <http://www.gnu.org/licenses/>.
*/
namespace QuackCompiler\Parser;
use \Exception;
use \QuackCompiler\Lexer\Tag;
use \QuackCompiler\Lexer\Token;
use \QuackCompiler\Lexer\Word;
define('BEGIN_RED', "\033[01;31m");
define('END_RED', "\033[0m");
define('BEGIN_GREEN', "\033[01;32m");
define('END_GREEN', "\033[0m");
define('BEGIN_BG_RED', "\033[01;41m");
define('END_BG_RED', "\033[0m");
define('BEGIN_BOLD', "\033[1m");
define('END_BOLD', "\033[0m");
define('BEGIN_BLUE', "\033[0;34m");
define('END_BLUE', "\033[0m");
class SyntaxError extends Exception
{
private $expected;
private $found;
private $parser;
public function __construct($parameters)
{
$this->expected = $parameters['expected'];
$this->found = $parameters['found'];
$this->reader = $parameters['parser'];
$this->hint = array_key_exists('hint', $parameters)
? $parameters['hint']
: null;
}
private function extractPieceOfSource()
{
$out_buffer = [];
$position = $this->getPosition();
$token_size = $this->getFoundTokenSize();
$new_column = $position['column'] - $token_size;
$error_line = str_split(
explode(PHP_EOL, $this->getOriginalSource()->input)[
$position['line'] - 1
]
);
$line_indicator = "{$position['line']}| ";
$correct_piece = $new_column - 1 <= 0
? []
: array_slice($error_line, 0, $new_column);
$error_piece = array_slice($error_line, $new_column, $new_column + 10);
$out_buffer[] = $line_indicator;
$out_buffer[] = BEGIN_GREEN . implode($correct_piece) . END_GREEN;
$out_buffer[] = BEGIN_BG_RED . implode($error_piece) . END_BG_RED;
$out_buffer[] = PHP_EOL . str_repeat(' ', strlen($line_indicator) + count($correct_piece));
$out_buffer[] = BEGIN_BOLD . str_repeat('^', count($error_piece)) . END_BOLD;
return implode($out_buffer);
}
public function getFormattedHint()
{
if (null === $this->hint) {
return '';
}
return PHP_EOL . PHP_EOL . BEGIN_BLUE . BEGIN_BOLD . "~Hint:" .
" {$this->hint}" . END_BLUE . END_BOLD . PHP_EOL;
}
public function __toString()
{
$source = $this->extractPieceOfSource();
$expected = $this->getExpectedTokenName();
$found = $this->getFoundTokenName();
$position = $this->getPosition();
$hint = $this->getFormattedHint();
return $source . PHP_EOL . join([
BEGIN_RED,
"*** Hey, I found a syntax error!", PHP_EOL,
" Expecting [", BEGIN_GREEN, $expected, END_GREEN, BEGIN_RED, "]", PHP_EOL,
" Found [", BEGIN_GREEN, $found, END_GREEN, BEGIN_RED, "]", PHP_EOL,
" Line {$position['line']}", PHP_EOL,
" Column ", ($position['column'] - $this->getFoundTokenSize() + 1), PHP_EOL,
END_RED,
$hint
]);
}
private function getExpectedTokenName()
{
return is_integer($this->expected)
? Tag::getName($this->expected)
: $this->expected;
}
protected function getFoundTokenName()
{
$found_tag = $this->found->getTag();
return Tag::getName($found_tag) ?: $found_tag;
}
private function getFoundTokenSize()
{
if ($this->found instanceof Word) {
// Keyword found
return strlen($this->found->lexeme);
}
// Operator, literal or EoF found
$offset = 0;
$found_tag = $this->found->getTag();
// String literals have quotes also!
if (Tag::T_STRING === $found_tag) {
$offset += 2;
}
$token_val = $this->found->getContent();
return $offset + (0 === $found_tag
? -1
: strlen(null !== $token_val ? $token_val : $found_tag));
}
private function getOriginalSource()
{
return $this->reader->input;
}
private function getPosition()
{
return $this->reader->position();
}
}