|
|
@ -3,7 +3,19 @@ declare(strict_types=1); |
|
|
|
namespace MensBeam\Docopt; |
|
|
|
|
|
|
|
/** |
|
|
|
* This is as direct a port of Docopt-ng as possible in PHP |
|
|
|
* This is as direct a port of Docopt-ng as practical in PHP. |
|
|
|
* |
|
|
|
* Language notes: |
|
|
|
* - An empty list in Python is falsy like an empty array in PHP |
|
|
|
* - (None == "") is false in Python, but true in PHP |
|
|
|
* - ([1,2] + [3,4]) is equivalent to array_merge([1,2], [3,4]), and this is |
|
|
|
* used a lot in Docopt-ng; adding two arrays like this in PHP is allowed, |
|
|
|
* but is _NOT_ equivalent |
|
|
|
* - ([1,2] * 3) is equivalent to array_merge($arr = [1,2], $arr, $arr); this is rare in Docopt-ng |
|
|
|
* - The Python code uses numerous list comprehensions, many of them inefficient |
|
|
|
* for their purpose. Some are ported as calls to array_map or array_filter, |
|
|
|
* while others are ported as loops for efficiency |
|
|
|
* - func(*list) in Python is equivalent to func(...$arr) in PHP, both in calls and definitions |
|
|
|
*/ |
|
|
|
|
|
|
|
class Docopt { |
|
|
@ -121,8 +133,8 @@ class Docopt { |
|
|
|
# while groups: |
|
|
|
while ($groups) { |
|
|
|
# children = groups.pop(0) |
|
|
|
$children = array_shift($groups); |
|
|
|
# parents = [Required, NotRequired, OptionsShortcut, Either, OneOrMore] |
|
|
|
$children = array_shift($groups); |
|
|
|
$parents = [Required::class, NotRequired::class, OptionsShortcut::class, Either::class, OneOrMore::class]; |
|
|
|
# if any(t in map(type, children) for t in parents): |
|
|
|
// This line and the next three lines do the following: |
|
|
@ -152,12 +164,12 @@ class Docopt { |
|
|
|
# groups.append(child.children + children) |
|
|
|
if ($child instanceof Either) { |
|
|
|
foreach ($child->children as $c) { |
|
|
|
$groups[] = [$c, ...$children]; |
|
|
|
$groups[] = array_merge([$c], $children); |
|
|
|
} |
|
|
|
} elseif ($child instanceof OneOrMore) { |
|
|
|
$groups[] = [...$child->children, ...$child->children, ...$children]; |
|
|
|
$groups[] = array_merge($child->children, $child->children, $children); |
|
|
|
} else { |
|
|
|
$groups[] = [...$child->children, ...$children]; |
|
|
|
$groups[] = array_merge($child->children, $children); |
|
|
|
} |
|
|
|
} |
|
|
|
# else: |
|
|
@ -234,48 +246,116 @@ class Pattern { |
|
|
|
} |
|
|
|
|
|
|
|
# TSingleMatch = Tuple[Union[int, None], Union["LeafPattern", None]] |
|
|
|
|
|
|
|
// DEVIATION: This appears to be a type declaration: a fixed-size array of two members with types ?int and ?LeafPattern |
|
|
|
// This is not representable directly in PHP, so uses elsewhere are replaced by array type declarations |
|
|
|
|
|
|
|
# class LeafPattern(Pattern): |
|
|
|
|
|
|
|
# """Leaf/terminal node of a pattern tree.""" |
|
|
|
class LeafPattern extends Pattern { |
|
|
|
# def __repr__(self) -> str: |
|
|
|
# return "%s(%r, %r)" % (self.__class__.__name__, self.name, self.value) |
|
|
|
public function __toString(): string { |
|
|
|
// This should be the functional PHP equivalent, depending on circumstance |
|
|
|
// TODO: Revisit this |
|
|
|
return serialize($this); |
|
|
|
} |
|
|
|
|
|
|
|
# def __repr__(self) -> str: |
|
|
|
# return "%s(%r, %r)" % (self.__class__.__name__, self.name, self.value) |
|
|
|
# def single_match(self, left: List["LeafPattern"]) -> TSingleMatch: |
|
|
|
# raise NotImplementedError # pragma: no cover |
|
|
|
public function single_match(array $left): array { |
|
|
|
throw new \Exception("Not implemented"); |
|
|
|
} |
|
|
|
|
|
|
|
# def single_match(self, left: List["LeafPattern"]) -> TSingleMatch: |
|
|
|
# raise NotImplementedError # pragma: no cover |
|
|
|
# def flat(self, *types) -> List["LeafPattern"]: |
|
|
|
# return [self] if not types or type(self) in types else [] |
|
|
|
public function flat(string ...$types): array { |
|
|
|
if (!$types || in_array(get_class($this), $types)) { |
|
|
|
return [$this]; |
|
|
|
} else { |
|
|
|
return []; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
# def flat(self, *types) -> List["LeafPattern"]: |
|
|
|
# return [self] if not types or type(self) in types else [] |
|
|
|
|
|
|
|
# def match(self, left: List["LeafPattern"], collected: List["Pattern"] = None) -> Tuple[bool, List["LeafPattern"], List["Pattern"]]: |
|
|
|
# collected = [] if collected is None else collected |
|
|
|
# increment: Optional[Any] = None |
|
|
|
# pos, match = self.single_match(left) |
|
|
|
# if match is None or pos is None: |
|
|
|
# return False, left, collected |
|
|
|
# left_ = left[:pos] + left[(pos + 1) :] |
|
|
|
# same_name = [a for a in collected if a.name == self.name] |
|
|
|
# if type(self.value) == int and len(same_name) > 0: |
|
|
|
# if isinstance(same_name[0].value, int): |
|
|
|
# same_name[0].value += 1 |
|
|
|
# return True, left_, collected |
|
|
|
# if type(self.value) == int and not same_name: |
|
|
|
# match.value = 1 |
|
|
|
# return True, left_, collected + [match] |
|
|
|
# if same_name and type(self.value) == list: |
|
|
|
# if type(match.value) == str: |
|
|
|
# increment = [match.value] |
|
|
|
# if same_name[0].value is not None and increment is not None: |
|
|
|
# if isinstance(same_name[0].value, type(increment)): |
|
|
|
# same_name[0].value += increment |
|
|
|
# return True, left_, collected |
|
|
|
# elif not same_name and type(self.value) == list: |
|
|
|
# if isinstance(match.value, str): |
|
|
|
# match.value = [match.value] |
|
|
|
# return True, left_, collected + [match] |
|
|
|
# return True, left_, collected + [match] |
|
|
|
# def match(self, left: List["LeafPattern"], collected: List["Pattern"] = None) -> Tuple[bool, List["LeafPattern"], List["Pattern"]]: |
|
|
|
public function match(array $left, ?array $collected = null): array { |
|
|
|
# collected = [] if collected is None else collected |
|
|
|
# increment: Optional[Any] = None |
|
|
|
# pos, match = self.single_match(left) |
|
|
|
$collected = $collected ?? []; |
|
|
|
$increment = null; |
|
|
|
[$pos, $match] = $this->single_match($left); |
|
|
|
# if match is None or pos is None: |
|
|
|
# return False, left, collected |
|
|
|
if ($match === null || $pos === null) { |
|
|
|
return [false, $left, $collected]; |
|
|
|
} |
|
|
|
# left_ = left[:pos] + left[(pos + 1) :] |
|
|
|
$left_ = array_merge(array_slice($left, 0, $pos), array_slice($left, $pos + 1)); |
|
|
|
# same_name = [a for a in collected if a.name == self.name] |
|
|
|
// DEVIATION: Here the Python original just wants the first match, but |
|
|
|
// filters every collected pattern anyway. We will instead just find the first |
|
|
|
$same_name = []; |
|
|
|
foreach ($collected as $a) { |
|
|
|
// NOTE: The type of Pattern::$name is ?string, and in Python (None == "") is false, so a strict equality is the correct operator |
|
|
|
if ($a->name === $this->name) { |
|
|
|
$same_name[] = $a; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
# if type(self.value) == int and len(same_name) > 0: |
|
|
|
# if isinstance(same_name[0].value, int): |
|
|
|
# same_name[0].value += 1 |
|
|
|
# return True, left_, collected |
|
|
|
if (is_int($this->value) && sizeof($same_name) > 0) { |
|
|
|
if (is_int($same_name[0]->value)) { |
|
|
|
$same_name[0]->value++; |
|
|
|
} |
|
|
|
return [true, $left_, $collected]; |
|
|
|
} |
|
|
|
# if type(self.value) == int and not same_name: |
|
|
|
# match.value = 1 |
|
|
|
# return True, left_, collected + [match] |
|
|
|
if (is_int($this->value) && !$same_name) { |
|
|
|
$match->value = 1; |
|
|
|
return [true, $left_, array_merge($collected, [$match])]; |
|
|
|
} |
|
|
|
# if same_name and type(self.value) == list: |
|
|
|
if ($same_name && is_array($this->value)) { |
|
|
|
# if type(match.value) == str: |
|
|
|
# increment = [match.value] |
|
|
|
if (is_string($match->value)) { |
|
|
|
$increment = [$match->value]; |
|
|
|
} |
|
|
|
# if same_name[0].value is not None and increment is not None: |
|
|
|
if ($name_name[0]->value !== null and $increment !== null) { |
|
|
|
# if isinstance(same_name[0].value, type(increment)): |
|
|
|
# same_name[0].value += increment |
|
|
|
// This is a weird way of asking whether the value and |
|
|
|
// increment are both lists; increment cannot be any other |
|
|
|
// type here per the logic as written, though it is allowed |
|
|
|
// to be other types per its initializer |
|
|
|
if (is_array($same_name[0]->value) && is_array($increment)) { |
|
|
|
$same_name[0]->value = array_merge($same_name[0]->value, $increment); |
|
|
|
} |
|
|
|
} |
|
|
|
# return True, left_, collected |
|
|
|
return [true, $left_, $coollected]; |
|
|
|
} |
|
|
|
# elif not same_name and type(self.value) == list: |
|
|
|
elseif (!$same_name && is_array($this->value)) { |
|
|
|
# if isinstance(match.value, str): |
|
|
|
# match.value = [match.value] |
|
|
|
if (is_string($match->value)) { |
|
|
|
$match->value = [$match->value]; |
|
|
|
} |
|
|
|
# return True, left_, collected + [match] |
|
|
|
return [true, $left_, array_merge($collected, [$match])]; |
|
|
|
} |
|
|
|
# return True, left_, collected + [match] |
|
|
|
return [true, $left_, array_merge($collected, [$match])]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# class BranchPattern(Pattern): |
|
|
|