Passed
Branch feature/first-release (5d23f8)
by Andrea Marco
10:49
created

Parser::fastForward()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.074

Importance

Changes 0
Metric Value
cc 4
eloc 5
c 0
b 0
f 0
nc 4
nop 0
dl 0
loc 10
ccs 5
cts 6
cp 0.8333
crap 4.074
rs 10
1
<?php
2
3
namespace Cerbero\JsonParser;
4
5
use Cerbero\JsonParser\Decoders\ConfigurableDecoder;
6
use Cerbero\JsonParser\Exceptions\SyntaxException;
7
use Cerbero\JsonParser\Sources\Source;
8
use Cerbero\JsonParser\Tokens\CompoundBegin;
9
use Cerbero\JsonParser\Tokens\CompoundEnd;
10
use Cerbero\JsonParser\Tokens\Token;
11
use Generator;
12
use IteratorAggregate;
13
use Traversable;
14
15
/**
16
 * The JSON parser.
17
 *
18
 * @implements IteratorAggregate<string|int, mixed>
19
 */
20
final class Parser implements IteratorAggregate
21
{
22
    /**
23
     * The tokens to parse.
24
     *
25
     * @var Generator<int, Token>
26
     */
27
    private Generator $tokens;
28
29
    /**
30
     * The decoder handling potential errors.
31
     *
32
     * @var ConfigurableDecoder
33
     */
34
    private ConfigurableDecoder $decoder;
35
36
    /**
37
     * Whether the parser is fast-forwarding.
38
     *
39
     * @var bool
40
     */
41
    private bool $isFastForwarding = false;
42
43
    /**
44
     * Instantiate the class.
45
     *
46
     * @param Lexer|Generator<int, Token> $lexer
47
     * @param Config $config
48
     */
49 152
    public function __construct(private Lexer|Generator $lexer, private Config $config)
50
    {
51
        /** @phpstan-ignore-next-line */
52 152
        $this->tokens = $lexer instanceof Lexer ? $lexer->getIterator() : $lexer;
53 152
        $this->decoder = new ConfigurableDecoder($config);
54
    }
55
56
    /**
57
     * Instantiate the class statically
58
     *
59
     * @param Source $source
60
     * @return self
61
     */
62 152
    public static function for(Source $source): self
63
    {
64 152
        return new self(new Lexer($source), $source->config());
65
    }
66
67
    /**
68
     * Retrieve the JSON fragments
69
     *
70
     * @return Traversable<string|int, mixed>
71
     */
72 140
    public function getIterator(): Traversable
73
    {
74 140
        $state = new State($this->config->pointers, fn () => new self($this->lazyLoad(), clone $this->config));
75
76 140
        foreach ($this->tokens as $token) {
77 136
            if ($this->isFastForwarding) {
78 7
                continue;
79 136
            } elseif (!$token->matches($state->expectedToken)) {
80 1
                throw new SyntaxException($token);
81
            }
82
83 136
            $state->mutateByToken($token);
84
85 136
            if (!$token->endsChunk() || $state->tree()->isDeep()) {
86 136
                continue;
87
            }
88
89 134
            if ($state->hasBuffer()) {
90
                /** @var string|int $key */
91 102
                $key = $this->decoder->decode($state->key());
92 102
                $value = $this->decoder->decode($state->value());
93
94 101
                yield $key => $state->callPointer($value, $key);
95
96 101
                $value instanceof self && $value->fastForward();
97
            }
98
99 133
            if ($state->canStopParsing()) {
100 58
                break;
101
            }
102
        }
103
    }
104
105
    /**
106
     * Retrieve the generator to lazy load the current compound
107
     *
108
     * @return Generator<int, Token>
109
     */
110 9
    public function lazyLoad(): Generator
111
    {
112 9
        $depth = 0;
113
114
        do {
115 9
            yield $token = $this->tokens->current();
116
117 9
            if ($token instanceof CompoundBegin) {
118 9
                $depth++;
119 9
            } elseif ($token instanceof CompoundEnd) {
120 9
                $depth--;
121
            }
122
123 9
            $depth > 0 && $this->tokens->next();
124 9
        } while ($depth > 0);
125
    }
126
127
    /**
128
     * Fast-forward the parser
129
     *
130
     * @return void
131
     */
132 9
    public function fastForward(): void
133
    {
134 9
        if (!$this->tokens->valid()) {
135 2
            return;
136
        }
137
138 7
        $this->isFastForwarding = true;
139
140 7
        foreach ($this as $value) {
141
            $value instanceof self && $value->fastForward();
142
        }
143
    }
144
145
    /**
146
     * Retrieve the parsing progress
147
     *
148
     * @return Progress
149
     */
150
    public function progress(): Progress
151
    {
152
        /** @phpstan-ignore-next-line */
153
        return $this->lexer->progress();
0 ignored issues
show
Bug introduced by
The method progress() does not exist on Generator. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

153
        return $this->lexer->/** @scrutinizer ignore-call */ progress();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
154
    }
155
156
    /**
157
     * Retrieve the parsing position
158
     *
159
     * @return int
160
     */
161 11
    public function position(): int
162
    {
163
        /** @phpstan-ignore-next-line */
164 11
        return $this->lexer->position();
0 ignored issues
show
Bug introduced by
The method position() does not exist on Generator. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

164
        return $this->lexer->/** @scrutinizer ignore-call */ position();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
165
    }
166
}
167