Passed
Push — master ( 7426d3...01dce1 )
by Marc
02:49
created

FbpParser::validate()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.432

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 7
cts 10
cp 0.7
rs 9.2
c 0
b 0
f 0
cc 4
eloc 6
nc 5
nop 2
crap 4.432
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 array
47
     */
48
    private $definition;
49
50
    /**
51
     * FbpParser constructor.
52
     *
53
     * @param string $source optional for initializing
54
     * @param array $settings optional settings for parser
55
     */
56 7
    public function __construct($source = '', $settings = [])
57
    {
58 7
        $this->source   = $source;
59 7
        $this->settings = array_replace_recursive(
60 7
            [],
61
            $settings
62 7
        );
63
64 7
        $this->schema = [
65 7
            self::PROPERTIES_LABEL => [
66 7
                'name' => '',
67 7
            ],
68 7
            self::INITIALIZERS_LABEL => [],
69 7
            self::PROCESSES_LABEL => [],
70 7
            self::CONNECTIONS_LABEL => [],
71
        ];
72
73 7
        $this->definition = [];
74 7
    }
75
76
    /**
77
     * @param string $source
78
     * @return FbpDefinition
79
     * @throws ParserException
80
     */
81 7
    public function run($source = '')
82
    {
83 7
        if ('' != $source) {
84 2
            $this->source = $source;
85 2
        }
86
87 7
        if (empty($this->source)) {
88 1
            throw new ParserException("FbpParser::run(): no source data or empty string given!");
89
        }
90
91 6
        $this->definition = $this->schema; // reset
92 6
        $this->linecount = 1;
93
94
        /*
95
         * split by lines, OS-independent
96
         * work each line and parse for definitions
97
         */
98 6
        foreach (preg_split('/' . self::NEWLINES . '/m', $this->source) as $line) {
99 6
            $subset = $this->examineSubset($line);
100 6
            $this->validate($subset, $line); // post-parse validation, easier that way
101 6
            $this->definition[self::CONNECTIONS_LABEL] = array_merge_recursive(
102 6
                $this->definition[self::CONNECTIONS_LABEL], $subset
103 6
            );
104 6
            $this->linecount++;
105 6
        }
106
107 5
        return new FbpDefinition($this->definition);
108
    }
109
110
    /**
111
     * @param string $line
112
     * @return array
113
     * @throws ParserDefinitionException
114
     */
115 6
    private function examineSubset($line)
116
    {
117 6
        $subset = [];
118 6
        $step = [];
119 6
        $nextSrc = null;
120 6
        $hasInitializer = false;
121
122 6
        if (1 == $this->linecount && 0 === strpos(trim($line), "'")) {
123 2
            $hasInitializer = true;
124 2
        }
125
126
        // subset
127 6
        foreach (explode(self::SOURCE_TARGET_SEPARATOR, $line) as $definition) {
128 6
            $resolved = [];
129
130 6
            if (!$hasInitializer) {
131 6
                $resolved = $this->examineDefinition($definition);
132 6
            }
133
134 6
            $hasInport = $this->hasValue($resolved, self::INPORT_LABEL);
135 6
            $hasOutport = $this->hasValue($resolved, self::OUTPORT_LABEL);
136
137
            //define states
138 6
            switch (true) {
139 6 View Code Duplication
                case !empty($step[self::DATA_LABEL]) && ($hasInport && $hasOutport):
140
                    // initializer + inport
141 1
                    $nextSrc = $resolved;
142 1
                    $step[self::TARGET_LABEL] = $this->addPort($resolved, self::INPORT_LABEL);
143
                    // multi def oneliner initializer resolved
144 1
                    array_push($this->definition[self::INITIALIZERS_LABEL], $step);
145 1
                    $step = [];
146 1
                    break;
147 6
                case !empty($nextSrc) && ($hasInport && $hasOutport):
148
                    // if there was an initializer, we get a full touple with this iteration
149
                    $step = [
150 1
                        self::SOURCE_LABEL => $this->addPort($nextSrc, self::OUTPORT_LABEL),
151 1
                        self::TARGET_LABEL => $this->addPort($resolved, self::INPORT_LABEL),
152 1
                    ];
153 1
                    $nextSrc = $resolved;
154 1
                    array_push($subset, $step);
155 1
                    $step = [];
156 1
                    break;
157 6 View Code Duplication
                case $hasInport && $hasOutport:
158
                    // tgt + multi def
159 3
                    $nextSrc = $resolved;
160 3
                    $step[self::TARGET_LABEL] = $this->addPort($resolved, self::INPORT_LABEL);
161
                    // check if we've already got the touple ready
162 3
                    if (!empty($step[self::SOURCE_LABEL])) {
163 3
                        array_push($subset, $step);
164 3
                        $step = [];
165 3
                    }
166 3
                    break;
167 6
                case $hasInport && $nextSrc:
168
                    // use previous OUT as src to fill touple
169 3
                    $step[self::SOURCE_LABEL] = $this->addPort($nextSrc, self::OUTPORT_LABEL);
170 3
                    $nextSrc = null;
171 6
                case $hasInport:
172 6
                    $step[self::TARGET_LABEL] = $this->addPort($resolved, self::INPORT_LABEL);
173
                    // resolved touple
174 6
                    if (empty($step[self::DATA_LABEL])) {
175 6
                        array_push($subset, $step);
176 6
                    } else {
177 1
                        array_push($this->definition[self::INITIALIZERS_LABEL], $step);
178
                    }
179 6
                    $nextSrc = null;
180 6
                    $step = [];
181 6
                    break;
182 6
                case $hasOutport:
183
                    // simplest case OUT -> IN
184 6
                    $step[self::SOURCE_LABEL] = $this->addPort($resolved, self::OUTPORT_LABEL);
185 6
                    break;
186 2
                case $hasInitializer:
187
                    // initialization value: at the moment we only support one
188 2
                    $step[self::DATA_LABEL] = trim($definition, " '");
189 2
                    $hasInitializer = false; // reset
190 2
                    break;
191
                default:
192
                    throw new ParserDefinitionException(
193
                        "Line ({$this->linecount}) {$line} does not contain in or out ports!"
194
                    );
195
            }
196 6
        }
197
198 6
        return $subset;
199
    }
200
201
    /**
202
     * Check if array has a specific key and is not empty.
203
     *
204
     * @param array $check
205
     * @param string $value
206
     * @return bool
207
     */
208 6
    private function hasValue(array $check, $value)
209
    {
210 6
        if (empty($check[$value])) {
211 6
            return false;
212
        }
213
214 6
        return true;
215
    }
216
217
    /**
218
     * @param array $definition
219
     * @param string $label
220
     * @return array
221
     */
222 6
    private function addPort(array $definition, $label)
223
    {
224
        return [
225 6
            self::PROCESS_LABEL => $definition[self::PROCESS_LABEL],
226 6
            self::PORT_LABEL => $definition[$label],
227 6
        ];
228
    }
229
230
    /**
231
     * @param string $line
232
     * @return array
233
     * @throws ParserDefinitionException
234
     */
235 6
    private function examineDefinition($line)
236
    {
237 6
        preg_match('/' . self::PROCESS_DEFINITION . '/', $line, $matches);
238 6
        foreach ($matches as $key => $value) {
239 6
            if (is_numeric($key)) {
240 6
                unset($matches[$key]);
241 6
            }
242 6
        }
243
244 6
        if (!empty($matches[self::PROCESS_LABEL])) {
245 6
            if (empty($matches[self::COMPONENT_LABEL])) {
246 5
                $matches[self::COMPONENT_LABEL] = $matches[self::PROCESS_LABEL];
247 5
            }
248
249 6
            $this->examineProcess($matches);
250 6
        } else {
251
            throw new ParserDefinitionException(
252
                "No process definition found in line ({$this->linecount}) {$line}"
253
            );
254
        }
255
256 6
        return $matches;
257
    }
258
259
    /**
260
     * Add entry to processes.
261
     *
262
     * @param array $process
263
     */
264 6
    private function examineProcess(array $process)
265
    {
266 6
        if (!isset($this->definition[self::PROCESSES_LABEL][$process[self::PROCESS_LABEL]])) {
267 6
            $component = $process[self::COMPONENT_LABEL];
268 6
            if (empty($component)) {
269
                $component = $process[self::PROCESS_LABEL];
270
            }
271
272 6
            $this->definition[self::PROCESSES_LABEL][$process[self::PROCESS_LABEL]] = [
273 6
                self::COMPONENT_LABEL => $component,
274 6
                self::METADATA_LABEL => [
275 6
                    'label' => $component,
276 6
                ],
277
            ];
278 6
        }
279 6
    }
280
281
    /**
282
     * @param array $subset
283
     * @param string $line
284
     */
285 6
    private function validate(array $subset, $line)
286
    {
287 6
        foreach ($subset as $touple) {
288 6
            if (empty($touple[self::SOURCE_LABEL])) {
289 1
                $this->validationError($line, self::SOURCE_LABEL);
290
            }
291
292 6
            if (empty($touple[self::TARGET_LABEL])) {
293
                $this->validationError($line, self::TARGET_LABEL);
294
            }
295 6
        }
296 6
    }
297
298
    /**
299
     * @param string $line
300
     * @param string $port
301
     * @throws ParserException
302
     */
303 1
    private function validationError($line, $port)
304
    {
305 1
        throw new ParserException(
306 1
            "Error on line ({$this->linecount}) {$line}: There is no {$port} defined. Maybe you forgot an in or out port?"
307 1
        );
308
    }
309
}
310