public function parse($argc, array $argv) {
		for ($f = false, $p = 0, $i = 0; $i < $argc; ++$i) {
			$o = $argv[$i];

			if ($o{0} === "-" && strlen($o) > 2 && $o{1} !== "-") {
				// multiple short opts, e.g. -vps
				$argc += strlen($o) - 2;
				array_splice($argv, $i, 1, array_map(function($s) {
					return "-$s";
				}, str_split(substr($o, 1))));
				$o = $argv[$i];
			} elseif ($o{0} === "-" && strlen($o) > 2 && $o{1} === "-" && 0 < ($eq = strpos($o, "="))) {
				// long opt with argument, e.g. --foo=bar
				$argc++;
				array_splice($argv, $i, 1, [
					substr($o, 0, $eq++),
					substr($o, $eq)
				]);
				$o = $argv[$i];
			} elseif ($o === "--") {
				// only positional args following
				$f = true;
				continue;
			}

			if ($f || !isset($this->spec[$o])) {
				if ($o{0} !== "-" && isset($this->spec["--$p"])) {
					$this[$p] = $o;
					if (!$this->optIsMulti($p)) {
						++$p;
					}
				} else {
					yield sprintf("Unknown option %s", $o);
				}
			} elseif (!$this->optAcceptsArg($o)) {
				$this[$o] = true;
			} elseif ($i+1 < $argc && !isset($this->spec[$argv[$i+1]])) {
				$this[$o] = $argv[++$i];
			} elseif ($this->optRequiresArg($o)) {
				yield sprintf("Option --%s requires an argument", $this->optLongName($o));
			} else {
				// OPTARG
				$this[$o] = $this->optDefaultArg($o);
			}

			if ($this->optHalts($o)) {
				return;
			}
		}
	}