phpyLang.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. <?php declare(strict_types=1);
  2. ///////////////////////////////
  3. /// Utility regex constants ///
  4. ///////////////////////////////
  5. const LIB = '(?(DEFINE)
  6. (?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
  7. (?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
  8. (?<string>(?&singleQuotedString)|(?&doubleQuotedString))
  9. (?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
  10. (?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
  11. )';
  12. const PARAMS = '\[(?<params>[^[\]]*+(?:\[(?&params)\][^[\]]*+)*+)\]';
  13. const ARGS = '\((?<args>[^()]*+(?:\((?&args)\)[^()]*+)*+)\)';
  14. ///////////////////////////////
  15. /// Preprocessing functions ///
  16. ///////////////////////////////
  17. function preprocessGrammar($code) {
  18. $code = resolveNodes($code);
  19. $code = resolveMacros($code);
  20. $code = resolveStackAccess($code);
  21. $code = str_replace('$this', '$self', $code);
  22. return $code;
  23. }
  24. function resolveNodes($code) {
  25. return preg_replace_callback(
  26. '~\b(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
  27. function ($matches) {
  28. // recurse
  29. $matches['params'] = resolveNodes($matches['params']);
  30. $params = magicSplit(
  31. '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
  32. $matches['params']
  33. );
  34. $paramCode = '';
  35. foreach ($params as $param) {
  36. $paramCode .= $param . ', ';
  37. }
  38. return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())';
  39. },
  40. $code
  41. );
  42. }
  43. function resolveMacros($code) {
  44. return preg_replace_callback(
  45. '~\b(?<!::|->)(?!array\()(?<name>[a-z][A-Za-z]++)' . ARGS . '~',
  46. function ($matches) {
  47. // recurse
  48. $matches['args'] = resolveMacros($matches['args']);
  49. $name = $matches['name'];
  50. $args = magicSplit(
  51. '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
  52. $matches['args']
  53. );
  54. if ('attributes' === $name) {
  55. assertArgs(0, $args, $name);
  56. return '$this->getAttributes($this->tokenStartStack[#1], $this->tokenEndStack[$stackPos])';
  57. }
  58. if ('stackAttributes' === $name) {
  59. assertArgs(1, $args, $name);
  60. return '$this->getAttributes($this->tokenStartStack[' . $args[0] . '], '
  61. . ' $this->tokenEndStack[' . $args[0] . '])';
  62. }
  63. if ('init' === $name) {
  64. return '$$ = array(' . implode(', ', $args) . ')';
  65. }
  66. if ('push' === $name) {
  67. assertArgs(2, $args, $name);
  68. return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
  69. }
  70. if ('pushNormalizing' === $name) {
  71. assertArgs(2, $args, $name);
  72. return 'if (' . $args[1] . ' !== null) { ' . $args[0] . '[] = ' . $args[1] . '; } $$ = ' . $args[0] . ';';
  73. }
  74. if ('toBlock' == $name) {
  75. assertArgs(1, $args, $name);
  76. return 'if (' . $args[0] . ' instanceof Stmt\Block) { $$ = ' . $args[0] . '->stmts; } '
  77. . 'else if (' . $args[0] . ' === null) { $$ = []; } '
  78. . 'else { $$ = [' . $args[0] . ']; }';
  79. }
  80. if ('parseVar' === $name) {
  81. assertArgs(1, $args, $name);
  82. return 'substr(' . $args[0] . ', 1)';
  83. }
  84. if ('parseEncapsed' === $name) {
  85. assertArgs(3, $args, $name);
  86. return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\InterpolatedStringPart) {'
  87. . ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
  88. }
  89. if ('makeNop' === $name) {
  90. assertArgs(1, $args, $name);
  91. return $args[0] . ' = $this->maybeCreateNop($this->tokenStartStack[#1], $this->tokenEndStack[$stackPos])';
  92. }
  93. if ('makeZeroLengthNop' == $name) {
  94. assertArgs(1, $args, $name);
  95. return $args[0] . ' = $this->maybeCreateZeroLengthNop($this->tokenPos);';
  96. }
  97. return $matches[0];
  98. },
  99. $code
  100. );
  101. }
  102. function assertArgs($num, $args, $name) {
  103. if ($num != count($args)) {
  104. die('Wrong argument count for ' . $name . '().');
  105. }
  106. }
  107. function resolveStackAccess($code) {
  108. $code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code);
  109. $code = preg_replace('/#(\d+)/', '$$1', $code);
  110. return $code;
  111. }
  112. function removeTrailingWhitespace($code) {
  113. $lines = explode("\n", $code);
  114. $lines = array_map('rtrim', $lines);
  115. return implode("\n", $lines);
  116. }
  117. //////////////////////////////
  118. /// Regex helper functions ///
  119. //////////////////////////////
  120. function regex($regex) {
  121. return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
  122. }
  123. function magicSplit($regex, $string) {
  124. $pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);
  125. foreach ($pieces as &$piece) {
  126. $piece = trim($piece);
  127. }
  128. if ($pieces === ['']) {
  129. return [];
  130. }
  131. return $pieces;
  132. }