Skip to content

Commit 8cf68fd

Browse files
kvzclaude
andauthored
feat: Improve PHP parity tests and verify 6 more functions (#528)
- Improve PHP parity test handler: - Add support for JS single-quoted strings with escape sequences (\n, \t, \r) - Add Unicode escape sequence conversion (\uXXXX → actual characters) - Fix double-quoted string skipping to avoid processing single quotes inside - Add parity verification to 6 PHP functions: - wordwrap, strcoll, quoted_printable_encode, quoted_printable_decode - ctype_cntrl, ctype_space PHP verification: 164 → 170 (53%) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7d6968a commit 8cf68fd

7 files changed

Lines changed: 151 additions & 40 deletions

File tree

src/php/ctype/ctype_cntrl.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
module.exports = function ctype_cntrl(text) {
2-
// discuss at: https://locutus.io/php/ctype_cntrl/
3-
// original by: Brett Zamir (https://brett-zamir.me)
4-
// example 1: ctype_cntrl('\u0020')
5-
// returns 1: false
6-
// example 2: ctype_cntrl('\u001F')
7-
// returns 2: true
2+
// discuss at: https://locutus.io/php/ctype_cntrl/
3+
// parity verified: PHP 8.3
4+
// original by: Brett Zamir (https://brett-zamir.me)
5+
// example 1: ctype_cntrl('\u0020')
6+
// returns 1: false
7+
// example 2: ctype_cntrl('\u001F')
8+
// returns 2: true
89

910
const setlocale = require('../strings/setlocale')
1011
if (typeof text !== 'string') {

src/php/ctype/ctype_space.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
module.exports = function ctype_space(text) {
2-
// discuss at: https://locutus.io/php/ctype_space/
3-
// original by: Brett Zamir (https://brett-zamir.me)
4-
// example 1: ctype_space('\t\n')
5-
// returns 1: true
2+
// discuss at: https://locutus.io/php/ctype_space/
3+
// parity verified: PHP 8.3
4+
// original by: Brett Zamir (https://brett-zamir.me)
5+
// example 1: ctype_space('\t\n')
6+
// returns 1: true
67

78
const setlocale = require('../strings/setlocale')
89
if (typeof text !== 'string') {

src/php/strings/quoted_printable_decode.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module.exports = function quoted_printable_decode(str) {
22
// discuss at: https://locutus.io/php/quoted_printable_decode/
3+
// parity verified: PHP 8.3
34
// original by: Ole Vrijenhoek
45
// bugfixed by: Brett Zamir (https://brett-zamir.me)
56
// bugfixed by: Theriault (https://github.com/Theriault)

src/php/strings/quoted_printable_encode.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
module.exports = function quoted_printable_encode(str) {
2-
// discuss at: https://locutus.io/php/quoted_printable_encode/
3-
// original by: Theriault (https://github.com/Theriault)
4-
// improved by: Brett Zamir (https://brett-zamir.me)
5-
// improved by: Theriault (https://github.com/Theriault)
6-
// example 1: quoted_printable_encode('a=b=c')
7-
// returns 1: 'a=3Db=3Dc'
8-
// example 2: quoted_printable_encode('abc \r\n123 \r\n')
9-
// returns 2: 'abc =20\r\n123 =20\r\n'
10-
// example 3: quoted_printable_encode('0123456789012345678901234567890123456789012345678901234567890123456789012345')
11-
// returns 3: '012345678901234567890123456789012345678901234567890123456789012345678901234=\r\n5'
2+
// discuss at: https://locutus.io/php/quoted_printable_encode/
3+
// parity verified: PHP 8.3
4+
// original by: Theriault (https://github.com/Theriault)
5+
// improved by: Brett Zamir (https://brett-zamir.me)
6+
// improved by: Theriault (https://github.com/Theriault)
7+
// example 1: quoted_printable_encode('a=b=c')
8+
// returns 1: 'a=3Db=3Dc'
9+
// example 2: quoted_printable_encode('abc \r\n123 \r\n')
10+
// returns 2: 'abc =20\r\n123 =20\r\n'
11+
// example 3: quoted_printable_encode('0123456789012345678901234567890123456789012345678901234567890123456789012345')
12+
// returns 3: '012345678901234567890123456789012345678901234567890123456789012345678901234=\r\n5'
1213

1314
const hexChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
1415
const RFC2045Encode1IN = / \r\n|\r\n|[^!-<>-~ ]/gm

src/php/strings/strcoll.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
module.exports = function strcoll(str1, str2) {
2-
// discuss at: https://locutus.io/php/strcoll/
3-
// original by: Brett Zamir (https://brett-zamir.me)
4-
// improved by: Brett Zamir (https://brett-zamir.me)
5-
// example 1: strcoll('a', 'b')
6-
// returns 1: -1
2+
// discuss at: https://locutus.io/php/strcoll/
3+
// parity verified: PHP 8.3
4+
// original by: Brett Zamir (https://brett-zamir.me)
5+
// improved by: Brett Zamir (https://brett-zamir.me)
6+
// example 1: strcoll('a', 'b')
7+
// returns 1: -1
78

89
const setlocale = require('../strings/setlocale')
910

src/php/strings/wordwrap.js

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
module.exports = function wordwrap(str, intWidth, strBreak, cut) {
2-
// discuss at: https://locutus.io/php/wordwrap/
3-
// original by: Jonas Raoni Soares Silva (https://www.jsfromhell.com)
4-
// improved by: Nick Callen
5-
// improved by: Kevin van Zonneveld (https://kvz.io)
6-
// improved by: Sakimori
7-
// revised by: Jonas Raoni Soares Silva (https://www.jsfromhell.com)
8-
// bugfixed by: Michael Grier
9-
// bugfixed by: Feras ALHAEK
10-
// improved by: Rafał Kukawski (https://kukawski.net)
11-
// example 1: wordwrap('Kevin van Zonneveld', 6, '|', true)
12-
// returns 1: 'Kevin|van|Zonnev|eld'
13-
// example 2: wordwrap('The quick brown fox jumped over the lazy dog.', 20, '<br />\n')
14-
// returns 2: 'The quick brown fox<br />\njumped over the lazy<br />\ndog.'
15-
// example 3: wordwrap('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.')
16-
// returns 3: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim\nveniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea\ncommodo consequat.'
2+
// discuss at: https://locutus.io/php/wordwrap/
3+
// parity verified: PHP 8.3
4+
// original by: Jonas Raoni Soares Silva (https://www.jsfromhell.com)
5+
// improved by: Nick Callen
6+
// improved by: Kevin van Zonneveld (https://kvz.io)
7+
// improved by: Sakimori
8+
// revised by: Jonas Raoni Soares Silva (https://www.jsfromhell.com)
9+
// bugfixed by: Michael Grier
10+
// bugfixed by: Feras ALHAEK
11+
// improved by: Rafał Kukawski (https://kukawski.net)
12+
// example 1: wordwrap('Kevin van Zonneveld', 6, '|', true)
13+
// returns 1: 'Kevin|van|Zonnev|eld'
14+
// example 2: wordwrap('The quick brown fox jumped over the lazy dog.', 20, '<br />\n')
15+
// returns 2: 'The quick brown fox<br />\njumped over the lazy<br />\ndog.'
16+
// example 3: wordwrap('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.')
17+
// returns 3: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim\nveniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea\ncommodo consequat.'
1718

1819
intWidth = arguments.length >= 2 ? +intWidth : 75
1920
strBreak = arguments.length >= 3 ? '' + strBreak : '\n'

test/parity/lib/languages/php.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,108 @@ function convertPropertyAccess(line: string): string {
185185
.join('')
186186
}
187187

188+
/**
189+
* Convert JS Unicode escape sequence to actual character
190+
* \uXXXX -> actual Unicode character
191+
*/
192+
function convertUnicodeEscape(hex: string): string {
193+
return String.fromCharCode(parseInt(hex, 16))
194+
}
195+
196+
/**
197+
* Convert JS single-quoted strings with escape sequences to PHP double-quoted strings
198+
* In JS, 'hello\nworld' interprets \n as newline
199+
* In PHP, 'hello\nworld' is literal backslash-n (no escape interpretation)
200+
* So we need to convert to double quotes when escape sequences are present
201+
*
202+
* Also handles Unicode escapes like \uXXXX by converting to actual characters
203+
*/
204+
function convertJsStringsToPhp(code: string): string {
205+
let result = ''
206+
let i = 0
207+
208+
while (i < code.length) {
209+
// Skip double-quoted strings (don't process them)
210+
if (code[i] === '"') {
211+
result += code[i]
212+
i++
213+
while (i < code.length && code[i] !== '"') {
214+
if (code[i] === '\\' && i + 1 < code.length) {
215+
result += code[i] + code[i + 1]
216+
i += 2
217+
} else {
218+
result += code[i]
219+
i++
220+
}
221+
}
222+
if (i < code.length) {
223+
result += code[i] // closing quote
224+
i++
225+
}
226+
continue
227+
}
228+
229+
// Check for single-quoted string
230+
if (code[i] === "'") {
231+
let str = ''
232+
i++
233+
let hasEscapeSequence = false
234+
let hasUnicodeEscape = false
235+
236+
while (i < code.length) {
237+
if (code[i] === '\\' && i + 1 < code.length) {
238+
const nextChar = code[i + 1]
239+
// Handle escaped single quote - don't end string
240+
if (nextChar === "'") {
241+
str += "\\'"
242+
i += 2
243+
continue
244+
}
245+
// Check for Unicode escape sequence \uXXXX
246+
if (nextChar === 'u' && i + 5 < code.length) {
247+
const hex = code.slice(i + 2, i + 6)
248+
if (/^[0-9A-Fa-f]{4}$/.test(hex)) {
249+
// Convert Unicode escape to actual character
250+
str += convertUnicodeEscape(hex)
251+
i += 6
252+
hasUnicodeEscape = true
253+
continue
254+
}
255+
}
256+
// Check for common escape sequences that need double quotes in PHP
257+
if ('nrtv0\\'.includes(nextChar)) {
258+
hasEscapeSequence = true
259+
}
260+
str += code[i] + code[i + 1]
261+
i += 2
262+
} else if (code[i] === "'") {
263+
// End of string
264+
break
265+
} else {
266+
str += code[i]
267+
i++
268+
}
269+
}
270+
i++ // skip closing quote
271+
272+
// If the string has escape sequences, convert to double quotes
273+
if (hasEscapeSequence || hasUnicodeEscape) {
274+
// Escape any existing double quotes in content, and convert \' to just '
275+
let escapedContent = str.replace(/(?<!\\)"/g, '\\"')
276+
escapedContent = escapedContent.replace(/\\'/g, "'") // In double quotes, ' doesn't need escaping
277+
result += '"' + escapedContent + '"'
278+
} else {
279+
result += "'" + str + "'"
280+
}
281+
} else {
282+
result += code[i]
283+
i++
284+
}
285+
}
286+
287+
return result
288+
}
289+
188290
/**
189291
* Convert a single JS line to PHP
190292
*/
@@ -200,6 +302,9 @@ function convertJsLineToPhp(line: string): string {
200302
// Strip trailing semicolons (we add them when building the full PHP)
201303
php = php.replace(/;+$/, '')
202304

305+
// Convert JS single-quoted strings with escapes to PHP double-quoted
306+
php = convertJsStringsToPhp(php)
307+
203308
php = php.replace(/^\s*(var|let|const)\s+/, '')
204309

205310
// JS static method conversions to PHP equivalents

0 commit comments

Comments
 (0)