Reader   A
last analyzed

Complexity

Total Complexity 11

Size/Duplication

Total Lines 100
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 11
lcom 1
cbo 10
dl 0
loc 100
ccs 0
cts 46
cp 0
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A boot() 0 6 1
A getParser() 0 10 2
B addGrammar() 0 26 7
1
<?php
2
/**
3
 * This file is part of Railt package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
declare(strict_types=1);
9
10
namespace Railt\Compiler\Grammar;
11
12
use Railt\Compiler\Grammar\Delegate\IncludeDelegate;
13
use Railt\Compiler\Grammar\Delegate\RuleDelegate;
14
use Railt\Compiler\Grammar\Delegate\TokenDelegate;
15
use Railt\Io\Readable;
16
use Railt\Lexer\Driver\NativeRegex;
17
use Railt\Lexer\LexerInterface;
18
use Railt\Parser\Driver\Llk;
19
use Railt\Parser\Grammar;
20
use Railt\Parser\GrammarInterface;
21
use Railt\Parser\ParserInterface;
22
23
/**
24
 * Class Reader
25
 */
26
class Reader
27
{
28
    /**
29
     * @var Readable
30
     */
31
    private $file;
32
33
    /**
34
     * @var ParserInterface
35
     */
36
    private $pp;
37
38
    /**
39
     * @var LexerInterface
40
     */
41
    private $lexer;
42
43
    /**
44
     * @var GrammarInterface
45
     */
46
    private $grammar;
47
48
    /**
49
     * @var Analyzer
50
     */
51
    private $analyzer;
52
53
    /**
54
     * Reader constructor.
55
     * @param Readable $file
56
     */
57
    public function __construct(Readable $file)
58
    {
59
        $this->file     = $file;
60
        $this->pp       = new Parser();
61
        $this->lexer    = new NativeRegex();
62
        $this->grammar  = new Grammar();
63
        $this->analyzer = new Analyzer();
64
65
        $this->boot();
66
    }
67
68
    /**
69
     * @return void
70
     */
71
    private function boot(): void
72
    {
73
        $this->pp->env(LexerInterface::class, $this->lexer);
74
        $this->pp->env(GrammarInterface::class, $this->grammar);
75
        $this->pp->env(self::class, $this);
76
    }
77
78
    /**
79
     * @return ParserInterface
80
     * @throws \Railt\Io\Exception\ExternalFileException
81
     * @throws \Railt\Io\Exception\NotReadableException
82
     */
83
    public function getParser(): ParserInterface
84
    {
85
        $this->addGrammar($this->file);
86
87
        foreach ($this->analyzer->analyze() as $rule) {
88
            $this->grammar->addRule($rule);
89
        }
90
91
        return new Llk($this->lexer, $this->grammar);
92
    }
93
94
    /**
95
     * @param Readable $file
96
     * @throws \Railt\Io\Exception\ExternalFileException
97
     * @throws \Railt\Io\Exception\NotReadableException
98
     */
99
    private function addGrammar(Readable $file): void
100
    {
101
        $ast = $this->pp->parse($file);
102
103
        foreach ($ast->getChildren() as $child) {
104
            switch (true) {
105
                case $child instanceof IncludeDelegate:
106
                    $this->addGrammar($child->getPathname($file));
107
                    break;
108
109
                case $child instanceof TokenDelegate:
110
                    $this->lexer->add($child->getTokenName(), $child->getTokenPattern());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Railt\Lexer\LexerInterface as the method add() does only exist in the following implementations of said interface: Railt\Lexer\Driver\MultistateLexer, Railt\Lexer\Driver\NativeRegex, Railt\Lexer\Driver\ParleLexer, Railt\Lexer\Driver\SimpleLexer.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
111
                    if (! $child->isKept()) {
112
                        $this->lexer->skip($child->getTokenName());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Railt\Lexer\LexerInterface as the method skip() does only exist in the following implementations of said interface: Railt\Lexer\Driver\MultistateLexer, Railt\Lexer\Driver\NativeRegex, Railt\Lexer\Driver\ParleLexer, Railt\Lexer\Driver\SimpleLexer.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
113
                    }
114
                    break;
115
116
                case $child instanceof RuleDelegate:
117
                    $this->analyzer->addRuleDelegate($child);
118
                    if ($child->getDelegate()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $child->getDelegate() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
119
                        $this->grammar->addDelegate($child->getRuleName(), $child->getDelegate());
0 ignored issues
show
Bug introduced by
The method addDelegate() does not exist on Railt\Parser\GrammarInterface. Did you maybe mean delegate()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
120
                    }
121
                    break;
122
            }
123
        }
124
    }
125
}
126