Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1431247
Emulative.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
8 KB
Referenced Files
None
Subscribers
None
Emulative.php
View Options
<?php
declare
(
strict_types
=
1
);
namespace
PhpParser\Lexer
;
use
PhpParser\Error
;
use
PhpParser\ErrorHandler
;
use
PhpParser\Lexer
;
use
PhpParser\Lexer\TokenEmulator\AttributeEmulator
;
use
PhpParser\Lexer\TokenEmulator\EnumTokenEmulator
;
use
PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator
;
use
PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator
;
use
PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator
;
use
PhpParser\Lexer\TokenEmulator\FnTokenEmulator
;
use
PhpParser\Lexer\TokenEmulator\MatchTokenEmulator
;
use
PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator
;
use
PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator
;
use
PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator
;
use
PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator
;
use
PhpParser\Lexer\TokenEmulator\ReverseEmulator
;
use
PhpParser\Lexer\TokenEmulator\TokenEmulator
;
class
Emulative
extends
Lexer
{
const
PHP_7_3
=
'7.3dev'
;
const
PHP_7_4
=
'7.4dev'
;
const
PHP_8_0
=
'8.0dev'
;
const
PHP_8_1
=
'8.1dev'
;
const
PHP_8_2
=
'8.2dev'
;
/** @var mixed[] Patches used to reverse changes introduced in the code */
private
$patches
=
[];
/** @var TokenEmulator[] */
private
$emulators
=
[];
/** @var string */
private
$targetPhpVersion
;
/**
* @param mixed[] $options Lexer options. In addition to the usual options,
* accepts a 'phpVersion' string that specifies the
* version to emulate. Defaults to newest supported.
*/
public
function
__construct
(
array
$options
=
[])
{
$this
->
targetPhpVersion
=
$options
[
'phpVersion'
]
??
Emulative
::
PHP_8_2
;
unset
(
$options
[
'phpVersion'
]);
parent
::
__construct
(
$options
);
$emulators
=
[
new
FlexibleDocStringEmulator
(),
new
FnTokenEmulator
(),
new
MatchTokenEmulator
(),
new
CoaleseEqualTokenEmulator
(),
new
NumericLiteralSeparatorEmulator
(),
new
NullsafeTokenEmulator
(),
new
AttributeEmulator
(),
new
EnumTokenEmulator
(),
new
ReadonlyTokenEmulator
(),
new
ExplicitOctalEmulator
(),
new
ReadonlyFunctionTokenEmulator
(),
];
// Collect emulators that are relevant for the PHP version we're running
// and the PHP version we're targeting for emulation.
foreach
(
$emulators
as
$emulator
)
{
$emulatorPhpVersion
=
$emulator
->
getPhpVersion
();
if
(
$this
->
isForwardEmulationNeeded
(
$emulatorPhpVersion
))
{
$this
->
emulators
[]
=
$emulator
;
}
else
if
(
$this
->
isReverseEmulationNeeded
(
$emulatorPhpVersion
))
{
$this
->
emulators
[]
=
new
ReverseEmulator
(
$emulator
);
}
}
}
public
function
startLexing
(
string
$code
,
?
ErrorHandler
$errorHandler
=
null
)
{
$emulators
=
array_filter
(
$this
->
emulators
,
function
(
$emulator
)
use
(
$code
)
{
return
$emulator
->
isEmulationNeeded
(
$code
);
});
if
(
empty
(
$emulators
))
{
// Nothing to emulate, yay
parent
::
startLexing
(
$code
,
$errorHandler
);
return
;
}
$this
->
patches
=
[];
foreach
(
$emulators
as
$emulator
)
{
$code
=
$emulator
->
preprocessCode
(
$code
,
$this
->
patches
);
}
$collector
=
new
ErrorHandler\Collecting
();
parent
::
startLexing
(
$code
,
$collector
);
$this
->
sortPatches
();
$this
->
fixupTokens
();
$errors
=
$collector
->
getErrors
();
if
(!
empty
(
$errors
))
{
$this
->
fixupErrors
(
$errors
);
foreach
(
$errors
as
$error
)
{
$errorHandler
->
handleError
(
$error
);
}
}
foreach
(
$emulators
as
$emulator
)
{
$this
->
tokens
=
$emulator
->
emulate
(
$code
,
$this
->
tokens
);
}
}
private
function
isForwardEmulationNeeded
(
string
$emulatorPhpVersion
):
bool
{
return
version_compare
(
\PHP_VERSION
,
$emulatorPhpVersion
,
'<'
)
&&
version_compare
(
$this
->
targetPhpVersion
,
$emulatorPhpVersion
,
'>='
);
}
private
function
isReverseEmulationNeeded
(
string
$emulatorPhpVersion
):
bool
{
return
version_compare
(
\PHP_VERSION
,
$emulatorPhpVersion
,
'>='
)
&&
version_compare
(
$this
->
targetPhpVersion
,
$emulatorPhpVersion
,
'<'
);
}
private
function
sortPatches
()
{
// Patches may be contributed by different emulators.
// Make sure they are sorted by increasing patch position.
usort
(
$this
->
patches
,
function
(
$p1
,
$p2
)
{
return
$p1
[
0
]
<=>
$p2
[
0
];
});
}
private
function
fixupTokens
()
{
if
(
\count
(
$this
->
patches
)
===
0
)
{
return
;
}
// Load first patch
$patchIdx
=
0
;
list
(
$patchPos
,
$patchType
,
$patchText
)
=
$this
->
patches
[
$patchIdx
];
// We use a manual loop over the tokens, because we modify the array on the fly
$pos
=
0
;
for
(
$i
=
0
,
$c
=
\count
(
$this
->
tokens
);
$i
<
$c
;
$i
++)
{
$token
=
$this
->
tokens
[
$i
];
if
(
\is_string
(
$token
))
{
if
(
$patchPos
===
$pos
)
{
// Only support replacement for string tokens.
assert
(
$patchType
===
'replace'
);
$this
->
tokens
[
$i
]
=
$patchText
;
// Fetch the next patch
$patchIdx
++;
if
(
$patchIdx
>=
\count
(
$this
->
patches
))
{
// No more patches, we're done
return
;
}
list
(
$patchPos
,
$patchType
,
$patchText
)
=
$this
->
patches
[
$patchIdx
];
}
$pos
+=
\strlen
(
$token
);
continue
;
}
$len
=
\strlen
(
$token
[
1
]);
$posDelta
=
0
;
while
(
$patchPos
>=
$pos
&&
$patchPos
<
$pos
+
$len
)
{
$patchTextLen
=
\strlen
(
$patchText
);
if
(
$patchType
===
'remove'
)
{
if
(
$patchPos
===
$pos
&&
$patchTextLen
===
$len
)
{
// Remove token entirely
array_splice
(
$this
->
tokens
,
$i
,
1
,
[]);
$i
--;
$c
--;
}
else
{
// Remove from token string
$this
->
tokens
[
$i
][
1
]
=
substr_replace
(
$token
[
1
],
''
,
$patchPos
-
$pos
+
$posDelta
,
$patchTextLen
);
$posDelta
-=
$patchTextLen
;
}
}
elseif
(
$patchType
===
'add'
)
{
// Insert into the token string
$this
->
tokens
[
$i
][
1
]
=
substr_replace
(
$token
[
1
],
$patchText
,
$patchPos
-
$pos
+
$posDelta
,
0
);
$posDelta
+=
$patchTextLen
;
}
else
if
(
$patchType
===
'replace'
)
{
// Replace inside the token string
$this
->
tokens
[
$i
][
1
]
=
substr_replace
(
$token
[
1
],
$patchText
,
$patchPos
-
$pos
+
$posDelta
,
$patchTextLen
);
}
else
{
assert
(
false
);
}
// Fetch the next patch
$patchIdx
++;
if
(
$patchIdx
>=
\count
(
$this
->
patches
))
{
// No more patches, we're done
return
;
}
list
(
$patchPos
,
$patchType
,
$patchText
)
=
$this
->
patches
[
$patchIdx
];
// Multiple patches may apply to the same token. Reload the current one to check
// If the new patch applies
$token
=
$this
->
tokens
[
$i
];
}
$pos
+=
$len
;
}
// A patch did not apply
assert
(
false
);
}
/**
* Fixup line and position information in errors.
*
* @param Error[] $errors
*/
private
function
fixupErrors
(
array
$errors
)
{
foreach
(
$errors
as
$error
)
{
$attrs
=
$error
->
getAttributes
();
$posDelta
=
0
;
$lineDelta
=
0
;
foreach
(
$this
->
patches
as
$patch
)
{
list
(
$patchPos
,
$patchType
,
$patchText
)
=
$patch
;
if
(
$patchPos
>=
$attrs
[
'startFilePos'
])
{
// No longer relevant
break
;
}
if
(
$patchType
===
'add'
)
{
$posDelta
+=
strlen
(
$patchText
);
$lineDelta
+=
substr_count
(
$patchText
,
"
\n
"
);
}
else
if
(
$patchType
===
'remove'
)
{
$posDelta
-=
strlen
(
$patchText
);
$lineDelta
-=
substr_count
(
$patchText
,
"
\n
"
);
}
}
$attrs
[
'startFilePos'
]
+=
$posDelta
;
$attrs
[
'endFilePos'
]
+=
$posDelta
;
$attrs
[
'startLine'
]
+=
$lineDelta
;
$attrs
[
'endLine'
]
+=
$lineDelta
;
$error
->
setAttributes
(
$attrs
);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 20:13 (1 d, 1 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
47/92/a22aa34286d0267007cd14134fe6
Default Alt Text
Emulative.php (8 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment