Completed
Push — master ( 73afad...15a795 )
by Nico
04:02
created

BaseNode::hasMethodCall()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 14
cts 14
cp 1
rs 8.5906
c 0
b 0
f 0
cc 5
eloc 15
nc 4
nop 0
crap 5
1
<?php
2
3
/**
4
 * @license     http://opensource.org/licenses/mit-license.php MIT
5
 * @link        https://github.com/nicoSWD
6
 * @author      Nicolas Oelgart <[email protected]>
7
 */
8
declare(strict_types = 1);
9
10
namespace nicoSWD\Rules\AST\Nodes;
11
12
use nicoSWD\Rules\AST;
13
use nicoSWD\Rules\AST\TokenCollection;
14
use nicoSWD\Rules\Constants;
15
use nicoSWD\Rules\Core\CallableFunction;
16
use nicoSWD\Rules\Exceptions\ParserException;
17
use nicoSWD\Rules\Tokens;
18
use nicoSWD\Rules\Tokens\{
19
    BaseToken,
20
    TokenComment,
21
    TokenMethod,
22
    TokenNewline,
23
    TokenSpace
24
};
25
26
abstract class BaseNode
27
{
28
    /**
29
     * @var AST
30
     */
31
    protected $ast;
32
33
    /**
34
     * @var string
35
     */
36
    protected $methodName = '';
37
38
    /**
39
     * @var int
40
     */
41
    protected $methodOffset = 0;
42
43
    /**
44
     * @param AST $ast
45
     */
46 206
    public function __construct(AST $ast)
47
    {
48 206
        $this->ast = $ast;
49 206
    }
50
51
    abstract public function getNode() : BaseToken;
52
53
    /**
54
     * Looks ahead, but does not move the pointer.
55
     */
56 194
    protected function hasMethodCall() : bool
57
    {
58 194
        $stackClone = $this->ast->getStack()->getClone();
59
60 194
        while ($stackClone->valid()) {
61 194
            $stackClone->next();
62
63 194
            if (!$token = $stackClone->current()) {
64 102
                break;
65 192
            } elseif ($this->isIgnoredToken($token)) {
66 130
                continue;
67 192
            } elseif ($token instanceof TokenMethod) {
68 108
                $this->methodName = $token->getValue();
69 108
                $this->methodOffset = $token->getOffset();
70
71 108
                return true;
72
            } else {
73 172
                break;
74
            }
75
        }
76
77 174
        return false;
78
    }
79
80
    /**
81
     * @throws ParserException
82
     */
83 108
    public function getMethod(BaseToken $token) : CallableFunction
84
    {
85 108
        $methodName = $this->getMethodName();
86 108
        $methodClass = '\nicoSWD\Rules\Core\Methods\\' . ucfirst($methodName);
87
88 108
        if (!class_exists($methodClass)) {
89 2
            $current = $this->ast->getStack()->current();
90
91 2
            throw new ParserException(sprintf(
92 2
                'undefined is not a function at position %d on line %d',
93 2
                $current->getPosition(),
94 2
                $current->getLine()
95
            ));
96
        }
97
98
        /** @var CallableFunction $instance */
99 106
        $instance = new $methodClass($token);
100
101 106
        if ($instance->getName() !== $methodName) {
102 2
            throw new ParserException(
103 2
                'undefined is not a function'
104
            );
105
        }
106
107 104
        return $instance;
108
    }
109
110 108
    private function getMethodName() : string
111
    {
112
        do {
113 108
            $this->ast->next();
114 108
        } while ($this->ast->getStack()->current()->getOffset() < $this->methodOffset);
115
116 108
        return trim(ltrim(rtrim($this->methodName, "\r\n("), '.'));
117
    }
118
119 52
    public function getArrayItems() : TokenCollection
120
    {
121 52
        return $this->getCommaSeparatedValues(']');
122
    }
123
124 126
    public function getArguments() : TokenCollection
125
    {
126 126
        return $this->getCommaSeparatedValues(')');
127
    }
128
129
    /**
130
     * @throws ParserException
131
     */
132 148
    private function getCommaSeparatedValues(string $stopAt) : TokenCollection
133
    {
134 148
        $commaExpected = false;
135 148
        $items = new TokenCollection();
136
137
        do {
138 148
            $this->ast->next();
139
140 148
            if (!$current = $this->ast->current()) {
141 4
                throw new ParserException(sprintf(
142 4
                    'Unexpected end of string. Expected "%s"',
143 4
                    $stopAt
144
                ));
145
            }
146
147 148
            if ($current->getGroup() === Constants::GROUP_VALUE) {
148 124
                if ($commaExpected) {
149 4
                    throw new ParserException(sprintf(
150 4
                        'Unexpected value at position %d on line %d',
151 4
                        $current->getPosition(),
152 4
                        $current->getLine()
153
                    ));
154
                }
155
156 124
                $commaExpected = true;
157 124
                $items->attach($current);
158 148
            } elseif ($current instanceof Tokens\TokenComma) {
159 60
                if (!$commaExpected) {
160 4
                    throw new ParserException(sprintf(
161 4
                        'Unexpected token "," at position %d on line %d',
162 4
                        $current->getPosition(),
163 4
                        $current->getLine()
164
                    ));
165
                }
166
167 60
                $commaExpected = false;
168 148
            } elseif ($current->getValue() === $stopAt) {
169 132
                break;
170 64
            } elseif (!$this->isIgnoredToken($current)) {
171 4
                throw new ParserException(sprintf(
172 4
                    'Unexpected token "%s" at position %d on line %d',
173 4
                    $current->getOriginalValue(),
174 4
                    $current->getPosition(),
175 4
                    $current->getLine()
176
                ));
177
            }
178 124
        } while ($this->ast->valid());
179
180 132
        if (!$commaExpected && $items->count() > 0) {
181 6
            throw new ParserException(sprintf(
182 6
                'Unexpected token "," at position %d on line %d',
183 6
                $current->getPosition(),
184 6
                $current->getLine()
185
            ));
186
        }
187
188 126
        $items->rewind();
189
190 126
        return $items;
191
    }
192
193 192
    private function isIgnoredToken(BaseToken $token) : bool
194
    {
195
        return (
196 192
            $token instanceof TokenSpace ||
197 192
            $token instanceof TokenNewline ||
198 192
            $token instanceof TokenComment
199
        );
200
    }
201
}
202