1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227:
<?php
namespace esperecyan\webidl\lib;
class UnionType
{
use Utility;
public static function toUnion($value, $unitTypeString, $pseudoTypes = [])
{
$flattenedTypesAndNullableNums = self::getFlattenedTypesAndNullableNums($unitTypeString);
if ($flattenedTypesAndNullableNums['numberOfNullableMemberTypes'] === 1 && is_null($value)) {
return null;
}
foreach ($flattenedTypesAndNullableNums['flattenedMemberTypes'] as $type) {
$genericTypes[$type] = self::getGenericType($type, $pseudoTypes);
}
if (is_object($value) || $value instanceof \__PHP_Incomplete_Class) {
foreach (array_keys($genericTypes, 'interface') as $interfaceType) {
try {
return Type::to($interfaceType, $value, $pseudoTypes);
} catch (\LogicException $exception) {
if ($exception instanceof \InvalidArgumentException) {
$lastInvalidArgumentException = $exception;
} elseif ($exception instanceof \DomainException) {
$lastDomainException = $exception;
} else {
throw $exception;
}
}
}
if (isset($genericTypes['object'])) {
return $value;
}
}
if (is_callable($value) && array_search('callback function', $genericTypes)) {
return $value;
}
try {
if (is_array($value) || is_object($value) || $value instanceof \__PHP_Incomplete_Class || is_null($value)) {
$dictionaryLike = array_search('record', $genericTypes) ?: array_search('dictionary', $genericTypes);
$sequenceLike = array_search('sequence', $genericTypes) ?: array_search('FrozenArray', $genericTypes);
if ($dictionaryLike !== false || $sequenceLike) {
if ($dictionaryLike !== false && $sequenceLike) {
$type = $sequenceLike;
$i = 0;
foreach (SequenceType::convertToRewindable($value) as $entryKey => $entryValue) {
if (!is_int($entryKey) || $entryKey !== $i) {
$type = $dictionaryLike;
break;
}
$i++;
}
} else {
$type = $sequenceLike ?: $dictionaryLike;
}
return Type::to($type, $value, $pseudoTypes);
}
foreach (array_keys($genericTypes, 'interface') as $interfaceType) {
if (isset($pseudoTypes[$interfaceType])
&& ($pseudoTypes[$interfaceType] === 'callback interface'
|| $pseudoTypes[$interfaceType] === 'single operation callback interface')) {
return Type::to($interfaceType, $value, $pseudoTypes);
}
}
}
if (is_bool($value) && isset($genericTypes['boolean'])) {
return $value;
}
if ((is_int($value) || is_float($value)) && ($type = array_search('numeric', $genericTypes))) {
return Type::to($type, $value);
}
if (($type = array_search('string', $genericTypes) ?: array_search('numeric', $genericTypes))) {
return Type::to($type, $value, $pseudoTypes);
}
if (isset($genericTypes['boolean'])) {
return BooleanType::toBoolean($value);
}
} catch (\LogicException $exception) {
if ($exception instanceof \InvalidArgumentException) {
$lastInvalidArgumentException = $exception;
} elseif ($exception instanceof \DomainException) {
$lastDomainException = $exception;
} else {
throw $exception;
}
}
$errorMessage = ErrorMessageCreator::create($value, $unitTypeString);
if (isset($lastDomainException)) {
throw new \DomainException($errorMessage, 0, $lastDomainException);
} elseif (isset($lastInvalidArgumentException)) {
throw new \InvalidArgumentException($errorMessage, 0, $lastInvalidArgumentException);
} else {
throw new \InvalidArgumentException($errorMessage);
}
}
public static function getFlattenedTypesAndNullableNums($unionTypeString)
{
$unionMemberTypes = preg_split(
'/^\\(|([^ (]*\\((?>[^()]+|(?1))*\\)[^ )]*)| or |\\)$/u',
$unionTypeString,
-1,
PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
);
$flattenedMemberTypes = [];
$numberOfNullableMemberTypes = 0;
foreach ($unionMemberTypes as $unionMemberType) {
preg_match('/^(?<type>(?<union>\\(.+\\))|.+?)(?<nullable>\\??)$/u', $unionMemberType, $matches);
if ($matches['nullable']) {
$numberOfNullableMemberTypes++;
}
if ($matches['union']) {
$flattenedTypesAndNullableNums = self::getFlattenedTypesAndNullableNums($matches['type']);
$flattenedMemberTypes
= array_merge($flattenedMemberTypes, $flattenedTypesAndNullableNums['flattenedMemberTypes']);
$numberOfNullableMemberTypes += $flattenedTypesAndNullableNums['numberOfNullableMemberTypes'];
} else {
$flattenedMemberTypes[] = $matches['type'];
}
}
return [
'flattenedMemberTypes' => $flattenedMemberTypes,
'numberOfNullableMemberTypes' => $numberOfNullableMemberTypes,
];
}
private static function getGenericType($type, $pseudoTypes)
{
$genericType = 'interface';
if (in_array($type, ['any', 'boolean', 'object'])) {
$genericType = (string)$type;
} elseif (in_array($type, ['[EnforceRange] byte', '[Clamp] byte', 'byte',
'[EnforceRange] octet', '[Clamp] octet', 'octet', '[EnforceRange] short', '[Clamp] short', 'short',
'[EnforceRange] unsigned short', '[Clamp] unsigned short', 'unsigned short',
'[EnforceRange] long', '[Clamp] long', 'long',
'[EnforceRange] unsigned long', '[Clamp] unsigned long', 'unsigned long',
'[EnforceRange] long long', '[Clamp] long long', 'long long',
'[EnforceRange] unsigned long long', '[Clamp] unsigned long long', 'unsigned long long',
'float', 'unrestricted float', 'double', 'unrestricted double'])) {
$genericType = 'numeric';
} elseif (in_array($type, ['DOMString', 'ByteString', 'USVString'])) {
$genericType = 'string';
} elseif (preg_match(
'/^(?:(?<nullable>.+)\\?|sequence<(?<sequence>.+)>|record<(?<recordK>(?:DOMString|USVString|ByteString)), (?<recordV>.+)>|(?<union>\\(.+\\))|FrozenArray<(?<FrozenArray>.+)>)$/u',
$type,
$matches
) === 1) {
if (!empty($matches['nullable'])) {
$genericType = 'nullable';
} elseif (!empty($matches['sequence'])) {
$genericType = 'sequence';
} elseif (!empty($matches['recordK'])) {
$genericType = 'record';
} elseif (!empty($matches['union'])) {
$genericType = 'union';
} elseif (!empty($matches['FrozenArray'])) {
$genericType = 'FrozenArray';
}
} elseif (isset($pseudoTypes[$type])) {
$pseudoType = $pseudoTypes[$type];
if ($pseudoType === 'callback function') {
$genericType = 'callback function';
} elseif (is_string($pseudoType) || isset($pseudoType[0])) {
$genericType = 'string';
} else {
$genericType = 'dictionary';
}
}
return $genericType;
}
}