Generator::bootAnalyzer()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * This file is part of Railt package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Railt\SDL\Frontend;
13
14
use Laminas\Code\Exception\InvalidArgumentException;
15
use Laminas\Code\Generator\Exception\RuntimeException;
16
use Laminas\Code\Generator\ValueGenerator;
17
use Phplrt\Compiler\Analyzer;
18
use Phplrt\Compiler\Compiler;
19
use Phplrt\Contracts\Grammar\RuleInterface;
20
use Phplrt\Source\Exception\NotFoundException;
21
use Phplrt\Source\Exception\NotReadableException;
22
use Phplrt\Source\File;
23
24
/**
25
 * Class Generator
26
 */
27
class Generator
28
{
29
    /**
30
     * @var string
31
     */
32
    private const GRAMMAR_PATHNAME = __DIR__ . '/../../resources/grammar/grammar.pp2';
33
34
    /**
35
     * @var string
36
     */
37
    private const GRAMMAR_TEMPLATE_PATHNAME = __DIR__ . '/../../resources/templates/grammar.tpl.php';
38
39
    /**
40
     * @var Analyzer
41
     */
42
    private Analyzer $analyzer;
43
44
    /**
45
     * Generator constructor.
46
     *
47
     * @throws NotFoundException
48
     * @throws NotReadableException
49
     * @throws \Throwable
50
     */
51
    public function __construct()
52
    {
53
        $this->analyzer = $this->bootAnalyzer();
54
    }
55
56
57
    /**
58
     * @return Analyzer
59
     * @throws NotFoundException
60
     * @throws NotReadableException
61
     * @throws \Throwable
62
     */
63
    private function bootAnalyzer(): Analyzer
64
    {
65
        $compiler = new Compiler();
66
        $compiler->load(File::fromPathname(self::GRAMMAR_PATHNAME));
67
68
        return $compiler->getAnalyzer();
69
    }
70
71
    /**
72
     * @return void
73
     * @throws RuntimeException
74
     * @throws InvalidArgumentException
75
     */
76
    public function generateAndSave(): void
77
    {
78
        \file_put_contents(__DIR__ . '/grammar.php', $this->generate());
79
    }
80
81
    /**
82
     * @return string
83
     * @throws RuntimeException
84
     * @throws InvalidArgumentException
85
     */
86
    public function generate(): string
87
    {
88
        return $this->render(self::GRAMMAR_TEMPLATE_PATHNAME, [
89
            'initial'  => $this->value($this->analyzer->initial),
90
            'lexemes'  => $this->value($this->analyzer->tokens[Analyzer::STATE_DEFAULT]),
91
            'skips'    => $this->value($this->analyzer->skip),
92
            'grammar'  => $this->getRulesString(),
93
            'reducers' => $this->getReducersString(),
94
        ]);
95
    }
96
97
    /**
98
     * @param string $pathname
99
     * @param array $variables
100
     * @return string
101
     */
102
    private function render(string $pathname, array $variables = []): string
103
    {
104
        \extract($variables, \EXTR_OVERWRITE);
105
106
        return require $pathname;
107
    }
108
109
    /**
110
     * @param mixed $value
111
     * @param int $depth
112
     * @return string
113
     * @throws InvalidArgumentException
114
     * @throws RuntimeException
115
     */
116
    private function value($value, int $depth = 1): string
117
    {
118
        $generator = new ValueGenerator($value);
119
        $generator->setArrayDepth($depth);
120
121
        return $generator->generate();
122
    }
123
124
    /**
125
     * @return string
126
     * @throws RuntimeException
127
     */
128
    private function getRulesString(): string
129
    {
130
        $result = [];
131
132
        foreach ($this->analyzer->rules as $index => $rule) {
133
            $value = \vsprintf('new \\%s(%s)', [
134
                \get_class($rule),
135
                \implode(', ', $this->getRuleArguments($rule)),
136
            ]);
137
138
            $result[] = $this->value($index) . ' => ' . $value;
139
        }
140
141
        return $this->formatArray($result);
142
    }
143
144
    /**
145
     * @param RuleInterface $rule
146
     * @return array
147
     * @throws RuntimeException
148
     */
149
    private function getRuleArguments(RuleInterface $rule): array
150
    {
151
        $result = [];
152
153
        foreach ($rule->getConstructorArguments() as $arg) {
154
            $result[] = $this->value($arg, 2);
155
        }
156
157
        return $result;
158
    }
159
160
    /**
161
     * @param array $result
162
     * @return string
163
     */
164
    private function formatArray(array $result): string
165
    {
166
        return \vsprintf('[%s%s%s]', [
167
            "\n        ",
168
            \implode(",\n        ", $result),
169
            "\n    ",
170
        ]);
171
    }
172
173
    /**
174
     * @return string
175
     * @throws RuntimeException
176
     */
177
    private function getReducersString(): string
178
    {
179
        $result = [];
180
181
        foreach ($this->analyzer->reducers as $state => $rule) {
182
            $lines = \explode("\n", $rule);
183
184
            foreach ($lines as $index => &$line) {
185
                $line = $line ? \str_repeat(' ', $index ? 8 : 12) . $line : '';
186
            }
187
188
            $template = " => static function (\$children) {\n%s\n        }";
189
190
            $result[] = $this->value($state) . \vsprintf($template, [
191
                    \implode("\n", $lines),
192
                ]);
193
        }
194
195
        return $this->formatArray($result);
196
    }
197
}
198