Completed
Pull Request — master (#80)
by Christoffer
02:18
created

StringReader   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 126
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 23
dl 0
loc 126
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
D read() 0 92 17
A isLineTerminator() 0 3 2
A supportsReader() 0 5 2
A isSourceCharacter() 0 3 2
1
<?php
2
3
namespace Digia\GraphQL\Language\Reader;
4
5
use Digia\GraphQL\Error\SyntaxErrorException;
6
use Digia\GraphQL\Language\Token;
7
use Digia\GraphQL\Language\TokenKindEnum;
8
use function Digia\GraphQL\Language\charCodeAt;
9
use function Digia\GraphQL\Language\printCharCode;
10
use function Digia\GraphQL\Language\sliceString;
11
use function Digia\GraphQL\Language\uniCharCode;
12
13
/**
14
 * Class StringReader
15
 *
16
 * @package Digia\GraphQL\Language\Reader
17
 * Reads a string token from the source file.
18
 * "([^"\\\u000A\u000D]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*"
19
 */
20
class StringReader extends AbstractReader
21
{
22
23
    /**
24
     * @inheritdoc
25
     */
26
    public function read(int $code, int $pos, int $line, int $col, Token $prev): Token
27
    {
28
        $body       = $this->lexer->getBody();
29
        $bodyLength = mb_strlen($body);
30
        $start      = $pos;
31
        $pos        = $start + 1;
32
        $chunkStart = $pos;
33
        $value      = '';
34
35
        while ($pos < $bodyLength && ($code = charCodeAt($body, $pos)) !== null && !$this->isLineTerminator($code)) {
36
            // Closing Quote (")
37
            if ($code === 34) {
38
                $value .= sliceString($body, $chunkStart, $pos);
39
40
                return new Token(TokenKindEnum::STRING, $start, $pos + 1, $line, $col, $prev, $value);
41
            }
42
43
            if ($this->isSourceCharacter($code)) {
44
                throw new SyntaxErrorException(
45
                    $this->lexer->getSource(),
46
                    $pos,
47
                    sprintf('Invalid character within String: %s', printCharCode($code))
48
                );
49
            }
50
51
            ++$pos;
52
53
            if ($code === 92) {
54
                // \
55
                $value .= sliceString($body, $chunkStart, $pos + 1);
56
                $code  = charCodeAt($body, $pos);
57
58
                switch ($code) {
59
                    case 34:
60
                        $value .= '"';
61
                        break;
62
                    case 47:
63
                        $value .= '/';
64
                        break;
65
                    case 92:
66
                        $value .= '\\';
67
                        break;
68
                    case 98:
69
                        $value .= '\b';
70
                        break;
71
                    case 102:
72
                        $value .= '\f';
73
                        break;
74
                    case 110:
75
                        $value .= '\n';
76
                        break;
77
                    case 114:
78
                        $value .= '\r';
79
                        break;
80
                    case 116:
81
                        $value .= '\t';
82
                        break;
83
                    case 117:
84
                        // u
85
                        $charCode = uniCharCode(
86
                            charCodeAt($body, $pos + 1),
87
                            charCodeAt($body, $pos + 2),
88
                            charCodeAt($body, $pos + 3),
89
                            charCodeAt($body, $pos + 4)
90
                        );
91
                        if ($charCode < 0) {
92
                            throw new SyntaxErrorException(
93
                                $this->lexer->getSource(),
94
                                $pos,
95
                                sprintf(
96
                                    'Invalid character escape sequence: \\u%s',
97
                                    sliceString($body, $pos + 1, $pos + 5)
98
                                )
99
                            );
100
                        }
101
                        $value .= chr($charCode);
0 ignored issues
show
Bug introduced by
$charCode of type string is incompatible with the type integer expected by parameter $ascii of chr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

101
                        $value .= chr(/** @scrutinizer ignore-type */ $charCode);
Loading history...
102
                        $pos   += 4;
103
                        break;
104
                    default:
105
                        throw new SyntaxErrorException(
106
                            $this->lexer->getSource(),
107
                            $pos,
108
                            sprintf('Invalid character escape sequence: \\%s', chr($code))
109
                        );
110
                }
111
112
                ++$pos;
113
                $chunkStart = $pos;
114
            }
115
        }
116
117
        throw new SyntaxErrorException($this->lexer->getSource(), $pos, 'Unterminated string.');
118
    }
119
120
    /**
121
     * @inheritdoc
122
     */
123
    public function supportsReader(int $code, int $pos): bool
124
    {
125
        $body = $this->lexer->getBody();
126
127
        return $code === 34 && charCodeAt($body, $pos + 1) !== 34;
128
    }
129
130
    /**
131
     * @param int $code
132
     * @return bool
133
     */
134
    protected function isLineTerminator(int $code): bool
135
    {
136
        return $code === 0x000a || $code === 0x000d;
137
    }
138
139
    /**
140
     * @param int $code
141
     * @return bool
142
     */
143
    protected function isSourceCharacter(int $code): bool
144
    {
145
        return $code < 0x0020 && $code !== 0x0009;
146
    }
147
}
148