Passed
Push — master ( f8387b...b124c6 )
by Martijn
02:24
created

Choice::add()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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