xref: /plugin/combo/vendor/carica/phpcss/src/PhpCss/Parser.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau/**
3*04fd306cSNickeau* Abstract class implementing functionality to ease parsing in extending
4*04fd306cSNickeau* subparsers.
5*04fd306cSNickeau*
6*04fd306cSNickeau* @license http://www.opensource.org/licenses/mit-license.php The MIT License
7*04fd306cSNickeau* @copyright Copyright 2010-2014 PhpCss Team
8*04fd306cSNickeau*/
9*04fd306cSNickeau
10*04fd306cSNickeaunamespace PhpCss {
11*04fd306cSNickeau
12*04fd306cSNickeau  use PhpCss\Exception\ParserException;
13*04fd306cSNickeau
14*04fd306cSNickeau  /**
15*04fd306cSNickeau  * Abstract class implementing functionality to ease parsing in extending
16*04fd306cSNickeau  * subparsers.
17*04fd306cSNickeau  */
18*04fd306cSNickeau  abstract class Parser {
19*04fd306cSNickeau
20*04fd306cSNickeau    /**
21*04fd306cSNickeau    * List of tokens from scanner
22*04fd306cSNickeau    *
23*04fd306cSNickeau    * @var array(Scanner\Token)
24*04fd306cSNickeau    */
25*04fd306cSNickeau    protected $_tokens = array();
26*04fd306cSNickeau
27*04fd306cSNickeau    /**
28*04fd306cSNickeau     * Construct a parser object taking the token list to operate on as
29*04fd306cSNickeau     * argument
30*04fd306cSNickeau     */
31*04fd306cSNickeau    public function __construct(array &$tokens) {
32*04fd306cSNickeau      $this->_tokens = &$tokens;
33*04fd306cSNickeau    }
34*04fd306cSNickeau
35*04fd306cSNickeau    /**
36*04fd306cSNickeau     * Return parser tokens list
37*04fd306cSNickeau     */
38*04fd306cSNickeau    public function getTokens(): array {
39*04fd306cSNickeau      return $this->_tokens;
40*04fd306cSNickeau    }
41*04fd306cSNickeau
42*04fd306cSNickeau    /**
43*04fd306cSNickeau     * Execute the parsing process on the provided token stream
44*04fd306cSNickeau     *
45*04fd306cSNickeau     * This method is supposed to handle all the steps needed to parse the
46*04fd306cSNickeau     * current subsegment of the token stream. It is supposed to return a valid
47*04fd306cSNickeau     * PhpCssAst.
48*04fd306cSNickeau     *
49*04fd306cSNickeau     * If the parsing process can't be completed because of invalid input a
50*04fd306cSNickeau     * PhpCssParserException needs to be thrown.
51*04fd306cSNickeau     *
52*04fd306cSNickeau     * The methods protected methods read and lookahead should be used to
53*04fd306cSNickeau     * operate on the token stream. They will throw PhpCssParserExceptions
54*04fd306cSNickeau     * automatically in case they do not succeed.
55*04fd306cSNickeau     *
56*04fd306cSNickeau     * @return Ast\Node
57*04fd306cSNickeau     */
58*04fd306cSNickeau    abstract public function parse(): Ast\Node;
59*04fd306cSNickeau
60*04fd306cSNickeau    /**
61*04fd306cSNickeau     * Try to read any of the $expectedTokens from the token list and return
62*04fd306cSNickeau     * the matching one.
63*04fd306cSNickeau     *
64*04fd306cSNickeau     * This method tries to match the current token list against all of the
65*04fd306cSNickeau     * provided tokens. If a match is found it is removed from the token list
66*04fd306cSNickeau     * and returned.
67*04fd306cSNickeau     *
68*04fd306cSNickeau     * If no match can be found a PhpCssParserException will thrown indicating what
69*04fd306cSNickeau     * has been expected and what was found.
70*04fd306cSNickeau     *
71*04fd306cSNickeau     * The $expectedTokens parameter may be an array of tokens or a scalar
72*04fd306cSNickeau     * value, which is handled the same way an array with only one entry would
73*04fd306cSNickeau     * be.
74*04fd306cSNickeau     *
75*04fd306cSNickeau     * The special Token Scanner\Token::ANY may be used to indicate
76*04fd306cSNickeau     * everything is valid and may be matched. However if it is used no other
77*04fd306cSNickeau     * token may be specified, which does not make any sense, anyway.
78*04fd306cSNickeau     *
79*04fd306cSNickeau     * @param array|integer|string $expectedTokens
80*04fd306cSNickeau     * @throws ParserException
81*04fd306cSNickeau     * @return Scanner\Token
82*04fd306cSNickeau     */
83*04fd306cSNickeau    protected function read($expectedTokens): Scanner\Token {
84*04fd306cSNickeau      // Allow scalar token values for better readability
85*04fd306cSNickeau      if (!is_array($expectedTokens)) {
86*04fd306cSNickeau        return $this->read(array($expectedTokens));
87*04fd306cSNickeau      }
88*04fd306cSNickeau
89*04fd306cSNickeau      foreach($expectedTokens as $token) {
90*04fd306cSNickeau        if ($this->matchToken(0, $token)) {
91*04fd306cSNickeau          return array_shift($this->_tokens);
92*04fd306cSNickeau        }
93*04fd306cSNickeau      }
94*04fd306cSNickeau
95*04fd306cSNickeau      // None of the given tokens matched
96*04fd306cSNickeau      throw $this->handleMismatch($expectedTokens);
97*04fd306cSNickeau    }
98*04fd306cSNickeau
99*04fd306cSNickeau    /**
100*04fd306cSNickeau     * Try to match any of the $expectedTokens against the given token stream
101*04fd306cSNickeau     * position and return the matching one.
102*04fd306cSNickeau     *
103*04fd306cSNickeau     * This method tries to match the current token stream at the provided
104*04fd306cSNickeau     * lookahead position against all of the provided tokens. If a match is
105*04fd306cSNickeau     * found it simply returned. The token stream remains unchanged.
106*04fd306cSNickeau     *
107*04fd306cSNickeau     * If no match can be found a PhpCssParserException will thrown indicating what
108*04fd306cSNickeau     * has been expected and what was found.
109*04fd306cSNickeau     *
110*04fd306cSNickeau     * The $expectedTokens parameter may be an array of tokens or a scalar
111*04fd306cSNickeau     * value, which is handled the same way an array with only one entry would
112*04fd306cSNickeau     * be.
113*04fd306cSNickeau     *
114*04fd306cSNickeau     * The special Token Scanner\Token::ANY may be used to indicate
115*04fd306cSNickeau     * everything is valid and may be matched. However if it is used no other
116*04fd306cSNickeau     * token may be specified, which does not make any sense, anyway.
117*04fd306cSNickeau     *
118*04fd306cSNickeau     * The position parameter may be provided to enforce a match on an
119*04fd306cSNickeau     * arbitrary token stream position. Therefore unlimited lookahead is
120*04fd306cSNickeau     * provided.
121*04fd306cSNickeau     *
122*04fd306cSNickeau     * @param array|integer|string $expectedTokens
123*04fd306cSNickeau     * @param int $position
124*04fd306cSNickeau     * @param bool $allowEndOfTokens
125*04fd306cSNickeau     * @throws ParserException
126*04fd306cSNickeau     * @return Scanner\Token|NULL
127*04fd306cSNickeau     */
128*04fd306cSNickeau    protected function lookahead(
129*04fd306cSNickeau      $expectedTokens, int $position = 0, bool $allowEndOfTokens = FALSE
130*04fd306cSNickeau    ): ?Scanner\Token {
131*04fd306cSNickeau      // Allow scalar token values for better readability
132*04fd306cSNickeau      if (!is_array($expectedTokens)) {
133*04fd306cSNickeau        return $this->lookahead(array($expectedTokens), $position, $allowEndOfTokens);
134*04fd306cSNickeau      }
135*04fd306cSNickeau
136*04fd306cSNickeau      // If the the requested characters is not available on the token stream
137*04fd306cSNickeau      // and this state is allowed return a special ANY token
138*04fd306cSNickeau      if ($allowEndOfTokens === TRUE && (!isset($this->_tokens[$position]))) {
139*04fd306cSNickeau        return new Scanner\Token(Scanner\Token::ANY, '', 0);
140*04fd306cSNickeau      }
141*04fd306cSNickeau
142*04fd306cSNickeau      foreach($expectedTokens as $token) {
143*04fd306cSNickeau        if ($this->matchToken($position, $token)) {
144*04fd306cSNickeau          return $this->_tokens[$position];
145*04fd306cSNickeau        }
146*04fd306cSNickeau      }
147*04fd306cSNickeau
148*04fd306cSNickeau      // None of the given tokens matched
149*04fd306cSNickeau      throw $this->handleMismatch($expectedTokens, $position);
150*04fd306cSNickeau    }
151*04fd306cSNickeau
152*04fd306cSNickeau    /**
153*04fd306cSNickeau     * Validate if the of the token stream is reached. The position parameter
154*04fd306cSNickeau     * may be provided to look forward.
155*04fd306cSNickeau     *
156*04fd306cSNickeau     * @param int $position
157*04fd306cSNickeau     * @return bool
158*04fd306cSNickeau     */
159*04fd306cSNickeau    protected function endOfTokens(int $position = 0): bool {
160*04fd306cSNickeau      return (count($this->_tokens) <= $position);
161*04fd306cSNickeau    }
162*04fd306cSNickeau
163*04fd306cSNickeau    /**
164*04fd306cSNickeau     * Try to read any of the $expectedTokens from the token stream and remove them
165*04fd306cSNickeau     * from it.
166*04fd306cSNickeau     *
167*04fd306cSNickeau     * This method tries to match the current token list against all of the
168*04fd306cSNickeau     * provided tokens. Matching tokens are removed from the list until a non
169*04fd306cSNickeau     * matching token is found or the token list ends.
170*04fd306cSNickeau     *
171*04fd306cSNickeau     * The $expectedTokens parameter may be an array of tokens or a scalar
172*04fd306cSNickeau     * value, which is handled the same way an array with only one entry would
173*04fd306cSNickeau     * be.
174*04fd306cSNickeau     *
175*04fd306cSNickeau     * The special Token Scanner\Token::ANY is not valid here.
176*04fd306cSNickeau     *
177*04fd306cSNickeau     * The method return TRUE if tokens were removed, otherwise FALSE.
178*04fd306cSNickeau     *
179*04fd306cSNickeau     * @param array|integer|string $expectedTokens
180*04fd306cSNickeau     * @param boolean
181*04fd306cSNickeau     * @return bool
182*04fd306cSNickeau     */
183*04fd306cSNickeau    protected function ignore($expectedTokens): bool {
184*04fd306cSNickeau      // Allow scalar token values for better readability
185*04fd306cSNickeau      if (!is_array($expectedTokens)) {
186*04fd306cSNickeau        return $this->ignore(array($expectedTokens));
187*04fd306cSNickeau      }
188*04fd306cSNickeau
189*04fd306cSNickeau      // increase position until the end of the token stream is reached or
190*04fd306cSNickeau      // a non matching token is found
191*04fd306cSNickeau      $position = 0;
192*04fd306cSNickeau      $found = FALSE;
193*04fd306cSNickeau      while (count($this->_tokens) > $position) {
194*04fd306cSNickeau        foreach ($expectedTokens as $token) {
195*04fd306cSNickeau          if ($found = $this->matchToken($position, $token)) {
196*04fd306cSNickeau            ++$position;
197*04fd306cSNickeau          }
198*04fd306cSNickeau        }
199*04fd306cSNickeau        if ($found) {
200*04fd306cSNickeau          continue;
201*04fd306cSNickeau        }
202*04fd306cSNickeau        break;
203*04fd306cSNickeau      }
204*04fd306cSNickeau
205*04fd306cSNickeau      // remove the tokens from the stream
206*04fd306cSNickeau      if ($position > 0) {
207*04fd306cSNickeau        array_splice($this->_tokens, 0, $position);
208*04fd306cSNickeau        return TRUE;
209*04fd306cSNickeau      }
210*04fd306cSNickeau      return FALSE;
211*04fd306cSNickeau    }
212*04fd306cSNickeau
213*04fd306cSNickeau    /**
214*04fd306cSNickeau     * Delegate the parsing process to a subparser
215*04fd306cSNickeau     *
216*04fd306cSNickeau     * The result of the subparser is returned
217*04fd306cSNickeau     *
218*04fd306cSNickeau     * Only the name of the subparser is expected here, the method takes care
219*04fd306cSNickeau     * of providing the current token stream as well as instantiating the
220*04fd306cSNickeau     * subparser.
221*04fd306cSNickeau     *
222*04fd306cSNickeau     * @param string $parserClass
223*04fd306cSNickeau     * @return Ast\Node
224*04fd306cSNickeau     */
225*04fd306cSNickeau    protected function delegate(string $parserClass): Ast\Node {
226*04fd306cSNickeau      /** @var Parser $parser */
227*04fd306cSNickeau      $parser = new $parserClass($this->_tokens);
228*04fd306cSNickeau      return $parser->parse();
229*04fd306cSNickeau    }
230*04fd306cSNickeau
231*04fd306cSNickeau    /**
232*04fd306cSNickeau     * Match a token on the token stream against a token type.
233*04fd306cSNickeau     *
234*04fd306cSNickeau     * Returns true if the token at the given position exists and the provided
235*04fd306cSNickeau     * token type matches type of the token at this position, false otherwise.
236*04fd306cSNickeau     *
237*04fd306cSNickeau     * @param int $position
238*04fd306cSNickeau     * @param int $type
239*04fd306cSNickeau     * @return bool
240*04fd306cSNickeau     */
241*04fd306cSNickeau    protected function matchToken(int $position, int $type): bool {
242*04fd306cSNickeau      if (!isset($this->_tokens[$position])) {
243*04fd306cSNickeau        return false;
244*04fd306cSNickeau      }
245*04fd306cSNickeau
246*04fd306cSNickeau      if ($type === Scanner\Token::ANY) {
247*04fd306cSNickeau        // A token has been found. We do not care which one it was
248*04fd306cSNickeau        return true;
249*04fd306cSNickeau      }
250*04fd306cSNickeau
251*04fd306cSNickeau      return ($this->_tokens[$position]->type === $type);
252*04fd306cSNickeau    }
253*04fd306cSNickeau
254*04fd306cSNickeau    /**
255*04fd306cSNickeau     * Handle the case if none of the expected tokens could be found.
256*04fd306cSNickeau     *
257*04fd306cSNickeau     * @param array() $expectedTokens
258*04fd306cSNickeau     * @param int $position
259*04fd306cSNickeau     * @return ParserException
260*04fd306cSNickeau     */
261*04fd306cSNickeau    private function handleMismatch($expectedTokens, $position = 0) {
262*04fd306cSNickeau      // If the token stream ended unexpectedly throw an appropriate exception
263*04fd306cSNickeau      if (!isset($this->_tokens[$position])) {
264*04fd306cSNickeau        return new Exception\UnexpectedEndOfFileException($expectedTokens);
265*04fd306cSNickeau      }
266*04fd306cSNickeau
267*04fd306cSNickeau      // We found a token but none of the expected ones.
268*04fd306cSNickeau      return new Exception\TokenMismatchException($this->_tokens[$position], $expectedTokens);
269*04fd306cSNickeau    }
270*04fd306cSNickeau  }
271*04fd306cSNickeau}
272