UNPKG

16.3 kBJavaScriptView Raw
1/*
2 *
3LispyScript - Javascript using tree syntax!
4This is the compiler written in javascipt
5 *
6*/
7
8define(['require', 'exports', 'module', 'underscore'], function(require, exports, module, _) {
9
10var _ = require('underscore');
11var fs;
12if (typeof window === "undefined")
13 fs = require('fs');
14
15String.prototype.repeat = function(num) {
16 return new Array(num + 1).join(this);
17};
18
19exports.version = "0.2.9";
20var 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 = {};
31templates["var"] = _.template("var <%= rest %>");
32templates["new"] = _.template("new <%= rest %>");
33templates["set"] = _.template("<%= name %> = <%= value %>");
34templates["function"] = _.template("function(<%= params %>) {\n<%= expressions %><%= indent %>}");
35templates["try"] = _.template("(function() {\n<%= indent %>try {\n<%= trypart %>\n<%= indent %>} catch (e) {\n<%= indent %>return (<%= catchpart %>)(e);\n<%= indent %>}\n<%= indent %>})()");
36templates["if"] = _.template("(<%= condition %> ?\n<%= indent %><%= trueexpr %> :\n<%= indent %><%= falseexpr %>)");
37//templates["if"] = _.template("(function() {return <%= condition %> ?\n<%= indent %><%= trueexpr %> :\n<%= indent %><%= falseexpr %>;})()");
38templates["get"] = _.template("<%= list %>[<%= key %>]");
39templates["operator"] = _.template("(<%= loperand %> <%= operator %> <%= roperand %>)");
40templates["str"] = _.template("[<%= elems %>].join('')");
41templates["throw"] = _.template("(function(){throw <%= rest %>;})()");
42
43var 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 // regex in function position with first char " " is a prob. Use \s instead.
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
177var 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
208var 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
232var handleSubExpressions = function(expr) {
233 _.each(expr, function(value, i, t) {
234 if (_.isArray(value)) t[i] = handleExpression(value);
235 });
236};
237
238var 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 // we are here if any macro arg is not set
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 // Handle homoiconic expressions in macro
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
315var 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
323keywords["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
328keywords["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
334keywords["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
339keywords["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
350keywords["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
359keywords["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
368keywords["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
381keywords["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
387keywords["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
393var 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
405keywords["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
425keywords["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
431keywords["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
439keywords["+"] = handleOperator;
440
441keywords["-"] = handleOperator;
442
443keywords["*"] = handleOperator;
444
445keywords["/"] = handleOperator;
446
447keywords["%"] = handleOperator;
448
449keywords["="] = handleOperator;
450
451keywords["!="] = handleOperator;
452
453keywords[">"] = handleOperator;
454
455keywords[">="] = handleOperator;
456
457keywords["<"] = handleOperator;
458
459keywords["<="] = handleOperator;
460
461keywords["||"] = handleOperator;
462
463keywords["&&"] = handleOperator;
464
465keywords["!"] = function(arr) {
466 if (arr.length != 2) handleError(0, arr._line, arr._filename);
467 handleSubExpressions(arr);
468 return "(!" + arr[1] + ")";
469};
470
471var 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
478errors = [];
479errors[0] = "Syntax Error";
480errors[1] = "Empty statement";
481errors[2] = "Invalid characters in function name";
482errors[3] = "End of File encountered, unterminated string";
483errors[4] = "Closing square bracket, without an opening square bracket";
484errors[5] = "End of File encountered, unterminated array";
485errors[6] = "Closing curly brace, without an opening curly brace";
486errors[7] = "End of File encountered, unterminated javascript object '}'";
487errors[8] = "End of File encountered, unterminated parenthesis";
488errors[9] = "Invalid character in var name";
489errors[10] = "Extra chars at end of file. Maybe an extra ')'.";
490errors[11] = "Cannot Open include File";
491errors[12] = "Invalid no of arguments to "
492errors[13] = "Invalid Argument type to "
493errors[14] = "End of File encountered, unterminated regular expression";
494
495exports._compile = function(code, filename) {
496 var tree = parse(code, filename);
497 return banner + handleExpressions(tree);
498};
499
500});