scripts/CodeMirror/mode/velocity/velocity.js (view raw)
1// CodeMirror, copyright (c) by Marijn Haverbeke and others
2// Distributed under an MIT license: https://codemirror.net/LICENSE
3
4(function(mod) {
5 if (typeof exports == "object" && typeof module == "object") // CommonJS
6 mod(require("../../lib/codemirror"));
7 else if (typeof define == "function" && define.amd) // AMD
8 define(["../../lib/codemirror"], mod);
9 else // Plain browser env
10 mod(CodeMirror);
11})(function(CodeMirror) {
12"use strict";
13
14CodeMirror.defineMode("velocity", function() {
15 function parseWords(str) {
16 var obj = {}, words = str.split(" ");
17 for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
18 return obj;
19 }
20
21 var keywords = parseWords("#end #else #break #stop #[[ #]] " +
22 "#{end} #{else} #{break} #{stop}");
23 var functions = parseWords("#if #elseif #foreach #set #include #parse #macro #define #evaluate " +
24 "#{if} #{elseif} #{foreach} #{set} #{include} #{parse} #{macro} #{define} #{evaluate}");
25 var specials = parseWords("$foreach.count $foreach.hasNext $foreach.first $foreach.last $foreach.topmost $foreach.parent.count $foreach.parent.hasNext $foreach.parent.first $foreach.parent.last $foreach.parent $velocityCount $!bodyContent $bodyContent");
26 var isOperatorChar = /[+\-*&%=<>!?:\/|]/;
27
28 function chain(stream, state, f) {
29 state.tokenize = f;
30 return f(stream, state);
31 }
32 function tokenBase(stream, state) {
33 var beforeParams = state.beforeParams;
34 state.beforeParams = false;
35 var ch = stream.next();
36 // start of unparsed string?
37 if ((ch == "'") && !state.inString && state.inParams) {
38 state.lastTokenWasBuiltin = false;
39 return chain(stream, state, tokenString(ch));
40 }
41 // start of parsed string?
42 else if ((ch == '"')) {
43 state.lastTokenWasBuiltin = false;
44 if (state.inString) {
45 state.inString = false;
46 return "string";
47 }
48 else if (state.inParams)
49 return chain(stream, state, tokenString(ch));
50 }
51 // is it one of the special signs []{}().,;? Seperator?
52 else if (/[\[\]{}\(\),;\.]/.test(ch)) {
53 if (ch == "(" && beforeParams)
54 state.inParams = true;
55 else if (ch == ")") {
56 state.inParams = false;
57 state.lastTokenWasBuiltin = true;
58 }
59 return null;
60 }
61 // start of a number value?
62 else if (/\d/.test(ch)) {
63 state.lastTokenWasBuiltin = false;
64 stream.eatWhile(/[\w\.]/);
65 return "number";
66 }
67 // multi line comment?
68 else if (ch == "#" && stream.eat("*")) {
69 state.lastTokenWasBuiltin = false;
70 return chain(stream, state, tokenComment);
71 }
72 // unparsed content?
73 else if (ch == "#" && stream.match(/ *\[ *\[/)) {
74 state.lastTokenWasBuiltin = false;
75 return chain(stream, state, tokenUnparsed);
76 }
77 // single line comment?
78 else if (ch == "#" && stream.eat("#")) {
79 state.lastTokenWasBuiltin = false;
80 stream.skipToEnd();
81 return "comment";
82 }
83 // variable?
84 else if (ch == "$") {
85 stream.eatWhile(/[\w\d\$_\.{}-]/);
86 // is it one of the specials?
87 if (specials && specials.propertyIsEnumerable(stream.current())) {
88 return "keyword";
89 }
90 else {
91 state.lastTokenWasBuiltin = true;
92 state.beforeParams = true;
93 return "builtin";
94 }
95 }
96 // is it a operator?
97 else if (isOperatorChar.test(ch)) {
98 state.lastTokenWasBuiltin = false;
99 stream.eatWhile(isOperatorChar);
100 return "operator";
101 }
102 else {
103 // get the whole word
104 stream.eatWhile(/[\w\$_{}@]/);
105 var word = stream.current();
106 // is it one of the listed keywords?
107 if (keywords && keywords.propertyIsEnumerable(word))
108 return "keyword";
109 // is it one of the listed functions?
110 if (functions && functions.propertyIsEnumerable(word) ||
111 (stream.current().match(/^#@?[a-z0-9_]+ *$/i) && stream.peek()=="(") &&
112 !(functions && functions.propertyIsEnumerable(word.toLowerCase()))) {
113 state.beforeParams = true;
114 state.lastTokenWasBuiltin = false;
115 return "keyword";
116 }
117 if (state.inString) {
118 state.lastTokenWasBuiltin = false;
119 return "string";
120 }
121 if (stream.pos > word.length && stream.string.charAt(stream.pos-word.length-1)=="." && state.lastTokenWasBuiltin)
122 return "builtin";
123 // default: just a "word"
124 state.lastTokenWasBuiltin = false;
125 return null;
126 }
127 }
128
129 function tokenString(quote) {
130 return function(stream, state) {
131 var escaped = false, next, end = false;
132 while ((next = stream.next()) != null) {
133 if ((next == quote) && !escaped) {
134 end = true;
135 break;
136 }
137 if (quote=='"' && stream.peek() == '$' && !escaped) {
138 state.inString = true;
139 end = true;
140 break;
141 }
142 escaped = !escaped && next == "\\";
143 }
144 if (end) state.tokenize = tokenBase;
145 return "string";
146 };
147 }
148
149 function tokenComment(stream, state) {
150 var maybeEnd = false, ch;
151 while (ch = stream.next()) {
152 if (ch == "#" && maybeEnd) {
153 state.tokenize = tokenBase;
154 break;
155 }
156 maybeEnd = (ch == "*");
157 }
158 return "comment";
159 }
160
161 function tokenUnparsed(stream, state) {
162 var maybeEnd = 0, ch;
163 while (ch = stream.next()) {
164 if (ch == "#" && maybeEnd == 2) {
165 state.tokenize = tokenBase;
166 break;
167 }
168 if (ch == "]")
169 maybeEnd++;
170 else if (ch != " ")
171 maybeEnd = 0;
172 }
173 return "meta";
174 }
175 // Interface
176
177 return {
178 startState: function() {
179 return {
180 tokenize: tokenBase,
181 beforeParams: false,
182 inParams: false,
183 inString: false,
184 lastTokenWasBuiltin: false
185 };
186 },
187
188 token: function(stream, state) {
189 if (stream.eatSpace()) return null;
190 return state.tokenize(stream, state);
191 },
192 blockCommentStart: "#*",
193 blockCommentEnd: "*#",
194 lineComment: "##",
195 fold: "velocity"
196 };
197});
198
199CodeMirror.defineMIME("text/velocity", "velocity");
200
201});