• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

brick / geo / 13716844819

07 Mar 2025 08:35AM UTC coverage: 47.964% (+3.9%) from 44.086%
13716844819

push

github

BenMorel
Add TypeChecker for engines

8 of 21 new or added lines in 5 files covered. (38.1%)

98 existing lines in 18 files now uncovered.

1684 of 3511 relevant lines covered (47.96%)

944.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.0
/src/IO/WKTParser.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Brick\Geo\IO;
6

7
use Brick\Geo\Exception\GeometryIOException;
8

9
/**
10
 * Well-Known Text parser.
11
 *
12
 * @internal
13
 */
14
class WKTParser
15
{
16
    protected const T_WORD   = 1;
17
    protected const T_NUMBER = 2;
18

19
    protected const REGEX_WORD   = '([a-z]+)';
20
    protected const REGEX_NUMBER = '(\-?[0-9]+(?:\.[0-9]+)?(?:e[\+\-]?[0-9]+)?)';
21

22
    /**
23
     * The list of tokens.
24
     *
25
     * @var list<array{int, string}>
26
     */
27
    protected array $tokens = [];
28

29
    /**
30
     * The current token pointer.
31
     */
32
    protected int $current = 0;
33

34
    public function __construct(string $wkt)
35
    {
36
        $this->scan($wkt);
14,569✔
37
    }
38

39
    /**
40
     * @return array<static::T_*, string>
41
     */
42
    protected function getRegex() : array
43
    {
44
        return [
9,809✔
45
            self::T_WORD   => self::REGEX_WORD,
9,809✔
46
            self::T_NUMBER => self::REGEX_NUMBER,
9,809✔
47
        ];
9,809✔
48
    }
49

50
    private function scan(string $wkt) : void
51
    {
52
        $regex = $this->getRegex();
14,569✔
53
        $regex[] = '\s+';
14,569✔
54
        $regex[] = '(.+?)';
14,569✔
55

56
        $regex = '/' . implode('|', $regex) . '/i';
14,569✔
57

58
        preg_match_all($regex, $wkt, $matches, PREG_SET_ORDER);
14,569✔
59

60
        foreach ($matches as $match) {
14,569✔
61
            foreach ($match as $key => $value) {
14,569✔
62
                /** @var int $key */
63

64
                if ($key === 0) {
14,569✔
65
                    continue;
14,569✔
66
                }
67

68
                if ($value !== '') {
14,569✔
69
                    $this->tokens[] = [$key, $value];
14,569✔
70
                }
71
            }
72
        }
73
    }
74

75
    /**
76
     * @return array{int, string}|null The next token, or null if there are no more tokens.
77
     */
78
    private function nextToken() : ?array
79
    {
80
        $token = $this->tokens[$this->current] ?? null;
14,569✔
81

82
        if ($token === null) {
14,569✔
83
            return null;
14,520✔
84
        }
85

86
        $this->current++;
14,569✔
87

88
        return $token;
14,569✔
89
    }
90

91
    /**
92
     * @throws GeometryIOException
93
     */
94
    public function matchOpener() : void
95
    {
96
        $token = $this->nextToken();
9,295✔
97

98
        if ($token === null) {
9,295✔
UNCOV
99
            throw new GeometryIOException("Expected '(' but encountered end of stream");
×
100
        }
101
        if ($token[1] !== '(') {
9,295✔
UNCOV
102
            throw new GeometryIOException("Expected '(' but encountered '" . $token[1] . "'");
×
103
        }
104
    }
105

106
    /**
107
     * @throws GeometryIOException
108
     */
109
    public function matchCloser() : void
110
    {
111
        $token = $this->nextToken();
1,522✔
112

113
        if ($token === null) {
1,522✔
UNCOV
114
            throw new GeometryIOException("Expected ')' but encountered end of stream");
×
115
        }
116
        if ($token[1] !== ')') {
1,522✔
UNCOV
117
            throw new GeometryIOException("Expected ')' but encountered '" . $token[1] . "'");
×
118
        }
119
    }
120

121
    /**
122
     * @throws GeometryIOException
123
     */
124
    public function getNextWord() : string
125
    {
126
        $token = $this->nextToken();
14,569✔
127

128
        if ($token === null) {
14,569✔
UNCOV
129
            throw new GeometryIOException("Expected word but encountered end of stream");
×
130
        }
131
        if ($token[0] !== static::T_WORD) {
14,569✔
UNCOV
132
            throw new GeometryIOException("Expected word but encountered '" . $token[1] . "'");
×
133
        }
134

135
        return $token[1];
14,569✔
136
    }
137

138
    /**
139
     * @return string|null The next word, or NULL if the next token is not a word, or there are no more tokens.
140
     */
141
    public function getOptionalNextWord() : ?string
142
    {
143
        $token = $this->tokens[$this->current] ?? null;
14,569✔
144

145
        if ($token === null) {
14,569✔
UNCOV
146
            return null;
×
147
        }
148

149
        if ($token[0] !== static::T_WORD) {
14,569✔
150
            return null;
9,295✔
151
        }
152

153
        $this->current++;
10,980✔
154

155
        return $token[1];
10,980✔
156
    }
157

158
    /**
159
     * Returns whether the next token is an opener or a word.
160
     *
161
     * @return bool True if the next token is an opener, false if it is a word.
162
     *
163
     * @throws GeometryIOException If the next token is not an opener or a word, or if there is no next token.
164
     */
165
    public function isNextOpenerOrWord() : bool
166
    {
167
        $token = $this->tokens[$this->current] ?? null;
1,323✔
168

169
        if ($token === null) {
1,323✔
UNCOV
170
            throw new GeometryIOException("Expected '(' or word but encountered end of stream");
×
171
        }
172

173
        if ($token[1] === '(') {
1,323✔
174
            return true;
1,295✔
175
        }
176

177
        if ($token[0] === static::T_WORD) {
1,127✔
178
            return false;
1,127✔
179
        }
180

UNCOV
181
        throw new GeometryIOException("Expected '(' or word but encountered '" . $token[1] . "'");
×
182
    }
183

184
    /**
185
     * @throws GeometryIOException
186
     */
187
    public function getNextNumber() : float
188
    {
189
        $token = $this->nextToken();
9,218✔
190

191
        if ($token === null) {
9,218✔
UNCOV
192
            throw new GeometryIOException("Expected number but encountered end of stream");
×
193
        }
194

195
        if ($token[0] !== static::T_NUMBER) {
9,218✔
UNCOV
196
            throw new GeometryIOException("Expected number but encountered '" . $token[1] . "'");
×
197
        }
198

199
        return (float) $token[1];
9,218✔
200
    }
201

202
    /**
203
     * @throws GeometryIOException
204
     */
205
    public function getNextCloserOrComma() : string
206
    {
207
        $token = $this->nextToken();
8,419✔
208

209
        if ($token === null) {
8,419✔
UNCOV
210
            throw new GeometryIOException("Expected ')' or ',' but encountered end of stream");
×
211
        }
212
        if ($token[1] !== ')' && $token[1] !== ',') {
8,419✔
UNCOV
213
            throw new GeometryIOException("Expected ')' or ',' but encountered '" . $token[1] . "'");
×
214
        }
215

216
        return $token[1];
8,419✔
217
    }
218

219
    public function isEndOfStream() : bool
220
    {
221
        return $this->nextToken() === null;
14,520✔
222
    }
223
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc