363 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			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;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 |