From beb975f7e76c734044fa35a2c9a64b97f9eb5c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Pi=C3=B3rkowski?= <radek25c@gmail.com> Date: Tue, 15 Apr 2014 21:13:31 +0200 Subject: [PATCH] [php mode] Add support for interpolated variables in double-quoted strings. --- mode/php/index.html | 6 +- mode/php/php.js | 97 ++++++++++++++++++++++++++++- mode/php/test.js | 145 ++++++++++++++++++++++++++++++++++++++++++++ test/index.html | 3 + 4 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 mode/php/test.js diff --git a/mode/php/index.html b/mode/php/index.html index 1fb7435bb..dd25a5e78 100644 --- a/mode/php/index.html +++ b/mode/php/index.html @@ -32,8 +32,12 @@ <h2>PHP mode</h2> <form><textarea id="code" name="code"> <?php +$a = array('a' => 1, 'b' => 2, 3 => 'c'); + +echo "$a[a] ${a[3] /* } comment */} {$a[b]} \$a[a]"; + function hello($who) { - return "Hello " . $who; + return "Hello $who!"; } ?> <p>The program says <?= hello("World") ?>.</p> diff --git a/mode/php/php.js b/mode/php/php.js index bcec3ebb2..184293b62 100644 --- a/mode/php/php.js +++ b/mode/php/php.js @@ -21,6 +21,82 @@ }; } + // Two helper functions for encapsList + function matchFirst(list) { + return function (stream) { + for (var i = 0; i < list.length; ++i) + if (stream.match(list[i][0])) + return list[i][1]; + return false; + }; + } + function matchSequence(list) { + if (list.length == 0) return encapsList; + return function (stream, state) { + var result = list[0](stream, state); + if (result !== false) { + state.tokenize = matchSequence(list.slice(1)); + return result; + } + else { + state.tokenize = encapsList; + return "string"; + } + }; + } + function encapsList(stream, state) { + var escaped = false, next, end = false; + + if (stream.current() == '"') return "string"; + + // "Complex" syntax + if (stream.match("${", false) || stream.match("{$", false)) { + state.tokenize = null; + return "string"; + } + + // Simple syntax + if (stream.match(/\$[a-zA-Z_][a-zA-Z0-9_]*/)) { + // After the variable name there may appear array or object operator. + if (stream.match("[", false)) { + // Match array operator + state.tokenize = matchSequence([ + matchFirst([["[", null]]), + matchFirst([ + [/\d[\w\.]*/, "number"], + [/\$[a-zA-Z_][a-zA-Z0-9_]*/, "variable-2"], + [/[\w\$]+/, "variable"] + ]), + matchFirst([["]", null]]) + ]); + } + if (stream.match(/\-\>\w/, false)) { + // Match object operator + state.tokenize = matchSequence([ + matchFirst([["->", null]]), + matchFirst([[/[\w]+/, "variable"]]) + ]); + } + return "variable-2"; + } + + // Normal string + while ( + !stream.eol() && + (!stream.match("{$", false)) && + (!stream.match(/(\$[a-zA-Z_][a-zA-Z0-9_]*|\$\{)/, false) || escaped) + ) { + next = stream.next(); + if (!escaped && next == '"') { end = true; break; } + escaped = !escaped && next == "\\"; + } + if (end) { + state.tokenize = null; + state.phpEncapsStack.pop(); + } + return "string"; + } + var phpKeywords = "abstract and array as break case catch class clone const continue declare default " + "do else elseif enddeclare endfor endforeach endif endswitch endwhile extends final " + "for foreach function global goto if implements interface instanceof namespace " + @@ -62,6 +138,24 @@ return "comment"; } return false; + }, + '"': function(stream, state) { + if (!state.phpEncapsStack) + state.phpEncapsStack = []; + state.phpEncapsStack.push(0); + state.tokenize = encapsList; + return state.tokenize(stream, state); + }, + "{": function(_stream, state) { + if (state.phpEncapsStack && state.phpEncapsStack.length > 0) + state.phpEncapsStack[state.phpEncapsStack.length - 1]++; + return false; + }, + "}": function(_stream, state) { + if (state.phpEncapsStack && state.phpEncapsStack.length > 0) + if (--state.phpEncapsStack[state.phpEncapsStack.length - 1] == 0) + state.tokenize = encapsList; + return false; } } }; @@ -101,7 +195,8 @@ state.curState = state.html; return "meta"; } else { - return phpMode.token(stream, state.curState); + var result = phpMode.token(stream, state.curState); + return (stream.pos <= stream.start) ? phpMode.token(stream, state.curState) : result; } } diff --git a/mode/php/test.js b/mode/php/test.js new file mode 100644 index 000000000..db68d75de --- /dev/null +++ b/mode/php/test.js @@ -0,0 +1,145 @@ +(function() { + var mode = CodeMirror.getMode({indentUnit: 2}, "php"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } + + MT('simple_test', + '[meta <?php] ' + + '[keyword echo] [string "aaa"]; ' + + '[meta ?>]'); + + MT('variable_interpolation_non_alphanumeric', + '[meta <?php]', + '[keyword echo] [string "aaa$~$!$@$#$$$%$^$&$*$($)$.$<$>$/$\\$}$\\\"$:$;$?$|$[[$]]$+$=aaa"]', + '[meta ?>]'); + + MT('variable_interpolation_digits', + '[meta <?php]', + '[keyword echo] [string "aaa$1$2$3$4$5$6$7$8$9$0aaa"]', + '[meta ?>]'); + + MT('variable_interpolation_simple_syntax_1', + '[meta <?php]', + '[keyword echo] [string "aaa][variable-2 $aaa][string .aaa"];', + '[meta ?>]'); + + MT('variable_interpolation_simple_syntax_2', + '[meta <?php]', + '[keyword echo] [string "][variable-2 $aaaa][[','[number 2]', ']][string aa"];', + '[keyword echo] [string "][variable-2 $aaaa][[','[number 2345]', ']][string aa"];', + '[keyword echo] [string "][variable-2 $aaaa][[','[number 2.3]', ']][string aa"];', + '[keyword echo] [string "][variable-2 $aaaa][[','[variable aaaaa]', ']][string aa"];', + '[keyword echo] [string "][variable-2 $aaaa][[','[variable-2 $aaaaa]',']][string aa"];', + + '[keyword echo] [string "1aaa][variable-2 $aaaa][[','[number 2]', ']][string aa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa][[','[number 2345]', ']][string aa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa][[','[number 2.3]', ']][string aa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa][[','[variable aaaaa]', ']][string aa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa][[','[variable-2 $aaaaa]',']][string aa"];', + '[meta ?>]'); + + MT('variable_interpolation_simple_syntax_3', + '[meta <?php]', + '[keyword echo] [string "aaa][variable-2 $aaaa]->[variable aaaaa][string .aaaaaa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa][string ->][variable-2 $aaaaa][string .aaaaaa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa]->[variable aaaaa][string [[2]].aaaaaa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa]->[variable aaaaa][string ->aaaa2.aaaaaa"];', + '[meta ?>]'); + + MT('variable_interpolation_escaping', + '[meta <?php] [comment /* Escaping */]', + '[keyword echo] [string "aaa\\$aaaa->aaa.aaa"];', + '[keyword echo] [string "aaa\\$aaaa[[2]]aaa.aaa"];', + '[keyword echo] [string "aaa\\$aaaa[[asd]]aaa.aaa"];', + '[keyword echo] [string "aaa{\\$aaaa->aaa.aaa"];', + '[keyword echo] [string "aaa{\\$aaaa[[2]]aaa.aaa"];', + '[keyword echo] [string "aaa{\\aaaaa[[asd]]aaa.aaa"];', + '[keyword echo] [string "aaa\\${aaaa->aaa.aaa"];', + '[keyword echo] [string "aaa\\${aaaa[[2]]aaa.aaa"];', + '[keyword echo] [string "aaa\\${aaaa[[asd]]aaa.aaa"];', + '[meta ?>]'); + + MT('variable_interpolation_complex_syntax_1', + '[meta <?php]', + '[keyword echo] [string "aaa][variable-2 $]{[variable aaaa]}[string ->aaa.aaa"];', + '[keyword echo] [string "aaa][variable-2 $]{[variable-2 $aaaa]}[string ->aaa.aaa"];', + '[keyword echo] [string "aaa][variable-2 $]{[variable-2 $aaaa][[',' [number 42]',']]}[string ->aaa.aaa"];', + '[keyword echo] [string "aaa][variable-2 $]{[variable aaaa][meta ?>]aaaaaa'); + + MT('variable_interpolation_complex_syntax_2', + '[meta <?php] [comment /* Monsters */]', + '[keyword echo] [string "][variable-2 $]{[variable aaa][comment /*}?>} $aaa<?php } */]}[string ->aaa.aaa"];', + '[keyword echo] [string "][variable-2 $]{[variable aaa][comment /*}?>*/][[',' [string "aaa][variable-2 $aaa][string {}][variable-2 $]{[variable aaa]}[string "]',']]}[string ->aaa.aaa"];', + '[keyword echo] [string "][variable-2 $]{[variable aaa][comment /*} } $aaa } */]}[string ->aaa.aaa"];'); + + + function build_recursive_monsters(nt, t, n){ + var monsters = [t]; + for (var i = 1; i <= n; ++i) + monsters[i] = nt.join(monsters[i - 1]); + return monsters; + } + + var m1 = build_recursive_monsters( + ['[string "][variable-2 $]{[variable aaa] [operator +] ', '}[string "]'], + '[comment /* }?>} */] [string "aaa][variable-2 $aaa][string .aaa"]', + 10 + ); + + MT('variable_interpolation_complex_syntax_3_1', + '[meta <?php] [comment /* Recursive monsters */]', + '[keyword echo] ' + m1[4] + ';', + '[keyword echo] ' + m1[7] + ';', + '[keyword echo] ' + m1[8] + ';', + '[keyword echo] ' + m1[5] + ';', + '[keyword echo] ' + m1[1] + ';', + '[keyword echo] ' + m1[6] + ';', + '[keyword echo] ' + m1[9] + ';', + '[keyword echo] ' + m1[0] + ';', + '[keyword echo] ' + m1[10] + ';', + '[keyword echo] ' + m1[2] + ';', + '[keyword echo] ' + m1[3] + ';', + '[keyword echo] [string "end"];', + '[meta ?>]'); + + var m2 = build_recursive_monsters( + ['[string "a][variable-2 $]{[variable aaa] [operator +] ', ' [operator +] ', '}[string .a"]'], + '[comment /* }?>{{ */] [string "a?>}{{aa][variable-2 $aaa][string .a}a?>a"]', + 5 + ); + + MT('variable_interpolation_complex_syntax_3_2', + '[meta <?php] [comment /* Recursive monsters 2 */]', + '[keyword echo] ' + m2[0] + ';', + '[keyword echo] ' + m2[1] + ';', + '[keyword echo] ' + m2[5] + ';', + '[keyword echo] ' + m2[4] + ';', + '[keyword echo] ' + m2[2] + ';', + '[keyword echo] ' + m2[3] + ';', + '[keyword echo] [string "end"];', + '[meta ?>]'); + + function build_recursive_monsters_2(mf1, mf2, nt, t, n){ + var monsters = [t]; + for (var i = 1; i <= n; ++i) + monsters[i] = nt[0] + mf1[i - 1] + nt[1] + mf2[i - 1] + nt[2] + monsters[i - 1] + nt[3]; + return monsters; + } + + var m3 = build_recursive_monsters_2( + m1, + m2, + ['[string "a][variable-2 $]{[variable aaa] [operator +] ', ' [operator +] ', ' [operator +] ', '}[string .a"]'], + '[comment /* }?>{{ */] [string "a?>}{{aa][variable-2 $aaa][string .a}a?>a"]', + 4 + ); + + MT('variable_interpolation_complex_syntax_3_3', + '[meta <?php] [comment /* Recursive monsters 2 */]', + '[keyword echo] ' + m3[4] + ';', + '[keyword echo] ' + m3[0] + ';', + '[keyword echo] ' + m3[3] + ';', + '[keyword echo] ' + m3[1] + ';', + '[keyword echo] ' + m3[2] + ';', + '[keyword echo] [string "end"];', + '[meta ?>]'); +})(); diff --git a/test/index.html b/test/index.html index 76fd835f2..752971fe4 100644 --- a/test/index.html +++ b/test/index.html @@ -15,6 +15,8 @@ <script src="../addon/edit/matchbrackets.js"></script> <script src="../addon/comment/comment.js"></script> <script src="../mode/javascript/javascript.js"></script> +<script src="../mode/clike/clike.js"></script> +<script src="../mode/php/php.js"></script> <script src="../mode/xml/xml.js"></script> <script src="../keymap/vim.js"></script> <script src="../keymap/emacs.js"></script> @@ -78,6 +80,7 @@ <script src="search_test.js"></script> <script src="mode_test.js"></script> <script src="../mode/javascript/test.js"></script> + <script src="../mode/php/test.js"></script> <script src="../mode/css/css.js"></script> <script src="../mode/css/test.js"></script> <script src="../mode/css/scss_test.js"></script> -- GitLab