Completed
Push — master ( c1a156...8cadde )
by Kirill
08:14
created

Compiler::compile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 5.667

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 15
ccs 3
cts 9
cp 0.3333
crap 5.667
rs 9.7666
c 0
b 0
f 0
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\SDL;
11
12
use Psr\Log\LoggerAwareInterface;
13
use Psr\Log\LoggerAwareTrait;
14
use Psr\Log\LoggerInterface;
15
use Railt\Io\File;
16
use Railt\Io\Readable;
17
use Railt\Parser\Ast\RuleInterface;
18
use Railt\Parser\Exception\UnexpectedTokenException;
19
use Railt\Parser\Exception\UnrecognizedTokenException;
20
use Railt\Reflection\Contracts\Document as DocumentInterface;
21
use Railt\Reflection\Contracts\Reflection as ReflectionInterface;
22
use Railt\Reflection\Reflection;
23
use Railt\SDL\Compiler\Builder;
24
use Railt\SDL\Compiler\Dictionary;
25
use Railt\SDL\Compiler\Parser;
26
use Railt\SDL\Exception\CompilerException;
27
use Railt\SDL\Exception\InternalException;
28
use Railt\SDL\Exception\SyntaxException;
29
30
/**
31
 * Class Compiler
32
 */
33
class Compiler implements LoggerAwareInterface
34
{
35
    use LoggerAwareTrait;
36
37
    /**
38
     * @var ReflectionInterface
39
     */
40
    private $reflection;
41
42
    /**
43
     * @var Dictionary
44
     */
45
    private $dictionary;
46
47
    /**
48
     * @var Parser
49
     */
50
    private $parser;
51
52
    /**
53
     * @var Builder
54
     */
55
    private $builder;
56
57
    /**
58
     * @var array|\Railt\Reflection\Contracts\Document[]
59
     */
60
    private $documents = [];
61
62
    /**
63
     * Compiler constructor.
64
     * @param LoggerInterface|null $logger
65
     * @throws \Railt\Io\Exception\ExternalFileException
66
     * @throws \Railt\Reflection\Exception\TypeConflictException
67
     */
68 119
    public function __construct(LoggerInterface $logger = null)
69
    {
70 119
        $this->parser = new Parser();
71 119
        $this->builder = new Builder();
72 119
        $this->dictionary = new Dictionary($this);
73 119
        $this->reflection = new Reflection($this->dictionary);
74
75 119
        if ($logger) {
76
            $this->setLogger($logger);
77
        }
78 119
    }
79
80
    /**
81
     * @param LoggerInterface $logger
82
     */
83
    public function setLogger(LoggerInterface $logger): void
84
    {
85
        $this->logger = $logger;
86
87
        $this->builder->setLogger($logger);
88
        $this->dictionary->setLogger($logger);
89
    }
90
91
    /**
92
     * @param \Closure $then
93
     * @return Compiler
94
     */
95
    public function autoload(\Closure $then): Compiler
96
    {
97
        $this->dictionary->onTypeNotFound($then);
98
99
        return $this;
100
    }
101
102
    /**
103
     * @param Readable $file
104
     * @return DocumentInterface
105
     * @throws CompilerException
106
     * @throws \Railt\Io\Exception\NotReadableException
107
     */
108
    public function compile(Readable $file): DocumentInterface
109
    {
110
        try {
111 119
            return $this->memoize($file, function (Readable $file): DocumentInterface {
112 119
                return $this->builder->run($this->reflection, $file, $this->parse($file));
113 119
            });
114
        } catch (CompilerException $e) {
115
            throw $e;
116
        } catch (\Throwable $e) {
117
            $error = new InternalException($e->getMessage(), $e->getCode());
118
            $error->throwsIn(File::fromPathname($e->getFile()), $e->getLine(), 0);
0 ignored issues
show
Bug introduced by
It seems like \Railt\Io\File::fromPathname($e->getFile()) targeting Railt\Io\File::fromPathname() can also be of type object<Railt\Io\File>; however, Railt\Io\Exception\Exter...leException::throwsIn() does only seem to accept object<Railt\Io\Readable>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
119
120
            throw $error;
121
        }
122
    }
123
124
    /**
125
     * @param Readable $file
126
     * @param \Closure $otherwise
127
     * @return DocumentInterface
128
     */
129 119
    private function memoize(Readable $file, \Closure $otherwise): DocumentInterface
130
    {
131 119
        if (isset($this->documents[$file->getHash()])) {
132
            //
133
            // Log memoized document selection
134
            //
135
            if ($this->logger) {
136
                $this->logger->debug(\sprintf('Avoid duplication compilation of %s', $file->getPathname()));
137
            }
138
139
            return $this->documents[$file->getHash()];
140
        }
141
142 119
        return $this->documents[$file->getHash()] = $otherwise($file);
143
    }
144
145
    /**
146
     * @param Readable $file
147
     * @return RuleInterface
148
     * @throws CompilerException
149
     */
150 119
    private function parse(Readable $file): RuleInterface
151
    {
152
        try {
153 119
            return $this->parser->parse($file);
154
        } catch (UnexpectedTokenException | UnrecognizedTokenException $e) {
155
            $error = new SyntaxException($e->getMessage(), $e->getCode());
156
            $error->throwsIn($file, $e->getLine(), $e->getColumn());
157
158
            throw $error;
159
        }
160
    }
161
}
162