| 148 |
lars |
1 |
<?php
|
|
|
2 |
|
|
|
3 |
///////////////////////////////
|
|
|
4 |
/// Utility regex constants ///
|
|
|
5 |
///////////////////////////////
|
|
|
6 |
|
|
|
7 |
const LIB = '(?(DEFINE)
|
|
|
8 |
(?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
|
|
|
9 |
(?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
|
|
|
10 |
(?<string>(?&singleQuotedString)|(?&doubleQuotedString))
|
|
|
11 |
(?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
|
|
|
12 |
(?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
|
|
|
13 |
)';
|
|
|
14 |
|
|
|
15 |
const PARAMS = '\[(?<params>[^[\]]*+(?:\[(?¶ms)\][^[\]]*+)*+)\]';
|
|
|
16 |
const ARGS = '\((?<args>[^()]*+(?:\((?&args)\)[^()]*+)*+)\)';
|
|
|
17 |
|
|
|
18 |
///////////////////////////////
|
|
|
19 |
/// Preprocessing functions ///
|
|
|
20 |
///////////////////////////////
|
|
|
21 |
|
|
|
22 |
function preprocessGrammar($code) {
|
|
|
23 |
$code = resolveNodes($code);
|
|
|
24 |
$code = resolveMacros($code);
|
|
|
25 |
$code = resolveStackAccess($code);
|
|
|
26 |
|
|
|
27 |
return $code;
|
|
|
28 |
}
|
|
|
29 |
|
|
|
30 |
function resolveNodes($code) {
|
|
|
31 |
return preg_replace_callback(
|
|
|
32 |
'~\b(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
|
|
|
33 |
function($matches) {
|
|
|
34 |
// recurse
|
|
|
35 |
$matches['params'] = resolveNodes($matches['params']);
|
|
|
36 |
|
|
|
37 |
$params = magicSplit(
|
|
|
38 |
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
|
|
|
39 |
$matches['params']
|
|
|
40 |
);
|
|
|
41 |
|
|
|
42 |
$paramCode = '';
|
|
|
43 |
foreach ($params as $param) {
|
|
|
44 |
$paramCode .= $param . ', ';
|
|
|
45 |
}
|
|
|
46 |
|
|
|
47 |
return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())';
|
|
|
48 |
},
|
|
|
49 |
$code
|
|
|
50 |
);
|
|
|
51 |
}
|
|
|
52 |
|
|
|
53 |
function resolveMacros($code) {
|
|
|
54 |
return preg_replace_callback(
|
|
|
55 |
'~\b(?<!::|->)(?!array\()(?<name>[a-z][A-Za-z]++)' . ARGS . '~',
|
|
|
56 |
function($matches) {
|
|
|
57 |
// recurse
|
|
|
58 |
$matches['args'] = resolveMacros($matches['args']);
|
|
|
59 |
|
|
|
60 |
$name = $matches['name'];
|
|
|
61 |
$args = magicSplit(
|
|
|
62 |
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
|
|
|
63 |
$matches['args']
|
|
|
64 |
);
|
|
|
65 |
|
|
|
66 |
if ('attributes' === $name) {
|
|
|
67 |
assertArgs(0, $args, $name);
|
|
|
68 |
return '$this->startAttributeStack[#1] + $this->endAttributes';
|
|
|
69 |
}
|
|
|
70 |
|
|
|
71 |
if ('stackAttributes' === $name) {
|
|
|
72 |
assertArgs(1, $args, $name);
|
|
|
73 |
return '$this->startAttributeStack[' . $args[0] . ']'
|
|
|
74 |
. ' + $this->endAttributeStack[' . $args[0] . ']';
|
|
|
75 |
}
|
|
|
76 |
|
|
|
77 |
if ('init' === $name) {
|
|
|
78 |
return '$$ = array(' . implode(', ', $args) . ')';
|
|
|
79 |
}
|
|
|
80 |
|
|
|
81 |
if ('push' === $name) {
|
|
|
82 |
assertArgs(2, $args, $name);
|
|
|
83 |
|
|
|
84 |
return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
|
|
|
85 |
}
|
|
|
86 |
|
|
|
87 |
if ('pushNormalizing' === $name) {
|
|
|
88 |
assertArgs(2, $args, $name);
|
|
|
89 |
|
|
|
90 |
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
|
|
|
91 |
. ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
|
|
|
92 |
}
|
|
|
93 |
|
|
|
94 |
if ('toArray' == $name) {
|
|
|
95 |
assertArgs(1, $args, $name);
|
|
|
96 |
|
|
|
97 |
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
|
|
|
98 |
}
|
|
|
99 |
|
|
|
100 |
if ('parseVar' === $name) {
|
|
|
101 |
assertArgs(1, $args, $name);
|
|
|
102 |
|
|
|
103 |
return 'substr(' . $args[0] . ', 1)';
|
|
|
104 |
}
|
|
|
105 |
|
|
|
106 |
if ('parseEncapsed' === $name) {
|
|
|
107 |
assertArgs(3, $args, $name);
|
|
|
108 |
|
|
|
109 |
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
|
|
|
110 |
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
|
|
|
111 |
}
|
|
|
112 |
|
|
|
113 |
if ('makeNop' === $name) {
|
|
|
114 |
assertArgs(3, $args, $name);
|
|
|
115 |
|
|
|
116 |
return '$startAttributes = ' . $args[1] . ';'
|
|
|
117 |
. ' if (isset($startAttributes[\'comments\']))'
|
|
|
118 |
. ' { ' . $args[0] . ' = new Stmt\Nop($startAttributes + ' . $args[2] . '); }'
|
|
|
119 |
. ' else { ' . $args[0] . ' = null; }';
|
|
|
120 |
}
|
|
|
121 |
|
|
|
122 |
if ('makeZeroLengthNop' == $name) {
|
|
|
123 |
assertArgs(2, $args, $name);
|
|
|
124 |
|
|
|
125 |
return '$startAttributes = ' . $args[1] . ';'
|
|
|
126 |
. ' if (isset($startAttributes[\'comments\']))'
|
|
|
127 |
. ' { ' . $args[0] . ' = new Stmt\Nop($this->createCommentNopAttributes($startAttributes[\'comments\'])); }'
|
|
|
128 |
. ' else { ' . $args[0] . ' = null; }';
|
|
|
129 |
}
|
|
|
130 |
|
|
|
131 |
if ('prependLeadingComments' === $name) {
|
|
|
132 |
assertArgs(1, $args, $name);
|
|
|
133 |
|
|
|
134 |
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '
|
|
|
135 |
. 'if (!empty($attrs[\'comments\'])) {'
|
|
|
136 |
. '$stmts[0]->setAttribute(\'comments\', '
|
|
|
137 |
. 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
|
|
|
138 |
}
|
|
|
139 |
|
|
|
140 |
return $matches[0];
|
|
|
141 |
},
|
|
|
142 |
$code
|
|
|
143 |
);
|
|
|
144 |
}
|
|
|
145 |
|
|
|
146 |
function assertArgs($num, $args, $name) {
|
|
|
147 |
if ($num != count($args)) {
|
|
|
148 |
die('Wrong argument count for ' . $name . '().');
|
|
|
149 |
}
|
|
|
150 |
}
|
|
|
151 |
|
|
|
152 |
function resolveStackAccess($code) {
|
|
|
153 |
$code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code);
|
|
|
154 |
$code = preg_replace('/#(\d+)/', '$$1', $code);
|
|
|
155 |
return $code;
|
|
|
156 |
}
|
|
|
157 |
|
|
|
158 |
function removeTrailingWhitespace($code) {
|
|
|
159 |
$lines = explode("\n", $code);
|
|
|
160 |
$lines = array_map('rtrim', $lines);
|
|
|
161 |
return implode("\n", $lines);
|
|
|
162 |
}
|
|
|
163 |
|
|
|
164 |
//////////////////////////////
|
|
|
165 |
/// Regex helper functions ///
|
|
|
166 |
//////////////////////////////
|
|
|
167 |
|
|
|
168 |
function regex($regex) {
|
|
|
169 |
return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
|
|
|
170 |
}
|
|
|
171 |
|
|
|
172 |
function magicSplit($regex, $string) {
|
|
|
173 |
$pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);
|
|
|
174 |
|
|
|
175 |
foreach ($pieces as &$piece) {
|
|
|
176 |
$piece = trim($piece);
|
|
|
177 |
}
|
|
|
178 |
|
|
|
179 |
if ($pieces === ['']) {
|
|
|
180 |
return [];
|
|
|
181 |
}
|
|
|
182 |
|
|
|
183 |
return $pieces;
|
|
|
184 |
}
|