Completed
Pull Request — master (#4)
by Nico
02:39
created

BaseNode::getArguments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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