1<?php
2/**
3* Scans a string and creates list of tokens.
4*
5* @license http://www.opensource.org/licenses/mit-license.php The MIT License
6* @copyright Copyright 2010-2014 PhpCss Team
7*/
8namespace PhpCss {
9
10  use UnexpectedValueException;
11
12  /**
13  * Scans a string and creates list of tokens.
14  *
15  * The actual result depends on the status, the status
16  * class does the actual token matching and generation, the scanner handles, to loops and
17  * delegations.
18  */
19  class Scanner {
20
21    /**
22    * Scanner status object
23    * @var Scanner\Status
24    */
25    private $_status;
26    /**
27    * string to parse
28    * @var string
29    */
30    private $_buffer = '';
31    /**
32    * current offset
33    * @var integer
34    */
35    private $_offset = 0;
36
37    /**
38    * Constructor, set status object
39    *
40    * @param Scanner\Status $status
41    */
42    public function __construct(Scanner\Status $status) {
43      $this->_status = $status;
44    }
45
46    /**
47     * Scan a string for tokens
48     *
49     * @param array &$target token target
50     * @param string $string content string
51     * @param integer $offset start offset
52     * @throws UnexpectedValueException
53     * @return integer new offset
54     */
55    public function scan(array &$target, string $string, int $offset = 0): int {
56      $this->_buffer = $string;
57      $this->_offset = $offset;
58      while ($token = $this->_next()) {
59        $target[] = $token;
60        // switch back to previous scanner
61        if ($this->_status->isEndToken($token)) {
62          return $this->_offset;
63        }
64        // check for status switch
65        if ($newStatus = $this->_status->getNewStatus($token)) {
66          // delegate to subscanner
67          $this->_offset = $this->_delegate($target, $newStatus);
68        }
69      }
70      if ($this->_offset < strlen($this->_buffer)) {
71        /**
72        * @todo a some substring logic for large strings
73        */
74        throw new Exception\InvalidCharacterException(
75          $this->_buffer,
76          $this->_offset,
77          $this->_status
78        );
79      }
80      return $this->_offset;
81    }
82
83    /**
84    * Get next token
85    *
86    * @return Scanner\Token|NULL
87    */
88    private function _next(): ?Scanner\Token {
89      if (($token = $this->_status->getToken($this->_buffer, $this->_offset)) &&
90          $token->length > 0) {
91        $this->_offset += $token->length;
92        return $token;
93      }
94      return NULL;
95    }
96
97    /**
98    * Got new status, delegate to subscanner.
99    *
100    * If the status returns a new status object, a new scanner is created to handle it.
101    *
102    * @param Scanner\Token[] $target
103    * @param Scanner\Status $status
104    * @return int
105    */
106    private function _delegate(array &$target, Scanner\Status $status): int {
107      $scanner = new self($status);
108      return $scanner->scan($target, $this->_buffer, $this->_offset);
109    }
110
111    public function getStatus(): Scanner\Status {
112      return $this->_status;
113    }
114  }
115}
116