|   1:  | <?php declare(strict_types = 1); | 
|   2:  |  | 
|   3:  | namespace PHPStan\PhpDocParser\Parser; | 
|   4:  |  | 
|   5:  | use PHPStan\PhpDocParser\Ast; | 
|   6:  | use PHPStan\PhpDocParser\Lexer\Lexer; | 
|   7:  | use PHPStan\PhpDocParser\ParserConfig; | 
|   8:  | use function str_replace; | 
|   9:  | use function strtolower; | 
|  10:  |  | 
|  11:  | class ConstExprParser | 
|  12:  | { | 
|  13:  |  | 
|  14:  | 	private ParserConfig $config; | 
|  15:  |  | 
|  16:  | 	private bool $parseDoctrineStrings; | 
|  17:  |  | 
|  18:  | 	public function __construct( | 
|  19:  | 		ParserConfig $config | 
|  20:  | 	) | 
|  21:  | 	{ | 
|  22:  | 		$this->config = $config; | 
|  23:  | 		$this->parseDoctrineStrings = false; | 
|  24:  | 	} | 
|  25:  |  | 
|  26:  | 	 | 
|  27:  |  | 
|  28:  |  | 
|  29:  | 	public function toDoctrine(): self | 
|  30:  | 	{ | 
|  31:  | 		$self = new self($this->config); | 
|  32:  | 		$self->parseDoctrineStrings = true; | 
|  33:  | 		return $self; | 
|  34:  | 	} | 
|  35:  |  | 
|  36:  | 	public function parse(TokenIterator $tokens): Ast\ConstExpr\ConstExprNode | 
|  37:  | 	{ | 
|  38:  | 		$startLine = $tokens->currentTokenLine(); | 
|  39:  | 		$startIndex = $tokens->currentTokenIndex(); | 
|  40:  | 		if ($tokens->isCurrentTokenType(Lexer::TOKEN_FLOAT)) { | 
|  41:  | 			$value = $tokens->currentTokenValue(); | 
|  42:  | 			$tokens->next(); | 
|  43:  |  | 
|  44:  | 			return $this->enrichWithAttributes( | 
|  45:  | 				$tokens, | 
|  46:  | 				new Ast\ConstExpr\ConstExprFloatNode(str_replace('_', '', $value)), | 
|  47:  | 				$startLine, | 
|  48:  | 				$startIndex, | 
|  49:  | 			); | 
|  50:  | 		} | 
|  51:  |  | 
|  52:  | 		if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) { | 
|  53:  | 			$value = $tokens->currentTokenValue(); | 
|  54:  | 			$tokens->next(); | 
|  55:  |  | 
|  56:  | 			return $this->enrichWithAttributes( | 
|  57:  | 				$tokens, | 
|  58:  | 				new Ast\ConstExpr\ConstExprIntegerNode(str_replace('_', '', $value)), | 
|  59:  | 				$startLine, | 
|  60:  | 				$startIndex, | 
|  61:  | 			); | 
|  62:  | 		} | 
|  63:  |  | 
|  64:  | 		if ($this->parseDoctrineStrings && $tokens->isCurrentTokenType(Lexer::TOKEN_DOCTRINE_ANNOTATION_STRING)) { | 
|  65:  | 			$value = $tokens->currentTokenValue(); | 
|  66:  | 			$tokens->next(); | 
|  67:  |  | 
|  68:  | 			return $this->enrichWithAttributes( | 
|  69:  | 				$tokens, | 
|  70:  | 				new Ast\ConstExpr\DoctrineConstExprStringNode(Ast\ConstExpr\DoctrineConstExprStringNode::unescape($value)), | 
|  71:  | 				$startLine, | 
|  72:  | 				$startIndex, | 
|  73:  | 			); | 
|  74:  | 		} | 
|  75:  |  | 
|  76:  | 		if ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING, Lexer::TOKEN_DOUBLE_QUOTED_STRING)) { | 
|  77:  | 			if ($this->parseDoctrineStrings) { | 
|  78:  | 				if ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) { | 
|  79:  | 					throw new ParserException( | 
|  80:  | 						$tokens->currentTokenValue(), | 
|  81:  | 						$tokens->currentTokenType(), | 
|  82:  | 						$tokens->currentTokenOffset(), | 
|  83:  | 						Lexer::TOKEN_DOUBLE_QUOTED_STRING, | 
|  84:  | 						null, | 
|  85:  | 						$tokens->currentTokenLine(), | 
|  86:  | 					); | 
|  87:  | 				} | 
|  88:  |  | 
|  89:  | 				$value = $tokens->currentTokenValue(); | 
|  90:  | 				$tokens->next(); | 
|  91:  |  | 
|  92:  | 				return $this->enrichWithAttributes( | 
|  93:  | 					$tokens, | 
|  94:  | 					$this->parseDoctrineString($value, $tokens), | 
|  95:  | 					$startLine, | 
|  96:  | 					$startIndex, | 
|  97:  | 				); | 
|  98:  | 			} | 
|  99:  |  | 
| 100:  | 			$value = StringUnescaper::unescapeString($tokens->currentTokenValue()); | 
| 101:  | 			$type = $tokens->currentTokenType(); | 
| 102:  | 			$tokens->next(); | 
| 103:  |  | 
| 104:  | 			return $this->enrichWithAttributes( | 
| 105:  | 				$tokens, | 
| 106:  | 				new Ast\ConstExpr\ConstExprStringNode( | 
| 107:  | 					$value, | 
| 108:  | 					$type === Lexer::TOKEN_SINGLE_QUOTED_STRING | 
| 109:  | 						? Ast\ConstExpr\ConstExprStringNode::SINGLE_QUOTED | 
| 110:  | 						: Ast\ConstExpr\ConstExprStringNode::DOUBLE_QUOTED, | 
| 111:  | 				), | 
| 112:  | 				$startLine, | 
| 113:  | 				$startIndex, | 
| 114:  | 			); | 
| 115:  |  | 
| 116:  | 		} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) { | 
| 117:  | 			$identifier = $tokens->currentTokenValue(); | 
| 118:  | 			$tokens->next(); | 
| 119:  |  | 
| 120:  | 			switch (strtolower($identifier)) { | 
| 121:  | 				case 'true': | 
| 122:  | 					return $this->enrichWithAttributes( | 
| 123:  | 						$tokens, | 
| 124:  | 						new Ast\ConstExpr\ConstExprTrueNode(), | 
| 125:  | 						$startLine, | 
| 126:  | 						$startIndex, | 
| 127:  | 					); | 
| 128:  | 				case 'false': | 
| 129:  | 					return $this->enrichWithAttributes( | 
| 130:  | 						$tokens, | 
| 131:  | 						new Ast\ConstExpr\ConstExprFalseNode(), | 
| 132:  | 						$startLine, | 
| 133:  | 						$startIndex, | 
| 134:  | 					); | 
| 135:  | 				case 'null': | 
| 136:  | 					return $this->enrichWithAttributes( | 
| 137:  | 						$tokens, | 
| 138:  | 						new Ast\ConstExpr\ConstExprNullNode(), | 
| 139:  | 						$startLine, | 
| 140:  | 						$startIndex, | 
| 141:  | 					); | 
| 142:  | 				case 'array': | 
| 143:  | 					$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); | 
| 144:  | 					return $this->parseArray($tokens, Lexer::TOKEN_CLOSE_PARENTHESES, $startIndex); | 
| 145:  | 			} | 
| 146:  |  | 
| 147:  | 			if ($tokens->tryConsumeTokenType(Lexer::TOKEN_DOUBLE_COLON)) { | 
| 148:  | 				$classConstantName = ''; | 
| 149:  | 				$lastType = null; | 
| 150:  | 				while (true) { | 
| 151:  | 					if ($lastType !== Lexer::TOKEN_IDENTIFIER && $tokens->currentTokenType() === Lexer::TOKEN_IDENTIFIER) { | 
| 152:  | 						$classConstantName .= $tokens->currentTokenValue(); | 
| 153:  | 						$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); | 
| 154:  | 						$lastType = Lexer::TOKEN_IDENTIFIER; | 
| 155:  |  | 
| 156:  | 						continue; | 
| 157:  | 					} | 
| 158:  |  | 
| 159:  | 					if ($lastType !== Lexer::TOKEN_WILDCARD && $tokens->tryConsumeTokenType(Lexer::TOKEN_WILDCARD)) { | 
| 160:  | 						$classConstantName .= '*'; | 
| 161:  | 						$lastType = Lexer::TOKEN_WILDCARD; | 
| 162:  |  | 
| 163:  | 						if ($tokens->getSkippedHorizontalWhiteSpaceIfAny() !== '') { | 
| 164:  | 							break; | 
| 165:  | 						} | 
| 166:  |  | 
| 167:  | 						continue; | 
| 168:  | 					} | 
| 169:  |  | 
| 170:  | 					if ($lastType === null) { | 
| 171:  | 						 | 
| 172:  | 						$tokens->consumeTokenType(Lexer::TOKEN_WILDCARD); | 
| 173:  | 					} | 
| 174:  |  | 
| 175:  | 					break; | 
| 176:  | 				} | 
| 177:  |  | 
| 178:  | 				return $this->enrichWithAttributes( | 
| 179:  | 					$tokens, | 
| 180:  | 					new Ast\ConstExpr\ConstFetchNode($identifier, $classConstantName), | 
| 181:  | 					$startLine, | 
| 182:  | 					$startIndex, | 
| 183:  | 				); | 
| 184:  |  | 
| 185:  | 			} | 
| 186:  |  | 
| 187:  | 			return $this->enrichWithAttributes( | 
| 188:  | 				$tokens, | 
| 189:  | 				new Ast\ConstExpr\ConstFetchNode('', $identifier), | 
| 190:  | 				$startLine, | 
| 191:  | 				$startIndex, | 
| 192:  | 			); | 
| 193:  |  | 
| 194:  | 		} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { | 
| 195:  | 			return $this->parseArray($tokens, Lexer::TOKEN_CLOSE_SQUARE_BRACKET, $startIndex); | 
| 196:  | 		} | 
| 197:  |  | 
| 198:  | 		throw new ParserException( | 
| 199:  | 			$tokens->currentTokenValue(), | 
| 200:  | 			$tokens->currentTokenType(), | 
| 201:  | 			$tokens->currentTokenOffset(), | 
| 202:  | 			Lexer::TOKEN_IDENTIFIER, | 
| 203:  | 			null, | 
| 204:  | 			$tokens->currentTokenLine(), | 
| 205:  | 		); | 
| 206:  | 	} | 
| 207:  |  | 
| 208:  |  | 
| 209:  | 	private function parseArray(TokenIterator $tokens, int $endToken, int $startIndex): Ast\ConstExpr\ConstExprArrayNode | 
| 210:  | 	{ | 
| 211:  | 		$items = []; | 
| 212:  |  | 
| 213:  | 		$startLine = $tokens->currentTokenLine(); | 
| 214:  |  | 
| 215:  | 		if (!$tokens->tryConsumeTokenType($endToken)) { | 
| 216:  | 			do { | 
| 217:  | 				$items[] = $this->parseArrayItem($tokens); | 
| 218:  | 			} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA) && !$tokens->isCurrentTokenType($endToken)); | 
| 219:  | 			$tokens->consumeTokenType($endToken); | 
| 220:  | 		} | 
| 221:  |  | 
| 222:  | 		return $this->enrichWithAttributes( | 
| 223:  | 			$tokens, | 
| 224:  | 			new Ast\ConstExpr\ConstExprArrayNode($items), | 
| 225:  | 			$startLine, | 
| 226:  | 			$startIndex, | 
| 227:  | 		); | 
| 228:  | 	} | 
| 229:  |  | 
| 230:  |  | 
| 231:  | 	 | 
| 232:  |  | 
| 233:  |  | 
| 234:  |  | 
| 235:  | 	public function parseDoctrineString(string $text, TokenIterator $tokens): Ast\ConstExpr\DoctrineConstExprStringNode | 
| 236:  | 	{ | 
| 237:  | 		 | 
| 238:  | 		 | 
| 239:  | 		while ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING, Lexer::TOKEN_DOCTRINE_ANNOTATION_STRING)) { | 
| 240:  | 			$text .= $tokens->currentTokenValue(); | 
| 241:  | 			$tokens->next(); | 
| 242:  | 		} | 
| 243:  |  | 
| 244:  | 		return new Ast\ConstExpr\DoctrineConstExprStringNode(Ast\ConstExpr\DoctrineConstExprStringNode::unescape($text)); | 
| 245:  | 	} | 
| 246:  |  | 
| 247:  |  | 
| 248:  | 	private function parseArrayItem(TokenIterator $tokens): Ast\ConstExpr\ConstExprArrayItemNode | 
| 249:  | 	{ | 
| 250:  | 		$startLine = $tokens->currentTokenLine(); | 
| 251:  | 		$startIndex = $tokens->currentTokenIndex(); | 
| 252:  |  | 
| 253:  | 		$expr = $this->parse($tokens); | 
| 254:  |  | 
| 255:  | 		if ($tokens->tryConsumeTokenType(Lexer::TOKEN_DOUBLE_ARROW)) { | 
| 256:  | 			$key = $expr; | 
| 257:  | 			$value = $this->parse($tokens); | 
| 258:  |  | 
| 259:  | 		} else { | 
| 260:  | 			$key = null; | 
| 261:  | 			$value = $expr; | 
| 262:  | 		} | 
| 263:  |  | 
| 264:  | 		return $this->enrichWithAttributes( | 
| 265:  | 			$tokens, | 
| 266:  | 			new Ast\ConstExpr\ConstExprArrayItemNode($key, $value), | 
| 267:  | 			$startLine, | 
| 268:  | 			$startIndex, | 
| 269:  | 		); | 
| 270:  | 	} | 
| 271:  |  | 
| 272:  | 	 | 
| 273:  |  | 
| 274:  |  | 
| 275:  |  | 
| 276:  |  | 
| 277:  | 	private function enrichWithAttributes(TokenIterator $tokens, Ast\ConstExpr\ConstExprNode $node, int $startLine, int $startIndex): Ast\ConstExpr\ConstExprNode | 
| 278:  | 	{ | 
| 279:  | 		if ($this->config->useLinesAttributes) { | 
| 280:  | 			$node->setAttribute(Ast\Attribute::START_LINE, $startLine); | 
| 281:  | 			$node->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine()); | 
| 282:  | 		} | 
| 283:  |  | 
| 284:  | 		if ($this->config->useIndexAttributes) { | 
| 285:  | 			$node->setAttribute(Ast\Attribute::START_INDEX, $startIndex); | 
| 286:  | 			$node->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken()); | 
| 287:  | 		} | 
| 288:  |  | 
| 289:  | 		return $node; | 
| 290:  | 	} | 
| 291:  |  | 
| 292:  | } | 
| 293:  |  |