Skip to content

Commit 0b65050

Browse files
radek-pmarijnh
authored andcommitted
[php mode] Add support for interpolated variables in double-quoted strings.
1 parent 61eb7e4 commit 0b65050

File tree

4 files changed

+249
-2
lines changed

4 files changed

+249
-2
lines changed

mode/php/index.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@
3232
<h2>PHP mode</h2>
3333
<form><textarea id="code" name="code">
3434
<?php
35+
$a = array('a' => 1, 'b' => 2, 3 => 'c');
36+
37+
echo "$a[a] ${a[3] /* } comment */} {$a[b]} \$a[a]";
38+
3539
function hello($who) {
36-
return "Hello " . $who;
40+
return "Hello $who!";
3741
}
3842
?>
3943
<p>The program says <?= hello("World") ?>.</p>

mode/php/php.js

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,82 @@
1212
};
1313
}
1414

15+
// Two helper functions for encapsList
16+
function matchFirst(list) {
17+
return function (stream) {
18+
for (var i = 0; i < list.length; ++i)
19+
if (stream.match(list[i][0]))
20+
return list[i][1];
21+
return false;
22+
};
23+
}
24+
function matchSequence(list) {
25+
if (list.length == 0) return encapsList;
26+
return function (stream, state) {
27+
var result = list[0](stream, state);
28+
if (result !== false) {
29+
state.tokenize = matchSequence(list.slice(1));
30+
return result;
31+
}
32+
else {
33+
state.tokenize = encapsList;
34+
return "string";
35+
}
36+
};
37+
}
38+
function encapsList(stream, state) {
39+
var escaped = false, next, end = false;
40+
41+
if (stream.current() == '"') return "string";
42+
43+
// "Complex" syntax
44+
if (stream.match("${", false) || stream.match("{$", false)) {
45+
state.tokenize = null;
46+
return "string";
47+
}
48+
49+
// Simple syntax
50+
if (stream.match(/\$[a-zA-Z_][a-zA-Z0-9_]*/)) {
51+
// After the variable name there may appear array or object operator.
52+
if (stream.match("[", false)) {
53+
// Match array operator
54+
state.tokenize = matchSequence([
55+
matchFirst([["[", null]]),
56+
matchFirst([
57+
[/\d[\w\.]*/, "number"],
58+
[/\$[a-zA-Z_][a-zA-Z0-9_]*/, "variable-2"],
59+
[/[\w\$]+/, "variable"]
60+
]),
61+
matchFirst([["]", null]])
62+
]);
63+
}
64+
if (stream.match(/\-\>\w/, false)) {
65+
// Match object operator
66+
state.tokenize = matchSequence([
67+
matchFirst([["->", null]]),
68+
matchFirst([[/[\w]+/, "variable"]])
69+
]);
70+
}
71+
return "variable-2";
72+
}
73+
74+
// 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();
87+
}
88+
return "string";
89+
}
90+
1591
var phpKeywords = "abstract and array as break case catch class clone const continue declare default " +
1692
"do else elseif enddeclare endfor endforeach endif endswitch endwhile extends final " +
1793
"for foreach function global goto if implements interface instanceof namespace " +
@@ -53,6 +129,24 @@
53129
return "comment";
54130
}
55131
return false;
132+
},
133+
'"': function(stream, state) {
134+
if (!state.phpEncapsStack)
135+
state.phpEncapsStack = [];
136+
state.phpEncapsStack.push(0);
137+
state.tokenize = encapsList;
138+
return state.tokenize(stream, state);
139+
},
140+
"{": function(_stream, state) {
141+
if (state.phpEncapsStack && state.phpEncapsStack.length > 0)
142+
state.phpEncapsStack[state.phpEncapsStack.length - 1]++;
143+
return false;
144+
},
145+
"}": function(_stream, state) {
146+
if (state.phpEncapsStack && state.phpEncapsStack.length > 0)
147+
if (--state.phpEncapsStack[state.phpEncapsStack.length - 1] == 0)
148+
state.tokenize = encapsList;
149+
return false;
56150
}
57151
}
58152
};
@@ -92,7 +186,8 @@
92186
state.curState = state.html;
93187
return "meta";
94188
} else {
95-
return phpMode.token(stream, state.curState);
189+
var result = phpMode.token(stream, state.curState);
190+
return (stream.pos <= stream.start) ? phpMode.token(stream, state.curState) : result;
96191
}
97192
}
98193

mode/php/test.js

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
(function() {
2+
var mode = CodeMirror.getMode({indentUnit: 2}, "php");
3+
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
4+
5+
MT('simple_test',
6+
'[meta <?php] ' +
7+
'[keyword echo] [string "aaa"]; ' +
8+
'[meta ?>]');
9+
10+
MT('variable_interpolation_non_alphanumeric',
11+
'[meta <?php]',
12+
'[keyword echo] [string "aaa$~$!$@$#$$$%$^$&$*$($)$.$<$>$/$\\$}$\\\"$:$;$?$|$[[$]]$+$=aaa"]',
13+
'[meta ?>]');
14+
15+
MT('variable_interpolation_digits',
16+
'[meta <?php]',
17+
'[keyword echo] [string "aaa$1$2$3$4$5$6$7$8$9$0aaa"]',
18+
'[meta ?>]');
19+
20+
MT('variable_interpolation_simple_syntax_1',
21+
'[meta <?php]',
22+
'[keyword echo] [string "aaa][variable-2 $aaa][string .aaa"];',
23+
'[meta ?>]');
24+
25+
MT('variable_interpolation_simple_syntax_2',
26+
'[meta <?php]',
27+
'[keyword echo] [string "][variable-2 $aaaa][[','[number 2]', ']][string aa"];',
28+
'[keyword echo] [string "][variable-2 $aaaa][[','[number 2345]', ']][string aa"];',
29+
'[keyword echo] [string "][variable-2 $aaaa][[','[number 2.3]', ']][string aa"];',
30+
'[keyword echo] [string "][variable-2 $aaaa][[','[variable aaaaa]', ']][string aa"];',
31+
'[keyword echo] [string "][variable-2 $aaaa][[','[variable-2 $aaaaa]',']][string aa"];',
32+
33+
'[keyword echo] [string "1aaa][variable-2 $aaaa][[','[number 2]', ']][string aa"];',
34+
'[keyword echo] [string "aaa][variable-2 $aaaa][[','[number 2345]', ']][string aa"];',
35+
'[keyword echo] [string "aaa][variable-2 $aaaa][[','[number 2.3]', ']][string aa"];',
36+
'[keyword echo] [string "aaa][variable-2 $aaaa][[','[variable aaaaa]', ']][string aa"];',
37+
'[keyword echo] [string "aaa][variable-2 $aaaa][[','[variable-2 $aaaaa]',']][string aa"];',
38+
'[meta ?>]');
39+
40+
MT('variable_interpolation_simple_syntax_3',
41+
'[meta <?php]',
42+
'[keyword echo] [string "aaa][variable-2 $aaaa]->[variable aaaaa][string .aaaaaa"];',
43+
'[keyword echo] [string "aaa][variable-2 $aaaa][string ->][variable-2 $aaaaa][string .aaaaaa"];',
44+
'[keyword echo] [string "aaa][variable-2 $aaaa]->[variable aaaaa][string [[2]].aaaaaa"];',
45+
'[keyword echo] [string "aaa][variable-2 $aaaa]->[variable aaaaa][string ->aaaa2.aaaaaa"];',
46+
'[meta ?>]');
47+
48+
MT('variable_interpolation_escaping',
49+
'[meta <?php] [comment /* Escaping */]',
50+
'[keyword echo] [string "aaa\\$aaaa->aaa.aaa"];',
51+
'[keyword echo] [string "aaa\\$aaaa[[2]]aaa.aaa"];',
52+
'[keyword echo] [string "aaa\\$aaaa[[asd]]aaa.aaa"];',
53+
'[keyword echo] [string "aaa{\\$aaaa->aaa.aaa"];',
54+
'[keyword echo] [string "aaa{\\$aaaa[[2]]aaa.aaa"];',
55+
'[keyword echo] [string "aaa{\\aaaaa[[asd]]aaa.aaa"];',
56+
'[keyword echo] [string "aaa\\${aaaa->aaa.aaa"];',
57+
'[keyword echo] [string "aaa\\${aaaa[[2]]aaa.aaa"];',
58+
'[keyword echo] [string "aaa\\${aaaa[[asd]]aaa.aaa"];',
59+
'[meta ?>]');
60+
61+
MT('variable_interpolation_complex_syntax_1',
62+
'[meta <?php]',
63+
'[keyword echo] [string "aaa][variable-2 $]{[variable aaaa]}[string ->aaa.aaa"];',
64+
'[keyword echo] [string "aaa][variable-2 $]{[variable-2 $aaaa]}[string ->aaa.aaa"];',
65+
'[keyword echo] [string "aaa][variable-2 $]{[variable-2 $aaaa][[',' [number 42]',']]}[string ->aaa.aaa"];',
66+
'[keyword echo] [string "aaa][variable-2 $]{[variable aaaa][meta ?>]aaaaaa');
67+
68+
MT('variable_interpolation_complex_syntax_2',
69+
'[meta <?php] [comment /* Monsters */]',
70+
'[keyword echo] [string "][variable-2 $]{[variable aaa][comment /*}?>} $aaa<?php } */]}[string ->aaa.aaa"];',
71+
'[keyword echo] [string "][variable-2 $]{[variable aaa][comment /*}?>*/][[',' [string "aaa][variable-2 $aaa][string {}][variable-2 $]{[variable aaa]}[string "]',']]}[string ->aaa.aaa"];',
72+
'[keyword echo] [string "][variable-2 $]{[variable aaa][comment /*} } $aaa } */]}[string ->aaa.aaa"];');
73+
74+
75+
function build_recursive_monsters(nt, t, n){
76+
var monsters = [t];
77+
for (var i = 1; i <= n; ++i)
78+
monsters[i] = nt.join(monsters[i - 1]);
79+
return monsters;
80+
}
81+
82+
var m1 = build_recursive_monsters(
83+
['[string "][variable-2 $]{[variable aaa] [operator +] ', '}[string "]'],
84+
'[comment /* }?>} */] [string "aaa][variable-2 $aaa][string .aaa"]',
85+
10
86+
);
87+
88+
MT('variable_interpolation_complex_syntax_3_1',
89+
'[meta <?php] [comment /* Recursive monsters */]',
90+
'[keyword echo] ' + m1[4] + ';',
91+
'[keyword echo] ' + m1[7] + ';',
92+
'[keyword echo] ' + m1[8] + ';',
93+
'[keyword echo] ' + m1[5] + ';',
94+
'[keyword echo] ' + m1[1] + ';',
95+
'[keyword echo] ' + m1[6] + ';',
96+
'[keyword echo] ' + m1[9] + ';',
97+
'[keyword echo] ' + m1[0] + ';',
98+
'[keyword echo] ' + m1[10] + ';',
99+
'[keyword echo] ' + m1[2] + ';',
100+
'[keyword echo] ' + m1[3] + ';',
101+
'[keyword echo] [string "end"];',
102+
'[meta ?>]');
103+
104+
var m2 = build_recursive_monsters(
105+
['[string "a][variable-2 $]{[variable aaa] [operator +] ', ' [operator +] ', '}[string .a"]'],
106+
'[comment /* }?>{{ */] [string "a?>}{{aa][variable-2 $aaa][string .a}a?>a"]',
107+
5
108+
);
109+
110+
MT('variable_interpolation_complex_syntax_3_2',
111+
'[meta <?php] [comment /* Recursive monsters 2 */]',
112+
'[keyword echo] ' + m2[0] + ';',
113+
'[keyword echo] ' + m2[1] + ';',
114+
'[keyword echo] ' + m2[5] + ';',
115+
'[keyword echo] ' + m2[4] + ';',
116+
'[keyword echo] ' + m2[2] + ';',
117+
'[keyword echo] ' + m2[3] + ';',
118+
'[keyword echo] [string "end"];',
119+
'[meta ?>]');
120+
121+
function build_recursive_monsters_2(mf1, mf2, nt, t, n){
122+
var monsters = [t];
123+
for (var i = 1; i <= n; ++i)
124+
monsters[i] = nt[0] + mf1[i - 1] + nt[1] + mf2[i - 1] + nt[2] + monsters[i - 1] + nt[3];
125+
return monsters;
126+
}
127+
128+
var m3 = build_recursive_monsters_2(
129+
m1,
130+
m2,
131+
['[string "a][variable-2 $]{[variable aaa] [operator +] ', ' [operator +] ', ' [operator +] ', '}[string .a"]'],
132+
'[comment /* }?>{{ */] [string "a?>}{{aa][variable-2 $aaa][string .a}a?>a"]',
133+
4
134+
);
135+
136+
MT('variable_interpolation_complex_syntax_3_3',
137+
'[meta <?php] [comment /* Recursive monsters 2 */]',
138+
'[keyword echo] ' + m3[4] + ';',
139+
'[keyword echo] ' + m3[0] + ';',
140+
'[keyword echo] ' + m3[3] + ';',
141+
'[keyword echo] ' + m3[1] + ';',
142+
'[keyword echo] ' + m3[2] + ';',
143+
'[keyword echo] [string "end"];',
144+
'[meta ?>]');
145+
})();

test/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
<script src="../addon/edit/matchbrackets.js"></script>
1515
<script src="../addon/comment/comment.js"></script>
1616
<script src="../mode/javascript/javascript.js"></script>
17+
<script src="../mode/clike/clike.js"></script>
18+
<script src="../mode/php/php.js"></script>
1719
<script src="../mode/xml/xml.js"></script>
1820
<script src="../keymap/vim.js"></script>
1921
<script src="../keymap/emacs.js"></script>
@@ -75,6 +77,7 @@ <h2>Test Suite</h2>
7577
<script src="search_test.js"></script>
7678
<script src="mode_test.js"></script>
7779
<script src="../mode/javascript/test.js"></script>
80+
<script src="../mode/php/test.js"></script>
7881
<script src="../mode/css/css.js"></script>
7982
<script src="../mode/css/test.js"></script>
8083
<script src="../mode/css/scss_test.js"></script>

0 commit comments

Comments
 (0)