Completed
Push — master ( 895d73...5bc057 )
by Federico
02:36
created

CodeHandler::parseSeparator()   D

Complexity

Conditions 9
Paths 7

Size

Total Lines 45
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 9
eloc 25
c 1
b 0
f 1
nc 7
nop 6
dl 0
loc 45
rs 4.909
1
<?php
2
3
namespace Jade\Compiler;
4
5
/**
6
 * Class Jade\Compiler\CodeHandler.
7
 */
8
class CodeHandler extends CompilerUtils
9
{
10
    protected $input;
11
    protected $name;
12
    protected $separators;
13
14
    public function __construct($input, $name)
15
    {
16
        if (!is_string($input)) {
17
            throw new \InvalidArgumentException('Expecting a string of PHP, got: ' . gettype($input), 11);
18
        }
19
20
        if (strlen($input) === 0) {
21
            throw new \InvalidArgumentException('Expecting a string of PHP, empty string received.', 12);
22
        }
23
24
        $this->input = trim(preg_replace('/\bvar\b/', '', $input));
25
        $this->name = $name;
26
        $this->separators = array();
27
    }
28
29
    public function innerCode($input, $name)
30
    {
31
        $handler = new static($input, $name);
32
33
        return $handler->parse();
34
    }
35
36
    public function parse()
37
    {
38
        if ($this->isQuotedString()) {
39
            return array($this->input);
40
        }
41
42
        if (strpos('=,;?', substr($this->input, 0, 1)) !== false) {
43
            throw new \ErrorException('Expecting a variable name or an expression, got: ' . $this->input, 13);
44
        }
45
46
        preg_match_all(
47
            '/(?<![<>=!])=(?!>|=)|[\[\]\{\}\(\),;\.]|(?!:):|->/', // punctuation
48
            preg_replace_callback('/[a-zA-Z0-9\\\\_\\x7f-\\xff]*\((?:[0-9\/%\.\s*+-]++|(?R))*+\)/', function ($match) {
49
                // no need to keep separators in simple PHP expressions (functions calls, parentheses, calculs)
50
                return str_repeat(' ', strlen($match[0]));
51
            }, preg_replace_callback('/([\'"]).*?(?<!\\\\)(?:\\\\{2})*\\1/', function ($match) {
52
                // do not take separators in strings
53
                return str_repeat(' ', strlen($match[0]));
54
            }, $this->input)),
55
            $separators,
56
            PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE
57
        );
58
59
        $this->separators = $separators[0];
60
61
        if (count($this->separators) === 0) {
62
            if (strstr('0123456789-+("\'$', substr($this->input, 0, 1)) === false) {
63
                $this->input = static::addDollarIfNeeded($this->input);
64
            }
65
66
            return array($this->input);
67
        }
68
69
        // add a pseudo separator for the end of the input
70
        array_push($this->separators, array(null, strlen($this->input)));
71
72
        return $this->parseBetweenSeparators();
73
    }
74
75
    protected function isQuotedString()
76
    {
77
        $firstChar = substr($this->input, 0, 1);
78
        $lastChar = substr($this->input, -1);
79
80
        return false !== strpos('"\'', $firstChar) && $lastChar === $firstChar;
81
    }
82
83
    protected function getVarname($separator)
84
    {
85
        // do not add $ if it is not like a variable
86
        $varname = static::convertVarPath(substr($this->input, 0, $separator[1]), '/^%s/');
87
88
        return $separator[0] !== '(' && $varname !== '' && strstr('0123456789-+("\'$', substr($varname, 0, 1)) === false
89
            ? static::addDollarIfNeeded($varname)
90
            : $varname;
91
    }
92
93
    protected function parseArrayString(&$argument, $match, $consume, &$quote, &$key, &$value)
94
    {
95
        $quote = $quote
96
            ? CommonUtils::escapedEnd($match[1])
97
                ? $quote
98
                : null
99
            : $match[2];
100
        ${is_null($value) ? 'key' : 'value'} .= $match[0];
101
        $consume($argument, $match[0]);
102
    }
103
104
    protected function parseArrayAssign(&$argument, $match, $consume, &$quote, &$key, &$value)
105
    {
106
        if ($quote) {
107
            ${is_null($value) ? 'key' : 'value'} .= $match[0];
108
            $consume($argument, $match[0]);
109
110
            return;
111
        }
112
113
        if (!is_null($value)) {
114
            throw new \ErrorException('Parse error on ' . substr($argument, strlen($match[1])), 15);
115
        }
116
117
        $key .= $match[1];
118
        $value = '';
119
        $consume($argument, $match[0]);
120
    }
121
122
    protected function parseArrayElement(&$argument, $match, $consume, &$quote, &$key, &$value)
123
    {
124
        switch ($match[2]) {
125
            case '"':
126
            case "'":
127
                $this->parseArrayString($argument, $match, $consume, $quote, $key, $value);
128
                break;
129
            case ':':
130
            case '=>':
131
                $this->parseArrayAssign($argument, $match, $consume, $quote, $key, $value);
132
                break;
133
            case ',':
134
                ${is_null($value) ? 'key' : 'value'} .= $match[0];
135
                $consume($argument, $match[0]);
136
                break;
137
        }
138
    }
139
140
    protected function parseArray($input, $subCodeHandler)
141
    {
142
        $output = array();
143
        $key = '';
144
        $value = null;
145
        $addToOutput = $subCodeHandler->addToOutput($output, $key, $value);
146
        $consume = $subCodeHandler->consume();
147
        foreach ($input as $argument) {
148
            $argument = ltrim($argument, '$');
149
            $quote = null;
150
            while (preg_match('/^(.*?)(=>|[\'",:])/', $argument, $match)) {
151
                $this->parseArrayElement($argument, $match, $consume, $quote, $key, $value);
152
            }
153
            ${is_null($value) ? 'key' : 'value'} .= $argument;
154
            $addToOutput();
155
        }
156
157
        return 'array(' . implode(', ', $output) . ')';
158
    }
159
160
    protected function parseEqual($sep, &$separators, &$result, $innerName, $subCodeHandler)
161
    {
162
        if (preg_match('/^[[:space:]]*$/', $innerName)) {
163
            next($separators);
164
            $handleCodeInbetween = $subCodeHandler->handleCodeInbetween($separators, $result);
165
166
            return implode($handleCodeInbetween());
167
        }
168
169
        $handleRecursion = $subCodeHandler->handleRecursion($result);
170
171
        return $handleRecursion(array($sep, end($separators)));
172
    }
173
174
    protected function parseSeparator($sep, &$separators, &$result, &$varname, $subCodeHandler, $innerName)
175
    {
176
        $handleCodeInbetween = $subCodeHandler->handleCodeInbetween($separators, $result);
177
        $var = '$__' . $this->name;
178
179
        switch ($sep[0]) {
180
            // translate the javascript's obj.attr into php's obj->attr or obj['attr']
181
            /*
182
            case '.':
183
                $result[] = sprintf("%s=is_array(%s)?%s['%s']:%s->%s",
184
                    $var, $varname, $varname, $innerName, $varname, $innerName
185
                );
186
                $varname = $var;
187
                break;
188
            //*/
189
190
            // funcall
191
            case '(':
192
                $arguments = $handleCodeInbetween();
193
                $call = $varname . '(' . implode(', ', $arguments) . ')';
194
                $call = static::addDollarIfNeeded($call);
195
                $varname = $var;
196
                array_push($result, "{$var}={$call}");
197
                break;
198
199
            case '[':
200
                if (preg_match('/[a-zA-Z0-9\\\\_\\x7f-\\xff]$/', $varname)) {
201
                    $varname .= $sep[0] . $innerName;
202
                    break;
203
                }
204
            case '{':
205
                $varname .= $this->parseArray($handleCodeInbetween(), $subCodeHandler);
206
                break;
207
208
            case '=':
209
                $varname .= '=' . $this->parseEqual($sep, $separators, $result, $innerName, $subCodeHandler);
210
                break;
211
212
            default:
213
                if (($innerName !== false && $innerName !== '') || $sep[0] !== ')') {
214
                    $varname .= $sep[0] . $innerName;
215
                }
216
                break;
217
        }
218
    }
219
220
    protected function parseBetweenSeparators()
221
    {
222
        $separators = $this->separators;
223
224
        $result = array();
225
226
        $varname = $this->getVarname($separators[0]);
227
228
        $subCodeHandler = new SubCodeHandler($this, $this->input, $this->name);
229
        $getMiddleString = $subCodeHandler->getMiddleString();
230
        $getNext = $subCodeHandler->getNext($separators);
231
232
        // using next() ourselves so that we can advance the array pointer inside inner loops
233
        while (($sep = current($separators)) && $sep[0] !== null) {
234
            // $sep[0] - the separator string due to PREG_SPLIT_OFFSET_CAPTURE flag or null if end of string
235
            // $sep[1] - the offset due to PREG_SPLIT_OFFSET_CAPTURE
236
237
            $innerName = $getMiddleString($sep, $getNext(key($separators)));
238
239
            $this->parseSeparator($sep, $separators, $result, $varname, $subCodeHandler, $innerName);
240
241
            next($separators);
242
        }
243
        array_push($result, $varname);
244
245
        return $result;
246
    }
247
}
248