Passed
Push — master ( 45927b...26b8fa )
by Marc
02:33
created

FbpParser::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 1

Importance

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