src/Handlers/Kinds/ArgumentList.php
<?php
declare(strict_types=1);
namespace Smuuf\Primi\Handlers\Kinds;
use \Smuuf\Primi\Context;
use \Smuuf\Primi\Ex\InternalPostProcessSyntaxError;
use \Smuuf\Primi\Helpers\Func;
use \Smuuf\Primi\Handlers\SimpleHandler;
use \Smuuf\Primi\Handlers\HandlerFactory;
use \Smuuf\Primi\Structures\CallArgs;
/**
* Node fields:
* function: Function name.
* args: List of arguments.
* body: Node representing contents of code to execute as a function..
*/
class ArgumentList extends SimpleHandler {
protected static function handle(array $node, Context $context) {
if (!\array_key_exists('args', $node)) {
return [];
}
$args = [];
$kwargs = [];
foreach ($node['args'] as $arg) {
if (\array_key_exists('argKey', $arg)) {
$kwargs[$arg['argKey']['text']] = HandlerFactory::runNode($arg['argVal'], $context);
} else {
$result = HandlerFactory::runNode($arg['argVal'], $context);
// Argument might have been a starred expression, in which case
// the result is an array (see StarredExpression handler) - so
// let's unpack it.
// Single-starred expression is returned as a list array.
// Double-starred expression is returned as a dict array.
if (\is_array($result)) {
if (\array_is_list($result)) {
$args = [...$args, ...$result];
} else {
$kwargs = \array_replace($kwargs, $result);
}
} else {
$args[] = $result;
}
}
}
return new CallArgs($args, $kwargs);
}
public static function reduce(array &$node): void {
// Make sure this is always list, even with one item.
if (isset($node['args'])) {
$node['args'] = Func::ensure_indexed($node['args']);
}
// Handle positional and keyword arguments.
// If both types of arguments are used, keyword arguments MUST be
// places after positional arguments. So let's check it.
$foundKwargs = [];
$foundAnyKwargs = \false;
foreach ($node['args'] as $arg) {
// Detect:
// 1. literal keyword arguments, or
// 2. Keyword arguments used as starred "**kwargs" argument.
$isLiteralKwarg = isset($arg['argKey']);
$areStarredKwargs = $arg['argVal']['name'] === 'StarredExpression'
&& $arg['argVal']['stars'] === StarredExpression::STARS_TWO;
$isAnyKwarg = $isLiteralKwarg || $areStarredKwargs;
if (!$isAnyKwarg && $foundAnyKwargs) {
// This is a positional argument, but we already encountered
// some keyword argument - that's a syntax error (easier to
// check and handle here and not via grammar).
//
// This happens if calling function like:
// > result = f(1, arg_b: 2, 3)
throw new InternalPostProcessSyntaxError(
"Keyword arguments must be placed after positional arguments"
);
}
if ($isLiteralKwarg) {
$kwargKey = $arg['argKey']['text'];
// Specifying a single kwarg multiple times is a syntax error.
//
// This happens if calling function like:
// > f = (a, b, c) => {}
// > result = f(1, b: 2, b: 3, c: 4)
if (\array_key_exists($kwargKey, $foundKwargs)) {
throw new InternalPostProcessSyntaxError(
"Repeated keyword argument '$kwargKey'"
);
}
// Monitor kwargs as keys in an array for faster lookup
// via array_key_exists() above.
$foundKwargs[$kwargKey] = \null;
}
$foundAnyKwargs |= $isAnyKwarg;
}
}
}