xarxaprod-wp-theme/vendor/mck89/peast/lib/Peast/Syntax/ParserAbstract.php

363 lines
9.1 KiB
PHP

<?php
/**
* This file is part of the Peast package
*
* (c) Marco Marchiò <marco.mm89@gmail.com>
*
* For the full copyright and license information refer to the LICENSE file
* distributed with this source code
*/
namespace Peast\Syntax;
/**
* Base class for parsers.
*
* @author Marco Marchiò <marco.mm89@gmail.com>
*
* @abstract
*/
abstract class ParserAbstract
{
/**
* Associated scanner
*
* @var Scanner
*/
protected $scanner;
/**
* Parser features
*
* @var Features
*/
protected $features;
/**
* Parser context
*
* @var \stdClass
*/
protected $context;
/**
* Source type
*
* @var string
*/
protected $sourceType;
/**
* Comments handling
*
* @var bool
*/
protected $comments;
/**
* JSX syntax handling
*
* @var bool
*/
protected $jsx;
/**
* Events emitter
*
* @var EventsEmitter
*/
protected $eventsEmitter;
/**
* Class constructor
*
* @param string $source Source code
* @param Features $features Parser features
* @param array $options Parsing options
*/
public function __construct(
$source, Features $features, $options = array()
) {
$this->features = $features;
$this->sourceType = isset($options["sourceType"]) ?
$options["sourceType"] :
\Peast\Peast::SOURCE_TYPE_SCRIPT;
//Create the scanner
$this->scanner = new Scanner($source, $features, $options);
//Enable module scanning if required
if ($this->sourceType === \Peast\Peast::SOURCE_TYPE_MODULE) {
$this->scanner->enableModuleMode();
}
//Enable comments scanning
$this->comments = isset($options["comments"]) && $options["comments"];
if ($this->comments) {
$this->scanner->enableComments();
//Create the comments registry
new CommentsRegistry($this);
}
// Enable jsx syntax if required
$this->jsx = isset($options["jsx"]) && $options["jsx"];
$this->initContext();
$this->postInit();
}
/**
* Initializes parser context
*
* @return void
*/
abstract protected function initContext();
/**
* Post initialize operations
*
* @return void
*/
abstract protected function postInit();
/**
* Parses the source
*
* @return Node\Node
*
* @abstract
*/
abstract public function parse();
/**
* Returns parsed tokens from the source code
*
* @return Token[]
*/
public function tokenize()
{
$this->scanner->enableTokenRegistration();
$this->parse();
return $this->scanner->getTokens();
}
/**
* Returns the scanner associated with the parser
*
* @return Scanner
*/
public function getScanner()
{
return $this->scanner;
}
/**
* Returns the parser features class
*
* @return Features
*/
public function getFeatures()
{
return $this->features;
}
/**
* Returns the parser's events emitter
*
* @return EventsEmitter
*/
public function getEventsEmitter()
{
if (!$this->eventsEmitter) {
//The event emitter is created here so that it won't exist if not
//necessary
$this->eventsEmitter = new EventsEmitter;
}
return $this->eventsEmitter;
}
/**
* Calls a method with an isolated parser context, applying the given flags,
* but restoring their values after the execution.
*
* @param array|null $flags Key/value array of changes to apply to the
* context flags. If it's null or the first
* element of the array is null the context will
* be reset before applying new values.
* @param string $fn Method to call
* @param array|null $args Method arguments
*
* @return mixed
*/
protected function isolateContext($flags, $fn, $args = null)
{
//Store the current context
$oldContext = clone $this->context;
//If flag argument is null reset the context
if ($flags === null) {
$this->initContext();
} else {
//Apply new values to the flags
foreach ($flags as $k => $v) {
// If null reset the context
if ($v === null) {
$this->initContext();
} else {
$this->context->$k = $v;
}
}
}
//Call the method with the given arguments
$ret = $args ? call_user_func_array(array($this, $fn), $args) : $this->$fn();
//Restore previous context
$this->context = $oldContext;
return $ret;
}
/**
* Creates a node
*
* @param string $nodeType Node's type
* @param mixed $position Node's start position
*
* @return Node\Node
*
* @codeCoverageIgnore
*/
protected function createNode($nodeType, $position)
{
//Use the right class to get an instance of the node
$nodeClass = "\\Peast\\Syntax\\Node\\" . $nodeType;
$node = new $nodeClass;
//Add the node start position
if ($position instanceof Node\Node || $position instanceof Token) {
$position = $position->location->start;
} elseif (is_array($position)) {
if (count($position)) {
$position = $position[0]->location->start;
} else {
$position = $this->scanner->getPosition();
}
}
$node->location->start = $position;
//Emit the NodeCreated event for the node
$this->eventsEmitter && $this->eventsEmitter->fire(
"NodeCreated", array($node)
);
return $node;
}
/**
* Completes a node by adding the end position
*
* @param Node\Node $node Node to complete
* @param Position $position Node's end position
*
* @return mixed It actually returns a Node but mixed solves
* a lot of PHPDoc problems
*
* @codeCoverageIgnore
*/
protected function completeNode(Node\Node $node, $position = null)
{
//Add the node end position
$node->location->end = $position ?: $this->scanner->getPosition();
//Emit the NodeCompleted event for the node
$this->eventsEmitter && $this->eventsEmitter->fire(
"NodeCompleted", array($node)
);
return $node;
}
/**
* Throws a syntax error
*
* @param string $message Error message
* @param Position $position Error position
*
* @return void
*
* @throws Exception
*/
protected function error($message = "", $position = null)
{
if (!$message) {
$token = $this->scanner->getToken();
if ($token === null) {
$message = "Unexpected end of input";
} else {
$position = $token->location->start;
$message = "Unexpected: " . $token->value;
}
}
if (!$position) {
$position = $this->scanner->getPosition();
}
throw new Exception($message, $position);
}
/**
* Asserts that a valid end of statement follows the current position
*
* @return boolean
*
* @throws Exception
*/
protected function assertEndOfStatement()
{
//The end of statement is reached when it is followed by line
//terminators, end of source, "}" or ";". In the last case the token
//must be consumed
if (!$this->scanner->noLineTerminators()) {
return true;
} else {
if ($this->scanner->consume(";")) {
return true;
}
$token = $this->scanner->getToken();
if (!$token || $token->value === "}") {
return true;
}
}
$this->error();
}
/**
* Parses a character separated list of instructions or null if the
* sequence is not valid
*
* @param callable $fn Parsing instruction function
* @param string $char Separator
*
* @return array
*
* @throws Exception
*/
protected function charSeparatedListOf($fn, $char = ",")
{
$list = array();
$valid = true;
while ($param = $this->$fn()) {
$list[] = $param;
$valid = true;
if (!$this->scanner->consume($char)) {
break;
} else {
$valid = false;
}
}
if (!$valid) {
$this->error();
}
return $list;
}
}