PageRenderTime 57ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/upload/system/storage/vendor/mtdowling/jmespath.php/src/Parser.php

https://github.com/ocStore/ocStore
PHP | 519 lines | 416 code | 70 blank | 33 comment | 50 complexity | e45f38b21830e1815d45420f4f3bebe5 MD5 | raw file
  1. <?php
  2. namespace JmesPath;
  3. use JmesPath\Lexer as T;
  4. /**
  5. * JMESPath Pratt parser
  6. * @link http://hall.org.ua/halls/wizzard/pdf/Vaughan.Pratt.TDOP.pdf
  7. */
  8. class Parser
  9. {
  10. /** @var Lexer */
  11. private $lexer;
  12. private $tokens;
  13. private $token;
  14. private $tpos;
  15. private $expression;
  16. private static $nullToken = ['type' => T::T_EOF];
  17. private static $currentNode = ['type' => T::T_CURRENT];
  18. private static $bp = [
  19. T::T_EOF => 0,
  20. T::T_QUOTED_IDENTIFIER => 0,
  21. T::T_IDENTIFIER => 0,
  22. T::T_RBRACKET => 0,
  23. T::T_RPAREN => 0,
  24. T::T_COMMA => 0,
  25. T::T_RBRACE => 0,
  26. T::T_NUMBER => 0,
  27. T::T_CURRENT => 0,
  28. T::T_EXPREF => 0,
  29. T::T_COLON => 0,
  30. T::T_PIPE => 1,
  31. T::T_OR => 2,
  32. T::T_AND => 3,
  33. T::T_COMPARATOR => 5,
  34. T::T_FLATTEN => 9,
  35. T::T_STAR => 20,
  36. T::T_FILTER => 21,
  37. T::T_DOT => 40,
  38. T::T_NOT => 45,
  39. T::T_LBRACE => 50,
  40. T::T_LBRACKET => 55,
  41. T::T_LPAREN => 60,
  42. ];
  43. /** @var array Acceptable tokens after a dot token */
  44. private static $afterDot = [
  45. T::T_IDENTIFIER => true, // foo.bar
  46. T::T_QUOTED_IDENTIFIER => true, // foo."bar"
  47. T::T_STAR => true, // foo.*
  48. T::T_LBRACE => true, // foo[1]
  49. T::T_LBRACKET => true, // foo{a: 0}
  50. T::T_FILTER => true, // foo.[?bar==10]
  51. ];
  52. /**
  53. * @param Lexer|null $lexer Lexer used to tokenize expressions
  54. */
  55. public function __construct(Lexer $lexer = null)
  56. {
  57. $this->lexer = $lexer ?: new Lexer();
  58. }
  59. /**
  60. * Parses a JMESPath expression into an AST
  61. *
  62. * @param string $expression JMESPath expression to compile
  63. *
  64. * @return array Returns an array based AST
  65. * @throws SyntaxErrorException
  66. */
  67. public function parse($expression)
  68. {
  69. $this->expression = $expression;
  70. $this->tokens = $this->lexer->tokenize($expression);
  71. $this->tpos = -1;
  72. $this->next();
  73. $result = $this->expr();
  74. if ($this->token['type'] === T::T_EOF) {
  75. return $result;
  76. }
  77. throw $this->syntax('Did not reach the end of the token stream');
  78. }
  79. /**
  80. * Parses an expression while rbp < lbp.
  81. *
  82. * @param int $rbp Right bound precedence
  83. *
  84. * @return array
  85. */
  86. private function expr($rbp = 0)
  87. {
  88. $left = $this->{"nud_{$this->token['type']}"}();
  89. while ($rbp < self::$bp[$this->token['type']]) {
  90. $left = $this->{"led_{$this->token['type']}"}($left);
  91. }
  92. return $left;
  93. }
  94. private function nud_identifier()
  95. {
  96. $token = $this->token;
  97. $this->next();
  98. return ['type' => 'field', 'value' => $token['value']];
  99. }
  100. private function nud_quoted_identifier()
  101. {
  102. $token = $this->token;
  103. $this->next();
  104. $this->assertNotToken(T::T_LPAREN);
  105. return ['type' => 'field', 'value' => $token['value']];
  106. }
  107. private function nud_current()
  108. {
  109. $this->next();
  110. return self::$currentNode;
  111. }
  112. private function nud_literal()
  113. {
  114. $token = $this->token;
  115. $this->next();
  116. return ['type' => 'literal', 'value' => $token['value']];
  117. }
  118. private function nud_expref()
  119. {
  120. $this->next();
  121. return ['type' => T::T_EXPREF, 'children' => [$this->expr(self::$bp[T::T_EXPREF])]];
  122. }
  123. private function nud_not()
  124. {
  125. $this->next();
  126. return ['type' => T::T_NOT, 'children' => [$this->expr(self::$bp[T::T_NOT])]];
  127. }
  128. private function nud_lparen()
  129. {
  130. $this->next();
  131. $result = $this->expr(0);
  132. if ($this->token['type'] !== T::T_RPAREN) {
  133. throw $this->syntax('Unclosed `(`');
  134. }
  135. $this->next();
  136. return $result;
  137. }
  138. private function nud_lbrace()
  139. {
  140. static $validKeys = [T::T_QUOTED_IDENTIFIER => true, T::T_IDENTIFIER => true];
  141. $this->next($validKeys);
  142. $pairs = [];
  143. do {
  144. $pairs[] = $this->parseKeyValuePair();
  145. if ($this->token['type'] == T::T_COMMA) {
  146. $this->next($validKeys);
  147. }
  148. } while ($this->token['type'] !== T::T_RBRACE);
  149. $this->next();
  150. return['type' => 'multi_select_hash', 'children' => $pairs];
  151. }
  152. private function nud_flatten()
  153. {
  154. return $this->led_flatten(self::$currentNode);
  155. }
  156. private function nud_filter()
  157. {
  158. return $this->led_filter(self::$currentNode);
  159. }
  160. private function nud_star()
  161. {
  162. return $this->parseWildcardObject(self::$currentNode);
  163. }
  164. private function nud_lbracket()
  165. {
  166. $this->next();
  167. $type = $this->token['type'];
  168. if ($type == T::T_NUMBER || $type == T::T_COLON) {
  169. return $this->parseArrayIndexExpression();
  170. } elseif ($type == T::T_STAR && $this->lookahead() == T::T_RBRACKET) {
  171. return $this->parseWildcardArray();
  172. } else {
  173. return $this->parseMultiSelectList();
  174. }
  175. }
  176. private function led_lbracket(array $left)
  177. {
  178. static $nextTypes = [T::T_NUMBER => true, T::T_COLON => true, T::T_STAR => true];
  179. $this->next($nextTypes);
  180. switch ($this->token['type']) {
  181. case T::T_NUMBER:
  182. case T::T_COLON:
  183. return [
  184. 'type' => 'subexpression',
  185. 'children' => [$left, $this->parseArrayIndexExpression()]
  186. ];
  187. default:
  188. return $this->parseWildcardArray($left);
  189. }
  190. }
  191. private function led_flatten(array $left)
  192. {
  193. $this->next();
  194. return [
  195. 'type' => 'projection',
  196. 'from' => 'array',
  197. 'children' => [
  198. ['type' => T::T_FLATTEN, 'children' => [$left]],
  199. $this->parseProjection(self::$bp[T::T_FLATTEN])
  200. ]
  201. ];
  202. }
  203. private function led_dot(array $left)
  204. {
  205. $this->next(self::$afterDot);
  206. if ($this->token['type'] == T::T_STAR) {
  207. return $this->parseWildcardObject($left);
  208. }
  209. return [
  210. 'type' => 'subexpression',
  211. 'children' => [$left, $this->parseDot(self::$bp[T::T_DOT])]
  212. ];
  213. }
  214. private function led_or(array $left)
  215. {
  216. $this->next();
  217. return [
  218. 'type' => T::T_OR,
  219. 'children' => [$left, $this->expr(self::$bp[T::T_OR])]
  220. ];
  221. }
  222. private function led_and(array $left)
  223. {
  224. $this->next();
  225. return [
  226. 'type' => T::T_AND,
  227. 'children' => [$left, $this->expr(self::$bp[T::T_AND])]
  228. ];
  229. }
  230. private function led_pipe(array $left)
  231. {
  232. $this->next();
  233. return [
  234. 'type' => T::T_PIPE,
  235. 'children' => [$left, $this->expr(self::$bp[T::T_PIPE])]
  236. ];
  237. }
  238. private function led_lparen(array $left)
  239. {
  240. $args = [];
  241. $this->next();
  242. while ($this->token['type'] != T::T_RPAREN) {
  243. $args[] = $this->expr(0);
  244. if ($this->token['type'] == T::T_COMMA) {
  245. $this->next();
  246. }
  247. }
  248. $this->next();
  249. return [
  250. 'type' => 'function',
  251. 'value' => $left['value'],
  252. 'children' => $args
  253. ];
  254. }
  255. private function led_filter(array $left)
  256. {
  257. $this->next();
  258. $expression = $this->expr();
  259. if ($this->token['type'] != T::T_RBRACKET) {
  260. throw $this->syntax('Expected a closing rbracket for the filter');
  261. }
  262. $this->next();
  263. $rhs = $this->parseProjection(self::$bp[T::T_FILTER]);
  264. return [
  265. 'type' => 'projection',
  266. 'from' => 'array',
  267. 'children' => [
  268. $left ?: self::$currentNode,
  269. [
  270. 'type' => 'condition',
  271. 'children' => [$expression, $rhs]
  272. ]
  273. ]
  274. ];
  275. }
  276. private function led_comparator(array $left)
  277. {
  278. $token = $this->token;
  279. $this->next();
  280. return [
  281. 'type' => T::T_COMPARATOR,
  282. 'value' => $token['value'],
  283. 'children' => [$left, $this->expr(self::$bp[T::T_COMPARATOR])]
  284. ];
  285. }
  286. private function parseProjection($bp)
  287. {
  288. $type = $this->token['type'];
  289. if (self::$bp[$type] < 10) {
  290. return self::$currentNode;
  291. } elseif ($type == T::T_DOT) {
  292. $this->next(self::$afterDot);
  293. return $this->parseDot($bp);
  294. } elseif ($type == T::T_LBRACKET || $type == T::T_FILTER) {
  295. return $this->expr($bp);
  296. }
  297. throw $this->syntax('Syntax error after projection');
  298. }
  299. private function parseDot($bp)
  300. {
  301. if ($this->token['type'] == T::T_LBRACKET) {
  302. $this->next();
  303. return $this->parseMultiSelectList();
  304. }
  305. return $this->expr($bp);
  306. }
  307. private function parseKeyValuePair()
  308. {
  309. static $validColon = [T::T_COLON => true];
  310. $key = $this->token['value'];
  311. $this->next($validColon);
  312. $this->next();
  313. return [
  314. 'type' => 'key_val_pair',
  315. 'value' => $key,
  316. 'children' => [$this->expr()]
  317. ];
  318. }
  319. private function parseWildcardObject(array $left = null)
  320. {
  321. $this->next();
  322. return [
  323. 'type' => 'projection',
  324. 'from' => 'object',
  325. 'children' => [
  326. $left ?: self::$currentNode,
  327. $this->parseProjection(self::$bp[T::T_STAR])
  328. ]
  329. ];
  330. }
  331. private function parseWildcardArray(array $left = null)
  332. {
  333. static $getRbracket = [T::T_RBRACKET => true];
  334. $this->next($getRbracket);
  335. $this->next();
  336. return [
  337. 'type' => 'projection',
  338. 'from' => 'array',
  339. 'children' => [
  340. $left ?: self::$currentNode,
  341. $this->parseProjection(self::$bp[T::T_STAR])
  342. ]
  343. ];
  344. }
  345. /**
  346. * Parses an array index expression (e.g., [0], [1:2:3]
  347. */
  348. private function parseArrayIndexExpression()
  349. {
  350. static $matchNext = [
  351. T::T_NUMBER => true,
  352. T::T_COLON => true,
  353. T::T_RBRACKET => true
  354. ];
  355. $pos = 0;
  356. $parts = [null, null, null];
  357. $expected = $matchNext;
  358. do {
  359. if ($this->token['type'] == T::T_COLON) {
  360. $pos++;
  361. $expected = $matchNext;
  362. } elseif ($this->token['type'] == T::T_NUMBER) {
  363. $parts[$pos] = $this->token['value'];
  364. $expected = [T::T_COLON => true, T::T_RBRACKET => true];
  365. }
  366. $this->next($expected);
  367. } while ($this->token['type'] != T::T_RBRACKET);
  368. // Consume the closing bracket
  369. $this->next();
  370. if ($pos === 0) {
  371. // No colons were found so this is a simple index extraction
  372. return ['type' => 'index', 'value' => $parts[0]];
  373. }
  374. if ($pos > 2) {
  375. throw $this->syntax('Invalid array slice syntax: too many colons');
  376. }
  377. // Sliced array from start (e.g., [2:])
  378. return [
  379. 'type' => 'projection',
  380. 'from' => 'array',
  381. 'children' => [
  382. ['type' => 'slice', 'value' => $parts],
  383. $this->parseProjection(self::$bp[T::T_STAR])
  384. ]
  385. ];
  386. }
  387. private function parseMultiSelectList()
  388. {
  389. $nodes = [];
  390. do {
  391. $nodes[] = $this->expr();
  392. if ($this->token['type'] == T::T_COMMA) {
  393. $this->next();
  394. $this->assertNotToken(T::T_RBRACKET);
  395. }
  396. } while ($this->token['type'] !== T::T_RBRACKET);
  397. $this->next();
  398. return ['type' => 'multi_select_list', 'children' => $nodes];
  399. }
  400. private function syntax($msg)
  401. {
  402. return new SyntaxErrorException($msg, $this->token, $this->expression);
  403. }
  404. private function lookahead()
  405. {
  406. return (!isset($this->tokens[$this->tpos + 1]))
  407. ? T::T_EOF
  408. : $this->tokens[$this->tpos + 1]['type'];
  409. }
  410. private function next(array $match = null)
  411. {
  412. if (!isset($this->tokens[$this->tpos + 1])) {
  413. $this->token = self::$nullToken;
  414. } else {
  415. $this->token = $this->tokens[++$this->tpos];
  416. }
  417. if ($match && !isset($match[$this->token['type']])) {
  418. throw $this->syntax($match);
  419. }
  420. }
  421. private function assertNotToken($type)
  422. {
  423. if ($this->token['type'] == $type) {
  424. throw $this->syntax("Token {$this->tpos} not allowed to be $type");
  425. }
  426. }
  427. /**
  428. * @internal Handles undefined tokens without paying the cost of validation
  429. */
  430. public function __call($method, $args)
  431. {
  432. $prefix = substr($method, 0, 4);
  433. if ($prefix == 'nud_' || $prefix == 'led_') {
  434. $token = substr($method, 4);
  435. $message = "Unexpected \"$token\" token ($method). Expected one of"
  436. . " the following tokens: "
  437. . implode(', ', array_map(function ($i) {
  438. return '"' . substr($i, 4) . '"';
  439. }, array_filter(
  440. get_class_methods($this),
  441. function ($i) use ($prefix) {
  442. return strpos($i, $prefix) === 0;
  443. }
  444. )));
  445. throw $this->syntax($message);
  446. }
  447. throw new \BadMethodCallException("Call to undefined method $method");
  448. }
  449. }