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

brick / geo / 13715715561

06 Mar 2025 10:47PM UTC coverage: 44.086% (-40.4%) from 84.507%
13715715561

push

github

BenMorel
Remove Psalm-specific annotations

1543 of 3500 relevant lines covered (44.09%)

270.91 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
class WKTParser
13
{
14
    protected const T_WORD   = 1;
15
    protected const T_NUMBER = 2;
16

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

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

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

32
    public function __construct(string $wkt)
33
    {
34
        $this->scan($wkt);
4,172✔
35
    }
36

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

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

54
        $regex = '/' . implode('|', $regex) . '/i';
4,172✔
55

56
        preg_match_all($regex, $wkt, $matches, PREG_SET_ORDER);
4,172✔
57

58
        foreach ($matches as $match) {
4,172✔
59
            foreach ($match as $key => $value) {
4,172✔
60
                /** @var int $key */
61

62
                if ($key === 0) {
4,172✔
63
                    continue;
4,172✔
64
                }
65

66
                if ($value !== '') {
4,172✔
67
                    $this->tokens[] = [$key, $value];
4,172✔
68
                }
69
            }
70
        }
71
    }
72

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

80
        if ($token === null) {
4,172✔
81
            return null;
4,158✔
82
        }
83

84
        $this->current++;
4,172✔
85

86
        return $token;
4,172✔
87
    }
88

89
    /**
90
     * @throws GeometryIOException
91
     */
92
    public function matchOpener() : void
93
    {
94
        $token = $this->nextToken();
2,656✔
95

96
        if ($token === null) {
2,656✔
97
            throw new GeometryIOException("Expected '(' but encountered end of stream");
×
98
        }
99
        if ($token[1] !== '(') {
2,656✔
100
            throw new GeometryIOException("Expected '(' but encountered '" . $token[1] . "'");
×
101
        }
102
    }
103

104
    /**
105
     * @throws GeometryIOException
106
     */
107
    public function matchCloser() : void
108
    {
109
        $token = $this->nextToken();
430✔
110

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

119
    /**
120
     * @throws GeometryIOException
121
     */
122
    public function getNextWord() : string
123
    {
124
        $token = $this->nextToken();
4,172✔
125

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

133
        return $token[1];
4,172✔
134
    }
135

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

143
        if ($token === null) {
4,172✔
144
            return null;
×
145
        }
146

147
        if ($token[0] !== static::T_WORD) {
4,172✔
148
            return null;
2,656✔
149
        }
150

151
        $this->current++;
3,150✔
152

153
        return $token[1];
3,150✔
154
    }
155

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

167
        if ($token === null) {
378✔
168
            throw new GeometryIOException("Expected '(' or word but encountered end of stream");
×
169
        }
170

171
        if ($token[1] === '(') {
378✔
172
            return true;
370✔
173
        }
174

175
        if ($token[0] === static::T_WORD) {
322✔
176
            return false;
322✔
177
        }
178

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

182
    /**
183
     * @throws GeometryIOException
184
     */
185
    public function getNextNumber() : float
186
    {
187
        $token = $this->nextToken();
2,634✔
188

189
        if ($token === null) {
2,634✔
190
            throw new GeometryIOException("Expected number but encountered end of stream");
×
191
        }
192

193
        if ($token[0] !== static::T_NUMBER) {
2,634✔
194
            throw new GeometryIOException("Expected number but encountered '" . $token[1] . "'");
×
195
        }
196

197
        return (float) $token[1];
2,634✔
198
    }
199

200
    /**
201
     * @throws GeometryIOException
202
     */
203
    public function getNextCloserOrComma() : string
204
    {
205
        $token = $this->nextToken();
2,410✔
206

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

214
        return $token[1];
2,410✔
215
    }
216

217
    public function isEndOfStream() : bool
218
    {
219
        return $this->nextToken() === null;
4,158✔
220
    }
221
}
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