Differential D20454 Diff 48950 src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php
<?php | <?php | ||||
final class PhabricatorChartFunctionArgumentParser | final class PhabricatorChartFunctionArgumentParser | ||||
extends Phobject { | extends Phobject { | ||||
private $function; | private $function; | ||||
private $rawArguments; | private $rawArguments; | ||||
private $unconsumedArguments; | private $unconsumedArguments; | ||||
private $haveAllArguments = false; | private $haveAllArguments = false; | ||||
private $unparsedArguments; | private $unparsedArguments; | ||||
private $argumentMap = array(); | private $argumentMap = array(); | ||||
private $argumentPosition = 0; | private $argumentPosition = 0; | ||||
private $argumentValues = array(); | private $argumentValues = array(); | ||||
private $repeatableArgument = null; | |||||
public function setFunction(PhabricatorChartFunction $function) { | public function setFunction(PhabricatorChartFunction $function) { | ||||
$this->function = $function; | $this->function = $function; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function getFunction() { | public function getFunction() { | ||||
return $this->function; | return $this->function; | ||||
Show All 28 Lines | if (isset($this->argumentMap[$name])) { | ||||
pht( | pht( | ||||
'Chart function "%s" emitted multiple argument specifications '. | 'Chart function "%s" emitted multiple argument specifications '. | ||||
'with the same name ("%s"). Each argument specification must have '. | 'with the same name ("%s"). Each argument specification must have '. | ||||
'a unique name.', | 'a unique name.', | ||||
$this->getFunctionArgumentSignature(), | $this->getFunctionArgumentSignature(), | ||||
$name)); | $name)); | ||||
} | } | ||||
if ($this->repeatableArgument) { | |||||
if ($spec->getRepeatable()) { | |||||
throw new Exception( | |||||
pht( | |||||
'Chart function "%s" emitted multiple repeatable argument '. | |||||
'specifications ("%s" and "%s"). Only one argument may be '. | |||||
'repeatable and it must be the last argument.', | |||||
$this->getFunctionArgumentSignature(), | |||||
$name, | |||||
$this->repeatableArgument->getName())); | |||||
} else { | |||||
throw new Exception( | |||||
pht( | |||||
'Chart function "%s" emitted a repeatable argument ("%s"), then '. | |||||
'another argument ("%s"). No arguments are permitted after a '. | |||||
'repeatable argument.', | |||||
$this->getFunctionArgumentSignature(), | |||||
$this->repeatableArgument->getName(), | |||||
$name)); | |||||
} | |||||
} | |||||
if ($spec->getRepeatable()) { | |||||
$this->repeatableArgument = $spec; | |||||
} | |||||
$this->argumentMap[$name] = $spec; | $this->argumentMap[$name] = $spec; | ||||
$this->unparsedArguments[] = $spec; | $this->unparsedArguments[] = $spec; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function parseArgument( | public function parseArgument( | ||||
PhabricatorChartFunctionArgument $spec) { | PhabricatorChartFunctionArgument $spec) { | ||||
$this->addArgument($spec); | $this->addArgument($spec); | ||||
return $this->parseArguments(); | return $this->parseArguments(); | ||||
} | } | ||||
public function setHaveAllArguments($have_all) { | public function setHaveAllArguments($have_all) { | ||||
$this->haveAllArguments = $have_all; | $this->haveAllArguments = $have_all; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function getAllArguments() { | |||||
return array_values($this->argumentMap); | |||||
} | |||||
public function parseArguments() { | public function parseArguments() { | ||||
$have_count = count($this->rawArguments); | $have_count = count($this->rawArguments); | ||||
$want_count = count($this->argumentMap); | $want_count = count($this->argumentMap); | ||||
if ($this->haveAllArguments) { | if ($this->haveAllArguments) { | ||||
if ($want_count !== $have_count) { | if ($this->repeatableArgument) { | ||||
if ($want_count > $have_count) { | |||||
throw new Exception( | |||||
pht( | |||||
'Function "%s" expects %s or more argument(s), but only %s '. | |||||
'argument(s) were provided.', | |||||
$this->getFunctionArgumentSignature(), | |||||
$want_count, | |||||
$have_count)); | |||||
} | |||||
} else if ($want_count !== $have_count) { | |||||
throw new Exception( | throw new Exception( | ||||
pht( | pht( | ||||
'Function "%s" expects %s argument(s), but %s argument(s) were '. | 'Function "%s" expects %s argument(s), but %s argument(s) were '. | ||||
'provided.', | 'provided.', | ||||
$this->getFunctionArgumentSignature(), | $this->getFunctionArgumentSignature(), | ||||
$want_count, | $want_count, | ||||
$have_count)); | $have_count)); | ||||
} | } | ||||
Show All 11 Lines | while ($this->unparsedArguments) { | ||||
$this->getFunctionArgumentSignature(), | $this->getFunctionArgumentSignature(), | ||||
$want_count, | $want_count, | ||||
$have_count)); | $have_count)); | ||||
} | } | ||||
$raw_argument = array_shift($this->unconsumedArguments); | $raw_argument = array_shift($this->unconsumedArguments); | ||||
$this->argumentPosition++; | $this->argumentPosition++; | ||||
$is_repeatable = $argument->getRepeatable(); | |||||
// If this argument is repeatable and we have more arguments, add it | |||||
// back to the end of the list so we can continue parsing. | |||||
if ($is_repeatable && $this->unconsumedArguments) { | |||||
$this->unparsedArguments[] = $argument; | |||||
} | |||||
try { | try { | ||||
$value = $argument->newValue($raw_argument); | $value = $argument->newValue($raw_argument); | ||||
} catch (Exception $ex) { | } catch (Exception $ex) { | ||||
throw new Exception( | throw new Exception( | ||||
pht( | pht( | ||||
'Argument "%s" (in position "%s") to function "%s" is '. | 'Argument "%s" (in position "%s") to function "%s" is '. | ||||
'invalid: %s', | 'invalid: %s', | ||||
$name, | $name, | ||||
$this->argumentPosition, | $this->argumentPosition, | ||||
$this->getFunctionArgumentSignature(), | $this->getFunctionArgumentSignature(), | ||||
$ex->getMessage())); | $ex->getMessage())); | ||||
} | } | ||||
if ($is_repeatable) { | |||||
if (!isset($this->argumentValues[$name])) { | |||||
$this->argumentValues[$name] = array(); | |||||
} | |||||
$this->argumentValues[$name][] = $value; | |||||
} else { | |||||
$this->argumentValues[$name] = $value; | $this->argumentValues[$name] = $value; | ||||
} | } | ||||
} | } | ||||
} | |||||
public function getArgumentValue($key) { | public function getArgumentValue($key) { | ||||
if (!array_key_exists($key, $this->argumentValues)) { | if (!array_key_exists($key, $this->argumentValues)) { | ||||
throw new Exception( | throw new Exception( | ||||
pht( | pht( | ||||
'Function "%s" is requesting an argument ("%s") that it did '. | 'Function "%s" is requesting an argument ("%s") that it did '. | ||||
'not define.', | 'not define.', | ||||
$this->getFunctionArgumentSignature(), | $this->getFunctionArgumentSignature(), | ||||
$key)); | $key)); | ||||
} | } | ||||
return $this->argumentValues[$key]; | return $this->argumentValues[$key]; | ||||
} | } | ||||
private function getFunctionArgumentSignature() { | private function getFunctionArgumentSignature() { | ||||
$argument_list = array(); | $argument_list = array(); | ||||
foreach ($this->argumentMap as $key => $spec) { | foreach ($this->argumentMap as $key => $spec) { | ||||
$argument_list[] = $key; | $argument_list[] = $key; | ||||
} | } | ||||
if (!$this->haveAllArguments) { | if (!$this->haveAllArguments || $this->repeatableArgument) { | ||||
$argument_list[] = '...'; | $argument_list[] = '...'; | ||||
} | } | ||||
return sprintf( | return sprintf( | ||||
'%s(%s)', | '%s(%s)', | ||||
$this->getFunction()->getFunctionKey(), | $this->getFunction()->getFunctionKey(), | ||||
implode(', ', $argument_list)); | implode(', ', $argument_list)); | ||||
} | } | ||||
public function getSourceFunctionArgument() { | |||||
$required_type = 'function'; | |||||
$sources = array(); | |||||
foreach ($this->argumentMap as $key => $argument) { | |||||
if (!$argument->getIsSourceFunction()) { | |||||
continue; | |||||
} | |||||
if ($argument->getType() !== $required_type) { | |||||
throw new Exception( | |||||
pht( | |||||
'Function "%s" defines an argument "%s" which is marked as a '. | |||||
'source function, but the type of this argument is not "%s".', | |||||
$this->getFunctionArgumentSignature(), | |||||
$argument->getName(), | |||||
$required_type)); | |||||
} | |||||
$sources[$key] = $argument; | |||||
} | |||||
if (!$sources) { | |||||
return null; | |||||
} | |||||
if (count($sources) > 1) { | |||||
throw new Exception( | |||||
pht( | |||||
'Function "%s" defines more than one argument as a source '. | |||||
'function (arguments: %s). Functions must have zero or one '. | |||||
'source function.', | |||||
$this->getFunctionArgumentSignature(), | |||||
implode(', ', array_keys($sources)))); | |||||
} | |||||
return head($sources); | |||||
} | |||||
} | } |