1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | define(['require', 'exports', 'module', 'underscore'], function(require, exports, module, _) {
|
9 |
|
10 | var _ = require('underscore');
|
11 | var fs;
|
12 | if (typeof window === "undefined")
|
13 | fs = require('fs');
|
14 |
|
15 | String.prototype.repeat = function(num) {
|
16 | return new Array(num + 1).join(this);
|
17 | };
|
18 |
|
19 | exports.version = "0.2.9";
|
20 | var banner = "// Generated by LispyScript v" + this.version + "\n",
|
21 | isWhitespace = /\s/,
|
22 | isFunction = /^function\b/,
|
23 | validName = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/,
|
24 | noReturn = /^var\b|^set\b|^throw\b/,
|
25 | isHomoiconicExpr = /^#args-if\b|^#args-shift\b|^#args-second\b/,
|
26 | noSemiColon = false;
|
27 | indent = -4,
|
28 | keywords = {},
|
29 | macros = {},
|
30 | templates = {};
|
31 | templates["var"] = _.template("var <%= rest %>");
|
32 | templates["new"] = _.template("new <%= rest %>");
|
33 | templates["set"] = _.template("<%= name %> = <%= value %>");
|
34 | templates["function"] = _.template("function(<%= params %>) {\n<%= expressions %><%= indent %>}");
|
35 | templates["try"] = _.template("(function() {\n<%= indent %>try {\n<%= trypart %>\n<%= indent %>} catch (e) {\n<%= indent %>return (<%= catchpart %>)(e);\n<%= indent %>}\n<%= indent %>})()");
|
36 | templates["if"] = _.template("(<%= condition %> ?\n<%= indent %><%= trueexpr %> :\n<%= indent %><%= falseexpr %>)");
|
37 |
|
38 | templates["get"] = _.template("<%= list %>[<%= key %>]");
|
39 | templates["operator"] = _.template("(<%= loperand %> <%= operator %> <%= roperand %>)");
|
40 | templates["str"] = _.template("[<%= elems %>].join('')");
|
41 | templates["throw"] = _.template("(function(){throw <%= rest %>;})()");
|
42 |
|
43 | var parse = function(code, filename) {
|
44 | code = "(" + code + ")";
|
45 | var length = code.length,
|
46 | pos = 1,
|
47 | lineno = 1;
|
48 |
|
49 | var parser = function() {
|
50 | var tree = [],
|
51 | token = "",
|
52 | isString = false,
|
53 | isSingleString = false,
|
54 | isJSArray = 0,
|
55 | isJSObject = 0,
|
56 | isListComplete = false,
|
57 | isComment = false,
|
58 | isRegex = false,
|
59 | isEscape = false;
|
60 | tree._line = lineno;
|
61 | tree._filename = filename;
|
62 | var handleToken = function() {
|
63 | if (token) {
|
64 | tree.push(token);
|
65 | token = "";
|
66 | }
|
67 | };
|
68 | while (pos < length) {
|
69 | var c = code.charAt(pos);
|
70 | pos++;
|
71 | if (c == "\n") {
|
72 | lineno++;
|
73 | if (isComment) isComment = false;
|
74 | }
|
75 | if (isComment) continue;
|
76 | if (isEscape) {
|
77 | isEscape = false;
|
78 | token += c;
|
79 | continue;
|
80 | }
|
81 | if (c == '"') {
|
82 | isString = !isString;
|
83 | token += c;
|
84 | continue;
|
85 | }
|
86 | if (isString) {
|
87 | if (c === "\n")
|
88 | token += "\\n";
|
89 | else {
|
90 | if (c === "\\") isEscape = true;
|
91 | token += c;
|
92 | }
|
93 | continue;
|
94 | }
|
95 | if (c == "'") {
|
96 | isSingleString = !isSingleString;
|
97 | token += c;
|
98 | continue;
|
99 | }
|
100 | if (isSingleString) {
|
101 | token += c;
|
102 | continue;
|
103 | }
|
104 | if (c == '[') {
|
105 | isJSArray++;
|
106 | token += c;
|
107 | continue;
|
108 | }
|
109 | if (c == ']') {
|
110 | if (isJSArray === 0) handleError(4, tree._line, tree._filename);
|
111 | isJSArray--;
|
112 | token += c;
|
113 | continue;
|
114 | }
|
115 | if (isJSArray) {
|
116 | token += c;
|
117 | continue;
|
118 | }
|
119 | if (c == '{') {
|
120 | isJSObject++;
|
121 | token += c;
|
122 | continue;
|
123 | }
|
124 | if (c == '}') {
|
125 | if (isJSObject === 0) handleError(6, tree._line, tree._filename);
|
126 | isJSObject--;
|
127 | token += c;
|
128 | continue;
|
129 | }
|
130 | if (isJSObject) {
|
131 | token += c;
|
132 | continue;
|
133 | }
|
134 | if (c == ";") {
|
135 | isComment = true;
|
136 | continue;
|
137 | }
|
138 |
|
139 | if (c === "/" && !(tree.length === 0 && token.length === 0 && isWhitespace.test(code.charAt(pos)))) {
|
140 | isRegex = !isRegex;
|
141 | token += c;
|
142 | continue;
|
143 | }
|
144 | if (isRegex) {
|
145 | if (c === "\\") isEscape = true;
|
146 | token += c;
|
147 | continue;
|
148 | }
|
149 | if (c == "(") {
|
150 | tree.push(parser());
|
151 | continue;
|
152 | }
|
153 | if (c == ")") {
|
154 | isListComplete = true;
|
155 | handleToken();
|
156 | break;
|
157 | }
|
158 | if (isWhitespace.test(c)) {
|
159 | handleToken();
|
160 | continue;
|
161 | }
|
162 | token += c;
|
163 | }
|
164 | if (isString) handleError(3, tree._line, tree._filename);
|
165 | if (isRegex) handleError(14, tree._line, tree._filename);
|
166 | if (isSingleString) handleError(3, tree._line, tree._filename);
|
167 | if (isJSArray > 0) handleError(5, tree._line, tree._filename);
|
168 | if (isJSObject > 0) handleError(7, tree._line, tree._filename);
|
169 | if (!isListComplete) handleError(8, tree._line, tree._filename);
|
170 | return tree;
|
171 | };
|
172 | var ret = parser();
|
173 | if (pos < length) handleError(10);
|
174 | return ret;
|
175 | };
|
176 |
|
177 | var handleExpressions = function(exprs) {
|
178 | indent += 4;
|
179 | var ret = "",
|
180 | l = exprs.length,
|
181 | indentstr = " ".repeat(indent);
|
182 | _.each(exprs, function(expr, i, exprs) {
|
183 | var exprName,
|
184 | tmp = "",
|
185 | r = "";
|
186 | if (_.isArray(expr)) {
|
187 | exprName = expr[0];
|
188 | if (exprName === "include")
|
189 | ret += handleExpression(expr);
|
190 | else
|
191 | tmp = handleExpression(expr);
|
192 | } else {
|
193 | tmp = expr;
|
194 | }
|
195 | if (i === l - 1 && indent) {
|
196 | if (!noReturn.test(exprName)) r = "return ";
|
197 | }
|
198 | if (tmp.length > 0) {
|
199 | var endline = noSemiColon ? "\n" : ";\n";
|
200 | noSemiColon = false;
|
201 | ret += indentstr + r + tmp + endline;
|
202 | }
|
203 | });
|
204 | indent -= 4;
|
205 | return ret;
|
206 | };
|
207 |
|
208 | var handleExpression = function(expr) {
|
209 | if (!expr) return "";
|
210 | var command = expr[0];
|
211 | if (macros[command]) {
|
212 | expr = macroExpand(expr);
|
213 | if (_.isArray(expr))
|
214 | return handleExpression(expr);
|
215 | else
|
216 | return expr;
|
217 | }
|
218 | if (_.isString(command)) {
|
219 | if (keywords[command])
|
220 | return keywords[command](expr);
|
221 | if (command.charAt(0) === ".") {
|
222 | return "(" + (_.isArray(expr[1]) ? handleExpression(expr[1]) : expr[1]) + ")" + command;
|
223 | }
|
224 | }
|
225 | handleSubExpressions(expr);
|
226 | var fName = expr[0];
|
227 | if (!fName) handleError(1, expr._line);
|
228 | if (isFunction.test(fName)) fName = "(" + fName + ")";
|
229 | return fName + "(" + expr.slice(1).join(",") + ")";
|
230 | };
|
231 |
|
232 | var handleSubExpressions = function(expr) {
|
233 | _.each(expr, function(value, i, t) {
|
234 | if (_.isArray(value)) t[i] = handleExpression(value);
|
235 | });
|
236 | };
|
237 |
|
238 | var macroExpand = function(tree) {
|
239 | var command = tree[0],
|
240 | template = macros[command]["template"],
|
241 | code = macros[command]["code"],
|
242 | replacements = {};
|
243 | for (var i = 0; i < template.length; i++) {
|
244 | if (template[i] == "rest...") {
|
245 | replacements["~rest..."] = tree.slice(i + 1);
|
246 | } else {
|
247 | if (tree.length === i + 1) {
|
248 |
|
249 | handleError(12, tree._line, tree._filename, command);
|
250 | }
|
251 | replacements["~" + template[i]] = tree[i + 1];
|
252 | }
|
253 | }
|
254 | var replaceCode = function(source) {
|
255 | var ret = [];
|
256 | ret._line = tree._line;
|
257 | ret._filename = tree._filename;
|
258 |
|
259 |
|
260 | var expr_name = source[0];
|
261 | if (isHomoiconicExpr.test(expr_name)) {
|
262 | var replarray = replacements["~" + source[1]];
|
263 | if (expr_name === "#args-shift") {
|
264 | if (!_.isArray(replarray)) handleError(13, tree._line, tree._filename, command);
|
265 | var argshift = replarray.shift();
|
266 | if (_.isUndefined(argshift)) handleError(12, tree._line, tree._filename, command);
|
267 | return argshift;
|
268 | }
|
269 | if (expr_name === "#args-second") {
|
270 | if (!_.isArray(replarray)) handleError(13, tree._line, tree._filename, command);
|
271 | var argsecond = replarray.splice(1, 1)[0];
|
272 | if (_.isUndefined(argsecond)) handleError(12, tree._line, tree._filename, command);
|
273 | return argsecond;
|
274 | }
|
275 | if (expr_name === "#args-if") {
|
276 | if (!_.isArray(replarray)) handleError(13, tree._line, tree._filename, command);
|
277 | if (replarray.length)
|
278 | return replaceCode(source[2]);
|
279 | else if (source[3])
|
280 | return replaceCode(source[3]);
|
281 | else
|
282 | return;
|
283 | }
|
284 | }
|
285 | for (var i = 0; i < source.length; i++) {
|
286 | if (typeof source[i] == "object") {
|
287 | var replcode = replaceCode(source[i]);
|
288 | if (!_.isUndefined(replcode)) ret.push(replcode);
|
289 | } else {
|
290 | var token = source[i];
|
291 | var tokenbak = token;
|
292 | var isATSign = false;
|
293 | if (token.indexOf("@") >= 0) {
|
294 | isATSign = true;
|
295 | tokenbak = token.replace("@", "") ;
|
296 | }
|
297 | if (replacements[tokenbak]) {
|
298 | var repl = replacements[tokenbak];
|
299 | if (isATSign || tokenbak == "~rest...") {
|
300 | for (var j = 0; j < repl.length; j++)
|
301 | ret.push(repl[j]);
|
302 | } else {
|
303 | ret.push(repl);
|
304 | }
|
305 | } else {
|
306 | ret.push(token);
|
307 | }
|
308 | }
|
309 | }
|
310 | return ret;
|
311 | };
|
312 | return replaceCode(code);
|
313 | };
|
314 |
|
315 | var handleOperator = function(arr) {
|
316 | if (arr.length != 3) handleError(0, arr._line);
|
317 | handleSubExpressions(arr);
|
318 | if (arr[0] == "=") arr[0] = "===";
|
319 | if (arr[0] == "!=") arr[0] = "!==";
|
320 | return templates["operator"]({operator: arr[0], loperand: arr[1], roperand: arr[2]});
|
321 | };
|
322 |
|
323 | keywords["var"] = function(arr) {
|
324 | if (!validName.test(arr[1])) handleError(9, arr._line, arr._filename);
|
325 | return templates["var"]({rest: keywords.set(arr)});
|
326 | };
|
327 |
|
328 | keywords["new"] = function(arr) {
|
329 | if (arr.length < 2) handleError(0, arr._line, arr._filename);
|
330 | return templates["new"]({ rest: handleExpression(arr.slice(1)) });
|
331 | };
|
332 |
|
333 |
|
334 | keywords["throw"] = function(arr) {
|
335 | if (arr.length != 2) handleError(0, arr._line, arr._filename);
|
336 | return templates["throw"]({ rest: (typeof arr[1] == "object") ? handleExpression(arr[1]) : arr[1] });
|
337 | };
|
338 |
|
339 | keywords["set"] = function(arr) {
|
340 | if (arr.length < 3 || arr.length > 4) handleError(0, arr._line, arr._filename);
|
341 | if (arr.length == 4) {
|
342 | arr[1] = ((typeof arr[2] == "object") ? handleExpression(arr[2]) : arr[2]) + "[" + arr[1] + "]";
|
343 | arr[2] = arr[3];
|
344 | }
|
345 | return templates["set"]({
|
346 | name: arr[1],
|
347 | value: (typeof arr[2] == "object") ? handleExpression(arr[2]) : arr[2]});
|
348 | };
|
349 |
|
350 | keywords["function"] = function(arr) {
|
351 | if (arr.length < 2) handleError(0, arr._line, arr._filename);
|
352 | if (typeof arr[1] != "object") handleError(0, arr._line);
|
353 | return templates["function"]({
|
354 | params: arr[1].join(","),
|
355 | expressions: handleExpressions(arr.slice(2)),
|
356 | indent: " ".repeat(indent)});
|
357 | };
|
358 |
|
359 | keywords["try"] = function(arr) {
|
360 | if (arr.length < 3) handleError(0, arr._line, arr._filename);
|
361 | var c = arr.pop();
|
362 | return templates["try"]({
|
363 | trypart: handleExpressions(arr.slice(1)),
|
364 | catchpart: _.isArray(c) ? handleExpression(c) : c,
|
365 | indent: " ".repeat(indent)});
|
366 | };
|
367 |
|
368 | keywords["if"] = function(arr) {
|
369 | if (arr.length < 3 || arr.length > 4) handleError(0, arr._line, arr._filename);
|
370 | indent += 4;
|
371 | handleSubExpressions(arr);
|
372 | var ret = templates["if"]({
|
373 | condition: arr[1],
|
374 | trueexpr: arr[2],
|
375 | falseexpr: arr[3] || "undefined",
|
376 | indent: " ".repeat(indent)});
|
377 | indent -= 4;
|
378 | return ret;
|
379 | };
|
380 |
|
381 | keywords["get"] = function(arr) {
|
382 | if (arr.length != 3) handleError(0, arr._line, arr._filename);
|
383 | handleSubExpressions(arr);
|
384 | return templates["get"]({key: arr[1], list: arr[2]});
|
385 | };
|
386 |
|
387 | keywords["str"] = function(arr) {
|
388 | if (arr.length < 2) handleError(0, arr._line, arr._filename);
|
389 | handleSubExpressions(arr);
|
390 | return templates["str"]({elems: arr.slice(1).join(",")});
|
391 | };
|
392 |
|
393 | var includeFile = (function () {
|
394 | var included = [];
|
395 | return function(filename) {
|
396 | var found = _.find(included, function(f) {return f === filename;});
|
397 | if (found) return "";
|
398 | included.push(filename);
|
399 | var code = fs.readFileSync(filename);
|
400 | var tree = parse(code, filename);
|
401 | return handleExpressions(tree);
|
402 | };
|
403 | })();
|
404 |
|
405 | keywords["include"] = function(arr) {
|
406 | if (arr.length != 2) handleError(0, arr._line, arr._filename);
|
407 | indent -= 4;
|
408 | var filename = arr[1];
|
409 | if (typeof filename === "string")
|
410 | filename = filename.replace(/["']/g, "");
|
411 | try {
|
412 | filename = fs.realpathSync(filename);
|
413 | } catch (err) {
|
414 | try {
|
415 | filename = fs.realpathSync(__dirname + "/../includes/" + filename);
|
416 | } catch (err) {
|
417 | handleError(11, arr._line, arr._filename);
|
418 | }
|
419 | }
|
420 | var ret = includeFile(filename);
|
421 | indent += 4;
|
422 | return ret;
|
423 | };
|
424 |
|
425 | keywords["javascript"] = function(arr) {
|
426 | if (arr.length != 2) handleError(0, arr._line, arr._filename);
|
427 | noSemiColon = true;
|
428 | return arr[1].replace(/"/g, '');
|
429 | }
|
430 |
|
431 | keywords["macro"] = function(arr) {
|
432 | if (arr.length != 4) handleError(0, arr._line, arr._filename);
|
433 | macros[arr[1]] = {template: arr[2], code: arr[3]};
|
434 | return "";
|
435 | };
|
436 |
|
437 |
|
438 |
|
439 | keywords["+"] = handleOperator;
|
440 |
|
441 | keywords["-"] = handleOperator;
|
442 |
|
443 | keywords["*"] = handleOperator;
|
444 |
|
445 | keywords["/"] = handleOperator;
|
446 |
|
447 | keywords["%"] = handleOperator;
|
448 |
|
449 | keywords["="] = handleOperator;
|
450 |
|
451 | keywords["!="] = handleOperator;
|
452 |
|
453 | keywords[">"] = handleOperator;
|
454 |
|
455 | keywords[">="] = handleOperator;
|
456 |
|
457 | keywords["<"] = handleOperator;
|
458 |
|
459 | keywords["<="] = handleOperator;
|
460 |
|
461 | keywords["||"] = handleOperator;
|
462 |
|
463 | keywords["&&"] = handleOperator;
|
464 |
|
465 | keywords["!"] = function(arr) {
|
466 | if (arr.length != 2) handleError(0, arr._line, arr._filename);
|
467 | handleSubExpressions(arr);
|
468 | return "(!" + arr[1] + ")";
|
469 | };
|
470 |
|
471 | var handleError = function(no, line, filename, extra) {
|
472 | throw new Error(errors[no] +
|
473 | ((extra) ? " - " + extra : "") +
|
474 | ((line) ? "\nLine no " + line : "") +
|
475 | ((filename) ? "\nFile " + filename : ""));
|
476 | };
|
477 |
|
478 | errors = [];
|
479 | errors[0] = "Syntax Error";
|
480 | errors[1] = "Empty statement";
|
481 | errors[2] = "Invalid characters in function name";
|
482 | errors[3] = "End of File encountered, unterminated string";
|
483 | errors[4] = "Closing square bracket, without an opening square bracket";
|
484 | errors[5] = "End of File encountered, unterminated array";
|
485 | errors[6] = "Closing curly brace, without an opening curly brace";
|
486 | errors[7] = "End of File encountered, unterminated javascript object '}'";
|
487 | errors[8] = "End of File encountered, unterminated parenthesis";
|
488 | errors[9] = "Invalid character in var name";
|
489 | errors[10] = "Extra chars at end of file. Maybe an extra ')'.";
|
490 | errors[11] = "Cannot Open include File";
|
491 | errors[12] = "Invalid no of arguments to "
|
492 | errors[13] = "Invalid Argument type to "
|
493 | errors[14] = "End of File encountered, unterminated regular expression";
|
494 |
|
495 | exports._compile = function(code, filename) {
|
496 | var tree = parse(code, filename);
|
497 | return banner + handleExpressions(tree);
|
498 | };
|
499 |
|
500 | });
|