Skip to content
Browse files

Merging event and 1.1.1

  • Loading branch information...
2 parents 9ac5096 + b0acc83 commit d043564affb5d57e1ed1d3515ef57dbd8147b109 @DavidSouther committed Jan 7, 2013
Showing with 906 additions and 475 deletions.
  1. +3 −0 .travis.yml
  2. +2 −0 README.md
  3. +3 −1 Slakefile
  4. +2 −2 extras/livescript.js
  5. +136 −68 lib/ast.js
  6. +6 −4 lib/command.js
  7. +35 −5 lib/grammar.js
  8. +64 −25 lib/lexer.js
  9. +1 −1 lib/livescript.js
  10. +201 −171 lib/parser.js
  11. +12 −1 package.json
  12. +14 −2 package.ls
  13. +110 −56 src/ast.ls
  14. +3 −5 src/command.ls
  15. +16 −5 src/grammar.ls
  16. +64 −26 src/lexer.ls
  17. +1 −1 src/livescript.ls
  18. +9 −4 test/assignment.ls
  19. +1 −0 test/chaining.ls
  20. +8 −6 test/comment.ls
  21. +0 −1 test/compilation.ls
  22. +2 −2 test/cs-classes.ls
  23. +68 −28 test/function.ls
  24. +40 −5 test/literal.ls
  25. +69 −1 test/loop.ls
  26. +4 −4 test/null.ls
  27. +25 −4 test/oo.ls
  28. +7 −47 test/operator.ls
View
3 .travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+ - 0.8
View
2 README.md
@@ -3,6 +3,8 @@ is a language which compiles to JavaScript. It has a straightforward mapping to
Check out **[livescript.net](http://livescript.net)** for more information, examples, usage, and a language reference.
+### Build Status
+[![Build Status](https://travis-ci.org/gkz/LiveScript.png?branch=master)](https://travis-ci.org/gkz/LiveScript)
### Install
Have Node.js installed. `sudo npm install -g LiveScript`
View
4 Slakefile
@@ -10,7 +10,7 @@ tint = (text, color ? green) -> color + text + reset
# Run our node/livescript interpreter.
run = (args) ->
- proc = spawn \node [\bin/livescript] ++ args
+ proc = spawn \node [\lib/command] ++ args
proc.stderr.on \data say
proc .on \exit -> process.exit it if it
@@ -145,6 +145,8 @@ function runTests global.LiveScript
say if failedTests
then tint "failed #failedTests and #message" red
else tint message
+ if failedTests
+ process.exit 1
dir(\test)forEach (file) ->
return unless /\.ls$/i.test file
code = slurp filename = path.join \test file
View
4 extras/livescript.js
2 additions, 2 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
204 lib/ast.js
@@ -183,9 +183,10 @@ var Node, Negatable, Block, Atom, Literal, Var, Key, Index, Slice, Chain, Call,
},
invertCheck: function(it){
if (it.inverted) {
- this.invert();
+ return this.invert();
+ } else {
+ return this;
}
- return this;
},
addElse: function($else){
this['else'] = $else;
@@ -382,6 +383,7 @@ exports.Block = Block = (function(superclass){
};
prototype.makeReturn = function(it){
var that, ref$, key$, ref1$;
+ this.chomp();
if (that = (ref1$ = ref$ = this.lines)[key$ = ref1$.length - 1] != null ? ref$[key$] = ref$[key$].makeReturn(it) : void 8) {
if (that instanceof Return && !that.it) {
--this.lines.length;
@@ -451,7 +453,7 @@ exports.Block = Block = (function(superclass){
};
prototype.compileExpressions = function(o, level){
var lines, i, that, code, last, i$, len$, node;
- lines = this.lines;
+ lines = this.chomp().lines;
i = -1;
while (that = lines[++i]) {
if (that.comment) {
@@ -736,7 +738,16 @@ exports.Chain = Chain = (function(superclass){
} function ctor$(){} ctor$.prototype = prototype;
prototype.children = ['head', 'tails'];
prototype.add = function(it){
- var ref$, bi, that, logics, call, f;
+ var last, ref$, index, bi, that, logics, call, f;
+ if (this.tails.length) {
+ last = (ref$ = this.tails)[ref$.length - 1];
+ if (last instanceof Call && ((ref$ = last.partialized) != null ? ref$.length : void 8) === 1 && it.args.length === 1) {
+ index = last.partialized[0].head.value;
+ delete last.partialized;
+ last.args[index] = it.args[0];
+ return this;
+ }
+ }
if (this.head instanceof Existence) {
ref$ = Chain(this.head.it), this.head = ref$.head, this.tails = ref$.tails;
it.soak = true;
@@ -885,7 +896,7 @@ exports.Chain = Chain = (function(superclass){
return [base.add(name), Chain(bref || base.head, [nref || name])];
};
prototype.compileNode = function(o){
- var head, tails, that, i$, len$, t, hasPartial, pre, rest, broken, partial, post, idt, func, base, news, ref$;
+ var head, tails, that, i$, len$, t, hasPartial, pre, rest, broken, partial, post, context, idt, func, base, news, ref$;
if (this.flip) {
util('flip');
util('curry');
@@ -923,7 +934,10 @@ exports.Chain = Chain = (function(superclass){
partial = rest[0], post = slice$.call(rest, 1);
}
this.tails = pre;
- return Chain(Chain(Var(util('partialize'))).add(Call([this, Arr(partial.args), Arr(partial.partialized)])), post).compile(o);
+ context = pre.length
+ ? Chain(head, slice$.call(pre, 0, -1))
+ : Literal('this');
+ return Chain(Chain(Var(util('partialize'))).add(Index(Key('apply'))).add(Call([context, Arr([this, Arr(partial.args), Arr(partial.partialized)])])), post).compile(o);
}
if (tails[0] instanceof Call && !head.isCallable()) {
this.carp('invalid callee');
@@ -2267,7 +2281,7 @@ exports.Assign = Assign = (function(superclass){
right.inClassStatic = slice$.call(dotSplit, 0, -1).join('');
}
}
- code = !o.level && right instanceof While && !right['else'] && (lvar || left.isSimpleAccess())
+ code = !o.level && right instanceof While && !right['else'] && (lvar || left instanceof Chain && left.isSimpleAccess())
? (empty = right.objComp ? '{}' : '[]', (res = o.scope.temporary('res')) + " = " + empty + ";\n" + this.tab + right.makeReturn(res).compile(o) + "\n" + this.tab + name + " " + sign + " " + o.scope.free(res))
: (name + " " + sign + " ") + (right.assigned = true, right).compile(o, LEVEL_LIST);
if (that = o.level) {
@@ -2652,7 +2666,7 @@ exports.Fun = Fun = (function(superclass){
if (this.bound === 'this$') {
if (this.ctor) {
scope.assign('this$', 'this instanceof ctor$ ? this : new ctor$');
- body.add(Return(Literal('this$')));
+ body.lines.push(Return(Literal('this$')));
} else if (that = (ref$ = sscope.fun) != null ? ref$.bound : void 8) {
this.bound = that;
} else {
@@ -2675,11 +2689,13 @@ exports.Fun = Fun = (function(superclass){
}
code += '}';
curryCodeCheck = function(){
- if (this$.curried) {
- if (this$.hasSplats) {
- this$.carp('cannot curry a function with a variable number of arguments');
- }
- return util('curry') + "(" + code + ")";
+ if (this$.curried && this$.hasSplats) {
+ this$.carp('cannot curry a function with a variable number of arguments');
+ }
+ if (this$.curried && this$.params.length > 1) {
+ return (util('curry') + "") + (this$.bound || this$.classBound
+ ? "((" + code + "), true)"
+ : "(" + code + ")");
} else {
return code;
}
@@ -2700,36 +2716,40 @@ exports.Fun = Fun = (function(superclass){
}
};
prototype.compileParams = function(scope){
- var params, body, names, assigns, i$, len$, i, p, splace, rest, that, dic, vr, df, v, ref$, ref1$, name;
- params = this.params, body = this.body;
- names = [];
- assigns = [];
+ var params, length, body, i$, p, len$, i, splace, rest, that, names, assigns, dic, vr, df, unaries, hasUnary, v, ref$, ref1$;
+ params = this.params, length = params.length, body = this.body;
+ for (i$ = params.length - 1; i$ >= 0; --i$) {
+ p = params[i$];
+ if (!(p.isEmpty() || p.filler)) {
+ break;
+ }
+ --params.length;
+ }
for (i$ = 0, len$ = params.length; i$ < len$; ++i$) {
i = i$;
p = params[i$];
if (p instanceof Splat) {
- splace = i;
this.hasSplats = true;
+ splace = i;
} else if (p.op === '=') {
params[i] = Binary(p.logic || '?', p.left, p.right);
}
}
if (splace != null) {
rest = params.splice(splace, 9e9);
- if (!rest[1] && rest[0].it.isEmpty()) {
- rest = 0;
- }
} else if (this.accessor) {
if (that = params[1]) {
that.carp('excess accessor parameter');
}
- } else if (!(params.length || this.wrapper)) {
+ } else if (!(length || this.wrapper)) {
if (body.traverseChildren(function(it){
return it.value === 'it' || null;
})) {
params[0] = Var('it');
}
}
+ names = [];
+ assigns = [];
if (params.length) {
dic = {};
for (i$ = 0, len$ = params.length; i$ < len$; ++i$) {
@@ -2741,16 +2761,19 @@ exports.Fun = Fun = (function(superclass){
if (vr.isEmpty()) {
vr = Var(scope.temporary('arg'));
} else if (!(vr instanceof Var)) {
+ unaries = [];
+ while (vr instanceof Unary) {
+ hasUnary = true;
+ unaries.push(vr);
+ vr = vr.it;
+ }
v = Var((ref1$ = (ref$ = vr.it || vr).name, delete ref$.name, ref1$) || vr.varName() || scope.temporary('arg'));
- assigns.push(Assign(vr, df ? Binary(p.op, v, p.second) : v));
+ assigns.push(Assign(vr, (fn$())));
vr = v;
} else if (df) {
assigns.push(Assign(vr, p.second, '=', p.op, true));
}
- names.push(name = scope.add(vr.value, 'arg', p));
- if (!(dic[name + "."] ^= 1)) {
- p.carp("duplicate parameter \"" + name + "\"");
- }
+ names.push(scope.add(vr.value, 'arg', p));
}
}
if (rest) {
@@ -2763,6 +2786,19 @@ exports.Fun = Fun = (function(superclass){
(ref$ = this.body).prepend.apply(ref$, assigns);
}
return names.join(', ');
+ function fn$(){
+ switch (false) {
+ case !df:
+ return Binary(p.op, v, p.second);
+ case !hasUnary:
+ return fold(function(x, y){
+ y.it = x;
+ return y;
+ }, v, unaries.reverse());
+ default:
+ return v;
+ }
+ }
};
return Fun;
}(Node));
@@ -2779,7 +2815,7 @@ exports.Class = Class = (function(superclass){
this.name = it.varName();
};
prototype.compile = function(o, level){
- var fun, body, lines, title, boundFuncs, decl, name, proto, ctorName, ctor, ctorPlace, importProtoObj, i$, len$, i, node, ref$, f, vname, args, that, imports, res$, clas;
+ var fun, body, lines, title, boundFuncs, decl, name, proto, ctorName, ctor, ctorPlace, importProtoObj, i$, len$, i, node, f, vname, args, that, imports, ref$, res$, clas;
fun = this.fun, body = fun.body, lines = body.lines, title = this.title;
boundFuncs = [];
decl = title != null ? title.varName() : void 8;
@@ -2792,30 +2828,34 @@ exports.Class = Class = (function(superclass){
proto = Var('prototype');
ctorName = 'constructor$$';
importProtoObj = function(node, i){
- var j, prop, ref$, i$, len$, f;
+ var j, prop, key, i$, ref$, len$, v;
j = 0;
for (; j < node.items.length; j++) {
prop = node.items[j];
- if ((ref$ = prop.key) instanceof Key || ref$ instanceof Literal) {
- if ((prop.key instanceof Key && prop.key.name === ctorName) || (prop.key instanceof Literal && prop.key.value === "'" + ctorName + "'")) {
- if (ctor) {
- node.carp('redundant constructor');
- }
- ctor = prop.val;
- node.items.splice(j--, 1);
- ctorPlace = i;
- } else if (prop.val instanceof Fun) {
- prop.val.meth = prop.key;
- if (prop.val.bound) {
- boundFuncs.push(prop.key);
- prop.val.bound = false;
- }
- } else if (prop.accessor) {
- for (i$ = 0, len$ = (ref$ = prop.val).length; i$ < len$; ++i$) {
- f = ref$[i$];
- f.meth = prop.key;
- }
+ key = prop.key;
+ if ((key instanceof Key && key.name === ctorName) || (key instanceof Literal && key.value === "'" + ctorName + "'")) {
+ if (ctor) {
+ node.carp('redundant constructor');
}
+ ctor = prop.val;
+ node.items.splice(j--, 1);
+ ctorPlace = i;
+ }
+ if (!(prop.val instanceof Fun || prop.accessor)) {
+ continue;
+ }
+ if (key.isComplex()) {
+ key = Var(o.scope.temporary('key'));
+ prop.key = Assign(key, prop.key);
+ }
+ if (prop.val.bound) {
+ boundFuncs.push(prop.key);
+ prop.val.bound = false;
+ prop.val.classBound = true;
+ }
+ for (i$ = 0, len$ = (ref$ = [].concat(prop.val)).length; i$ < len$; ++i$) {
+ v = ref$[i$];
+ v.meth = key;
}
}
if (node.items.length) {
@@ -2838,7 +2878,7 @@ exports.Class = Class = (function(superclass){
node.traverseChildren(fn$, true);
}
}
- ctor || (ctor = lines[lines.length] = this.sup && ((ref$ = this.sup) instanceof Fun || ref$ instanceof Var)
+ ctor || (ctor = lines[lines.length] = this.sup
? Fun([], Block(Chain(new Super).add(Call([Splat(Literal('arguments'))]))))
: Fun());
if (!(ctor instanceof Fun)) {
@@ -3179,6 +3219,9 @@ exports.While = While = (function(superclass){
};
prototype.makeReturn = function(it){
var last, ref$;
+ if (this.hasReturned) {
+ return this;
+ }
if (it) {
if (this.objComp) {
this.body = Block(this.body.makeObjReturn(it));
@@ -3195,6 +3238,7 @@ exports.While = While = (function(superclass){
if ((ref$ = this['else']) != null) {
ref$.makeReturn(it);
}
+ this.hasReturned = true;
} else {
this.resVar = it;
if ((ref$ = this['else']) != null) {
@@ -3240,7 +3284,8 @@ exports.While = While = (function(superclass){
last = lines != null ? lines[lines.length - 1] : void 8;
if (!((this.isComprehension || this.inComprehension) && !(last != null && last.isComprehension))) {
this.traverseChildren(function(it){
- if (it instanceof While) {
+ var ref$;
+ if (it instanceof Block && (ref$ = it.lines)[ref$.length - 1] instanceof While) {
hasLoop = true;
}
});
@@ -3340,6 +3385,9 @@ exports.For = For = (function(superclass){
? idx + " " + '<>'.charAt(pvar < 0) + eq + " " + tvar
: pvar + " < 0 ? " + idx + " >" + eq + " " + tvar + " : " + idx + " <" + eq + " " + tvar;
} else {
+ if (this.ref) {
+ this.item = Var(o.scope.temporary('x'));
+ }
if (this.item || this.object && this.own) {
ref$ = this.source.compileLoopReference(o, 'ref', !this.object), svar = ref$[0], srcPart = ref$[1];
svar === srcPart || temps.push(svar);
@@ -3385,6 +3433,9 @@ exports.For = For = (function(superclass){
if (this.item && !this.item.isEmpty()) {
head += '\n' + o.indent + Assign(this.item, JS(svar + "[" + idx + "]")).compile(o, LEVEL_TOP) + ';';
}
+ if (this.ref) {
+ o.ref = this.item.value;
+ }
body = this.compileBody(o);
if ((this.item || (this.index && !this.object)) && '}' === body.charAt(0)) {
head += '\n' + this.tab;
@@ -3520,7 +3571,11 @@ exports.Switch = Switch = (function(superclass){
return false;
}
}
- return (ref$ = this['default']) != null ? ref$.isCallable() : void 8;
+ if (this['default']) {
+ return this['default'].isCallable();
+ } else {
+ return true;
+ }
};
prototype.getJump = function(ctx){
var i$, ref$, len$, c, that;
@@ -3770,15 +3825,20 @@ exports.Label = Label = (function(superclass){
}(Node));
exports.Cascade = Cascade = (function(superclass){
var prototype = extend$((import$(Cascade, superclass).displayName = 'Cascade', Cascade), superclass).prototype, constructor = Cascade;
- function Cascade(input, output, implicit){
- this.input = input;
- this.output = output;
- this.implicit = implicit;
- }
+ function Cascade(input, output, prog1){
+ var this$ = this instanceof ctor$ ? this : new ctor$;
+ this$.input = input;
+ this$.output = output;
+ this$.prog1 = prog1;
+ return this$;
+ } function ctor$(){} ctor$.prototype = prototype;
+ prototype.show = function(){
+ return this.prog1;
+ };
prototype.children = ['input', 'output'];
prototype.terminator = '';
prototype.delegate(['isCallable', 'isArray', 'isString', 'isRegex'], function(it){
- return this.output[it]();
+ return this[this.prog1 ? 'input' : 'output'][it]();
});
prototype.getJump = function(it){
return this.output.getJump(it);
@@ -3788,30 +3848,29 @@ exports.Cascade = Cascade = (function(superclass){
return this;
};
prototype.compileNode = function(o){
- var level, input, output, ref, ref$, code, out;
+ var level, input, output, prog1, ref, ref$, code, out;
level = o.level;
- input = this.input, output = this.output, ref = this.ref;
- if ('ret' in this || level && !this['void']) {
+ input = this.input, output = this.output, prog1 = this.prog1, ref = this.ref;
+ if (prog1 && ('ret' in this || level && !this['void'])) {
output.add((ref$ = Literal('..'), ref$.cascadee = true, ref$));
}
if ('ret' in this) {
output = output.makeReturn(this.ret);
}
if (ref) {
- output = Assign(Arr(), output);
+ prog1 || (output = Assign(Var(ref), output));
} else {
- this.temps = [ref = o.scope.temporary('x')];
+ ref = o.scope.temporary('x');
}
if (input instanceof Cascade) {
input.ref = ref;
} else {
- input = Assign(JS(ref), input);
+ input && (input = Assign(Var(ref), input));
}
o.level && (o.level = LEVEL_PAREN);
code = input.compile(o);
- o.ref = new String(ref);
- out = Block(output).compile(o);
- if (this.implicit && !o.ref.erred) {
+ out = Block(output).compile((o.ref = new String(ref), o));
+ if (prog1 === 'cascade' && !o.ref.erred) {
this.carp("unreferred cascadee");
}
if (!level) {
@@ -3901,6 +3960,7 @@ exports.Require = Require = (function(superclass){
};
processItem = function(item){
var ref$, asg, value, main;
+ chain = null;
ref$ = (function(){
var ref$;
switch (false) {
@@ -4195,10 +4255,10 @@ UTILS = {
repeatArray: 'function(arr, n){\n for (var r = []; n > 0; (n >>= 1) && (arr = arr.concat(arr)))\n if (n & 1) r.push.apply(r, arr);\n return r;\n}',
'in': 'function(x, arr){\n var i = -1, l = arr.length >>> 0;\n while (++i < l) if (x === arr[i] && i in arr) return true;\n return false;\n}',
out: 'typeof exports != \'undefined\' && exports || this',
- curry: 'function(f, args){\n return f.length > 1 ? function(){\n var params = args ? args.concat() : [];\n return params.push.apply(params, arguments) < f.length && arguments.length ?\n curry$.call(this, f, params) : f.apply(this, params);\n } : f;\n}',
+ curry: 'function(f, bound){\n var context,\n _curry = function(args) {\n return f.length > 1 ? function(){\n var params = args ? args.concat() : [];\n context = bound ? context || this : this;\n return params.push.apply(params, arguments) <\n f.length && arguments.length ?\n _curry.call(context, params) : f.apply(context, params);\n } : f;\n };\n return _curry();\n}',
compose: 'function(fs){\n return function(){\n var i, args = arguments;\n for (i = fs.length; i > 0; --i) { args = [fs[i-1].apply(this, args)]; }\n return args[0];\n };\n}',
flip: 'function(f){\n return curry$(function (x, y) { return f(y, x); });\n}',
- partialize: 'function(f, args, where){\n return function(){\n var params = slice$.call(arguments), i,\n len = params.length, wlen = where.length,\n ta = args ? args.concat() : [], tw = where ? where.concat() : [];\n for(i = 0; i < len; ++i) { ta[tw[0]] = params[i]; tw.shift(); }\n return len < wlen && len ? partialize$(f, ta, tw) : f.apply(this, ta);\n };\n}',
+ partialize: 'function(f, args, where){\n var context = this;\n return function(){\n var params = slice$.call(arguments), i,\n len = params.length, wlen = where.length,\n ta = args ? args.concat() : [], tw = where ? where.concat() : [];\n for(i = 0; i < len; ++i) { ta[tw[0]] = params[i]; tw.shift(); }\n return len < wlen && len ?\n partialize$.apply(context, [f, ta, tw]) : f.apply(context, ta);\n };\n}',
not: 'function(x){ return !x; }',
deepEq: 'function(x, y, type){\n var toString = {}.toString, hasOwnProperty = {}.hasOwnProperty,\n has = function (obj, key) { return hasOwnProperty.call(obj, key); };\n first = true;\n return eq(x, y, []);\n function eq(a, b, stack) {\n var className, length, size, result, alength, blength, r, key, ref, sizeB;\n if (a == null || b == null) { return a === b; }\n if (a.__placeholder__ || b.__placeholder__) { return true; }\n if (a === b) { return a !== 0 || 1 / a == 1 / b; }\n className = toString.call(a);\n if (toString.call(b) != className) { return false; }\n switch (className) {\n case \'[object String]\': return a == String(b);\n case \'[object Number]\':\n return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);\n case \'[object Date]\':\n case \'[object Boolean]\':\n return +a == +b;\n case \'[object RegExp]\':\n return a.source == b.source &&\n a.global == b.global &&\n a.multiline == b.multiline &&\n a.ignoreCase == b.ignoreCase;\n }\n if (typeof a != \'object\' || typeof b != \'object\') { return false; }\n length = stack.length;\n while (length--) { if (stack[length] == a) { return true; } }\n stack.push(a);\n size = 0;\n result = true;\n if (className == \'[object Array]\') {\n alength = a.length;\n blength = b.length;\n if (first) { \n switch (type) {\n case \'===\': result = alength === blength; break;\n case \'<==\': result = alength <= blength; break;\n case \'<<=\': result = alength < blength; break;\n }\n size = alength;\n first = false;\n } else {\n result = alength === blength;\n size = alength;\n }\n if (result) {\n while (size--) {\n if (!(result = size in a == size in b && eq(a[size], b[size], stack))){ break; }\n }\n }\n } else {\n if (\'constructor\' in a != \'constructor\' in b || a.constructor != b.constructor) {\n return false;\n }\n for (key in a) {\n if (has(a, key)) {\n size++;\n if (!(result = has(b, key) && eq(a[key], b[key], stack))) { break; }\n }\n }\n if (result) {\n sizeB = 0;\n for (key in b) {\n if (has(b, key)) { ++sizeB; }\n }\n if (first) {\n if (type === \'<<=\') {\n result = size < sizeB;\n } else if (type === \'<==\') {\n result = size <= sizeB\n } else {\n result = size === sizeB;\n }\n } else {\n first = false;\n result = size === sizeB;\n }\n }\n }\n stack.pop();\n return result;\n }\n}',
observe: function(){
@@ -4315,6 +4375,14 @@ function util(fn, options){
function entab(code, tab){
return code.replace(/\n/g, '\n' + tab);
}
+function fold(f, memo, xs){
+ var i$, len$, x;
+ for (i$ = 0, len$ = xs.length; i$ < len$; ++i$) {
+ x = xs[i$];
+ memo = f(memo, x);
+ }
+ return memo;
+}
function import$(obj, src){
var own = {}.hasOwnProperty;
for (var key in src) if (own.call(src, key)) obj[key] = src[key];
View
10 lib/command.js
@@ -163,9 +163,6 @@ function compileScript(filename, input, base){
}
LiveScript.emit('parse', t);
t.ast = LiveScript.ast(t.tokens);
- if (o.prelude) {
- t.ast.lines.unshift(LiveScript.ast(LiveScript.tokens('if window?\nthen prelude.installPrelude window\nelse (require \'prelude-ls\').installPrelude global')));
- }
if (o.ast) {
say(o.json
? t.ast.stringify(2)
@@ -178,6 +175,9 @@ function compileScript(filename, input, base){
t.ast.makeReturn();
}
t.output = t.ast.compileRoot(options);
+ if (o.prelude) {
+ t.output = "if (typeof window == 'undefined' || window === null) {\n require('prelude-ls').installPrelude(global);\n} else {\n prelude.installPrelude(window);\n}\n" + t.output;
+ }
if (o.json || o.run) {
LiveScript.emit('run', t);
t.result = LiveScript.run(t.output, options, true);
@@ -405,7 +405,9 @@ function repl(){
};
}
x = vm.runInNewContext(LiveScript.compile(code, ops), replCtx, 'repl');
- x != null && (replCtx._ = x);
+ if (x != null) {
+ replCtx._ = x;
+ }
pp(x);
if (typeof x === 'function') {
say(x);
View
40 lib/grammar.js
@@ -24,8 +24,6 @@ bnf = {
return Chain(Existence($1.unwrap()));
}), o('LET CALL( ArgList OptComma )CALL Block', function(){
return Chain(Call['let']($3, $6));
- }), o('WITH Expression Block', function(){
- return Chain(new Cascade($2, $3));
}), o('[ Expression LoopHeads ]', function(){
return Chain($3[0].makeComprehension($2, $3.slice(1)));
}), o('[ Expression LoopHeads DEDENT ]', function(){
@@ -120,6 +118,14 @@ bnf = {
type: $4,
target: $1
}));
+ }), o('WITH Expression Block', function(){
+ return Chain(Cascade($2, $3, 'with'));
+ }), o('FOR Expression Block', function(){
+ return Chain(new For({
+ source: $2,
+ body: $3,
+ ref: true
+ }));
})
],
List: [
@@ -171,7 +177,7 @@ bnf = {
],
Line: [
o('Expression'), o('Expression Block', function(){
- return new Cascade($1, $2, true);
+ return Cascade($1, $2, 'cascade');
}), o('PARAM( ArgList OptComma )PARAM <- Expression', function(){
return Call.back($2, $6, $5.charAt(1) === '~', $5.length === 3);
}), o('COMMENT', function(){
@@ -183,7 +189,7 @@ bnf = {
})
],
Block: [o('INDENT Lines DEDENT', function(){
- return $2.chomp();
+ return $2;
})],
Cascade: [
o('Chain CASCADE', function(){
@@ -194,7 +200,7 @@ bnf = {
],
Expression: [
o('Cascade Chain', function(){
- return new Cascade($1[0], Block($1.slice(1).concat($2)));
+ return Cascade($1[0], Block($1.slice(1).concat($2)), 'cascade');
}), o('Expression WHERE CALL( ArgList OptComma )CALL', function(){
return Chain(Call.where($4, Block([$1])));
}), o('Expression WHERE Block', function(){
@@ -414,6 +420,30 @@ bnf = {
step: $6,
guard: $8
});
+ }), o('FOR Expression', function(){
+ return new For({
+ source: $2,
+ ref: true
+ });
+ }), o('FOR Expression CASE Expression', function(){
+ return new For({
+ source: $2,
+ ref: true,
+ guard: $4
+ });
+ }), o('FOR Expression BY Expression', function(){
+ return new For({
+ source: $2,
+ ref: true,
+ step: $4
+ });
+ }), o('FOR Expression BY Expression CASE Expression', function(){
+ return new For({
+ source: $2,
+ ref: true,
+ step: $4,
+ guard: $6
+ });
}), o('FOR ID OF Expression', function(){
return new For({
object: true,
View
89 lib/lexer.js
@@ -1,4 +1,4 @@
-var string, TABS, unlines, enlines, enslash, reslash, camelize, character, KEYWORDS_SHARED, KEYWORDS_UNUSED, KEYWORDS, ID, SYMBOL, SPACE, MULTIDENT, SIMPLESTR, JSTOKEN, BSTOKEN, NUMBER, NUMBER_OMIT, REGEX, HEREGEX_OMIT, LASTDENT, INLINEDENT, NONASCII, OPENERS, CLOSERS, INVERSES, i, o, c, CHAIN, ARG, BLOCK_USERS, slice$ = [].slice;
+var string, TABS, unlines, enlines, enslash, reslash, camelize, character, KEYWORDS_SHARED, KEYWORDS_UNUSED, JS_KEYWORDS, LS_KEYWORDS, ID, SYMBOL, SPACE, MULTIDENT, SIMPLESTR, JSTOKEN, BSTOKEN, NUMBER, NUMBER_OMIT, REGEX, HEREGEX_OMIT, LASTDENT, INLINEDENT, NONASCII, OPENERS, CLOSERS, INVERSES, i, o, c, CHAIN, ARG, BLOCK_USERS, slice$ = [].slice;
exports.lex = function(code, options){
return clone$(exports).tokenize(code || '', options || {});
};
@@ -119,7 +119,7 @@ exports.doID = function(code, index){
}
last = this.last;
if (regexMatch[2] || last[0] === 'DOT' || this.adi()) {
- this.token('ID', in$(id, KEYWORDS) ? (ref$ = Object(id), ref$.reserved = true, ref$) : id);
+ this.token('ID', in$(id, JS_KEYWORDS) ? (ref$ = Object(id), ref$.reserved = true, ref$) : id);
if (regexMatch[2]) {
this.token(':', ':');
}
@@ -137,6 +137,11 @@ exports.doID = function(code, index){
case 'undefined':
case 'arguments':
case 'debugger':
+ if (id === 'undefined') {
+ if (typeof console != 'undefined' && console !== null) {
+ console.warn("WARNING on line " + this.line + ": `undefined` as an alias to `void` is deprecated and will be removed in a future LiveScript release. Please use `void` instead.");
+ }
+ }
tag = 'LITERAL';
break;
case 'new':
@@ -159,15 +164,19 @@ exports.doID = function(code, index){
return this.token('LITERAL', id, true).length;
case 'for':
this.seenFor = true;
- // fallthrough
- case 'then':
this.wantBy = false;
break;
+ case 'then':
+ this.seenFor = this.wantBy = false;
+ break;
case 'catch':
case 'function':
id = '';
break;
case 'where':
+ if (typeof console != 'undefined' && console !== null) {
+ console.warn("WARNING on line " + this.line + ": the `where` statement is deprecated and will be removed in a future LiveScript release. Please use `let` or local variables instead.");
+ }
break;
case 'in':
case 'of':
@@ -354,7 +363,7 @@ exports.doID = function(code, index){
return input.length;
};
exports.doNumber = function(code, lastIndex){
- var input, regexMatch, last, radix, num, rnum, ref$;
+ var input, regexMatch, last, radix, num, rnum, bound, ref$;
NUMBER.lastIndex = lastIndex;
if (!(input = (regexMatch = NUMBER.exec(code))[0])) {
return 0;
@@ -366,11 +375,19 @@ exports.doNumber = function(code, lastIndex){
}
if (radix = regexMatch[1]) {
num = parseInt(rnum = regexMatch[2].replace(NUMBER_OMIT, ''), radix);
+ bound = false;
if (radix > 36 || radix < 2) {
- this.carp("invalid number base " + radix + " (with number " + rnum + "), base must be from 2 to 36");
+ if (/[0-9]/.exec(rnum)) {
+ this.carp("invalid number base " + radix + " (with number " + rnum + "),base must be from 2 to 36");
+ } else {
+ bound = true;
+ }
}
if (isNaN(num) || num === parseInt(rnum.slice(0, -1), radix)) {
- this.carp("invalid number " + rnum + " in base " + radix);
+ this.strnum(regexMatch[1]);
+ this.token('DOT', '.~');
+ this.token('ID', regexMatch[2]);
+ return input.length;
}
num += '';
} else {
@@ -553,7 +570,7 @@ exports.doLine = function(code, index){
this.newline();
}
}
- this.wantBy = false;
+ this.seenFor = this.wantBy = false;
return length;
};
exports.doSpace = function(code, lastIndex){
@@ -566,22 +583,19 @@ exports.doSpace = function(code, lastIndex){
};
exports.doCase = function(){
var ref$, ref1$;
+ this.seenFor = false;
if (((ref$ = this.last[0]) == 'ASSIGN' || ref$ == '->' || ref$ == ':') || (this.last[0] === 'INDENT' && ((ref$ = (ref1$ = this.tokens)[ref1$.length - 2][0]) == 'ASSIGN' || ref$ == '->' || ref$ == ':'))) {
this.token('SWITCH', 'switch');
this.line++;
return this.token('CASE', 'case');
}
};
exports.doLiteral = function(code, index){
- var sym, tag, val, ref$, ref1$, arrow, i$, i, t, that, up, this$ = this;
+ var sym, tag, val, ref$, ref1$, arrow, i$, i, t, that, up;
if (!(sym = (SYMBOL.lastIndex = index, SYMBOL).exec(code)[0])) {
return 0;
}
switch (tag = val = sym) {
- case '=>':
- tag = 'THEN';
- this.unline();
- break;
case '|':
tag = 'CASE';
if (this.doCase()) {
@@ -624,7 +638,10 @@ exports.doLiteral = function(code, index){
case '?':
case '!?':
if (this.last[0] === '(') {
- createItFunc();
+ this.token('PARAM(', '(');
+ this.token(')PARAM', ')');
+ this.token('->', '->');
+ this.token('ID', 'it');
} else {
if (this.last.spaced) {
tag = 'LOGIC';
@@ -940,6 +957,11 @@ exports.doLiteral = function(code, index){
val = 'prototype';
tag = 'ID';
break;
+ case '=>':
+ this.unline();
+ this.seenFor = false;
+ tag = 'THEN';
+ break;
default:
switch (val.charAt(0)) {
case '(':
@@ -961,12 +983,6 @@ exports.doLiteral = function(code, index){
if (tag == ',' || tag == 'CASE' || tag == 'PIPE' || tag == 'BACKPIPE' || tag == 'DOT' || tag == 'LOGIC' || tag == 'COMPARE' || tag == 'MATH' || tag == 'POWER' || tag == 'IMPORT' || tag == 'SHIFT' || tag == 'BITWISE') {
this.unline();
}
- function createItFunc(){
- this$.token('PARAM(', '(');
- this$.token(')PARAM', ')');
- this$.token('->', '->');
- return this$.token('ID', 'it');
- }
this.token(tag, val);
return sym.length;
};
@@ -1280,7 +1296,7 @@ function decode(val, lno){
}
val = val.length > 8
? 'ng'
- : Function('return' + val)();
+ : Function('return ' + val)();
val.length === 1 || carp('bad string in range', lno);
return [val.charCodeAt(), true];
}
@@ -1305,6 +1321,14 @@ function firstPass(tokens){
while (token = tokens[++i]) {
tag = token[0], val = token[1], line = token[2];
switch (false) {
+ case !(tag === '!?' || tag === 'LOGIC' && val === '!?'):
+ if (typeof console != 'undefined' && console !== null) {
+ console.warn("WARNING on line " + line + ": the `!?` inexistance operator is deprecated and will be removed in a future LiveScript release. Please use a negated existance instead, eg. change `x!?` to `not x?`. For its use as a logic operator, change `x !? y` to `if x? then y else void`.");
+ }
+ break;
+ case !(tag === 'ASSIGN' && in$(prev[1], LS_KEYWORDS) && tokens[i - 2][0] !== 'DOT'):
+ carp("cannot assign to reserved word \"" + prev[1] + "\"", line);
+ break;
case !(tag === 'DOT' && prev[0] === ']' && tokens[i - 2][0] === '[' && tokens[i - 3][0] === 'DOT'):
tokens.splice(i - 2, 3);
tokens[i - 3][1] = '[]';
@@ -1636,7 +1660,7 @@ function addImplicitBraces(tokens){
}
}
function expandLiterals(tokens){
- var i, token, sig, lno, ref$, fromNum, char, toNum, tochar, byNum, byp, ts, enc, add, i$, n, len$, word, that;
+ var i, fromNum, token, sig, ref$, lno, char, toNum, tochar, byNum, byp, ts, enc, add, i$, n, len$, word, that;
i = 0;
while (token = tokens[++i]) {
switch (token[0]) {
@@ -1649,10 +1673,23 @@ function expandLiterals(tokens){
continue;
}
break;
+ case 'TO':
+ case 'TIL':
+ if (!(tokens[i - 1][0] === '[' && ((tokens[i + 2][0] === ']' && (((ref$ = tokens[i + 1][1].charAt(0)) == '\'' || ref$ == '"') || +tokens[i + 1][1] >= 0)) || (tokens[i + 2][0] === 'BY' && ((ref$ = tokens[i + 3]) != null ? ref$[0] : void 8) === 'STRNUM' && ((ref$ = tokens[i + 4]) != null ? ref$[0] : void 8) === ']')))) {
+ continue;
+ }
+ if (tokens[i + 2][0] === 'BY') {
+ tokens[i + 2][0] = 'RANGE_BY';
+ }
+ token.op = token[1];
+ fromNum = 0;
+ // fallthrough
case 'RANGE':
lno = token[2];
- if (tokens[i - 1][0] === '[' && tokens[i + 1][0] === 'STRNUM' && ((tokens[i + 2][0] === ']' && (((ref$ = tokens[i + 1][1].charAt(0)) == '\'' || ref$ == '"') || +tokens[i + 1][1] >= 0)) || (tokens[i + 2][0] === 'RANGE_BY' && ((ref$ = tokens[i + 3]) != null ? ref$[0] : void 8) === 'STRNUM' && ((ref$ = tokens[i + 4]) != null ? ref$[0] : void 8) === ']'))) {
- ref$ = decode(token[1], lno), fromNum = ref$[0], char = ref$[1];
+ if (fromNum != null || (tokens[i - 1][0] === '[' && tokens[i + 1][0] === 'STRNUM' && ((tokens[i + 2][0] === ']' && (((ref$ = tokens[i + 1][1].charAt(0)) == '\'' || ref$ == '"') || +tokens[i + 1][1] >= 0)) || (tokens[i + 2][0] === 'RANGE_BY' && ((ref$ = tokens[i + 3]) != null ? ref$[0] : void 8) === 'STRNUM' && ((ref$ = tokens[i + 4]) != null ? ref$[0] : void 8) === ']')))) {
+ if (fromNum == null) {
+ ref$ = decode(token[1], lno), fromNum = ref$[0], char = ref$[1];
+ }
ref$ = decode(tokens[i + 1][1], lno), toNum = ref$[0], tochar = ref$[1];
if (toNum == null || char ^ tochar) {
carp('bad "to" in range', lno);
@@ -1689,6 +1726,7 @@ function expandLiterals(tokens){
}
tokens.splice(i + 1, 0, ['TO', token.op, lno]);
}
+ fromNum = null;
break;
case 'WORDS':
ts = [['[', '[', lno = token[2]]];
@@ -1779,7 +1817,8 @@ function indexOfPair(tokens, i){
}
KEYWORDS_SHARED = ['true', 'false', 'null', 'this', 'void', 'super', 'return', 'throw', 'break', 'continue', 'if', 'else', 'for', 'while', 'switch', 'case', 'default', 'try', 'catch', 'finally', 'function', 'class', 'extends', 'implements', 'new', 'do', 'delete', 'typeof', 'in', 'instanceof', 'let', 'with', 'var', 'const', 'import', 'export', 'debugger'];
KEYWORDS_UNUSED = ['enum', 'interface', 'package', 'private', 'protected', 'public', 'static', 'yield'];
-KEYWORDS = KEYWORDS_SHARED.concat(KEYWORDS_UNUSED);
+JS_KEYWORDS = KEYWORDS_SHARED.concat(KEYWORDS_UNUSED);
+LS_KEYWORDS = ['xor', 'match', 'where'];
ID = /((?!\s)[a-z_$\xAA-\uFFDC](?:(?!\s)[\w$\xAA-\uFFDC]|-[a-z])*)([^\n\S]*:(?![:=>]))?|/ig;
SYMBOL = /<:|-?[:\?]>|[-\/^]=|[%+:*]{1,2}=|\.(?:[&\|\^]|<<|>>>?)\.=?|\.{1,3}|\^\^|\+\+\+|-->|~~>|<--|<~~|([-+&|:])\1|%%|&|\([^\n\S]*\)|[-~]>|<[-~]|[!=]==?|!?\~=|@@?|<\[(?:[\s\S]*?\]>)?|<<<<?|<\||[<>]==|<<=|>>=|<<|>>|[<>]\??=?|!\?|\|>|\||=>|\*\*|\^|`|[^\s#]?/g;
SPACE = /[^\n\S]*(?:#.*)?/g;
View
2 lib/livescript.js
@@ -16,7 +16,7 @@ parser.lexer = {
return '';
}
};
-exports.VERSION = '1.1.0';
+exports.VERSION = '1.1.1';
exports.compile = function(code, options){
var e, that;
try {
View
372 lib/parser.js
201 additions, 171 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
13 package.json
@@ -1,6 +1,6 @@
{
"name": "dslivescript",
- "version": "1.1.0-DS",
+ "version": "1.1.1-DS",
"description": "LiveScript is a language which compiles to JavaScript. It has a straightforward mapping to JavaScript and allows you to write expressive code devoid of repetitive boilerplate. While LiveScript adds many features to assist in functional style programming, it also has many improvements for object oriented and imperative programming. DS LiveScript adds eventing operations, making observers first-class objects in the language.",
"keywords": [
"language",
@@ -32,6 +32,17 @@
"LICENSE"
],
"main": "./lib/livescript",
+ "bin": {
+ "livescript": "./bin/livescript",
+ "lsc": "./bin/lsc",
+ "slake": "./bin/slake"
+ },
+ "scripts": {
+ "pretest": "bin/slake build && bin/slake build:parser && bin/slake build",
+ "test": "bin/slake test",
+ "posttest": "git checkout -- lib"
+ },
+ "preferGlobal": true,
"repository": {
"type": "git",
"url": "git://github.com/DavidSouther/LiveScript.git"
View
16 package.ls
@@ -1,5 +1,5 @@
name : \dslivescript
-version : \1.1.0-DS
+version : \1.1.1-DS
description : 'LiveScript is a language which compiles to JavaScript. It has a straightforward mapping to JavaScript and allows you to write expressive code devoid of repetitive boilerplate. While LiveScript adds many features to assist in functional style programming, it also has many improvements for object oriented and imperative programming. DS LiveScript adds eventing operations, making observers first-class objects in the language.'
@@ -26,7 +26,19 @@ files :
\bin
\README.md
\LICENSE
-main: \./lib/livescript
+
+main : \./lib/livescript
+bin :
+ livescript: \./bin/livescript
+ lsc: \./bin/lsc
+ slake: \./bin/slake
+
+scripts:
+ pretest: "bin/slake build && bin/slake build:parser && bin/slake build"
+ test: "bin/slake test"
+ posttest: "git checkout -- lib"
+
+preferGlobal: true
repository: type: \git, url: \git://github.com/DavidSouther/LiveScript.git
View
166 src/ast.ls
@@ -150,8 +150,7 @@
invert: -> Unary \! this, true
invertCheck: ->
- if it.inverted then @invert!
- this
+ if it.inverted then @invert! else this
addElse: (@else) -> this
@@ -164,7 +163,7 @@
base = this.lines.0
base.=then.lines.0 if this.lines.0 instanceof If
items = base.items
- if items.0!? or items.1!?
+ if not items.0? or not items.1?
@carp 'must specify both key and value for object comprehension'
Assign (Chain Var arref).add(Index items.0, \., true), items.1
else Return this
@@ -262,6 +261,7 @@ class exports.Block extends Node
# **Block** does not return its entire body, rather it
# ensures that the final line is returned.
makeReturn: ->
+ @chomp!
if @lines[*-1]?=makeReturn it
--@lines.length if that instanceof Return and not that.it
this
@@ -305,7 +305,7 @@ class exports.Block extends Node
# Compile to a comma-separated list of expressions.
compileExpressions: (o, level) ->
- {lines} = this; i = -1
+ {lines} = @chomp!; i = -1
while lines[++i] then lines.splice i-- 1 if that.comment
lines.push Literal \void unless lines.length
lines.0 <<< {@front}; lines[*-1] <<< {@void}
@@ -447,6 +447,16 @@ class exports.Chain extends Node
children: <[ head tails ]>
add: ->
+ if @tails.length
+ last = @tails[*-1]
+ # optimize `x |> f 1, _` to `f(1, x)`
+ if last instanceof Call
+ and last.partialized?length is 1
+ and it.args.length is 1
+ index = last.partialized.0.head.value # Chain Literal i
+ delete last.partialized
+ last.args[index] = it.args.0 # extract the single arg from pipe call
+ return this
if @head instanceof Existence
{@head, @tails} = Chain @head.it
it.soak = true
@@ -574,8 +584,10 @@ class exports.Chain extends Node
else pre .push t
[partial, ...post] = rest if rest?
@tails = pre
+ context = if pre.length then Chain head, pre[til -1] else Literal \this
return (Chain (Chain Var util \partialize
- .add Call [this; Arr partial.args; Arr partial.partialized]), post).compile o
+ .add Index Key \apply
+ .add Call [context, Arr [this; Arr partial.args; Arr partial.partialized]]), post).compile o
@carp 'invalid callee' if tails.0 instanceof Call and not head.isCallable!
@expandSlice o; @expandBind o; @expandSplat o; @expandStar o
if @splatted-new-args
@@ -955,7 +967,7 @@ class exports.Unary extends Node
show: -> [\@ if @post] + @op
- isCallable: -> @op in <[ do new delete ]> or @it!?
+ isCallable: -> @op in <[ do new delete ]> or not @it?
isArray: -> @it instanceof Arr and @it.items.length
or @it instanceof Chain and @it.isArray!
@@ -981,7 +993,7 @@ class exports.Unary extends Node
function crement then {'++':\in '--':\de}[it] + \crement
compileNode: (o) ->
- return @compileAsFunc o if @it!?
+ return @compileAsFunc o if not @it?
return that if @compileSpread o
{op, it} = this
switch op
@@ -1054,7 +1066,7 @@ class exports.Binary extends Node
op = | logic => that
| op is \= => \?
| _ => \=
- @partial = first!? or second!?
+ @partial = not first? or not second?
if not @partial
if \= is op.charAt op.length-1 and op.charAt(op.length-2) not in <[ = < > ! ]>
return Assign first.unwrap!, second, op
@@ -1248,7 +1260,7 @@ class exports.Binary extends Node
compilePartial: (o) ->
vit = Var \it
switch
- case @first!? and @second!?
+ case not @first? and not @second?
x = Var \x$; y = Var \y$
(Fun [x, y], Block((Binary @op, x, y).invertCheck this), false, true).compile o
case @first?
@@ -1354,7 +1366,7 @@ class exports.Assign extends Node
else if dot-split.length > 1
right.in-class-static = dot-split[til -1].join ''
code = if not o.level and right instanceof While and not right.else and
- (lvar or left.isSimpleAccess!)
+ (lvar or left instanceof Chain and left.isSimpleAccess!)
# Optimize `a = while ...`.
empty = if right.objComp then '{}' else '[]'
"""#{ res = o.scope.temporary \res } = #empty;
@@ -1617,7 +1629,7 @@ class exports.Fun extends Node
if @bound is \this$
if @ctor
scope.assign \this$ 'this instanceof ctor$ ? this : new ctor$'
- body.add Return Literal \this$
+ body.lines.push Return Literal \this$
else if sscope.fun?bound
then @bound = that
else sscope.assign \this$ \this
@@ -1633,10 +1645,13 @@ class exports.Fun extends Node
code += "\n#that\n#tab" if body.compileWithDeclarations o
code += \}
curry-code-check = ~>
- if @curried
- if @hasSplats
+ if @curried and @has-splats
@carp 'cannot curry a function with a variable number of arguments'
- "#{ util \curry }(#code)"
+ if @curried and @params.length > 1
+ "#{ util \curry }" + if @bound or @class-bound
+ "((#code), true)"
+ else
+ "(#code)"
else code
if inLoop then return pscope.assign pscope.temporary(\fn), curry-code-check!
if @returns
@@ -1647,20 +1662,27 @@ class exports.Fun extends Node
if @front and not @statement then "(#code)" else code
compileParams: (scope) ->
- {params, body} = this; names = []; assigns = []
+ {{length}:params, body} = this
+ # Remove trailing placeholders.
+ for p in params by -1
+ break unless p.isEmpty! or p.filler
+ --params.length
for p, i in params
- if p instanceof Splat then splace = i; @hasSplats = true
+ if p instanceof Splat
+ @has-splats = true
+ splace = i
# `(a = x) ->` => `(a ? x) ->`
else if p.op is \=
params[i] = Binary (p.logic or \?), p.left, p.right
# `(a, ...b, c) ->` => `(a) -> [[] ...b, c] = @@`
if splace?
rest = params.splice splace, 9e9
- rest = 0 if not rest.1 and rest.0.it.isEmpty! # ignore trailing `...`
else if @accessor
that.carp 'excess accessor parameter' if params.1
- else unless params.length or @wrapper
+ else unless length or @wrapper
params.0 = Var \it if body.traverseChildren -> it.value is \it or null
+ names = []
+ assigns = []
if params.length
dic = {}
for p in params
@@ -1669,14 +1691,21 @@ class exports.Fun extends Node
if vr.isEmpty!
vr = Var scope.temporary \arg
else if vr not instanceof Var
+ unaries = []
+ while vr instanceof Unary
+ has-unary = true
+ unaries.push vr
+ vr.=it
v = Var delete (vr.it || vr)name || vr.varName! ||
scope.temporary \arg
- assigns.push Assign vr, if df then Binary p.op, v, p.second else v
+ assigns.push Assign vr, switch
+ | df => Binary p.op, v, p.second
+ | has-unary => fold ((x, y) -> y.it = x; y), v, unaries.reverse!
+ | otherwise => v
vr = v
else if df
assigns.push Assign vr, p.second, \=, p.op, true
- names.push name = scope.add vr.value, \arg, p
- p.carp "duplicate parameter \"#name\"" unless dic"#name." .^.= 1
+ names.push scope.add vr.value, \arg, p
if rest
while splace-- then rest.unshift Arr!
assigns.push Assign Arr(rest), Literal \arguments
@@ -1706,21 +1735,26 @@ class exports.Class extends Node
j = 0
while j < node.items.length, j++
prop = node.items[j]
- if prop.key instanceof [Key, Literal]
- if (prop.key instanceof Key and prop.key.name is ctor-name)
- or (prop.key instanceof Literal and prop.key.value is "'#ctor-name'")
- node.carp 'redundant constructor' if ctor
- ctor := prop.val
- node.items.splice j--, 1
- ctor-place := i
- else if prop.val instanceof Fun
- prop.val.meth = prop.key
- if prop.val.bound
- bound-funcs.push prop.key
- prop.val.bound = false
- else if prop.accessor
- for f in prop.val then f.meth = prop.key
+ key = prop.key
+ if (key instanceof Key and key.name is ctor-name)
+ or (key instanceof Literal and key.value is "'#ctor-name'")
+ node.carp 'redundant constructor' if ctor
+ ctor := prop.val
+ node.items.splice j--, 1
+ ctor-place := i
+ continue unless prop.val instanceof Fun or prop.accessor
+ if key.isComplex!
+ key = Var o.scope.temporary \key
+ prop.key = Assign key, prop.key
+ if prop.val.bound
+ bound-funcs.push prop.key
+ prop.val.bound = false
+ # need to know whether bound param of curry$ should be true
+ prop.val.class-bound = true
+ for v in [] ++ prop.val
+ v.meth = key
if node.items.length then Import proto, node else Literal 'void'
+
for node, i in lines
if node instanceof Obj
lines[i] = import-proto-obj node, i
@@ -1737,7 +1771,7 @@ class exports.Class extends Node
it.lines[k] = import-proto-obj child, i
), true
- ctor ||= lines.* = if @sup and @sup instanceof [Fun, Var]
+ ctor ||= lines.* = if @sup
then Fun [] Block Chain(new Super).add Call [Splat Literal \arguments]
else Fun!
unless ctor instanceof Fun
@@ -1944,6 +1978,7 @@ class exports.While extends Node
addObjComp: (@objComp = true) -> this
makeReturn: ->
+ return this if @has-returned
if it
if @objComp
@body = Block @body.makeObjReturn it
@@ -1955,6 +1990,7 @@ class exports.While extends Node
if (@is-comprehension or @in-comprehension) and not last?is-comprehension
@body.makeReturn it
@else?makeReturn it
+ @has-returned = true
else
@res-var = it
@else?makeReturn it
@@ -1984,7 +2020,8 @@ class exports.While extends Node
last = lines?[*-1]
unless (@is-comprehension or @in-comprehension) and not last?is-comprehension
var has-loop
- @traverseChildren !-> if it instanceof While then has-loop := true
+ @traverseChildren !-> if it instanceof Block and it.lines[*-1] instanceof While
+ has-loop := true
if @returns and not @res-var
@res-var = res = o.scope.assign \results$ empty
if @res-var and (last instanceof While or has-loop)
@@ -2051,6 +2088,7 @@ class exports.For extends While
then "#idx #{ '<>'charAt pvar < 0 }#eq #tvar"
else "#pvar < 0 ? #idx >#eq #tvar : #idx <#eq #tvar"
else
+ @item = Var o.scope.temporary \x if @ref
if @item or @object and @own
[svar, srcPart] = @source.compileLoopReference o, \ref, not @object
svar is srcPart or temps.push svar
@@ -2088,6 +2126,7 @@ class exports.For extends While
if @item and not @item.isEmpty!
head += \\n + o.indent +
Assign(@item, JS "#svar[#idx]")compile(o, LEVEL_TOP) + \;
+ o.ref = @item.value if @ref
body = @compileBody o
head += \\n + @tab if (@item or (@index and not @object)) and \} is body.charAt 0
head + body
@@ -2167,7 +2206,7 @@ class exports.Switch extends Node
isCallable: ->
for c in @cases when not c.isCallable! then return false
- @default?isCallable!
+ if @default then @default.isCallable! else true
getJump: (ctx or {}) ->
ctx.break = true
@@ -2321,35 +2360,37 @@ class exports.Label extends Node
#### Cascade
class exports.Cascade extends Node
- (@input, @output, @implicit) ->
+ (@input, @output, @prog1) ~>
+
+ show: -> @prog1
children: <[ input output ]>
terminator: ''
- ::delegate <[ isCallable isArray isString isRegex ]> -> @output[it]!
+ ::delegate <[ isCallable isArray isString isRegex ]> ->
+ @[if @prog1 then \input else \output][it]!
getJump: -> @output.getJump it
makeReturn: (@ret) -> this
compileNode: ({level}:o) ->
- {input, output, ref} = this
- if \ret of this or level and not @void
- output.add (Literal \..) <<< cascadee: true
+ {input, output, prog1, ref} = this
+ if prog1 and (\ret of this or level and not @void)
+ output.add (Literal(\..) <<< {+cascadee})
if \ret of this
output.=makeReturn @ret
if ref
- then output = Assign Arr!, output
- else @temps = [ref = o.scope.temporary \x]
+ then prog1 or output = Assign Var(ref), output
+ else ref = o.scope.temporary \x
if input instanceof Cascade
then input <<< {ref}
- else input = Assign JS(ref), input
+ else input &&= Assign Var(ref), input
o.level &&= LEVEL_PAREN
code = input.compile o
- o.ref = new String ref
- out = Block output .compile o
- @carp "unreferred cascadee" if @implicit and not o.ref.erred
+ out = Block output .compile o <<< ref: new String ref
+ @carp "unreferred cascadee" if prog1 is \cascade and not o.ref.erred
return "#code#{input.terminator}\n#out" unless level
code += ", #out"
if level > LEVEL_PAREN then "(#code)" else code
@@ -2391,6 +2432,7 @@ class exports.Require extends Node
get-value item.head
| otherwise => item
process-item = (item) ->
+ chain := null
[asg, value] = switch
| item instanceof Prop => [get-value item.key; item.val]
| item instanceof Chain =>
@@ -2645,12 +2687,18 @@ UTILS =
out: '''typeof exports != 'undefined' && exports || this'''
- curry: '''function(f, args){
- return f.length > 1 ? function(){
- var params = args ? args.concat() : [];
- return params.push.apply(params, arguments) < f.length && arguments.length ?
- curry$.call(this, f, params) : f.apply(this, params);
- } : f;
+ curry: '''function(f, bound){
+ var context,
+ _curry = function(args) {
+ return f.length > 1 ? function(){
+ var params = args ? args.concat() : [];
+ context = bound ? context || this : this;
+ return params.push.apply(params, arguments) <
+ f.length && arguments.length ?
+ _curry.call(context, params) : f.apply(context, params);
+ } : f;
+ };
+ return _curry();
}'''
compose: '''function(fs){
@@ -2666,12 +2714,14 @@ UTILS =
}'''
partialize: '''function(f, args, where){
+ var context = this;
return function(){
var params = slice$.call(arguments), i,
len = params.length, wlen = where.length,
ta = args ? args.concat() : [], tw = where ? where.concat() : [];
for(i = 0; i < len; ++i) { ta[tw[0]] = params[i]; tw.shift(); }
- return len < wlen && len ? partialize$(f, ta, tw) : f.apply(this, ta);
+ return len < wlen && len ?
+ partialize$.apply(context, [f, ta, tw]) : f.apply(context, ta);
};
}'''
not: '''function(x){ return !x; }'''
@@ -2862,3 +2912,7 @@ function util fn, options
return fn+\$
function entab code, tab then code.replace /\n/g \\n + tab
+
+function fold f, memo, xs
+ for x in xs then memo = f memo, x
+ memo
View
8 src/command.ls
@@ -113,17 +113,15 @@ switch
throw
LiveScript.emit \parse t
t.ast = LiveScript.ast t.tokens
- if o.prelude
- t.ast.lines.unshift LiveScript.ast LiveScript.tokens '''if window?
- then prelude.installPrelude window
- else (require 'prelude-ls').installPrelude global'''
if o.ast
say if o.json then t.ast.stringify 2 else ''trim.call t.ast
throw
LiveScript.emit \compile t
options.bare ||= o.json or o.run
t.ast.makeReturn! if o.json or o.run and o.print
t.output = t.ast.compileRoot options
+ if o.prelude
+ t.output = "if (typeof window == 'undefined' || window === null) {\n require('prelude-ls').installPrelude(global);\n} else {\n prelude.installPrelude(window);\n}\n#{t.output}"
if o.json or o.run
LiveScript.emit \run t
t.result = LiveScript.run t.output, options, true
@@ -265,7 +263,7 @@ switch
ops = {\eval, +bare, saveScope:LiveScript}
ops = {+bare} if code.match(/^\s*!?function/)
x = vm.runInNewContext LiveScript.compile(code, ops), repl-ctx, \repl
- x !? repl-ctx <<< {_:x}
+ repl-ctx <<< {_:x} if x?
pp x
say x if typeof x is \function
catch then say e
View
21 src/grammar.ls
@@ -68,8 +68,6 @@ bnf =
o 'LET CALL( ArgList OptComma )CALL Block' -> Chain Call.let $3, $6
- o 'WITH Expression Block' -> Chain new Cascade $2, $3
-
o '[ Expression LoopHeads ]' -> Chain $3.0.makeComprehension $2, $3.slice 1
o '[ Expression LoopHeads DEDENT ]' -> Chain $3.0.makeComprehension $2, $3.slice 1
o '{ [ ArgList OptComma ] LoopHeads }'
@@ -124,6 +122,9 @@ bnf =
o 'Chain DOT [ TO ]'
, -> Chain Slice type: $4, target: $1
+ o 'WITH Expression Block' -> Chain Cascade $2, $3, \with
+ o 'FOR Expression Block' -> Chain new For source: $2, body: $3, ref: true
+
# An array or object
List:
o '[ ArgList OptComma ]' -> L Arr $2
@@ -170,7 +171,7 @@ bnf =
o \Expression
# Cascade without `with`
- o 'Expression Block' -> new Cascade $1, $2, true
+ o 'Expression Block' -> Cascade $1, $2, \cascade
o 'PARAM( ArgList OptComma )PARAM <- Expression'
, -> Call.back $2, $6, $5.charAt(1) is \~, $5.length is 3
@@ -185,7 +186,7 @@ bnf =
# An indented block of expressions.
# Note that [Lexer](#lexer) rewrites some single-line forms into blocks.
Block:
- o 'INDENT Lines DEDENT' -> $2.chomp!
+ o 'INDENT Lines DEDENT' -> $2
...
Cascade:
@@ -195,7 +196,8 @@ bnf =
# All the different types of expressions in our language.
Expression:
o 'Cascade Chain'
- , -> new Cascade $1.0, Block $1.slice(1) ++ $2
+ , -> Cascade $1.0, (Block $1.slice(1) ++ $2), \cascade
+
o 'Expression WHERE CALL( ArgList OptComma )CALL' -> Chain Call.where $4, Block [$1]
o 'Expression WHERE Block' -> Chain Call.where $3.lines, Block [$1]
@@ -369,6 +371,15 @@ bnf =
o 'FOR Chain IN Expression BY Expression CASE Expression'
, -> new For item: $2.unwrap!, index: $3, source: $4, step: $6, guard: $8
+ o 'FOR Expression'
+ , -> new For source: $2, ref: true
+ o 'FOR Expression CASE Expression'
+ , -> new For source: $2, ref: true, guard: $4
+ o 'FOR Expression BY Expression'
+ , -> new For source: $2, ref: true, step: $4
+ o 'FOR Expression BY Expression CASE Expression'
+ , -> new For source: $2, ref: true, step: $4, guard: $6
+
o 'FOR ID OF Expression'
, -> new For {+object, index: $2, source: $4}
o 'FOR ID OF Expression CASE Expression'
View
90 src/lexer.ls
@@ -114,21 +114,24 @@ exports import
{last} = this
# `id:_` `_.id` `@id`
if regex-match.2 or last.0 is \DOT or @adi!
- @token \ID if id in KEYWORDS then Object(id) <<< {+reserved} else id
+ @token \ID if id in JS_KEYWORDS then Object(id) <<< {+reserved} else id
@token \: \: if regex-match.2
return input.length
# keywords
switch id
case <[ true false on off yes no null void undefined arguments debugger ]>
+ if id is \undefined
+ console?warn "WARNING on line #{@line}: `undefined` as an alias to `void` is deprecated and will be removed in a future LiveScript release. Please use `void` instead."
tag = \LITERAL
case \new \do \typeof \delete then tag = \UNARY
case \return \throw then tag = \HURL
case \break \continue then tag = \JUMP
case \this \eval \super then return @token(\LITERAL id, true)length
- case \for then @seenFor = true; fallthrough
- case \then then @wantBy = false
+ case \for then @seenFor = true; @wantBy = false
+ case \then then @seenFor = @wantBy = false
case \catch \function then id = ''
- case \where then
+ case \where
+ console?warn "WARNING on line #{@line}: the `where` statement is deprecated and will be removed in a future LiveScript release. Please use `let` or local variables instead."
case \in \of
if @seenFor
@seenFor = false
@@ -238,11 +241,18 @@ exports import
return regex-match.4.length
if radix = regex-match.1
num = parseInt rnum = regex-match.2.replace(NUMBER_OMIT, ''), radix
+ bound = false
if radix > 36 or radix < 2
- @carp "invalid number base #radix (with number #rnum),
- base must be from 2 to 36"
+ if rnum is /[0-9]/
+ @carp "invalid number base #radix (with number #rnum),
+ base must be from 2 to 36"
+ else
+ bound = true
if isNaN num or num is parseInt rnum.slice(0 -1), radix
- @carp "invalid number #rnum in base #radix"
+ @strnum regex-match.1
+ @token \DOT \.~
+ @token \ID regex-match.2
+ return input.length
num += ''
else
num = (regex-match.3 or input)replace NUMBER_OMIT, ''
@@ -385,7 +395,7 @@ exports import
IN OF TO BY FROM EXTENDS IMPLEMENTS ]>
return length
if delta then @indent delta else @newline!
- @wantBy = false
+ @seenFor = @wantBy = false
length
# Consumes non-newline whitespaces and/or a line comment.
@@ -395,6 +405,7 @@ exports import
# Used from both doLiteral (|) and doID (case): adds swtich if required
doCase: ->
+ @seenFor = false
if @last.0 in <[ ASSIGN -> : ]>
or (@last.0 is \INDENT and @tokens[*-2].0 in <[ ASSIGN -> : ]>)
@token \SWITCH \switch
@@ -409,7 +420,6 @@ exports import
doLiteral: (code, index) ->
return 0 unless sym = (SYMBOL <<< lastIndex: index)exec(code)0
switch tag = val = sym
- case \=> then tag = \THEN; @unline!
case \|
tag = \CASE
return sym.length if @doCase!
@@ -423,9 +433,13 @@ exports import
case \^^ then tag = \CLONE
case \** \^ then tag = \POWER
case \? \!?
- if @last.0 is \(
- then create-it-func!
- else tag = \LOGIC if @last.spaced
+ if @last.0 is \(
+ @token \PARAM( \(
+ @token \)PARAM \)
+ @token \-> \->
+ @token \ID \it
+ else
+ tag = \LOGIC if @last.spaced
case \/ \% \%% then tag = \MATH
case \+++
console?warn "WARNING on line #{ @line }: the `+++` concat operator is deprecated and will be removed in a future LiveScript release. Please use a spaced `++` for concatenation instead."
@@ -589,25 +603,25 @@ exports import
@adi!
val = \prototype
tag = \ID
+ case \=>
+ @unline!
+ @seenFor = false
+ tag = \THEN
default switch val.charAt 0
case \( then @token \CALL( \(; tag = \)CALL; val = \)
case \<
@carp 'unterminated words' if val.length < 4
@token \WORDS, val.slice(2, -2), @adi!
return val.length
- if tag in <[ +- COMPARE LOGIC MATH POWER SHIFT BITWISE CONCAT
+ if tag in <[ +- COMPARE LOGIC MATH POWER SHIFT BITWISE CONCAT
COMPOSE RELATION PIPE BACKPIPE IMPORT ]> and @last.0 is \(
tag = if tag is \BACKPIPE then \BIOPBP else \BIOP
- @unline! if tag in <[ , CASE PIPE BACKPIPE DOT LOGIC COMPARE
+ @unline! if tag in <[ , CASE PIPE BACKPIPE DOT LOGIC COMPARE
MATH POWER IMPORT SHIFT BITWISE ]>
- ~function create-it-func
- @token \PARAM( \(
- @token \)PARAM \)
- @token \-> \->
- @token \ID \it
@token tag, val
sym.length
+
#### Token Manipulators
# Adds a token to the results,
@@ -852,12 +866,12 @@ function lchomp then it.slice 1 + it.lastIndexOf \\n 0
function decode val, lno
return [+val] unless isNaN val
- val = if val.length > 8 then \ng else do Function \return + val
+ val = if val.length > 8 then \ng else do Function 'return ' + val
val.length is 1 or carp 'bad string in range' lno
[val.charCodeAt!, true]
function uxxxx then \"\\u + (\000 + it.toString 16)slice(-4) + \"
-character = if JSON!? then uxxxx else ->
+character = if not JSON? then uxxxx else ->
switch it | 0x2028 0x2029 => uxxxx it
default JSON.stringify String.fromCharCode it
@@ -876,6 +890,10 @@ character = if JSON!? then uxxxx else ->
while token = tokens[++i]
[tag, val, line] = token
switch
+ case tag is \!? or tag is \LOGIC and val is \!?
+ console?warn "WARNING on line #line: the `!?` inexistance operator is deprecated and will be removed in a future LiveScript release. Please use a negated existance instead, eg. change `x!?` to `not x?`. For its use as a logic operator, change `x !? y` to `if x? then y else void`."
+ case tag is \ASSIGN and prev.1 in LS_KEYWORDS and tokens[i-2].0 isnt \DOT
+ carp "cannot assign to reserved word \"#{prev.1}\"" line
case tag is \DOT and prev.0 is \] and tokens[i-2].0 is \[ and tokens[i-3].0 is \DOT
tokens.splice i-2, 3
tokens[i-3].1 = '[]'
@@ -1090,26 +1108,43 @@ character = if JSON!? then uxxxx else ->
# - Insert `, ` after each non-callable token facing an argument token.
!function expandLiterals tokens
i = 0
+ var fromNum
while token = tokens[++i]
switch token.0
case \STRNUM
if ~'-+'indexOf sig = token.1.charAt 0
token.1.=slice 1
tokens.splice i++ 0 [\+- sig, token.2]
continue if token.callable
+ case \TO \TIL
+ unless tokens[i-1]0 is \[
+ and ((tokens[i+2]0 is \]
+ and (tokens[i+1]1.charAt(0) in [\' \"]
+ or +tokens[i+1]1 >= 0))
+ or (tokens[i+2]0 is \BY
+ and tokens[i+3]?0 is \STRNUM
+ and tokens[i+4]?0 is \]))
+ continue
+
+ if tokens[i+2]0 is \BY
+ tokens[i+2]0 = \RANGE_BY
+ token.op = token.1
+ fromNum = 0
+ fallthrough
case \RANGE
lno = token.2
- if tokens[i-1]0 is \[
+ if fromNum? or (tokens[i-1]0 is \[
and tokens[i+1]0 is \STRNUM
and ((tokens[i+2]0 is \]
and (tokens[i+1]1.charAt(0) in [\' \"]
or +tokens[i+1]1 >= 0))
or (tokens[i+2]0 is \RANGE_BY
and tokens[i+3]?0 is \STRNUM
- and tokens[i+4]?0 is \]))
- [fromNum, char] = decode token.1, lno
+ and tokens[i+4]?0 is \])))
+ unless fromNum?
+ [fromNum, char] = decode token.1, lno
[toNum, tochar] = decode tokens[i+1].1, lno
- carp 'bad "to" in range' lno if toNum!? or char .^. tochar
+ carp 'bad "to" in range' lno if not toNum? or char .^. tochar
byNum = 1
if byp = tokens[i+2]?0 is \RANGE_BY
carp 'bad "by" in range' tokens[i+2]2 unless byNum = +tokens[i+3]?1
@@ -1132,6 +1167,7 @@ character = if JSON!? then uxxxx else ->
if tokens[i+2]?0 is \RANGE_BY
tokens.splice i+2, 1, [\BY \by lno]
tokens.splice i+1, 0, [\TO, token.op, lno]
+ fromNum = null
case \WORDS
ts = [[\[ \[ lno = token.2]]
for word in token.1.match /\S+/g or ''
@@ -1193,7 +1229,9 @@ KEYWORDS_SHARED = <[
KEYWORDS_UNUSED =
<[ enum interface package private protected public static yield ]>
-KEYWORDS = KEYWORDS_SHARED ++ KEYWORDS_UNUSED
+JS_KEYWORDS = KEYWORDS_SHARED ++ KEYWORDS_UNUSED
+
+LS_KEYWORDS = <[ xor match where ]>
##### Regexes
# Some of these are given `g` flag and made sure to match empty string
View
2 src/livescript.ls
@@ -15,7 +15,7 @@ parser import
upcomingInput : -> ''
exports import
- VERSION: \1.1.0
+ VERSION: \1.1.1
# Compiles a string of LiveScript code to JavaScript.
compile: (code, options) ->