Skip to content

Commit f8f7cac

Browse files
committed
[php mode] Highlight interpolated vars in heredoc strings
Closes #2855
1 parent 62754f6 commit f8f7cac

File tree

2 files changed

+43
-44
lines changed

2 files changed

+43
-44
lines changed

mode/php/php.js

Lines changed: 37 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,32 @@
1616
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
1717
return obj;
1818
}
19-
function heredoc(delim) {
20-
return function(stream, state) {
21-
if (stream.match(delim)) state.tokenize = null;
22-
else stream.skipToEnd();
23-
return "string";
24-
};
25-
}
2619

2720
// Helper for stringWithEscapes
28-
function matchSequence(list) {
29-
if (list.length == 0) return stringWithEscapes;
21+
function matchSequence(list, end) {
22+
if (list.length == 0) return stringWithEscapes(end);
3023
return function (stream, state) {
3124
var patterns = list[0];
3225
for (var i = 0; i < patterns.length; i++) if (stream.match(patterns[i][0])) {
33-
state.tokenize = matchSequence(list.slice(1));
26+
state.tokenize = matchSequence(list.slice(1), end);
3427
return patterns[i][1];
3528
}
36-
state.tokenize = stringWithEscapes;
29+
state.tokenize = stringWithEscapes(end);
3730
return "string";
3831
};
3932
}
40-
function stringWithEscapes(stream, state) {
41-
var escaped = false, next, end = false;
42-
43-
if (stream.current() == '"') return "string";
44-
33+
function stringWithEscapes(closing) {
34+
return function(stream, state) { return stringWithEscapes_(stream, state, closing); };
35+
}
36+
function stringWithEscapes_(stream, state, closing) {
4537
// "Complex" syntax
4638
if (stream.match("${", false) || stream.match("{$", false)) {
4739
state.tokenize = null;
4840
return "string";
4941
}
5042

5143
// Simple syntax
52-
if (stream.match(/\$[a-zA-Z_][a-zA-Z0-9_]*/)) {
44+
if (stream.match(/^\$[a-zA-Z_][a-zA-Z0-9_]*/)) {
5345
// After the variable name there may appear array or object operator.
5446
if (stream.match("[", false)) {
5547
// Match array operator
@@ -59,31 +51,29 @@
5951
[/\$[a-zA-Z_][a-zA-Z0-9_]*/, "variable-2"],
6052
[/[\w\$]+/, "variable"]],
6153
[["]", null]]
62-
]);
54+
], closing);
6355
}
6456
if (stream.match(/\-\>\w/, false)) {
6557
// Match object operator
6658
state.tokenize = matchSequence([
6759
[["->", null]],
6860
[[/[\w]+/, "variable"]]
69-
]);
61+
], closing);
7062
}
7163
return "variable-2";
7264
}
7365

66+
var escaped = false;
7467
// Normal string
75-
while (
76-
!stream.eol() &&
77-
(!stream.match("{$", false)) &&
78-
(!stream.match(/(\$[a-zA-Z_][a-zA-Z0-9_]*|\$\{)/, false) || escaped)
79-
) {
80-
next = stream.next();
81-
if (!escaped && next == '"') { end = true; break; }
82-
escaped = !escaped && next == "\\";
83-
}
84-
if (end) {
85-
state.tokenize = null;
86-
state.phpEncapsStack.pop();
68+
while (!stream.eol() &&
69+
(escaped || (!stream.match("{$", false) &&
70+
!stream.match(/^(\$[a-zA-Z_][a-zA-Z0-9_]*|\$\{)/, false)))) {
71+
if (!escaped && stream.match(closing)) {
72+
state.tokenize = null;
73+
state.tokStack.pop(); state.tokStack.pop();
74+
break;
75+
}
76+
escaped = stream.next() == "\\" && !escaped;
8777
}
8878
return "string";
8979
}
@@ -115,8 +105,12 @@
115105
"<": function(stream, state) {
116106
if (stream.match(/<</)) {
117107
stream.eatWhile(/[\w\.]/);
118-
state.tokenize = heredoc(stream.current().slice(3));
119-
return state.tokenize(stream, state);
108+
var delim = stream.current().slice(3);
109+
if (delim) {
110+
(state.tokStack || (state.tokStack = [])).push(delim, 0);
111+
state.tokenize = stringWithEscapes(delim);
112+
return "string";
113+
}
120114
}
121115
return false;
122116
},
@@ -131,22 +125,21 @@
131125
}
132126
return false;
133127
},
134-
'"': function(stream, state) {
135-
if (!state.phpEncapsStack)
136-
state.phpEncapsStack = [];
137-
state.phpEncapsStack.push(0);
138-
state.tokenize = stringWithEscapes;
139-
return state.tokenize(stream, state);
128+
'"': function(_stream, state) {
129+
(state.tokStack || (state.tokStack = [])).push('"', 0);
130+
state.tokenize = stringWithEscapes('"');
131+
return "string";
140132
},
141133
"{": function(_stream, state) {
142-
if (state.phpEncapsStack && state.phpEncapsStack.length > 0)
143-
state.phpEncapsStack[state.phpEncapsStack.length - 1]++;
134+
if (state.tokStack && state.tokStack.length)
135+
state.tokStack[state.tokStack.length - 1]++;
144136
return false;
145137
},
146138
"}": function(_stream, state) {
147-
if (state.phpEncapsStack && state.phpEncapsStack.length > 0)
148-
if (--state.phpEncapsStack[state.phpEncapsStack.length - 1] == 0)
149-
state.tokenize = stringWithEscapes;
139+
if (state.tokStack && state.tokStack.length > 0 &&
140+
!--state.tokStack[state.tokStack.length - 1]) {
141+
state.tokenize = stringWithEscapes(state.tokStack[state.tokStack.length - 2]);
142+
}
150143
return false;
151144
}
152145
}

mode/php/test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,10 @@
145145
'[keyword echo] ' + m3[2] + ';',
146146
'[keyword echo] [string "end"];',
147147
'[meta ?>]');
148+
149+
MT("variable_interpolation_heredoc",
150+
"[meta <?php]",
151+
"[string <<<here]",
152+
"[string doc ][variable-2 $]{[variable yay]}[string more]",
153+
"[string here]; [comment // normal]");
148154
})();

0 commit comments

Comments
 (0)