Passed
Push — master ( 633b25...e5131a )
by Alexander
01:44 queued 10s
created

Lexer::getNextToken()   C

Complexity

Conditions 14
Paths 24

Size

Total Lines 58
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 36
c 1
b 0
f 0
nc 24
nop 0
dl 0
loc 58
rs 6.2666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
4
namespace App;
5
6
7
use App\Exception\LexerException;
8
use App\Exception\UnknownIdentifier;
9
10
class Lexer
11
{
12
    /**
13
     * @var string
14
     */
15
    private $input;
16
    /**
17
     * @var string|null
18
     */
19
    private $currentChar;
20
    /**
21
     * @var int
22
     */
23
    private $pos = 0;
24
25
    /**
26
     * Lexer constructor.
27
     * @param string $input
28
     * @throws LexerException
29
     */
30
    public function __construct(
31
        string $input
32
    )
33
    {
34
        $this->input       = $input;
35
        $this->currentChar = $this->input[$this->pos];
36
        if ($this->currentChar === '') {
37
            throw new LexerException('Lexer input is empty');
38
        }
39
    }
40
41
    private function isAlpha(string $char): bool
42
    {
43
        return (bool)preg_match('/[a-zA-Z_]/', $char);
44
    }
45
46
    /**
47
     * @return Token
48
     * @throws UnknownIdentifier
49
     */
50
    private function identifier(): Token
51
    {
52
        $identifier = '';
53
        while ($this->isAlpha($this->currentChar)) {
0 ignored issues
show
Bug introduced by
It seems like $this->currentChar can also be of type null; however, parameter $char of App\Lexer::isAlpha() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

53
        while ($this->isAlpha(/** @scrutinizer ignore-type */ $this->currentChar)) {
Loading history...
54
            $identifier .= $this->currentChar;
55
            $this->advance();
56
        }
57
58
        if (isset(FunctionModel::BUILT_IN_FUNCTIONS[$identifier])) {
59
            return new Token(Token::FUNCTION_CALL, $identifier);
60
        }
61
62
        throw new UnknownIdentifier('Unknown identifier ' . $identifier);
63
    }
64
65
    /**
66
     * @return Token
67
     * @throws LexerException
68
     * @throws UnknownIdentifier
69
     */
70
    public function getNextToken(): Token
71
    {
72
        while ($this->currentChar !== null) {
73
            if ($this->isAlpha($this->currentChar)) {
74
                return $this->identifier();
75
            }
76
77
            if ($this->currentChar === ' ') {
78
                $this->skipWhitespace();
79
            }
80
81
            if ($this->currentChar === ',') {
82
                $this->advance();
83
                return new Token(Token::COMMA, ',');
84
            }
85
86
            if ($this->currentChar === '+') {
87
                $this->advance();
88
                return new Token(Token::PLUS, '+');
89
            }
90
            if ($this->currentChar === '-') {
91
                $this->advance();
92
                return new Token(Token::MINUS, '-');
93
            }
94
            if ($this->currentChar === '^') {
95
                $this->advance();
96
                return new Token(Token::POWER, '^');
97
            }
98
            if ($this->currentChar === '*') {
99
                if ($this->peek() === '*') {
100
                    $this->advance();
101
                    $this->advance();
102
                    return new Token(Token::POWER, '**');
103
                }
104
105
                $this->advance();
106
                return new Token(Token::MUL, '*');
107
            }
108
            if ($this->currentChar === '/') {
109
                $this->advance();
110
                return new Token(Token::REALDIV, '/');
111
            }
112
113
            if ($this->currentChar === '(') {
114
                $this->advance();
115
                return new Token(Token::LPAREN, '(');
116
            }
117
            if ($this->currentChar === ')') {
118
                $this->advance();
119
                return new Token(Token::RPAREN, ')');
120
            }
121
122
            if (is_numeric($this->currentChar)) {
123
                return $this->parseNumber();
124
            }
125
        }
126
127
        return new Token(Token::EOF, Token::EOF);
128
    }
129
130
    private function skipWhitespace(): void
131
    {
132
        if ($this->currentChar === ' ') {
133
            $this->advance();
134
        }
135
    }
136
137
    private function advance(): void
138
    {
139
        $this->pos++;
140
        $this->currentChar = $this->input[$this->pos] ?? null;
141
    }
142
143
    private function peek(): ?string
144
    {
145
        $pos = $this->pos + 1;
146
        return $this->input[$pos] ?? null;
147
    }
148
149
    /**
150
     * @return Token
151
     * @throws LexerException
152
     */
153
    private function parseNumber(): Token
154
    {
155
        if ($this->currentChar === null) {
156
            throw new LexerException('Cannot found current char');
157
        }
158
        $number = '';
159
        while (is_numeric($this->currentChar) || ($number !== '' && $this->currentChar === '.')) {
160
            $number .= $this->currentChar;
161
            $this->advance();
162
        }
163
164
        if ($this->validateNumber($number) === false) {
165
            throw new LexerException('Parsed number not valid');
166
        }
167
168
        if (strpos($number, '.')) {
169
            return new Token(Token::REAL, (float)$number);
170
        }
171
172
        return new Token(Token::INT, (int)$number);
173
    }
174
175
    private function validateNumber(string $number): bool
176
    {
177
        return (bool)preg_match('/^[\d]+\.?[\d]*/', $number);
178
    }
179
}