Choice::shortest()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Vanderlee\Comprehend\Parser\Structure;
4
5
use InvalidArgumentException;
6
use Vanderlee\Comprehend\Core\ArgumentsTrait;
7
use Vanderlee\Comprehend\Core\Context;
8
use Vanderlee\Comprehend\Directive\Prefer;
9
use Vanderlee\Comprehend\Match\Failure;
10
use Vanderlee\Comprehend\Match\AbstractMatch;
11
use Vanderlee\Comprehend\Match\Success;
12
use Vanderlee\Comprehend\Parser\Parser;
13
14
/**
15
 * Match one of the provided parsers.
16
 *
17
 * @author Martijn
18
 */
19
class Choice extends IterableParser
20
{
21
    use ArgumentsTrait,
22
        PreferTrait;
23
24
    /**
25
     * @param mixed ...$arguments
26
     */
27 17
    public function __construct(...$arguments)
28
    {
29 17
        if (empty($arguments)) {
30 1
            throw new InvalidArgumentException('No arguments');
31
        }
32
33 16
        $this->parsers = self::getArguments($arguments);
34 16
    }
35
36
    /**
37
     * @param mixed ...$arguments
38
     *
39
     * @return self
40
     */
41 1
    public static function first(...$arguments): self
42
    {
43 1
        return (new self(...$arguments))->preferFirst();
44
    }
45
46
    /**
47
     * @param mixed ...$arguments
48
     *
49
     * @return self
50
     */
51 1
    public static function shortest(...$arguments): self
52
    {
53 1
        return (new self(...$arguments))->preferShortest();
54
    }
55
56
    /**
57
     * @param mixed ...$arguments
58
     *
59
     * @return self
60
     */
61 2
    public static function longest(...$arguments): self
62
    {
63 2
        return (new self(...$arguments))->preferLongest();
64
    }
65
66
    /**
67
     * @param string $input
68
     * @param int $offset
69
     * @param Context $context
70
     *
71
     * @return Failure|Success
72
     */
73 77
    private function parseFirst(&$input, $offset, Context $context)
74
    {
75 77
        $max = 0;
76
        /** @var Parser $parser */
77 77
        foreach ($this->parsers as $parser) {
78 77
            $match = $parser->parse($input, $offset, $context);
79 77
            if ($match instanceof Success) {
80 54
                return $this->success($input, $offset, $match->length, $match);
81
            }
82 63
            $max = max($max, $match->length);
83
        }
84
85 33
        return $this->failure($input, $offset, $max);
86
    }
87
88
    /**
89
     * Determine the longest and most successful match.
90
     *
91
     * @param AbstractMatch $attempt
92
     * @param AbstractMatch $match
93
     *
94
     * @return AbstractMatch
95
     */
96 16
    private static function determineLongestOf(AbstractMatch $attempt, AbstractMatch $match): AbstractMatch
97
    {
98 16
        if ($attempt->match === $match->match) {
99 14
            if ($attempt->length > $match->length) {
100 8
                return $attempt;
101
            }
102
103 10
            return $match;
104
        }
105
106 11
        if ($attempt instanceof Success) {
107 11
            return $attempt;
108
        }
109
110 2
        return $match;
111
    }
112
113
    /**
114
     * @param string $input
115
     * @param int $offset
116
     * @param Context $context
117
     *
118
     * @return Failure|Success
119
     */
120 16
    private function parseLongest(&$input, $offset, Context $context)
121
    {
122 16
        $match = $this->failure($input, $offset);
123
124
        /** @var Parser $parser */
125 16
        foreach ($this->parsers as $parser) {
126 16
            $match = self::determineLongestOf($parser->parse($input, $offset, $context), $match);
127
        }
128
129 16
        return ($match instanceof Success)
130 11
            ? $this->success($input, $offset, $match->length, $match)
131 16
            : $this->failure($input, $offset, $match->length);
132
    }
133
134
    /**
135
     * Determine the shortest and most successful match.
136
     *
137
     * @param AbstractMatch|null $attempt
138
     * @param AbstractMatch $match
139
     *
140
     * @return AbstractMatch
141
     */
142 15
    private static function determineShortestOf(AbstractMatch $attempt, AbstractMatch $match = null)
143
    {
144 15
        if ($match === null) {
145 15
            return $attempt;
146
        }
147
148 15
        if ($attempt instanceof Success
149 15
            && $match instanceof Failure) {
150 1
            return $attempt;
151
        }
152
153 14
        if ($attempt->match === $match->match
154 14
            && $attempt->length < $match->length) {
155 6
            return $attempt;
156
        }
157
158 11
        return $match;
159
    }
160
161
    /**
162
     * @param string $input
163
     * @param int $offset
164
     * @param Context $context
165
     *
166
     * @return Failure|Success
167
     */
168 15
    private function parseShortest(&$input, $offset, Context $context)
169
    {
170
        /** @var AbstractMatch $match */
171 15
        $match = null;
172
173
        /** @var Parser $parser */
174 15
        foreach ($this->parsers as $parser) {
175 15
            $match = self::determineShortestOf($parser->parse($input, $offset, $context), $match);
176
        }
177
178 15
        return ($match instanceof Success)
179 10
            ? $this->success($input, $offset, $match->length, $match)
180 15
            : $this->failure($input, $offset, $match->length);
181
    }
182
183
    /**
184
     * @param string $input
185
     * @param int $offset
186
     * @param Context $context
187
     *
188
     * @return Failure|Success
189
     */
190 104
    protected function parse(&$input, $offset, Context $context)
191
    {
192 104
        $this->pushPreferenceToContext($context);
193
194 104
        switch ($context->getPreference()) {
195
            default:
196 104
            case Prefer::FIRST:
197 77
                $match = $this->parseFirst($input, $offset, $context);
198 77
                break;
199
200 29
            case Prefer::LONGEST:
201 16
                $match = $this->parseLongest($input, $offset, $context);
202 16
                break;
203
204 15
            case Prefer::SHORTEST:
205 15
                $match = $this->parseShortest($input, $offset, $context);
206 15
                break;
207
        }
208
209 104
        $this->popPreferenceFromContext($context);
210
211 104
        return $match;
212
    }
213
214
    /**
215
     * Add one or more parsers as choices.
216
     *
217
     * @param string[]|int[]|Parser[] $arguments
218
     *
219
     * @return self
220
     */
221 1
    public function add(...$arguments): self
222
    {
223 1
        $this->parsers = array_merge($this->parsers, self::getArguments($arguments));
224
225 1
        return $this;
226
    }
227
228
    /**
229
     * @return string
230
     */
231 90
    public function __toString()
232
    {
233 90
        $prefix = $this->preference === Prefer::LONGEST
0 ignored issues
show
introduced by
The condition $this->preference === Va...rective\Prefer::LONGEST is always false.
Loading history...
234 11
            ? 'longest-of'
235
            :
236 79
            ($this->preference === Prefer::SHORTEST
0 ignored issues
show
introduced by
The condition $this->preference === Va...ective\Prefer::SHORTEST is always false.
Loading history...
237 11
                ? 'shortest-of'
238
                :
239 90
                '');
240
241 90
        return $prefix . '( ' . implode(' | ', $this->parsers) . ' )';
242
    }
243
}
244