Parser::statement()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Diezz\YamlToObjectMapper\Resolver\Parser;
4
5
use Diezz\YamlToObjectMapper\Resolver\Parser\AST\ArrayExpression;
6
use Diezz\YamlToObjectMapper\Resolver\Parser\AST\ASTNode;
7
use Diezz\YamlToObjectMapper\Resolver\Parser\AST\Expression;
8
use Diezz\YamlToObjectMapper\Resolver\Parser\AST\ResolverExpression;
9
use Diezz\YamlToObjectMapper\Resolver\Parser\AST\StringLiteral;
10
11
class Parser
12
{
13
    private Tokenizer $tokenizer;
14
    private ?Token $lookahead;
15
    private ?Token $current = null;
16
    private ?string $context = null;
17
18
    private const CONTEXT_ARGUMENT_RESOLVER = 'argumentResolver';
19
20 17
    public function __construct(string $string)
21
    {
22 17
        $this->tokenizer = new Tokenizer($string);
23 17
    }
24
25
    /**
26
     * @throws SyntaxException
27
     */
28 17
    public function parse(): ASTNode
29
    {
30 17
        $this->lookahead = $this->tokenizer->getNextToken();
31
32 17
        return $this->expression();
33
    }
34
35
    /**
36
     * @throws SyntaxException
37
     */
38 17
    private function expression(): Expression
39
    {
40 17
        if ($this->tokenizer->isScalar()) {
41 4
            $node = [new StringLiteral($this->tokenizer->getString())];
42
        } else {
43 13
            $node = $this->statementList();
44
        }
45
46 17
        return new Expression($node);
47
    }
48
49
    /**
50
     * @throws SyntaxException
51
     * @return ASTNode[]
52
     */
53 13
    private function statementList(?int $stopLookahead = null): array
54
    {
55 13
        $list = [$this->statement()];
56
57 13
        while ($this->lookahead !== null && $this->lookahead->getTokenType() !== $stopLookahead) {
58 3
            $list[] = $this->statement();
59
        }
60
61 13
        return $list;
62
    }
63
64
    /**
65
     * @throws SyntaxException
66
     */
67 13
    private function statement(): ASTNode
68
    {
69 13
        if ($this->context === self::CONTEXT_ARGUMENT_RESOLVER) {
70 3
            return $this->resolverContext();
71
        }
72
73 13
        return $this->stringContext();
74
    }
75
76
    /**
77
     * @throws SyntaxException
78
     */
79 12
    private function resolverContext(): ASTNode
80
    {
81
        //In scope of resolver we skip all space symbols, in other cases treat them as StringLiteral
82 12
        while ($this->lookahead->getTokenType() === Tokenizer::T_SPACE) {
0 ignored issues
show
Bug introduced by
The method getTokenType() does not exist on null. ( Ignorable by Annotation )

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

82
        while ($this->lookahead->/** @scrutinizer ignore-call */ getTokenType() === Tokenizer::T_SPACE) {

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...
83 3
            $this->eat(Tokenizer::T_SPACE);
84
        }
85
86 12
        return match ($this->lookahead->getTokenType()) {
87 12
            Tokenizer::T_ARRAY_BEGIN => $this->arrayExpression(),
88 12
            Tokenizer::T_STRING_LITERAL => $this->argument(),
89 4
            Tokenizer::T_BEGIN_OF_EXPRESSION => $this->resolverExpression(),
90 2
            Tokenizer::T_QUOTED_STRING_LITERAL => $this->quotedStringLiteral(),
91 12
            default => throw new SyntaxException("Unexpected literal: {$this->lookahead->getValue()}"),
92
        };
93
    }
94
95
    /**
96
     * @throws SyntaxException
97
     */
98 13
    private function stringContext(): ASTNode
99
    {
100 13
        if ($this->lookahead->getTokenType() === Tokenizer::T_BEGIN_OF_EXPRESSION) {
101 13
            return $this->resolverExpression();
102
        }
103
104 3
        return $this->asStringLiteral($this->lookahead->getTokenType());
105
    }
106
107
    /**
108
     * @throws SyntaxException
109
     */
110 3
    private function arrayExpression(): ArrayExpression
111
    {
112 3
        $this->eat(Tokenizer::T_ARRAY_BEGIN);
113
114 3
        $list = [];
115
116 3
        while ($this->lookahead->getTokenType() !== Tokenizer::T_ARRAY_END) {
117 3
            $list[] = $this->statement();
118 3
            if ($this->lookahead->getTokenType() === Tokenizer::T_COMMA) {
119 3
                $this->eat(Tokenizer::T_COMMA);
120
            }
121
        }
122
123 3
        $this->eat(Tokenizer::T_ARRAY_END);
124
125 3
        return new ArrayExpression($list);
126
    }
127
128
    /**
129
     * @throws SyntaxException
130
     */
131 3
    private function asStringLiteral(int $tokenType): StringLiteral
132
    {
133 3
        return new StringLiteral($this->eat($tokenType)->getValue());
134
    }
135
136
    /**
137
     * @throws SyntaxException
138
     */
139 13
    private function resolverExpression(): ResolverExpression
140
    {
141 13
        $this->context = self::CONTEXT_ARGUMENT_RESOLVER;
142 13
        $this->eat(Tokenizer::T_BEGIN_OF_EXPRESSION);
143 13
        $provider = $this->eat(Tokenizer::T_STRING_LITERAL);
144 13
        $arguments = [];
145 13
        if ($this->lookahead->getTokenType() === Tokenizer::T_SEMICOLON) {
146 12
            $this->eat(Tokenizer::T_SEMICOLON);
147 12
            $arguments = $this->argumentList();
148
        }
149 13
        $this->eat(Tokenizer::T_END_OF_EXPRESSION);
150 13
        $this->context = null;
151
152 13
        return new ResolverExpression($provider->getValue(), $arguments);
153
    }
154
155
    /**
156
     * @throws SyntaxException
157
     * @return ASTNode[]
158
     */
159 12
    private function argumentList(): array
160
    {
161 12
        $list = [];
162
163 12
        while ($this->lookahead->getTokenType() !== Tokenizer::T_END_OF_EXPRESSION) {
164 12
            $list[] = $this->resolverContext();
165 12
            if ($this->lookahead->getTokenType() === Tokenizer::T_SEMICOLON) {
166 3
                $this->eat(Tokenizer::T_SEMICOLON);
167
            }
168
        }
169
170 12
        return $list;
171
    }
172
173
    /**
174
     * @throws SyntaxException
175
     */
176 10
    private function argument(): ASTNode
177
    {
178 10
        $token = $this->eat(Tokenizer::T_STRING_LITERAL);
179 10
        if ($this->lookahead->getTokenType() === Tokenizer::T_DOT) {
180 1
            return $this->pathArgument($token);
181
        }
182
183 9
        return new StringLiteral($token->getValue());
184
    }
185
186
    /**
187
     * @throws SyntaxException
188
     */
189 1
    private function pathArgument(Token $token): ASTNode
190
    {
191 1
        $pathArgument = $token->getValue();
192
193 1
        $stop = [Tokenizer::T_ARRAY_END, Tokenizer::T_END_OF_EXPRESSION, Tokenizer::T_COMMA];
194
        do {
195 1
            $this->eat(Tokenizer::T_DOT);
196 1
            $token = $this->eat(Tokenizer::T_STRING_LITERAL);
197 1
            $pathArgument .= '.' . $token->getValue();
198 1
        } while (!in_array($this->lookahead->getTokenType(), $stop, true));
199
200 1
        return new StringLiteral($pathArgument);
201
    }
202
203
    /**
204
     * @throws SyntaxException
205
     */
206
    private function stringLiteral(): StringLiteral
0 ignored issues
show
Unused Code introduced by
The method stringLiteral() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
207
    {
208
        return new StringLiteral($this->eat(Tokenizer::T_STRING_LITERAL)->getValue());
209
    }
210
211
    /**
212
     * @throws SyntaxException
213
     */
214 2
    private function quotedStringLiteral(): StringLiteral
215
    {
216 2
        $token = $this->eat(Tokenizer::T_QUOTED_STRING_LITERAL);
217
218 2
        return new StringLiteral(substr($token->getValue(), 1, -1));
219
    }
220
221
    /**
222
     * @throws SyntaxException
223
     */
224 13
    private function eat(int $tokenType = null): Token
225
    {
226 13
        $token = $this->lookahead;
227
228 13
        if (null === $token) {
229
            throw new SyntaxException("Unexpected end of input, expected $tokenType");
230
        }
231
232 13
        if ($tokenType !== null && $token->getTokenType() !== $tokenType) {
233
            throw new SyntaxException("Unexpected token: {$token->getValue()}, expected: $tokenType");
234
        }
235
236 13
        $this->current = $this->lookahead;
237 13
        $this->lookahead = $this->tokenizer->getNextToken();
238
239 13
        return $token;
240
    }
241
}
242