Compare commits

...

2 Commits

Author SHA1 Message Date
J. King 7aeb6808ba Implement LeafPattern class 2 years ago
J. King c52ee8b537 Avoid array (but not argument) unpacking 2 years ago
  1. 160
      lib/Docopt.php

160
lib/Docopt.php

@ -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):

Loading…
Cancel
Save