Passed
Push — master ( 01dce1...1829db )
by Marc
02:13
created

FbpParser::doSkip()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 15
cts 15
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 13
nc 4
nop 1
crap 4
1
<?php
2
/*
3
 * This file is part of the phpflo\phpflo-fbp package.
4
 *
5
 * (c) Marc Aschmann <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace PhpFlo\Fbp;
11
12
use PhpFlo\Common\FbpDefinitionsInterface;
13
use PhpFlo\Exception\ParserDefinitionException;
14
use PhpFlo\Exception\ParserException;
15
16
/**
17
 * Class FbpParser
18
 *
19
 * @package PhpFlo\Parser
20
 * @author Marc Aschmann <[email protected]>
21
 */
22
final class FbpParser implements FbpDefinitionsInterface
23
{
24
25
    /**
26
     * @var string
27
     */
28
    private $source;
29
30
    /**
31
     * @var array
32
     */
33
    private $settings;
34
35
    /**
36
     * @var array
37
     */
38
    private $schema;
39
40
    /**
41
     * @var int
42
     */
43
    private $linecount;
44
45
    /**
46
     * @var int
47
     */
48
    private $linecountOverall;
49
50
    /**
51
     * @var array
52
     */
53
    private $definition;
54
55
    /**
56
     * FbpParser constructor.
57
     *
58
     * @param string $source optional for initializing
59
     * @param array $settings optional settings for parser
60
     */
61 7
    public function __construct($source = '', $settings = [])
62
    {
63 7
        $this->source   = $source;
64 7
        $this->settings = array_replace_recursive(
65 7
            [],
66
            $settings
67 7
        );
68
69 7
        $this->schema = [
70 7
            self::PROPERTIES_LABEL => [
71 7
                'name' => '',
72 7
            ],
73 7
            self::INITIALIZERS_LABEL => [],
74 7
            self::PROCESSES_LABEL => [],
75 7
            self::CONNECTIONS_LABEL => [],
76
        ];
77
78 7
        $this->definition = [];
79 7
    }
80
81
    /**
82
     * @param string $source
83
     * @return FbpDefinition
84
     * @throws ParserException
85
     */
86 7
    public function run($source = '')
87
    {
88 7
        if ('' != $source) {
89 2
            $this->source = $source;
90 2
        }
91
92 7
        if (empty($this->source)) {
93 1
            throw new ParserException("FbpParser::run(): no source data or empty string given!");
94
        }
95
96 6
        $this->definition = $this->schema; // reset
97 6
        $this->linecount = 1;
98 6
        $this->linecountOverall = 0;
99
100
        /*
101
         * split by lines, OS-independent
102
         * work each line and parse for definitions
103
         */
104 6
        foreach (preg_split('/' . self::NEWLINES . '/m', $this->source) as $line) {
105 6
            $this->linecountOverall++;
106
            // skip lines if empty or comments
107 6
            if ($this->doSkip($line)) {
108 1
                continue;
109
            }
110 6
            $subset = $this->examineSubset($line);
111 6
            $this->validate($subset, $line); // post-parse validation, easier that way
112 6
            $this->definition[self::CONNECTIONS_LABEL] = array_merge_recursive(
113 6
                $this->definition[self::CONNECTIONS_LABEL], $subset
114 6
            );
115
116 6
            $this->linecount;
117 6
        }
118
119 5
        return new FbpDefinition($this->definition);
120
    }
121
122
    /**
123
     * @param string $line
124
     * @return array
125
     * @throws ParserDefinitionException
126
     */
127 6
    private function examineSubset($line)
128
    {
129 6
        $subset = [];
130 6
        $step = [];
131 6
        $nextSrc = null;
132 6
        $hasInitializer = false;
133
134 6
        if (1 == $this->linecount && 0 === strpos(trim($line), "'")) {
135 2
            $hasInitializer = true;
136 2
        }
137
138
        // subset
139 6
        foreach (explode(self::SOURCE_TARGET_SEPARATOR, $line) as $definition) {
140 6
            $resolved = [];
141
142 6
            if (!$hasInitializer) {
143 6
                $resolved = $this->examineDefinition($definition);
144 6
            }
145
146 6
            $hasInport = $this->hasValue($resolved, self::INPORT_LABEL);
147 6
            $hasOutport = $this->hasValue($resolved, self::OUTPORT_LABEL);
148
149
            //define states
150 6
            switch (true) {
151 6 View Code Duplication
                case !empty($step[self::DATA_LABEL]) && ($hasInport && $hasOutport):
152
                    // initializer + inport
153 1
                    $nextSrc = $resolved;
154 1
                    $step[self::TARGET_LABEL] = $this->addPort($resolved, self::INPORT_LABEL);
155
                    // multi def oneliner initializer resolved
156 1
                    array_push($this->definition[self::INITIALIZERS_LABEL], $step);
157 1
                    $step = [];
158 1
                    break;
159 6
                case !empty($nextSrc) && ($hasInport && $hasOutport):
160
                    // if there was an initializer, we get a full touple with this iteration
161
                    $step = [
162 1
                        self::SOURCE_LABEL => $this->addPort($nextSrc, self::OUTPORT_LABEL),
163 1
                        self::TARGET_LABEL => $this->addPort($resolved, self::INPORT_LABEL),
164 1
                    ];
165 1
                    $nextSrc = $resolved;
166 1
                    array_push($subset, $step);
167 1
                    $step = [];
168 1
                    break;
169 6 View Code Duplication
                case $hasInport && $hasOutport:
170
                    // tgt + multi def
171 3
                    $nextSrc = $resolved;
172 3
                    $step[self::TARGET_LABEL] = $this->addPort($resolved, self::INPORT_LABEL);
173
                    // check if we've already got the touple ready
174 3
                    if (!empty($step[self::SOURCE_LABEL])) {
175 3
                        array_push($subset, $step);
176 3
                        $step = [];
177 3
                    }
178 3
                    break;
179 6
                case $hasInport && $nextSrc:
180
                    // use previous OUT as src to fill touple
181 3
                    $step[self::SOURCE_LABEL] = $this->addPort($nextSrc, self::OUTPORT_LABEL);
182 3
                    $nextSrc = null;
183 6
                case $hasInport:
184 6
                    $step[self::TARGET_LABEL] = $this->addPort($resolved, self::INPORT_LABEL);
185
                    // resolved touple
186 6
                    if (empty($step[self::DATA_LABEL])) {
187 6
                        array_push($subset, $step);
188 6
                    } else {
189 1
                        array_push($this->definition[self::INITIALIZERS_LABEL], $step);
190
                    }
191 6
                    $nextSrc = null;
192 6
                    $step = [];
193 6
                    break;
194 6
                case $hasOutport:
195
                    // simplest case OUT -> IN
196 6
                    $step[self::SOURCE_LABEL] = $this->addPort($resolved, self::OUTPORT_LABEL);
197 6
                    break;
198 2
                case $hasInitializer:
199
                    // initialization value: at the moment we only support one
200 2
                    $step[self::DATA_LABEL] = trim($definition, " '");
201 2
                    $hasInitializer = false; // reset
202 2
                    break;
203
                default:
204
                    throw new ParserDefinitionException(
205
                        "Line ({$this->linecountOverall}) {$line} does not contain in or out ports!"
206
                    );
207
            }
208 6
        }
209
210 6
        return $subset;
211
    }
212
213
    /**
214
     * Check if array has a specific key and is not empty.
215
     *
216
     * @param array $check
217
     * @param string $value
218
     * @return bool
219
     */
220 6
    private function hasValue(array $check, $value)
221
    {
222 6
        if (empty($check[$value])) {
223 6
            return false;
224
        }
225
226 6
        return true;
227
    }
228
229
    /**
230
     * @param array $definition
231
     * @param string $label
232
     * @return array
233
     */
234 6
    private function addPort(array $definition, $label)
235
    {
236
        return [
237 6
            self::PROCESS_LABEL => $definition[self::PROCESS_LABEL],
238 6
            self::PORT_LABEL => $definition[$label],
239 6
        ];
240
    }
241
242
    /**
243
     * @param string $line
244
     * @return array
245
     * @throws ParserDefinitionException
246
     */
247 6
    private function examineDefinition($line)
248
    {
249 6
        preg_match('/' . self::PROCESS_DEFINITION . '/', $line, $matches);
250 6
        foreach ($matches as $key => $value) {
251 6
            if (is_numeric($key)) {
252 6
                unset($matches[$key]);
253 6
            }
254 6
        }
255
256 6
        if (!empty($matches[self::PROCESS_LABEL])) {
257 6
            if (empty($matches[self::COMPONENT_LABEL])) {
258 5
                $matches[self::COMPONENT_LABEL] = $matches[self::PROCESS_LABEL];
259 5
            }
260
261 6
            $this->examineProcess($matches);
262 6
        } else {
263
            throw new ParserDefinitionException(
264
                "No process definition found in line ({$this->linecountOverall}) {$line}"
265
            );
266
        }
267
268 6
        return $matches;
269
    }
270
271
    /**
272
     * Add entry to processes.
273
     *
274
     * @param array $process
275
     */
276 6
    private function examineProcess(array $process)
277
    {
278 6
        if (!isset($this->definition[self::PROCESSES_LABEL][$process[self::PROCESS_LABEL]])) {
279 6
            $component = $process[self::COMPONENT_LABEL];
280 6
            if (empty($component)) {
281
                $component = $process[self::PROCESS_LABEL];
282
            }
283
284 6
            $this->definition[self::PROCESSES_LABEL][$process[self::PROCESS_LABEL]] = [
285 6
                self::COMPONENT_LABEL => $component,
286 6
                self::METADATA_LABEL => [
287 6
                    'label' => $component,
288 6
                ],
289
            ];
290 6
        }
291 6
    }
292
293
    /**
294
     * Add name to definition
295
     *
296
     * @param string $line
297
     */
298 1
    private function addName($line)
299
    {
300 1
        $this->definition[self::PROPERTIES_LABEL]['name'] = trim(str_replace('#', '', $line));
301 1
    }
302
303
    /**
304
     * Check if line is empty or has comment.
305
     * In case of comments, add name to definition.
306
     *
307
     * @param string $line
308
     * @return bool
309
     */
310 6
    private function doSkip($line)
311
    {
312 6
        switch (true) {
313 6
            case (empty(trim($line))):
314
                // empty line
315 1
                $skip = true;
316 1
                break;
317 6
            case (1 == preg_match('/(#[\s\w]+)/', $line)):
318 1
                if (1 === $this->linecountOverall) {
319 1
                    $this->addName($line);
320 1
                }
321 1
                $skip = true;
322 1
                break;
323 6
            default:
324 6
                $skip = false;
325 6
        }
326
327 6
        return $skip;
328
    }
329
330
    /**
331
     * @param array $subset
332
     * @param string $line
333
     */
334 6
    private function validate(array $subset, $line)
335
    {
336 6
        foreach ($subset as $touple) {
337 6
            if (empty($touple[self::SOURCE_LABEL])) {
338 1
                $this->validationError($line, self::SOURCE_LABEL);
339
            }
340
341 6
            if (empty($touple[self::TARGET_LABEL])) {
342
                $this->validationError($line, self::TARGET_LABEL);
343
            }
344 6
        }
345 6
    }
346
347
    /**
348
     * @param string $line
349
     * @param string $port
350
     * @throws ParserException
351
     */
352 1
    private function validationError($line, $port)
353
    {
354 1
        throw new ParserException(
355 1
            "Error on line ({$this->linecountOverall}) {$line}: There is no {$port} defined. Maybe you forgot an in or out port?"
356 1
        );
357
    }
358
}
359