Passed
Push — master ( eff209...0e7332 )
by Edward
05:02
created

UnicodeDataRangeIterator::fetchUnicodeDataRange()   B

Complexity

Conditions 9
Paths 6

Size

Total Lines 35
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 20
c 1
b 0
f 0
dl 0
loc 35
rs 8.0555
cc 9
nc 6
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Remorhaz\UniLex\Tool\RegExp;
6
7
use Iterator;
8
use IteratorAggregate;
9
use Remorhaz\UniLex\RegExp\FSM\Range;
10
use SplFileObject;
11
use Throwable;
12
13
use function strlen;
14
15
final class UnicodeDataRangeIterator implements IteratorAggregate
16
{
17
18
    private $file;
19
20
    private $onProgress;
21
22
    private $code;
23
24
    private $name;
25
26
    private $prop;
27
28
    private $lastCode;
29
30
    private $lastProp;
31
32
    private $rangeStart;
33
34
    private $namedStarts = [];
35
36
    public function __construct(SplFileObject $file, callable $onProgress)
37
    {
38
        $this->file = $file;
39
        $this->onProgress = $onProgress;
40
    }
41
42
    public function getIterator(): Iterator
43
    {
44
        while (!$this->file->eof()) {
45
            $line = $this->fetchNextLine($this->file);
46
            if (!isset($line)) {
47
                continue;
48
            }
49
            $range = $this->fetchUnicodeDataRange($line);
50
            if (isset($range)) {
51
                yield $this->lastProp => $range;
52
            }
53
54
            $this->lastCode = $this->code;
55
            $this->lastProp = $this->prop;
56
57
            ($this->onProgress)(strlen($line));
58
        }
59
    }
60
61
    private function fetchNextLine(SplFileObject $file): ?string
62
    {
63
        $line = $file->fgets();
64
        if (false === $line) {
65
            throw new Exception\LineNotReadException($file->getFilename());
66
        }
67
68
        return '' == $line ? null : $line;
69
    }
70
71
    private function parseUnicodeDataLineLine(string $line): void
72
    {
73
        $splitLine = explode(';', $line);
74
        $codeHex = $splitLine[0] ?? null;
75
        $name = $splitLine[1] ?? null;
76
        $prop = $splitLine[2] ?? null;
77
        if (!isset($codeHex, $name, $prop)) {
78
            throw new Exception\InvalidLineException($line);
79
        }
80
        $this->code = hexdec($codeHex);
81
        $this->name = $name;
82
        $this->prop = $prop;
83
    }
84
85
    private function fetchUnicodeDataRange(string $line): ?Range
86
    {
87
        $this->parseUnicodeDataLineLine($line);
88
89
        [$firstName, $lastName] = $this->parseRangeBoundary($this->name);
90
        if (isset($firstName)) {
91
            $this->namedStarts[$firstName] = $this->code;
92
            $this->rangeStart = null;
93
94
            return null;
95
        }
96
97
        if (isset($lastName)) {
98
            if (
99
                !isset($this->namedStarts[$lastName]) ||
100
                isset($this->rangeStart) ||
101
                $this->lastCode !== $this->namedStarts[$lastName]
102
            ) {
103
                throw new Exception\InvalidLineException($line);
104
            }
105
106
            return $this->createRange($this->lastCode, $this->code);
107
        }
108
109
        if ($this->prop === $this->lastProp && $this->code - 1 === $this->lastCode) {
110
            return null;
111
        }
112
113
        $range = isset($this->rangeStart, $this->lastCode)
114
            ? $this->createRange($this->rangeStart, $this->lastCode)
115
            : null;
116
117
        $this->rangeStart = $this->code;
118
119
        return $range;
120
    }
121
122
    private function parseRangeBoundary(string $name): array
123
    {
124
        try {
125
            $isFirst = 1 === \Safe\preg_match('#^<(.+), First>$#', $name, $matches);
126
            if ($isFirst) {
127
                return [$matches[1] ?? null, null];
128
            }
129
130
            $isLast = 1 === \Safe\preg_match('#^<(.+), Last>$#', $name, $matches);
131
132
            return $isLast
133
                ? [null, $matches[1] ?? null]
134
                : [null, null];
135
        } catch (Throwable $e) {
136
            throw new Exception\CodePointNameNotParsedException($name, $e);
137
        }
138
    }
139
140
    private function createRange(int $start, ?int $finish): Range
141
    {
142
        try {
143
            return new Range($start, $finish);
144
        } catch (Throwable $e) {
145
            throw new Exception\RangeNotCreatedException($e);
146
        }
147
    }
148
}
149