1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Highlighter |
4
|
|
|
* |
5
|
|
|
* Copyright (C) 2016, Some right reserved. |
6
|
|
|
* @author Kacper "Kadet" Donat <[email protected]> |
7
|
|
|
* |
8
|
|
|
* Contact with author: |
9
|
|
|
* Xmpp: [email protected] |
10
|
|
|
* E-mail: [email protected] |
11
|
|
|
* |
12
|
|
|
* From Kadet with love. |
13
|
|
|
*/ |
14
|
|
|
|
15
|
|
|
namespace Kadet\Highlighter\Language; |
16
|
|
|
|
17
|
|
|
use Kadet\Highlighter\Matcher\CommentMatcher; |
18
|
|
|
use Kadet\Highlighter\Matcher\DelegateRegexMatcher; |
19
|
|
|
use Kadet\Highlighter\Matcher\RegexMatcher; |
20
|
|
|
use Kadet\Highlighter\Matcher\WordMatcher; |
21
|
|
|
use Kadet\Highlighter\Parser\CloseRule; |
22
|
|
|
use Kadet\Highlighter\Parser\Token\LanguageToken; |
23
|
|
|
use Kadet\Highlighter\Parser\Rule; |
24
|
|
|
use Kadet\Highlighter\Parser\OpenRule; |
25
|
|
|
use Kadet\Highlighter\Parser\Token\Token; |
26
|
|
|
use Kadet\Highlighter\Parser\TokenFactory; |
27
|
|
|
use Kadet\Highlighter\Parser\TokenFactoryInterface; |
28
|
|
|
|
29
|
|
|
class Php extends GreedyLanguage |
30
|
|
|
{ |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Tokenization rules |
34
|
|
|
*/ |
35
|
1 |
|
public function setupRules() |
36
|
|
|
{ |
37
|
1 |
|
$this->rules->addMany([ |
38
|
1 |
|
'string' => CommonFeatures::strings(['single' => '\'', 'double' => '"'], [ |
39
|
1 |
|
'context' => ['!operator.escape', '!comment', '!string', '!expression'], |
40
|
1 |
|
]), |
41
|
|
|
|
42
|
1 |
|
'string.heredoc' => new Rule(new RegexMatcher('/<<<\s*(\w+)\R(?P<string>.*?)\R\1;/sm', ['string' => Token::NAME, 0 => 'keyword.heredoc']), ['context' => ['!comment']]), |
43
|
1 |
|
'string.nowdoc' => new Rule(new RegexMatcher('/<<<\s*\'(\w+)\'\R(?P<string>.*?)\R\1;/sm', ['string' => Token::NAME, 0 => 'keyword.nowdoc']), ['context' => ['!comment']]), |
44
|
|
|
|
45
|
1 |
|
'variable' => new Rule(new RegexMatcher('/(?:[^\\\]|^)(\$[a-z_]\w*)/i'), [ |
46
|
1 |
|
'context' => ['*comment.docblock', '!string.nowdoc', '!string.single', '!comment'] |
47
|
1 |
|
]), |
48
|
1 |
|
'variable.property' => new Rule(new RegexMatcher('/(?=(?:\w|\)|\])\s*->([a-z_]\w*))/i'), [ |
49
|
1 |
|
'priority' => -2, |
50
|
1 |
|
'context' => ['*comment.docblock', '!string.nowdoc', '!string.single', '!comment'] |
51
|
1 |
|
]), |
52
|
|
|
|
53
|
1 |
|
'symbol.function' => new Rule(new RegexMatcher('/function\s+([a-z_]\w+)\s*\(/i')), |
54
|
|
|
'symbol.class' => [ |
55
|
1 |
|
new Rule(new RegexMatcher('/(?:class|new|use|extends)\s+([\w\\\]+)/i')), |
56
|
1 |
|
new Rule(new RegexMatcher('/([\w\\\]+)::/i')), |
57
|
1 |
|
new Rule(new RegexMatcher('/@(?:var|property(?:-read|-write)?)(?:\s+|\s+\$\w+\s+)([^$][\w\\\]+)/i'), ['context' => ['comment.docblock']]), |
58
|
1 |
|
], |
59
|
|
|
|
60
|
1 |
|
'expression.in-string' => new Rule(new RegexMatcher('/(?=(\{\$((?>[^${}]+|(?1))+)\}))/x'), [ |
61
|
1 |
|
'context' => ['string'], |
62
|
1 |
|
'factory' => new TokenFactory(LanguageToken::class), |
63
|
|
|
'inject' => $this |
64
|
1 |
|
]), |
65
|
|
|
|
66
|
|
|
'symbol.class.interface' => [ |
67
|
1 |
|
new Rule(new RegexMatcher('/interface\s+([\w\\\]+)/i')), |
68
|
1 |
|
new Rule(new DelegateRegexMatcher( |
69
|
1 |
|
'/implements\s+((?:[\w\\\]+)(?:,\s*([\w\\\]+))+)/i', |
70
|
1 |
|
function($match, TokenFactoryInterface $factory) { |
71
|
|
|
foreach (preg_split('/,\s*/', $match[1][0], 0, PREG_SPLIT_OFFSET_CAPTURE) as $interface) { |
72
|
|
|
yield $factory->create(Token::NAME, [ |
73
|
1 |
|
'pos' => $match[1][1] + $interface[1], |
74
|
|
|
'length' => strlen($interface[0])] |
75
|
|
|
); |
76
|
|
|
} |
77
|
|
|
} |
78
|
1 |
|
)), |
79
|
1 |
|
], |
80
|
|
|
|
81
|
|
|
'symbol.namespace' => [ |
82
|
|
|
/*new Rule(new RegexMatcher('/(\\\{0,2}(?:\w+\\\{1,2})+)\w+/i'), [ |
83
|
|
|
'context' => ['*symbol', '*none'] |
84
|
|
|
]),*/ |
85
|
|
|
|
86
|
1 |
|
new Rule(new RegexMatcher('/namespace\s*(\\\{0,2}(?:\w+\\\{1,2})+\w+);/i'), [ |
87
|
1 |
|
'context' => ['*symbol', '*none'] |
88
|
1 |
|
]), |
89
|
1 |
|
], |
90
|
|
|
|
91
|
|
|
'operator.escape' => [ |
92
|
1 |
|
new Rule(new RegexMatcher('/(\\\(?:x[0-9a-fA-F]{1,2}|u\{[0-9a-fA-F]{1,6}\}|[0-7]{1,3}|[^\'\\\]))/i'), [ |
93
|
1 |
|
'context' => ['string.double', '!operator.escape'] |
94
|
1 |
|
]), |
95
|
1 |
|
new Rule(new RegexMatcher('/(\\\[\'\\\])/i'), [ |
96
|
1 |
|
'context' => ['string', '!operator.escape'] |
97
|
1 |
|
]), |
98
|
1 |
|
], |
99
|
|
|
|
100
|
1 |
|
'comment' => new Rule(new CommentMatcher(['//', '#'], [ |
101
|
1 |
|
'$.docblock' => ['/**', '*/'], |
102
|
1 |
|
['/*', '*/'] |
103
|
1 |
|
]), ['priority' => 4]), |
104
|
|
|
|
105
|
1 |
|
'symbol.annotation' => new Rule(new RegexMatcher('/[\s]+(@[\w-]+)/i'), [ |
106
|
1 |
|
'context' => ['comment.docblock'] |
107
|
1 |
|
]), |
108
|
|
|
|
109
|
1 |
|
'call' => new Rule(new RegexMatcher('/([a-z_]\w*)\s*\(/i'), ['priority' => -1]), |
110
|
|
|
|
111
|
1 |
|
'constant' => new Rule(new WordMatcher(array_merge([ |
112
|
1 |
|
'__CLASS__', '__DIR__', '__FILE__', '__FUNCTION__', |
113
|
1 |
|
'__LINE__', '__METHOD__', '__NAMESPACE__', '__TRAIT__', |
114
|
1 |
|
], array_keys(get_defined_constants(true)["Core"]))), ['priority' => -2]), |
115
|
1 |
|
'constant.static' => new Rule(new RegexMatcher('/(?:[\w\\\]+::|const\s+)(\w+)/i'), ['priority' => -2]), |
116
|
|
|
|
117
|
1 |
|
'keyword' => new Rule(new WordMatcher([ |
118
|
1 |
|
'__halt_compiler', 'abstract', 'and', 'array', |
119
|
1 |
|
'as', 'break', 'callable', 'case', 'catch', |
120
|
1 |
|
'class', 'clone', 'const', 'continue', 'declare', |
121
|
1 |
|
'default', 'die', 'do', 'echo', 'else', 'elseif', |
122
|
1 |
|
'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', |
123
|
1 |
|
'endswitch', 'endwhile', 'eval', 'exit', 'extends', |
124
|
1 |
|
'final', 'finally', 'for', 'foreach', 'function', |
125
|
1 |
|
'global', 'goto', 'if', 'implements', 'include', 'include_once', |
126
|
1 |
|
'instanceof', 'insteadof', 'interface', 'isset', 'list', |
127
|
1 |
|
'namespace', 'new', 'or', 'print', 'private', 'protected', |
128
|
1 |
|
'public', 'require', 'require_once', 'return', 'static', |
129
|
1 |
|
'switch', 'throw', 'trait', 'try', 'unset', 'parent', 'self', |
130
|
1 |
|
'use', 'var', 'while', 'xor', 'yield' |
131
|
1 |
|
]), ['context' => ['!string', '!variable', '!comment']]), |
132
|
|
|
|
133
|
1 |
|
'keyword.cast' => new Rule( |
134
|
1 |
|
new RegexMatcher('/(\((?:int|integer|bool|boolean|float|double|real|string|array|object|unset)\))/') |
135
|
1 |
|
), |
136
|
|
|
|
137
|
1 |
|
'delimiter' => new Rule(new RegexMatcher('/(<\?php|<\?=|\?>)/')), |
138
|
1 |
|
'number' => new Rule(new RegexMatcher('/(-?(?:0[0-7]+|0[xX][0-9a-fA-F]+|0b[01]+|\d+))/')), |
139
|
|
|
|
140
|
1 |
|
'operator.punctuation' => new Rule(new WordMatcher([',', ';'], ['separated' => false]), ['priority' => 0]), |
141
|
1 |
|
]); |
142
|
1 |
|
} |
143
|
|
|
|
144
|
|
|
/** {@inheritdoc} */ |
145
|
5 |
|
public function getEnds($embedded = false) |
146
|
|
|
{ |
147
|
5 |
|
return $embedded ? [ |
148
|
|
|
new OpenRule(new RegexMatcher('/(<\?php|<\?=)/si'), [ |
149
|
|
|
'factory' => new TokenFactory(LanguageToken::class), |
150
|
|
|
'priority' => 1000, |
151
|
|
|
'context' => ['*'], |
152
|
|
|
'inject' => $this, |
153
|
|
|
'language' => null |
154
|
|
|
]), |
155
|
|
|
new CloseRule(new RegexMatcher('/(\?>|$)/'), [ |
156
|
|
|
'context' => ['!string', '!comment'], |
157
|
|
|
'priority' => 1000, |
158
|
|
|
'factory' => new TokenFactory(LanguageToken::class), |
159
|
|
|
'language' => $this |
160
|
|
|
]) |
161
|
5 |
|
] : parent::getEnds(false); |
162
|
|
|
} |
163
|
|
|
|
164
|
6 |
|
public function getIdentifier() |
165
|
|
|
{ |
166
|
6 |
|
return 'php'; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
public static function getMetadata() |
170
|
|
|
{ |
171
|
|
|
return [ |
172
|
|
|
'name' => ['php'], |
173
|
|
|
'mime' => ['text/x-php', 'application/x-php'], |
174
|
|
|
'extension' => ['*.php', '*.phtml', '*.inc', '*.php?'], |
175
|
|
|
'injectable' => true |
176
|
|
|
]; |
177
|
|
|
} |
178
|
|
|
} |
179
|
|
|
|