Skip to content

Commit 7d2fdb1

Browse files
committed
PHP 5.2 support: Eliminate anonymous functions from generated code
This commit also replaces a complicated function `$stringEscape` that was only used to generate error messages with a much simpler equivalent (`json_encode`). In addition this functionality broke at some point. The next commit will add a bunch of tests.
1 parent 412807b commit 7d2fdb1

File tree

3 files changed

+114
-184
lines changed

3 files changed

+114
-184
lines changed

src/passes/generate-php.js

Lines changed: 38 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -557,32 +557,31 @@ module.exports = function(ast, options) {
557557
' throw $this->peg_buildException($message, null, $this->peg_reportedPos);',
558558
' }',
559559
'',
560-
' private function peg_computePosDetails($pos) {',
561-
' $self = $this;',
562-
' $advance = function(&$details, $startPos, $endPos) use($self) {',
563-
' for ($p = $startPos; $p < $endPos; $p++) {',
564-
' $ch = mb_substr($self->input, $p, 1, "UTF-8");',
565-
' if ($ch === "\\n") {',
566-
' if (!$details["seenCR"]) { $details["line"]++; }',
567-
' $details["column"] = 1;',
568-
' $details["seenCR"] = false;',
569-
' } else if ($ch === "\\r" || $ch === "\\u2028" || $ch === "\\u2029") {',
570-
' $details["line"]++;',
571-
' $details["column"] = 1;',
572-
' $details["seenCR"] = true;',
573-
' } else {',
574-
' $details["column"]++;',
575-
' $details["seenCR"] = false;',
576-
' }',
560+
' private function peg_advancePos(&$details, $startPos, $endPos) {',
561+
' for ($p = $startPos; $p < $endPos; $p++) {',
562+
' $ch = mb_substr($this->input, $p, 1, "UTF-8");',
563+
' if ($ch === "\\n") {',
564+
' if (!$details["seenCR"]) { $details["line"]++; }',
565+
' $details["column"] = 1;',
566+
' $details["seenCR"] = false;',
567+
' } else if ($ch === "\\r" || $ch === "\\u2028" || $ch === "\\u2029") {',
568+
' $details["line"]++;',
569+
' $details["column"] = 1;',
570+
' $details["seenCR"] = true;',
571+
' } else {',
572+
' $details["column"]++;',
573+
' $details["seenCR"] = false;',
577574
' }',
578-
' };',
575+
' }',
576+
' }',
579577
'',
578+
' private function peg_computePosDetails($pos) {',
580579
' if ($this->peg_cachedPos !== $pos) {',
581580
' if ($this->peg_cachedPos > $pos) {',
582581
' $this->peg_cachedPos = 0;',
583582
' $this->peg_cachedPosDetails = array( "line" => 1, "column" => 1, "seenCR" => false );',
584583
' }',
585-
' $advance($this->peg_cachedPosDetails, $this->peg_cachedPos, $pos);',
584+
' $this->peg_advancePos($this->peg_cachedPosDetails, $this->peg_cachedPos, $pos);',
586585
' $this->peg_cachedPos = $pos;',
587586
' }',
588587
'',
@@ -600,20 +599,23 @@ module.exports = function(ast, options) {
600599
' $this->peg_maxFailExpected[] = $expected;',
601600
' }',
602601
'',
603-
' private function peg_buildException($message, $expected, $pos) {',
604-
' $cleanupExpected = function (&$expected){',
605-
' $i = 1;',
602+
' private function peg_buildException_expectedComparator($a, $b) {',
603+
' if ($a["description"] < $b["description"]) {',
604+
' return -1;',
605+
' } else if ($a["description"] > $b["description"]) {',
606+
' return 1;',
607+
' } else {',
608+
' return 0;',
609+
' }',
610+
' }',
606611
'',
607-
' usort($expected, function($a, $b) {',
608-
' if ($a["description"] < $b["description"]) {',
609-
' return -1;',
610-
' } else if ($a["description"] > $b["description"]) {',
611-
' return 1;',
612-
' } else {',
613-
' return 0;',
614-
' }',
615-
' });',
612+
' private function peg_buildException($message, $expected, $pos) {',
613+
' $posDetails = $this->peg_computePosDetails($pos);',
614+
' $found = $pos < mb_strlen($this->input, "UTF-8") ? mb_substr($this->input, $pos, 1, "UTF-8") : null;',
616615
'',
616+
' if ($expected !== null) {',
617+
' usort($expected, array($this, "peg_buildException_expectedComparator"));',
618+
' $i = 1;',
617619
/*
618620
* This works because the bytecode generator guarantees that every
619621
* expectation object exists only once, so it's enough to use |===| instead
@@ -626,34 +628,9 @@ module.exports = function(ast, options) {
626628
' $i++;',
627629
' }',
628630
' }',
629-
' };',
630-
'',
631-
' $buildMessage = function ($expected, $found) {',
632-
' $stringEscape = function ($s) {',
633-
' $hex = function($ch) { return strtoupper(dechex(ord($ch[0])));};',
634-
'',
635-
/*
636-
* ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
637-
* literal except for the closing quote character, backslash, carriage
638-
* return, line separator, paragraph separator, and line feed. Any character
639-
* may appear in the form of an escape sequence.
640-
*
641-
* For portability, we also escape all control and non-ASCII characters.
642-
* Note that "\0" and "\v" escape sequences are not used because JSHint does
643-
* not like the first and IE the second.
644-
*/
645-
' $s = str_replace("\\\\", "\\\\\\\\", $s);', // backslash
646-
' $s = str_replace("\\"", "\\\\\\"", $s);', // closing double quote
647-
' $s = str_replace(\'\\x08\', \'\\\\b\', $s);', // backspace
648-
' $s = str_replace(\'\\t\', \'\\\\t\', $s);', // horizontal tab
649-
' $s = str_replace(\'\\n\', \'\\\\n\', $s);', // line feed
650-
' $s = str_replace(\'\\f\', \'\\\\f\', $s);', // form feed
651-
' $s = str_replace(\'\\r\', \'\\\\r\', $s);', // carriage return
652-
' $s = preg_replace_callback(\'/[\\\\x00-\\\\x07\\\\x0B\\\\x0E\\\\x0F]/u\', function($ch) use($hex) { return \'\\\\x0\' + $hex($ch[0]); }, $s);',
653-
' $s = preg_replace_callback(\'/[\\\\x10-\\\\x1F\\\\x80-\\\\xFF]/u\', function($ch) use($hex) { return \'\\\\x\' + $hex($ch[0]); }, $s);',
654-
' return $s;',
655-
' };',
631+
' }',
656632
'',
633+
' if ($message === null) {',
657634
' $expectedDescs = array_fill(0, count($expected), null);',
658635
'',
659636
' for ($i = 0; $i < count($expected); $i++) {',
@@ -666,20 +643,13 @@ module.exports = function(ast, options) {
666643
' . $expectedDescs[count($expected) - 1]',
667644
' : $expectedDescs[0];',
668645
'',
669-
' $foundDesc = $found ? "\\"" . $stringEscape($found) . "\\"" : "end of input";',
670-
'',
671-
' return "Expected " . $expectedDesc . " but " . $foundDesc . " found.";',
672-
' };',
673-
'',
674-
' $posDetails = $this->peg_computePosDetails($pos);',
675-
' $found = $pos < mb_strlen($this->input, "UTF-8") ? mb_substr($this->input, $pos, 1, "UTF-8") : null;',
646+
' $foundDesc = $found ? json_encode($found) : "end of input";',
676647
'',
677-
' if ($expected !== null) {',
678-
' $cleanupExpected($expected);',
648+
' $message = "Expected " . $expectedDesc . " but " . $foundDesc . " found.";',
679649
' }',
680650
'',
681651
' return new SyntaxError(',
682-
' $message !== null ? $message : $buildMessage($expected, $found),',
652+
' $message,',
683653
' $expected,',
684654
' $found,',
685655
' $pos,',

test/fixtures/wp-gutenberg-post-with-errors.php

Lines changed: 38 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -92,32 +92,31 @@ private function error($message) {
9292
throw $this->peg_buildException($message, null, $this->peg_reportedPos);
9393
}
9494

95-
private function peg_computePosDetails($pos) {
96-
$self = $this;
97-
$advance = function(&$details, $startPos, $endPos) use($self) {
98-
for ($p = $startPos; $p < $endPos; $p++) {
99-
$ch = mb_substr($self->input, $p, 1, "UTF-8");
100-
if ($ch === "\n") {
101-
if (!$details["seenCR"]) { $details["line"]++; }
102-
$details["column"] = 1;
103-
$details["seenCR"] = false;
104-
} else if ($ch === "\r" || $ch === "\u2028" || $ch === "\u2029") {
105-
$details["line"]++;
106-
$details["column"] = 1;
107-
$details["seenCR"] = true;
108-
} else {
109-
$details["column"]++;
110-
$details["seenCR"] = false;
111-
}
95+
private function peg_advancePos(&$details, $startPos, $endPos) {
96+
for ($p = $startPos; $p < $endPos; $p++) {
97+
$ch = mb_substr($this->input, $p, 1, "UTF-8");
98+
if ($ch === "\n") {
99+
if (!$details["seenCR"]) { $details["line"]++; }
100+
$details["column"] = 1;
101+
$details["seenCR"] = false;
102+
} else if ($ch === "\r" || $ch === "\u2028" || $ch === "\u2029") {
103+
$details["line"]++;
104+
$details["column"] = 1;
105+
$details["seenCR"] = true;
106+
} else {
107+
$details["column"]++;
108+
$details["seenCR"] = false;
112109
}
113-
};
110+
}
111+
}
114112

113+
private function peg_computePosDetails($pos) {
115114
if ($this->peg_cachedPos !== $pos) {
116115
if ($this->peg_cachedPos > $pos) {
117116
$this->peg_cachedPos = 0;
118117
$this->peg_cachedPosDetails = array( "line" => 1, "column" => 1, "seenCR" => false );
119118
}
120-
$advance($this->peg_cachedPosDetails, $this->peg_cachedPos, $pos);
119+
$this->peg_advancePos($this->peg_cachedPosDetails, $this->peg_cachedPos, $pos);
121120
$this->peg_cachedPos = $pos;
122121
}
123122

@@ -135,45 +134,33 @@ private function peg_fail($expected) {
135134
$this->peg_maxFailExpected[] = $expected;
136135
}
137136

138-
private function peg_buildException($message, $expected, $pos) {
139-
$cleanupExpected = function (&$expected){
140-
$i = 1;
137+
private function peg_buildException_expectedComparator($a, $b) {
138+
if ($a["description"] < $b["description"]) {
139+
return -1;
140+
} else if ($a["description"] > $b["description"]) {
141+
return 1;
142+
} else {
143+
return 0;
144+
}
145+
}
141146

142-
usort($expected, function($a, $b) {
143-
if ($a["description"] < $b["description"]) {
144-
return -1;
145-
} else if ($a["description"] > $b["description"]) {
146-
return 1;
147-
} else {
148-
return 0;
149-
}
150-
});
147+
private function peg_buildException($message, $expected, $pos) {
148+
$posDetails = $this->peg_computePosDetails($pos);
149+
$found = $pos < mb_strlen($this->input, "UTF-8") ? mb_substr($this->input, $pos, 1, "UTF-8") : null;
151150

151+
if ($expected !== null) {
152+
usort($expected, array($this, "peg_buildException_expectedComparator"));
153+
$i = 1;
152154
while ($i < count($expected)) {
153155
if ($expected[$i - 1] === $expected[$i]) {
154156
array_splice($expected, $i, 1);
155157
} else {
156158
$i++;
157159
}
158160
}
159-
};
160-
161-
$buildMessage = function ($expected, $found) {
162-
$stringEscape = function ($s) {
163-
$hex = function($ch) { return strtoupper(dechex(ord($ch[0])));};
164-
165-
$s = str_replace("\\", "\\\\", $s);
166-
$s = str_replace("\"", "\\\"", $s);
167-
$s = str_replace('\x08', '\\b', $s);
168-
$s = str_replace('\t', '\\t', $s);
169-
$s = str_replace('\n', '\\n', $s);
170-
$s = str_replace('\f', '\\f', $s);
171-
$s = str_replace('\r', '\\r', $s);
172-
$s = preg_replace_callback('/[\\x00-\\x07\\x0B\\x0E\\x0F]/u', function($ch) use($hex) { return '\\x0' + $hex($ch[0]); }, $s);
173-
$s = preg_replace_callback('/[\\x10-\\x1F\\x80-\\xFF]/u', function($ch) use($hex) { return '\\x' + $hex($ch[0]); }, $s);
174-
return $s;
175-
};
161+
}
176162

163+
if ($message === null) {
177164
$expectedDescs = array_fill(0, count($expected), null);
178165

179166
for ($i = 0; $i < count($expected); $i++) {
@@ -186,20 +173,13 @@ private function peg_buildException($message, $expected, $pos) {
186173
. $expectedDescs[count($expected) - 1]
187174
: $expectedDescs[0];
188175

189-
$foundDesc = $found ? "\"" . $stringEscape($found) . "\"" : "end of input";
190-
191-
return "Expected " . $expectedDesc . " but " . $foundDesc . " found.";
192-
};
193-
194-
$posDetails = $this->peg_computePosDetails($pos);
195-
$found = $pos < mb_strlen($this->input, "UTF-8") ? mb_substr($this->input, $pos, 1, "UTF-8") : null;
176+
$foundDesc = $found ? json_encode($found) : "end of input";
196177

197-
if ($expected !== null) {
198-
$cleanupExpected($expected);
178+
$message = "Expected " . $expectedDesc . " but " . $foundDesc . " found.";
199179
}
200180

201181
return new SyntaxError(
202-
$message !== null ? $message : $buildMessage($expected, $found),
182+
$message,
203183
$expected,
204184
$found,
205185
$pos,

0 commit comments

Comments
 (0)