| <?php | |
| function num_val(&$n, $keyMode) | |
| { | |
| $ncopy = $n; | |
| if (!is_scalar($n)) { | |
| $ncopy = gettype($ncopy); | |
| /* (string)array() throws a notice on 5.4 */ | |
| } elseif ((string)$n === (string)(int)$n) { | |
| $n = (int)$n; | |
| return true; | |
| } elseif ($keyMode && is_string($n)) { | |
| return true; | |
| } | |
| trigger_error("Not a valid " | |
| . ($keyMode ? 'array key' : 'integer') . ": '$ncopy'", E_USER_WARNING); | |
| return false; | |
| } | |
| function conv_part($arr, $n, $keyMode) | |
| { | |
| if (!is_array($arr)) { | |
| trigger_error("The depth of the part specification is too large", | |
| E_USER_WARNING); | |
| return false; | |
| } | |
| if (empty($arr)) { | |
| trigger_error("Tried to access part of empty array", E_USER_WARNING); | |
| return false; | |
| } | |
| if (!$keyMode) { | |
| $count = count($arr); | |
| if ($n >= 0) { | |
| if ($n >= $count) { | |
| trigger_error("Index '$n' is too large for the input", E_USER_WARNING); | |
| return false; | |
| } | |
| return $n; | |
| } else { | |
| if (-($n + 1) >= count($arr)) { | |
| trigger_error("Index '$n' is too large in absolute value " | |
| . "for the input", E_USER_WARNING); | |
| return false; | |
| } | |
| return count($arr) + $n; | |
| } | |
| } else { | |
| if ($n === false) { | |
| return 0; | |
| } | |
| if ($n === true) { | |
| return count($arr) - 1; | |
| } | |
| $keys = array_keys($arr); | |
| $search = array_search($n, $keys, true); | |
| if ($search === false) { | |
| trigger_error("Index '$n' not found in input", E_USER_WARNING); | |
| } | |
| return $search; | |
| } | |
| } | |
| function do_unit($read, &$write, &$newReadTargets, &$newWriteTargets, | |
| $index, $nextType) | |
| { | |
| $value = array_values($read)[$index]; | |
| if ($nextType === 'none') { | |
| $write[] = $value; | |
| } elseif ($nextType === 'multiple') { | |
| $newSubArray = array(); | |
| $write[] =& $newSubArray; | |
| $newReadTargets[] = $value; | |
| $newWriteTargets[] =& $newSubArray; | |
| } else { //single | |
| $newReadTargets[] = $value; | |
| $newWriteTargets[] =& $write; | |
| } | |
| } | |
| function span_is_all($start, $end, $step, $keyMode) | |
| { | |
| return ($step === 1 && | |
| (($keyMode && $start === false && $end === true) | |
| || | |
| (!$keyMode && $start === 0 && $end === -1))) | |
| || | |
| ($step === -1 && | |
| (($keyMode && $start === true && $end === false) | |
| || | |
| (!$keyMode && $start === -1 && $end === 0))); | |
| } | |
| function do_work_span(&$readTargets, &$writeTargets, | |
| $start, $end, $step, $type, $keyMode) | |
| { | |
| $newReadTargets = array(); | |
| $newWriteTargets = array(); | |
| if (($count = count($readTargets)) !== count($writeTargets)) { | |
| trigger_error("Logic error in the algorithm", E_USER_ERROR); | |
| } | |
| for ($i = 0; $i < $count; $i++) { | |
| $r = $readTargets[$i]; | |
| $w =& $writeTargets[$i]; | |
| /* As an exception, if we request all elements allow empty arrays */ | |
| if ($r === array() && span_is_all($start, $end, $step, $keyMode)) { | |
| continue; | |
| } | |
| $s = conv_part($r, $start, $keyMode); | |
| $e = conv_part($r, $end, $keyMode); /* inclusive */ | |
| if ($s === false || $e === false) | |
| return false; | |
| for ($j = $s; | |
| $step > 0 && $j <= $e || $step < 0 && $j >= $e; | |
| $j += $step) | |
| { | |
| do_unit($r, $w, $newReadTargets, $newWriteTargets, $j, $type); | |
| } | |
| } | |
| $readTargets = $newReadTargets; | |
| $writeTargets = $newWriteTargets; | |
| return true; | |
| } | |
| function do_work_indexes(&$readTargets, &$writeTargets, | |
| $indexes, $type, $keyMode) | |
| { | |
| $newReadTargets = array(); | |
| $newWriteTargets = array(); | |
| if (($count = count($readTargets)) !== count($writeTargets)) { | |
| trigger_error("Logic error in the algorithm", E_USER_ERROR); | |
| } | |
| for ($i = 0; $i < $count; $i++) { | |
| $r = $readTargets[$i]; | |
| $w =& $writeTargets[$i]; | |
| foreach ($indexes as $ind) { | |
| $j = conv_part($r, $ind, $keyMode); | |
| if ($j === false) | |
| return false; | |
| do_unit($r, $w, $newReadTargets, $newWriteTargets, $j, $type); | |
| } | |
| } | |
| $readTargets = $newReadTargets; | |
| $writeTargets = $newWriteTargets; | |
| return true; | |
| } | |
| function do_work_single_index(&$readTargets, &$writeTargets, | |
| $index, $type, $keyMode) | |
| { | |
| $newReadTargets = array(); | |
| $newWriteTargets = array(); | |
| if (($count = count($readTargets)) !== count($writeTargets)) { | |
| trigger_error("Logic error in the algorithm", E_USER_ERROR); | |
| } | |
| for ($i = 0; $i < $count; $i++) { | |
| $r = $readTargets[$i]; | |
| $w =& $writeTargets[$i]; | |
| $j = conv_part($r, $index, $keyMode); | |
| if ($j === false) { | |
| return false; | |
| } | |
| // var_dump('pre_do_unit', $r, $w); | |
| do_unit($r, $w, $newReadTargets, $newWriteTargets, $j, $type); | |
| } | |
| $readTargets = $newReadTargets; | |
| $writeTargets = $newWriteTargets; | |
| return true; | |
| } | |
| function array_part($arr, array $definition, $keyMode = false) | |
| { | |
| $result = array(); | |
| $readTargets = array($arr); | |
| $firstPart = reset($definition); | |
| if (is_array($firstPart)) { | |
| $result[] = array(); | |
| $writeTargets = array(&$result[0]); | |
| } else { | |
| $writeTargets = array(&$result); | |
| } | |
| for ($depth = 0; | |
| ($partSpec = current($definition)) !== false; | |
| $depth++) { | |
| $nextPartSpec = next($definition); | |
| if ($nextPartSpec === false) | |
| $nextPartSpec = 'none'; | |
| elseif (is_array($nextPartSpec)) | |
| $nextPartSpec = 'multiple'; | |
| else | |
| $nextPartSpec = 'single'; | |
| // var_dump($readTargets, $writeTargets, $nextPartSpec); | |
| if (is_array($partSpec)) { | |
| if (key_exists('start', $partSpec) || | |
| key_exists('end', $partSpec)) { | |
| $start = $keyMode ? false /* special value */ : 0; | |
| $end = $keyMode ? true /* special value */ : -1; | |
| $step = 1; | |
| foreach ($partSpec as $k => $v) { | |
| if ($v !== null && !num_val($v, $keyMode)) { | |
| return false; | |
| } | |
| if ($k === 'start') { | |
| if ($v === null) { | |
| continue; /* allow null default to 1st element */ | |
| } | |
| $start = $v; | |
| } elseif ($k === 'end') { | |
| if ($v === null) { | |
| continue; /* allow null default to last element */ | |
| } | |
| $end = $v; | |
| } elseif ($k === 'step') { | |
| if (!num_val($v, false)) { | |
| return false; | |
| } | |
| $step = $v; | |
| if ($step === 0) { | |
| trigger_error("Step cannot be 0", E_USER_WARNING); | |
| return false; | |
| } | |
| } else { | |
| trigger_error("Span definitions should only include " | |
| . "elements with keys 'start', 'end' and 'step', " | |
| . "given '$k", E_USER_WARNING); | |
| return false; | |
| } | |
| } | |
| if ($step < 0) { | |
| /* revert defaults for start and end | |
| * if we're moving backwards */ | |
| if ($start === false) { | |
| $start = true; | |
| } | |
| if ($end === true) { | |
| $end = false; | |
| } | |
| } | |
| if (!do_work_span($readTargets, $writeTargets, | |
| $start, $end, $step, $nextPartSpec, $keyMode)) { | |
| return false; | |
| } | |
| } else { | |
| $previousKey = -1; | |
| foreach ($partSpec as $k => $v) { | |
| if ($previousKey + 1 !== $k) { | |
| trigger_error("Part definition at depth " | |
| . ($depth + 1) . " is not a sequential array"); | |
| } | |
| if (!num_val($v, $keyMode)) { | |
| return false; | |
| } | |
| } | |
| if (!do_work_indexes($readTargets, $writeTargets, | |
| $partSpec, $nextPartSpec, $keyMode)) { | |
| return false; | |
| } | |
| } | |
| } else { | |
| if (!num_val($partSpec, $keyMode)) { | |
| return false; | |
| } | |
| if (!do_work_single_index($readTargets, $writeTargets, | |
| $partSpec, $nextPartSpec, $keyMode)) | |
| return false; | |
| } | |
| } | |
| return $result[0]; | |
| } |