1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15:
16: namespace TokenReflection\Exception;
17:
18: use TokenReflection\Stream\StreamBase;
19: use TokenReflection\IReflection;
20:
21: 22: 23: 24: 25:
26: class ParseException extends StreamException
27: {
28: 29: 30: 31: 32:
33: const UNEXPECTED_TOKEN = 1101;
34:
35: 36: 37: 38: 39:
40: const LOGICAL_ERROR = 1102;
41:
42: 43: 44: 45: 46:
47: const INVALID_PARENT = 1103;
48:
49: 50: 51: 52: 53:
54: const SOURCE_LINES_AROUND = 5;
55:
56: 57: 58: 59: 60:
61: private $token;
62:
63: 64: 65: 66: 67:
68: private $tokenName;
69:
70: 71: 72: 73: 74:
75: private $exceptionLine;
76:
77: 78: 79: 80: 81:
82: private $scopeBoundaries = array();
83:
84: 85: 86: 87: 88:
89: private $sender;
90:
91: 92: 93: 94: 95: 96: 97: 98:
99: public function __construct(IReflection $sender, StreamBase $tokenStream, $message, $code)
100: {
101: parent::__construct($tokenStream, $message, $code);
102:
103: $this->sender = $sender;
104:
105: $token = $tokenStream->current();
106: $position = $tokenStream->key();
107:
108: if (!empty($token) && !empty($position)) {
109: $this->token = $token;
110: $this->tokenName = $tokenStream->getTokenName();
111:
112: $line = $this->token[2];
113: $min = $max = $position;
114: } else {
115: $min = $max = $tokenStream->count() - 1;
116: $line = $tokenStream[$min][2];
117: }
118:
119: $this->exceptionLine = $line;
120:
121: static $skip = array(T_WHITESPACE => true, T_COMMENT => true, T_DOC_COMMENT => true);
122:
123: $significant = array();
124: while (isset($tokenStream[$min - 1])) {
125: if (!isset($significant[$tokenStream[$min][2]])) {
126: if (self::SOURCE_LINES_AROUND <= array_sum($significant)) {
127: break;
128: }
129:
130: $significant[$tokenStream[$min][2]] = !isset($skip[$tokenStream[$min][0]]);
131: } else {
132: $significant[$tokenStream[$min][2]] |= !isset($skip[$tokenStream[$min][0]]);
133: }
134:
135: $min--;
136: }
137:
138: $significant = array();
139: while (isset($tokenStream[$max + 1])) {
140: if (!isset($significant[$tokenStream[$max][2]])) {
141: if (self::SOURCE_LINES_AROUND <= array_sum($significant)) {
142: break;
143: }
144:
145: $significant[$tokenStream[$max][2]] = !isset($skip[$tokenStream[$max][0]]);
146: } else {
147: $significant[$tokenStream[$max][2]] |= !isset($skip[$tokenStream[$max][0]]);
148: }
149:
150: $max++;
151: }
152:
153: $this->scopeBoundaries = array($min, $max);
154: }
155:
156: 157: 158: 159: 160:
161: public function getToken()
162: {
163: return $this->token;
164: }
165:
166: 167: 168: 169: 170:
171: public function getTokenName()
172: {
173: return $this->tokenName;
174: }
175:
176: 177: 178: 179: 180:
181: public function getExceptionLine()
182: {
183: return $this->exceptionLine;
184: }
185:
186: 187: 188: 189: 190:
191: public function getTokenLine()
192: {
193: return null === $this->token ? null : $this->token[2];
194: }
195:
196: 197: 198: 199: 200: 201:
202: public function getSourcePart($lineNumbers = false)
203: {
204: if (empty($this->scopeBoundaries)) {
205: return null;
206: }
207:
208: list($lo, $hi) = $this->scopeBoundaries;
209: $stream = $this->getStream();
210:
211: $code = $stream->getSourcePart($lo, $hi);
212:
213: if ($lineNumbers) {
214: $lines = explode("\n", $code);
215:
216: $startLine = $stream[$lo][2];
217: $width = strlen($startLine + count($lines) - 1);
218: $errorLine = $this->token[2];
219: $actualLine = $startLine;
220:
221: $code = implode(
222: "\n",
223: array_map(function($line) use (&$actualLine, $width, $errorLine) {
224: return ($actualLine === $errorLine ? '*' : ' ') . str_pad($actualLine++, $width, ' ', STR_PAD_LEFT) . ': ' . $line;
225: }, $lines)
226: );
227: }
228:
229: return $code;
230: }
231:
232: 233: 234: 235: 236:
237: public function getSender()
238: {
239: return $this->sender;
240: }
241:
242: 243: 244: 245: 246:
247: public function getDetail()
248: {
249: if (0 === $this->getStream()->count()) {
250: return parent::getDetail() . 'The token stream was empty.';
251: } elseif (empty($this->token)) {
252: return parent::getDetail() . 'The token stream was read out of its bounds.';
253: } else {
254: return parent::getDetail() .
255: sprintf(
256: "\nThe cause of the exception was the %s token (line %s) in following part of %s source code:\n\n%s",
257: $this->tokenName,
258: $this->token[2],
259: $this->sender && $this->sender->getName() ? $this->sender->getPrettyName() : 'the',
260: $this->getSourcePart(true)
261: );
262: }
263: }
264: }
265: