Completed
Pull Request — master (#223)
by Jaap
07:24
created

BaseParser::traceDiscard()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
/**
3
 * This file is part of phpDocumentor.
4
 *
5
 *  For the full copyright and license information, please view the LICENSE
6
 *  file that was distributed with this source code.
7
 *
8
 *  @link      http://phpdoc.org
9
 */
10
11
declare(strict_types=1);
12
13
namespace phpDocumentor\Reflection;
14
15
use phpDocumentor\Reflection\Types\Context;
16
use RangeException;
17
use RuntimeException;
18
use function count;
19
use function implode;
20
use function sprintf;
21
22
abstract class BaseParser
23
{
24
    private const SYMBOL_NONE = -1;
25
26
    /*
27
     * The following members will be filled with generated parsing data:
28
     */
29
30
    /** @var int Size of $tokenToSymbol map */
31
    protected $tokenToSymbolMapSize;
32
    /** @var int Size of $action table */
33
    protected $actionTableSize;
34
    /** @var int Size of $goto table */
35
    protected $gotoTableSize;
36
37
    /** @var int Symbol number signifying an invalid token */
38
    protected $invalidSymbol;
39
    /** @var int Symbol number of error recovery token */
40
    protected $errorSymbol;
41
    /** @var int Action number signifying default action */
42
    protected $defaultAction;
43
    /** @var int Rule number signifying that an unexpected token was encountered */
44
    protected $unexpectedTokenRule;
45
    /** @var int states */
46
    protected $YY2TBLSTATE;
47
    /** @var int Number of non-leaf states */
48
    protected $numNonLeafStates;
49
50
    /** @var int[] Map of lexer tokens to internal symbols */
51
    protected $tokenToSymbol;
52
    /** @var string[] Map of symbols to their names */
53
    protected $symbolToName;
54
    /** @var string[] Names of the production rules (only necessary for debugging) */
55
    protected $productions;
56
57
    /**
58
     * @var int[] Map of states to a displacement into the $action table. The corresponding action for this
59
     *             state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the
60
     * action is defaulted, i.e. $actionDefault[$state] should be used instead.
61
     */
62
    protected $actionBase;
63
    /** @var int[] Table of actions. Indexed according to $actionBase comment. */
64
    protected $action;
65
    /**
66
     * @var int[] Table indexed analogously to $action. If $actionCheck[$actionBase[$state] + $symbol] != $symbol
67
     *             then the action is defaulted, i.e. $actionDefault[$state] should be used instead.
68
     */
69
    protected $actionCheck;
70
    /** @var int[] Map of states to their default action */
71
    protected $actionDefault;
72
    /** @var callable[] Semantic action callbacks */
73
    protected $reduceCallbacks;
74
75
76
    /** @var TypeLexer */
77
    private $lexer;
78
79
    /** @var FqsenResolver */
80
    protected $fqsenResolver;
81
82
    /** @var Context */
83
    protected $context;
84
85
    /** @var Type|Type[]|null Temporary value containing the result of last semantic action (reduction) */
86
    protected $semValue;
87
88
    /** @var Type[] Semantic value stack (contains values of tokens and semantic action results) */
89
    protected $semStack;
90
91
    /**
92
     * @return void
93
     */
94
    //phpcs:ignore SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint
95
    abstract protected function initReduceCallbacks();
96
97
    public function __construct()
98
    {
99
        $this->lexer = new DocBlockLexer();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \phpDocumentor\Reflection\DocBlockLexer() of type object<phpDocumentor\Reflection\DocBlockLexer> is incompatible with the declared type object<phpDocumentor\Reflection\TypeLexer> of property $lexer.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
100
        $this->initReduceCallbacks();
101
    }
102
103
    public function parse(string $type)
104
    {
105
        $this->lexer->setInput($type);
106
        $this->lexer->moveNext();
107
108
        // Start off in the initial state and keep a stack of previous states
109
        $state = 0;
110
        $stateStack = [$state];
111
        $symbol = self::SYMBOL_NONE;
112
113
        // Semantic value stack (contains values of tokens and semantic action results)
114
        $this->semStack = [];
115
        $this->semValue = null;
116
117
        $stackPos = 0;
118
119
        while (true) {
120
            $this->traceNewState($state, $symbol);
121
            if ($this->actionBase[$state] === 0) {
122
                $rule = $this->actionDefault[$state];
123
            } else {
124
                if ($symbol === self::SYMBOL_NONE) {
125
                    $this->lexer->moveNext();
126
                    $tokenId    = $this->lexer->token['type'] ?? 0;
127
                    $tokenValue = $this->lexer->token['value'] ?? null;
128
129
                    // map the lexer token id to the internally used symbols
130
                    $symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize
131
                        ? $this->tokenToSymbol[$tokenId]
132
                        : $this->invalidSymbol;
133
134
                    if ($symbol === $this->invalidSymbol) {
135
                        throw new RangeException(sprintf(
136
                            'The lexer returned an invalid token (id=%d, value=%s)',
137
                            $tokenId,
138
                            $tokenValue
139
                        ));
140
                    }
141
142
                    $this->traceRead($symbol, $tokenValue);
143
                }
144
145
                $idx = $this->actionBase[$state] + $symbol;
146
                if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol)
147
                        || ($state < $this->YY2TBLSTATE
148
                            && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0
149
                            && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol))
150
                    && ($action = $this->action[$idx]) !== $this->defaultAction) {
151
                    if ($action > 0) {
152
                        /** shift */
153
                        $this->traceShift($symbol);
154
155
                        ++$stackPos;
156
                        $stateStack[$stackPos] = $state = $action;
157
                        $this->semStack[$stackPos] = $tokenValue;
0 ignored issues
show
Bug introduced by
The variable $tokenValue does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
158
                        $symbol = self::SYMBOL_NONE;
159
160
                        if ($action < $this->numNonLeafStates) {
161
                            continue;
162
                        }
163
164
                        /* $yyn >= numNonLeafStates means shift-and-reduce */
165
                        $rule = $action - $this->numNonLeafStates;
166
                    } else {
167
                        $rule = -$action;
168
                    }
169
                } else {
170
                    $rule = $this->actionDefault[$state];
171
                }
172
            }
173
174
            for (;;) {
175
                if ($rule === 0) {
176
                    /* accept */
177
                    $this->traceAccept();
178
179
                    return $this->semValue;
180
                }
181
182
                if ($rule === $this->unexpectedTokenRule) {
183
                    /* error */
184
                    $msg = $this->getErrorMessage($symbol, $state);
185
186
                    throw new RuntimeException($msg);
187
                }
188
189
                /* reduce */
190
                $this->traceReduce($rule);
191
                $this->reduceCallbacks[$rule]($stackPos);
192
193
                $stackPos    -= $this->ruleToLength[$rule];
0 ignored issues
show
Bug introduced by
The property ruleToLength does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
194
                $nonTerminal = $this->ruleToNonTerminal[$rule];
0 ignored issues
show
Bug introduced by
The property ruleToNonTerminal does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
195
                $idx         = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos];
0 ignored issues
show
Bug introduced by
The property gotoBase does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
196
                if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) {
0 ignored issues
show
Bug introduced by
The property gotoCheck does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
197
                    $state = $this->goto[$idx];
0 ignored issues
show
Bug introduced by
The property goto does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
198
                } else {
199
                    $state = $this->gotoDefault[$nonTerminal];
0 ignored issues
show
Bug introduced by
The property gotoDefault does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
200
                }
201
202
                ++$stackPos;
203
                $stateStack[$stackPos]     = $state;
204
                $this->semStack[$stackPos] = $this->semValue;
205
206
                if ($state < $this->numNonLeafStates) {
207
                    break;
208
                }
209
210
                /* >= numNonLeafStates means shift-and-reduce */
211
                $rule = $state - $this->numNonLeafStates;
212
            }
213
        }
214
215
        throw new RuntimeException('Reached end of parser loop');
216
    }
217
218
    /**
219
     * Format error message including expected tokens.
220
     *
221
     * @param int $symbol Unexpected symbol
222
     * @param int $state  State at time of error
223
     *
224
     * @return string Formatted error message
225
     */
226
    protected function getErrorMessage(int $symbol, int $state) : string
227
    {
228
        $expectedString = '';
229
        if ($expected = $this->getExpectedTokens($state)) {
230
            $expectedString = ', expecting ' . implode(' or ', $expected);
231
        }
232
233
        return 'Docblock syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString;
234
    }
235
236
    /**
237
     * Get limited number of expected tokens in given state.
238
     *
239
     * @param int $state State
240
     *
241
     * @return string[] Expected tokens. If too many, an empty array is returned.
242
     */
243
    protected function getExpectedTokens(int $state) : array
244
    {
245
        $expected = [];
246
247
        $base = $this->actionBase[$state];
248
        foreach ($this->symbolToName as $symbol => $name) {
249
            $idx = $base + $symbol;
250
            if ($idx < 0 || $idx >= $this->actionTableSize || ($this->actionCheck[$idx] !== $symbol
251
                    && $state >= $this->YY2TBLSTATE)
252
                || (isset($this->actionBase[$state + $this->numNonLeafStates]) &&
253
                    $idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) < 0
254
                || $idx >= $this->actionTableSize ||
255
                (isset($this->actionCheck[$idx]) && $this->actionCheck[$idx] !== $symbol)
256
            ) {
257
                continue;
258
            }
259
260
            if (
261
                (isset($this->action[$idx]) &&
262
                    (
263
                        $this->action[$idx] === $this->unexpectedTokenRule
264
                        || $this->action[$idx] === $this->defaultAction
265
                    )
266
                )
267
                || $symbol === $this->errorSymbol
268
            ) {
269
                continue;
270
            }
271
272
            if (count($expected) === 4) {
273
                /* Too many expected tokens */
274
                return [];
275
            }
276
277
            $expected[] = $name;
278
        }
279
280
        return $expected;
281
    }
282
283
    protected function traceNewState($state, $symbol) : void
284
    {
285
        echo '% State ' . $state
286
            . ', Lookahead ' . ($symbol === self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n";
287
    }
288
289
    protected function traceRead($symbol, $value) : void
290
    {
291
        echo '% Reading ' . $this->symbolToName[$symbol] . " with value " . $value . "\n";
292
    }
293
294
    protected function traceShift($symbol) : void
295
    {
296
        echo '% Shift ' . $this->symbolToName[$symbol] . "\n";
297
    }
298
299
    protected function traceAccept() : void
300
    {
301
        echo "% Accepted.\n";
302
    }
303
304
    protected function traceReduce($n) : void
305
    {
306
        echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n";
307
    }
308
309
    protected function tracePop($state) : void
310
    {
311
        echo '% Recovering, uncovered state ' . $state . "\n";
312
    }
313
314
    protected function traceDiscard($symbol) : void
315
    {
316
        echo '% Discard ' . $this->symbolToName[$symbol] . "\n";
317
    }
318
}
319