Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. CommentsRegistry.php

class CommentsRegistry

Comments registry class. Internal class used to manage comments

@author Marco Marchiò <marco.mm89@gmail.com>

Hierarchy

  • class \Peast\Syntax\CommentsRegistry

Expanded class hierarchy of CommentsRegistry

File

vendor/mck89/peast/lib/Peast/Syntax/CommentsRegistry.php, line 17

Namespace

Peast\Syntax
View source
class CommentsRegistry {
    
    /**
     * Map of the indices where nodes start
     * 
     * @var int 
     */
    protected $nodesStartMap = array();
    
    /**
     * Map of the indices where nodes end
     * 
     * @var int 
     */
    protected $nodesEndMap = array();
    
    /**
     * Comments buffer
     * 
     * @var array
     */
    protected $buffer = null;
    
    /**
     * Last token index
     * 
     * @var int
     */
    protected $lastTokenIndex = null;
    
    /**
     * Comments registry
     * 
     * @var array
     */
    protected $registry = array();
    
    /**
     * Class constructor
     * 
     * @param Parser    $parser     Parser
     */
    public function __construct(Parser $parser) {
        $parser->getEventsEmitter()
            ->addListener("NodeCompleted", array(
            $this,
            "onNodeCompleted",
        ))
            ->addListener("EndParsing", array(
            $this,
            "onEndParsing",
        ));
        $parser->getScanner()
            ->getEventsEmitter()
            ->addListener("TokenConsumed", array(
            $this,
            "onTokenConsumed",
        ))
            ->addListener("EndReached", array(
            $this,
            "onTokenConsumed",
        ))
            ->addListener("FreezeState", array(
            $this,
            "onScannerFreezeState",
        ))
            ->addListener("ResetState", array(
            $this,
            "onScannerResetState",
        ));
    }
    
    /**
     * Listener called every time the scanner compose the array that represents
     * its current state
     * 
     * @param array   $state   State
     * 
     * @return void
     */
    public function onScannerFreezeState(&$state) {
        
        //Register the current last token index
        $state["commentsLastTokenIndex"] = $this->lastTokenIndex;
    }
    
    /**
     * Listener called every time the scanner reset its state using the given
     * array
     * 
     * @param array   $state   State
     * 
     * @return void
     */
    public function onScannerResetState(&$state) {
        
        //Reset the last token index and delete it from the state array
        $this->lastTokenIndex = $state["commentsLastTokenIndex"];
        unset($state["commentsLastTokenIndex"]);
    }
    
    /**
     * Listener called every time a token is consumed and when the scanner
     * reaches the end of the source
     * 
     * @param Token|null   $token   Consumed token or null if the end has
     *                              been reached
     * 
     * @return void
     */
    public function onTokenConsumed($token = null) {
        
        //Check if it's a comment
        if ($token && $token->type === Token::TYPE_COMMENT) {
            
            //If there is not an open comments buffer, create it
            if (!$this->buffer) {
                $this->buffer = array(
                    "prev" => $this->lastTokenIndex,
                    "next" => null,
                    "comments" => array(),
                );
            }
            
            //Add the comment token to the buffer
            $this->buffer["comments"][] = $token;
        }
        else {
            if ($token) {
                $loc = $token->location;
                
                //Store the token end position
                $this->lastTokenIndex = $loc->end
                    ->getIndex();
                if ($this->buffer) {
                    
                    //Fill the "next" key on the comments buffer with the token
                    
                    //start position
                    $this->buffer["next"] = $loc->start
                        ->getIndex();
                }
            }
            
            //If there is an open comment buffer, close it and move it to the
            
            //registry
            if ($buffer = $this->buffer) {
                
                //Use the location as key to add the group of comments to the
                
                //registry, in this way if comments are reprocessed they won't
                
                //be duplicated
                $key = implode("-", array(
                    $buffer["prev"] !== null ? $buffer["prev"] : "",
                    $buffer["next"] !== null ? $buffer["next"] : "",
                ));
                $this->registry[$key] = $this->buffer;
                $this->buffer = null;
            }
        }
    }
    
    /**
     * Listener called every time a node is completed by the parser
     * 
     * @param Node\Node   $node     Completed node
     * 
     * @return void
     */
    public function onNodeCompleted(Node\Node $node) {
        
        //Every time a node is completed, register its start and end indices
        
        //in the relative properties
        $loc = $node->location;
        foreach (array(
            "Start",
            "End",
        ) as $pos) {
            $val = $loc->{"get{$pos}"}()
                ->getIndex();
            $map =& $this->{"nodes{$pos}Map"};
            if (!isset($map[$val])) {
                $map[$val] = array();
            }
            $map[$val][] = $node;
        }
    }
    
    /**
     * Listener called when parsing process ends
     * 
     * @return void
     */
    public function onEndParsing() {
        
        //Return if there are no comments to process
        if ($this->registry) {
            
            //Make sure nodes start indices map is sorted
            ksort($this->nodesStartMap);
            
            //Loop all comment groups in the registry
            foreach ($this->registry as $group) {
                $this->findNodeForCommentsGroup($group);
            }
        }
    }
    
    /**
     * Finds the node to attach the given comments group
     * 
     * @param array    $group   Comments group
     * 
     * @return void
     */
    public function findNodeForCommentsGroup($group) {
        $next = $group["next"];
        $prev = $group["prev"];
        $comments = $group["comments"];
        $leading = true;
        
        //If the group of comments has a next token index that appears
        
        //in the map of start node indices, add the group to the
        
        //corresponding node's leading comments. This associates
        
        //comments that appear immediately before a node.
        
        //For example: /*comment*/ for (;;){}
        if (isset($this->nodesStartMap[$next])) {
            $nodes = $this->nodesStartMap[$next];
        }
        elseif (isset($this->nodesEndMap[$prev])) {
            $nodes = $this->nodesEndMap[$prev];
            $leading = false;
        }
        else {
            
            //Calculate comments group boundaries
            $start = $comments[0]->location->start
                ->getIndex();
            $end = $comments[count($comments) - 1]->location->end
                ->getIndex();
            $nodes = array();
            
            //Loop all the entries in the start index map
            foreach ($this->nodesStartMap as $idx => $ns) {
                
                //If the index is higher than the start index of the comments
                
                //group, stop
                if ($idx > $start) {
                    break;
                }
                foreach ($ns as $node) {
                    
                    //Check if the comments group is inside node indices range
                    if ($node->location->end
                        ->getIndex() >= $end) {
                        $nodes[] = $node;
                    }
                }
            }
            
            //If comments can't be associated with any node, associate it as
            
            //leading comments of the program, this happens when the source is
            
            //empty
            if (!$nodes) {
                $firstNode = array_values($this->nodesStartMap);
                $nodes = array(
                    $firstNode[0][0],
                );
            }
        }
        
        //If there are multiple possible nodes to associate the comments to,
        
        //find the shortest one
        if (count($nodes) > 1) {
            usort($nodes, array(
                $this,
                "compareNodesLength",
            ));
        }
        $this->associateComments($nodes[0], $comments, $leading);
    }
    
    /**
     * Compares node length 
     * 
     * @param Node\Node  $node1     First node
     * @param Node\Node  $node2     Second node
     * 
     * @return int
     * 
     * @codeCoverageIgnore
     */
    public function compareNodesLength($node1, $node2) {
        $loc1 = $node1->location;
        $length1 = $loc1->end
            ->getIndex() - $loc1->start
            ->getIndex();
        $loc2 = $node2->location;
        $length2 = $loc2->end
            ->getIndex() - $loc2->start
            ->getIndex();
        
        //If the nodes have the same length make sure to choose nodes
        
        //different from Program nodes
        if ($length1 === $length2) {
            if ($node1 instanceof Node\Program) {
                $length1 += 1000;
            }
            elseif ($node2 instanceof Node\Program) {
                $length2 += 1000;
            }
        }
        return $length1 < $length2 ? -1 : 1;
    }
    
    /**
     * Adds comments to the given node
     * 
     * @param Node\Node     $node       Node
     * @param array         $comments   Array of comments to add
     * @param bool          $leading    True to add comments as leading comments
     *                                  or false to add them as trailing comments
     * 
     * @return void
     */
    public function associateComments($node, $comments, $leading) {
        $fn = ($leading ? "Leading" : "Trailing") . "Comments";
        $currentComments = $node->{"get{$fn}"}();
        foreach ($comments as $comment) {
            $loc = $comment->location;
            $commentNode = new Node\Comment();
            $commentNode->location->start = $loc->start;
            $commentNode->location->end = $loc->end;
            $commentNode->setRawText($comment->value);
            $currentComments[] = $commentNode;
        }
        $node->{"set{$fn}"}($currentComments);
    }

}

Members

Title Sort descending Modifiers Object type Summary
CommentsRegistry::$buffer protected property Comments buffer
CommentsRegistry::$lastTokenIndex protected property Last token index
CommentsRegistry::$nodesEndMap protected property Map of the indices where nodes end
CommentsRegistry::$nodesStartMap protected property Map of the indices where nodes start
CommentsRegistry::$registry protected property Comments registry
CommentsRegistry::associateComments public function Adds comments to the given node
CommentsRegistry::compareNodesLength public function Compares node length
CommentsRegistry::findNodeForCommentsGroup public function Finds the node to attach the given comments group
CommentsRegistry::onEndParsing public function Listener called when parsing process ends
CommentsRegistry::onNodeCompleted public function Listener called every time a node is completed by the parser
CommentsRegistry::onScannerFreezeState public function Listener called every time the scanner compose the array that represents
its current state
CommentsRegistry::onScannerResetState public function Listener called every time the scanner reset its state using the given
array
CommentsRegistry::onTokenConsumed public function Listener called every time a token is consumed and when the scanner
reaches the end of the source
CommentsRegistry::__construct public function Class constructor
RSS feed
Powered by Drupal