Refactor `Literal` into several subtypes

Previously, the parser created `Literal` nodes for many things. This resulted in
information loss. Instead of being able to check the node type, we had to use
regexes to tell the different types of `Literal`s apart. That was a bit like
parsing literals twice: Once in the lexer, and once (or more) in the compiler.
It also caused problems, such as `` `this` `` and `this` being indistinguishable
(fixes #2009).

Instead returning `new Literal` in the grammar, subtypes of it are now returned
instead, such as `NumberLiteral`, `StringLiteral` and `IdentifierLiteral`. `new
Literal` by itself is only used to represent code chunks that fit no category.
(While mentioning `NumberLiteral`, there's also `InfinityLiteral` now, which is
a subtype of `NumberLiteral`.)

`StringWithInterpolations` has been added as a subtype of `Parens`, and
`RegexWithInterpolations` as a subtype of `Call`. This makes it easier for other
programs to make use of CoffeeScript's "AST" (nodes). For example, it is now
possible to distinguish between `"a #{b} c"` and `"a " + b + " c"`. Fixes #4192.

`SuperCall` has been added as a subtype of `Call`.

Note, though, that some information is still lost, especially in the lexer. For
example, there is no way to distinguish a heredoc from a regular string, or a
heregex without interpolations from a regular regex. Binary and octal number
literals are indistinguishable from hexadecimal literals.

After the new subtypes were added, they were taken advantage of, removing most
regexes in nodes.coffee. `SIMPLENUM` (which matches non-hex integers) had to be
kept, though, because such numbers need special handling in JavaScript (for
example in `1..toString()`).

An especially nice hack to get rid of was using `new String()` for the token
value for reserved identifiers (to be able to set a property on them which could
survive through the parser). Now it's a good old regular string.

In range literals, slices, splices and for loop steps when number literals
are involved, CoffeeScript can do some optimizations, such as precomputing the
value of, say, `5 - 3` (outputting `2` instead of `5 - 3` literally). As a side
bonus, this now also works with hexadecimal number literals, such as `0x02`.

Finally, this also improves the output of `coffee --nodes`:

    # Before:
    $ bin/coffee -ne 'while true
      "#{a}"
      break'
    Block
      While
        Value
          Bool
        Block
          Value
            Parens
              Block
                Op +
                  Value """"
                  Value
                    Parens
                      Block
                        Value "a" "break"

    # After:
    $ bin/coffee -ne 'while true
      "#{a}"
      break'
    Block
      While
        Value BooleanLiteral: true
        Block
          Value
            StringWithInterpolations
              Block
                Op +
                  Value StringLiteral: ""
                  Value
                    Parens
                      Block
                        Value IdentifierLiteral: a
          StatementLiteral: break
This commit is contained in:
Simon Lydell 2016-01-31 20:24:31 +01:00
parent 34b4311544
commit 021d2e4376
15 changed files with 778 additions and 571 deletions

View File

@ -299,7 +299,7 @@
return 'end of input';
case errorTag !== 'INDENT' && errorTag !== 'OUTDENT':
return 'indentation';
case errorTag !== 'IDENTIFIER' && errorTag !== 'NUMBER' && errorTag !== 'STRING' && errorTag !== 'STRING_START' && errorTag !== 'REGEX' && errorTag !== 'REGEX_START':
case errorTag !== 'IDENTIFIER' && errorTag !== 'NUMBER' && errorTag !== 'INFINITY' && errorTag !== 'STRING' && errorTag !== 'STRING_START' && errorTag !== 'REGEX' && errorTag !== 'REGEX_START':
return errorTag.replace(/_START$/, '').toLowerCase();
default:
return helpers.nameWhitespaceCharacter(errorText);

View File

@ -44,7 +44,7 @@
Line: [o('Expression'), o('Statement'), o('YieldReturn')],
Statement: [
o('Return'), o('Comment'), o('STATEMENT', function() {
return new Literal($1);
return new StatementLiteral($1);
})
],
Expression: [o('Value'), o('Invocation'), o('Code'), o('Operation'), o('Assign'), o('If'), o('Try'), o('While'), o('For'), o('Switch'), o('Class'), o('Throw'), o('Yield')],
@ -66,39 +66,39 @@
],
Identifier: [
o('IDENTIFIER', function() {
return new Literal($1);
return new IdentifierLiteral($1);
})
],
AlphaNumeric: [
o('NUMBER', function() {
return new Literal($1);
return new NumberLiteral($1);
}), o('INFINITY', function() {
return new InfinityLiteral($1);
}), o('String')
],
String: [
o('STRING', function() {
return new Literal($1);
return new StringLiteral($1);
}), o('STRING_START Body STRING_END', function() {
return new Parens($2);
return new StringWithInterpolations($2);
})
],
Regex: [
o('REGEX', function() {
return new Literal($1);
return new RegexLiteral($1);
}), o('REGEX_START Invocation REGEX_END', function() {
return $2;
return new RegexWithInterpolations($2.args);
})
],
Literal: [
o('AlphaNumeric'), o('JS', function() {
return new Literal($1);
}), o('Regex'), o('DEBUGGER', function() {
return new Literal($1);
}), o('UNDEFINED', function() {
return new Undefined;
return new PassthroughLiteral($1);
}), o('Regex'), o('UNDEFINED', function() {
return new UndefinedLiteral;
}), o('NULL', function() {
return new Null;
return new NullLiteral;
}), o('BOOL', function() {
return new Bool($1);
return new BooleanLiteral($1);
})
],
Assign: [
@ -228,11 +228,11 @@
}), o('?. Identifier', function() {
return new Access($2, 'soak');
}), o(':: Identifier', function() {
return [LOC(1)(new Access(new Literal('prototype'))), LOC(2)(new Access($2))];
return [LOC(1)(new Access(new IdentifierLiteral('prototype'))), LOC(2)(new Access($2))];
}), o('?:: Identifier', function() {
return [LOC(1)(new Access(new Literal('prototype'), 'soak')), LOC(2)(new Access($2))];
return [LOC(1)(new Access(new IdentifierLiteral('prototype'), 'soak')), LOC(2)(new Access($2))];
}), o('::', function() {
return new Access(new Literal('prototype'));
return new Access(new IdentifierLiteral('prototype'));
}), o('Index')
],
Index: [
@ -293,10 +293,13 @@
return new Call($1, $3, $2);
}), o('Invocation OptFuncExist Arguments', function() {
return new Call($1, $3, $2);
}), o('SUPER', function() {
return new Call('super', [new Splat(new Literal('arguments'))]);
}), o('Super')
],
Super: [
o('SUPER', function() {
return new SuperCall;
}), o('SUPER Arguments', function() {
return new Call('super', $2);
return new SuperCall($2);
})
],
OptFuncExist: [
@ -315,14 +318,14 @@
],
This: [
o('THIS', function() {
return new Value(new Literal('this'));
return new Value(new ThisLiteral);
}), o('@', function() {
return new Value(new Literal('this'));
return new Value(new ThisLiteral);
})
],
ThisProperty: [
o('@ Identifier', function() {
return new Value(LOC(1)(new Literal('this')), [LOC(2)(new Access($2))], 'this');
return new Value(LOC(1)(new ThisLiteral), [LOC(2)(new Access($2))], 'this');
})
],
Array: [
@ -441,9 +444,9 @@
],
Loop: [
o('LOOP Block', function() {
return new While(LOC(1)(new Literal('true'))).addBody($2);
return new While(LOC(1)(new BooleanLiteral('true'))).addBody($2);
}), o('LOOP Expression', function() {
return new While(LOC(1)(new Literal('true'))).addBody(LOC(2)(Block.wrap([$2])));
return new While(LOC(1)(new BooleanLiteral('true'))).addBody(LOC(2)(Block.wrap([$2])));
})
],
For: [

View File

@ -84,9 +84,10 @@
};
exports.some = (ref = Array.prototype.some) != null ? ref : function(fn) {
var e, i, len1;
for (i = 0, len1 = this.length; i < len1; i++) {
e = this[i];
var e, i, len1, ref1;
ref1 = this;
for (i = 0, len1 = ref1.length; i < len1; i++) {
e = ref1[i];
if (fn(e)) {
return true;
}

View File

@ -109,8 +109,6 @@
if (indexOf.call(JS_FORBIDDEN, id) >= 0) {
if (forcedIdentifier) {
tag = 'IDENTIFIER';
id = new String(id);
id.reserved = true;
} else if (indexOf.call(RESERVED, id) >= 0) {
this.error("reserved word '" + id + "'", {
length: id.length
@ -137,6 +135,7 @@
return 'BOOL';
case 'break':
case 'continue':
case 'debugger':
return 'STATEMENT';
default:
return tag;
@ -159,7 +158,7 @@
};
Lexer.prototype.numberToken = function() {
var binaryLiteral, lexedLength, match, number, octalLiteral;
var binaryLiteral, lexedLength, match, number, numberValue, octalLiteral, tag;
if (!(match = NUMBER.exec(this.chunk))) {
return 0;
}
@ -183,12 +182,16 @@
});
}
if (octalLiteral = /^0o([0-7]+)/.exec(number)) {
number = '0x' + parseInt(octalLiteral[1], 8).toString(16);
numberValue = parseInt(octalLiteral[1], 8);
number = "0x" + (numberValue.toString(16));
} else if (binaryLiteral = /^0b([01]+)/.exec(number)) {
numberValue = parseInt(binaryLiteral[1], 2);
number = "0x" + (numberValue.toString(16));
} else {
numberValue = parseFloat(number);
}
if (binaryLiteral = /^0b([01]+)/.exec(number)) {
number = '0x' + parseInt(binaryLiteral[1], 2).toString(16);
}
this.token('NUMBER', number, 0, lexedLength);
tag = numberValue === Infinity ? 'INFINITY' : 'NUMBER';
this.token(tag, number, 0, lexedLength);
return lexedLength;
};
@ -504,7 +507,7 @@
tag = value;
ref2 = this.tokens, prev = ref2[ref2.length - 1];
if (value === '=' && prev) {
if (!prev[1].reserved && (ref3 = prev[1], indexOf.call(JS_FORBIDDEN, ref3) >= 0)) {
if (prev.variable && (ref3 = prev[1], indexOf.call(JS_FORBIDDEN, ref3) >= 0)) {
if (prev.origin) {
prev = prev.origin;
}
@ -920,6 +923,8 @@
exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED;
exports.JS_FORBIDDEN = JS_FORBIDDEN;
BOM = 65279;
IDENTIFIER = /^(?!\d)((?:(?!\s)[$\w\x7f-\uffff])+)([^\n\S]*:(?!:))?/;
@ -1000,7 +1005,7 @@
CALLABLE = ['IDENTIFIER', ')', ']', '?', '@', 'THIS', 'SUPER'];
INDEXABLE = CALLABLE.concat(['NUMBER', 'STRING', 'STRING_END', 'REGEX', 'REGEX_END', 'BOOL', 'NULL', 'UNDEFINED', '}', '::']);
INDEXABLE = CALLABLE.concat(['NUMBER', 'INFINITY', 'STRING', 'STRING_END', 'REGEX', 'REGEX_END', 'BOOL', 'NULL', 'UNDEFINED', '}', '::']);
NOT_REGEX = INDEXABLE.concat(['++', '--']);

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.10.0
(function() {
var Access, Arr, Assign, Base, Block, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, Extends, For, HEXNUM, IDENTIFIER, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, NEGATE, NO, NUMBER, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, locationDataToString, merge, multident, parseNum, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility,
var Access, Arr, Assign, Base, Block, BooleanLiteral, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, Extends, For, IdentifierLiteral, If, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, NEGATE, NO, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, RESERVED, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, SuperCall, Switch, TAB, THIS, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, locationDataToString, merge, multident, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility,
extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
@ -10,7 +10,7 @@
Scope = require('./scope').Scope;
ref1 = require('./lexer'), RESERVED = ref1.RESERVED, STRICT_PROSCRIBED = ref1.STRICT_PROSCRIBED;
ref1 = require('./lexer'), RESERVED = ref1.RESERVED, STRICT_PROSCRIBED = ref1.STRICT_PROSCRIBED, JS_FORBIDDEN = ref1.JS_FORBIDDEN;
ref2 = require('./helpers'), compact = ref2.compact, flatten = ref2.flatten, extend = ref2.extend, merge = ref2.merge, del = ref2.del, starts = ref2.starts, ends = ref2.ends, some = ref2.some, addLocationDataFn = ref2.addLocationDataFn, locationDataToString = ref2.locationDataToString, throwSyntaxError = ref2.throwSyntaxError;
@ -95,14 +95,14 @@
func = new Code([], Block.wrap([this]));
args = [];
if ((argumentsNode = this.contains(isLiteralArguments)) || this.contains(isLiteralThis)) {
args = [new Literal('this')];
args = [new ThisLiteral];
if (argumentsNode) {
meth = 'apply';
args.push(new Literal('arguments'));
args.push(new IdentifierLiteral('arguments'));
} else {
meth = 'call';
}
func = new Value(func, [new Access(new Literal(meth))]);
func = new Value(func, [new Access(new IdentifierLiteral(meth))]);
}
parts = (new Call(func, args)).compileNode(o);
if (func.isGenerator || ((ref3 = func.base) != null ? ref3.isGenerator : void 0)) {
@ -116,7 +116,7 @@
var complex, ref, sub;
complex = isComplex != null ? isComplex(this) : this.isComplex();
if (complex) {
ref = new Literal(o.scope.freeVariable('ref'));
ref = new IdentifierLiteral(o.scope.freeVariable('ref'));
sub = new Assign(ref, this);
if (level) {
return [sub.compileToFragments(o, level), [this.makeCode(ref.value)]];
@ -240,6 +240,8 @@
Base.prototype.isAssignable = NO;
Base.prototype.isNumber = NO;
Base.prototype.unwrap = THIS;
Base.prototype.unfoldSoak = NO;
@ -522,30 +524,111 @@
this.value = value1;
}
Literal.prototype.makeReturn = function() {
if (this.isStatement()) {
return this;
} else {
return Literal.__super__.makeReturn.apply(this, arguments);
}
};
Literal.prototype.isAssignable = function() {
return IDENTIFIER.test(this.value);
};
Literal.prototype.isStatement = function() {
var ref3;
return (ref3 = this.value) === 'break' || ref3 === 'continue' || ref3 === 'debugger';
};
Literal.prototype.isComplex = NO;
Literal.prototype.assigns = function(name) {
return name === this.value;
};
Literal.prototype.jumps = function(o) {
Literal.prototype.compileNode = function(o) {
return [this.makeCode(this.value)];
};
Literal.prototype.toString = function() {
return " " + (this.isStatement() ? Literal.__super__.toString.apply(this, arguments) : this.constructor.name) + ": " + this.value;
};
return Literal;
})(Base);
exports.NumberLiteral = NumberLiteral = (function(superClass1) {
extend1(NumberLiteral, superClass1);
function NumberLiteral() {
return NumberLiteral.__super__.constructor.apply(this, arguments);
}
return NumberLiteral;
})(Literal);
exports.InfinityLiteral = InfinityLiteral = (function(superClass1) {
extend1(InfinityLiteral, superClass1);
function InfinityLiteral() {
return InfinityLiteral.__super__.constructor.apply(this, arguments);
}
InfinityLiteral.prototype.compileNode = function() {
return [this.makeCode('Infinity')];
};
return InfinityLiteral;
})(NumberLiteral);
exports.StringLiteral = StringLiteral = (function(superClass1) {
extend1(StringLiteral, superClass1);
function StringLiteral() {
return StringLiteral.__super__.constructor.apply(this, arguments);
}
return StringLiteral;
})(Literal);
exports.RegexLiteral = RegexLiteral = (function(superClass1) {
extend1(RegexLiteral, superClass1);
function RegexLiteral() {
return RegexLiteral.__super__.constructor.apply(this, arguments);
}
return RegexLiteral;
})(Literal);
exports.PassthroughLiteral = PassthroughLiteral = (function(superClass1) {
extend1(PassthroughLiteral, superClass1);
function PassthroughLiteral() {
return PassthroughLiteral.__super__.constructor.apply(this, arguments);
}
return PassthroughLiteral;
})(Literal);
exports.IdentifierLiteral = IdentifierLiteral = (function(superClass1) {
extend1(IdentifierLiteral, superClass1);
function IdentifierLiteral() {
return IdentifierLiteral.__super__.constructor.apply(this, arguments);
}
IdentifierLiteral.prototype.isAssignable = function() {
var ref3;
return ref3 = this.value, indexOf.call(RESERVED, ref3) < 0;
};
return IdentifierLiteral;
})(Literal);
exports.StatementLiteral = StatementLiteral = (function(superClass1) {
extend1(StatementLiteral, superClass1);
function StatementLiteral() {
return StatementLiteral.__super__.constructor.apply(this, arguments);
}
StatementLiteral.prototype.isStatement = YES;
StatementLiteral.prototype.makeReturn = THIS;
StatementLiteral.prototype.jumps = function(o) {
if (this.value === 'break' && !((o != null ? o.loop : void 0) || (o != null ? o.block : void 0))) {
return this;
}
@ -554,77 +637,67 @@
}
};
Literal.prototype.compileNode = function(o) {
var answer, code, ref3;
code = this.value === 'this' ? ((ref3 = o.scope.method) != null ? ref3.bound : void 0) ? o.scope.method.context : this.value : this.value.reserved ? "\"" + this.value + "\"" : this.value;
answer = this.isStatement() ? "" + this.tab + code + ";" : code;
return [this.makeCode(answer)];
StatementLiteral.prototype.compileNode = function(o) {
return [this.makeCode("" + this.tab + this.value + ";")];
};
Literal.prototype.toString = function() {
return ' "' + this.value + '"';
};
return StatementLiteral;
return Literal;
})(Literal);
})(Base);
exports.ThisLiteral = ThisLiteral = (function(superClass1) {
extend1(ThisLiteral, superClass1);
exports.Undefined = (function(superClass1) {
extend1(Undefined, superClass1);
function Undefined() {
return Undefined.__super__.constructor.apply(this, arguments);
function ThisLiteral() {
ThisLiteral.__super__.constructor.call(this, 'this');
}
Undefined.prototype.isAssignable = NO;
ThisLiteral.prototype.compileNode = function(o) {
var code, ref3;
code = ((ref3 = o.scope.method) != null ? ref3.bound : void 0) ? o.scope.method.context : this.value;
return [this.makeCode(code)];
};
Undefined.prototype.isComplex = NO;
return ThisLiteral;
Undefined.prototype.compileNode = function(o) {
})(Literal);
exports.UndefinedLiteral = UndefinedLiteral = (function(superClass1) {
extend1(UndefinedLiteral, superClass1);
function UndefinedLiteral() {
UndefinedLiteral.__super__.constructor.call(this, 'undefined');
}
UndefinedLiteral.prototype.compileNode = function(o) {
return [this.makeCode(o.level >= LEVEL_ACCESS ? '(void 0)' : 'void 0')];
};
return Undefined;
return UndefinedLiteral;
})(Base);
})(Literal);
exports.Null = (function(superClass1) {
extend1(Null, superClass1);
exports.NullLiteral = NullLiteral = (function(superClass1) {
extend1(NullLiteral, superClass1);
function Null() {
return Null.__super__.constructor.apply(this, arguments);
function NullLiteral() {
NullLiteral.__super__.constructor.call(this, 'null');
}
Null.prototype.isAssignable = NO;
return NullLiteral;
Null.prototype.isComplex = NO;
})(Literal);
Null.prototype.compileNode = function() {
return [this.makeCode("null")];
};
exports.BooleanLiteral = BooleanLiteral = (function(superClass1) {
extend1(BooleanLiteral, superClass1);
return Null;
})(Base);
exports.Bool = (function(superClass1) {
extend1(Bool, superClass1);
Bool.prototype.isAssignable = NO;
Bool.prototype.isComplex = NO;
Bool.prototype.compileNode = function() {
return [this.makeCode(this.val)];
};
function Bool(val1) {
this.val = val1;
function BooleanLiteral() {
return BooleanLiteral.__super__.constructor.apply(this, arguments);
}
return Bool;
return BooleanLiteral;
})(Base);
})(Literal);
exports.Return = Return = (function(superClass1) {
extend1(Return, superClass1);
@ -730,16 +803,28 @@
return this.hasProperties() || this.base.isAssignable();
};
Value.prototype.isSimpleNumber = function() {
return this.bareLiteral(Literal) && SIMPLENUM.test(this.base.value);
Value.prototype.isNumber = function() {
return this.bareLiteral(NumberLiteral);
};
Value.prototype.isString = function() {
return this.bareLiteral(Literal) && IS_STRING.test(this.base.value);
return this.bareLiteral(StringLiteral);
};
Value.prototype.isRegex = function() {
return this.bareLiteral(Literal) && IS_REGEX.test(this.base.value);
return this.bareLiteral(RegexLiteral);
};
Value.prototype.isUndefined = function() {
return this.bareLiteral(UndefinedLiteral);
};
Value.prototype.isNull = function() {
return this.bareLiteral(NullLiteral);
};
Value.prototype.isBoolean = function() {
return this.bareLiteral(BooleanLiteral);
};
Value.prototype.isAtomic = function() {
@ -755,7 +840,7 @@
};
Value.prototype.isNotCallable = function() {
return this.isSimpleNumber() || this.isString() || this.isRegex() || this.isArray() || this.isRange() || this.isSplice() || this.isObject();
return this.isNumber() || this.isString() || this.isRegex() || this.isArray() || this.isRange() || this.isSplice() || this.isObject() || this.isUndefined() || this.isNull() || this.isBoolean();
};
Value.prototype.isStatement = function(o) {
@ -804,14 +889,14 @@
}
base = new Value(this.base, this.properties.slice(0, -1));
if (base.isComplex()) {
bref = new Literal(o.scope.freeVariable('base'));
bref = new IdentifierLiteral(o.scope.freeVariable('base'));
base = new Value(new Parens(new Assign(bref, base)));
}
if (!name) {
return [base, bref];
}
if (name.isComplex()) {
nref = new Literal(o.scope.freeVariable('name'));
nref = new IdentifierLiteral(o.scope.freeVariable('name'));
name = new Index(new Assign(nref, name.index));
nref = new Index(nref);
}
@ -823,7 +908,7 @@
this.base.front = this.front;
props = this.properties;
fragments = this.base.compileToFragments(o, (props.length ? LEVEL_ACCESS : null));
if ((this.base instanceof Parens || props.length) && SIMPLENUM.test(fragmentsToText(fragments))) {
if (props.length && SIMPLENUM.test(fragmentsToText(fragments))) {
fragments.push(this.makeCode('.'));
}
for (j = 0, len1 = props.length; j < len1; j++) {
@ -851,7 +936,7 @@
fst = new Value(_this.base, _this.properties.slice(0, i));
snd = new Value(_this.base, _this.properties.slice(i));
if (fst.isComplex()) {
ref = new Literal(o.scope.freeVariable('ref'));
ref = new IdentifierLiteral(o.scope.freeVariable('ref'));
fst = new Parens(new Assign(ref, fst));
snd.base = ref;
}
@ -896,14 +981,13 @@
exports.Call = Call = (function(superClass1) {
extend1(Call, superClass1);
function Call(variable, args1, soak) {
function Call(variable1, args1, soak) {
this.variable = variable1;
this.args = args1 != null ? args1 : [];
this.soak = soak;
this.isNew = false;
this.isSuper = variable === 'super';
this.variable = this.isSuper ? null : variable;
if (variable instanceof Value && variable.isNotCallable()) {
variable.error("literal is not a function");
if (this.variable instanceof Value && this.variable.isNotCallable()) {
this.variable.error("literal is not a function");
}
}
@ -920,53 +1004,17 @@
return this;
};
Call.prototype.superReference = function(o) {
var accesses, base, bref, klass, method, name, nref, variable;
method = o.scope.namedMethod();
if (method != null ? method.klass : void 0) {
klass = method.klass, name = method.name, variable = method.variable;
if (klass.isComplex()) {
bref = new Literal(o.scope.parent.freeVariable('base'));
base = new Value(new Parens(new Assign(bref, klass)));
variable.base = base;
variable.properties.splice(0, klass.properties.length);
}
if (name.isComplex() || (name instanceof Index && name.index.isAssignable())) {
nref = new Literal(o.scope.parent.freeVariable('name'));
name = new Index(new Assign(nref, name.index));
variable.properties.pop();
variable.properties.push(name);
}
accesses = [new Access(new Literal('__super__'))];
if (method["static"]) {
accesses.push(new Access(new Literal('constructor')));
}
accesses.push(nref != null ? new Index(nref) : name);
return (new Value(bref != null ? bref : klass, accesses)).compile(o);
} else if (method != null ? method.ctor : void 0) {
return method.name + ".__super__.constructor";
} else {
return this.error('cannot call super outside of an instance method.');
}
};
Call.prototype.superThis = function(o) {
var method;
method = o.scope.method;
return (method && !method.klass && method.context) || "this";
};
Call.prototype.unfoldSoak = function(o) {
var call, ifn, j, left, len1, list, ref3, ref4, rite;
if (this.soak) {
if (this.variable) {
if (this instanceof SuperCall) {
left = new Literal(this.superReference(o));
rite = new Value(left);
} else {
if (ifn = unfoldSoak(o, this, 'variable')) {
return ifn;
}
ref3 = new Value(this.variable).cacheReference(o), left = ref3[0], rite = ref3[1];
} else {
left = new Literal(this.superReference(o));
rite = new Value(left);
}
rite = new Call(rite, this.args);
rite.isNew = this.isNew;
@ -1025,7 +1073,7 @@
compiledArgs.push.apply(compiledArgs, arg.compileToFragments(o, LEVEL_LIST));
}
fragments = [];
if (this.isSuper) {
if (this instanceof SuperCall) {
preface = this.superReference(o) + (".call(" + (this.superThis(o)));
if (compiledArgs.length) {
preface += ", ";
@ -1045,7 +1093,7 @@
Call.prototype.compileSplat = function(o, splatArgs) {
var answer, base, fun, idt, name, ref;
if (this.isSuper) {
if (this instanceof SuperCall) {
return [].concat(this.makeCode((this.superReference(o)) + ".apply(" + (this.superThis(o)) + ", "), splatArgs, this.makeCode(")"));
}
if (this.isNew) {
@ -1077,6 +1125,68 @@
})(Base);
exports.SuperCall = SuperCall = (function(superClass1) {
extend1(SuperCall, superClass1);
function SuperCall(args) {
SuperCall.__super__.constructor.call(this, null, args != null ? args : [new Splat(new IdentifierLiteral('arguments'))]);
this.isBare = args != null;
}
SuperCall.prototype.superReference = function(o) {
var accesses, base, bref, klass, method, name, nref, variable;
method = o.scope.namedMethod();
if (method != null ? method.klass : void 0) {
klass = method.klass, name = method.name, variable = method.variable;
if (klass.isComplex()) {
bref = new IdentifierLiteral(o.scope.parent.freeVariable('base'));
base = new Value(new Parens(new Assign(bref, klass)));
variable.base = base;
variable.properties.splice(0, klass.properties.length);
}
if (name.isComplex() || (name instanceof Index && name.index.isAssignable())) {
nref = new IdentifierLiteral(o.scope.parent.freeVariable('name'));
name = new Index(new Assign(nref, name.index));
variable.properties.pop();
variable.properties.push(name);
}
accesses = [new Access(new IdentifierLiteral('__super__'))];
if (method["static"]) {
accesses.push(new Access(new IdentifierLiteral('constructor')));
}
accesses.push(nref != null ? new Index(nref) : name);
return (new Value(bref != null ? bref : klass, accesses)).compile(o);
} else if (method != null ? method.ctor : void 0) {
return method.name + ".__super__.constructor";
} else {
return this.error('cannot call super outside of an instance method.');
}
};
SuperCall.prototype.superThis = function(o) {
var method;
method = o.scope.method;
return (method && !method.klass && method.context) || "this";
};
return SuperCall;
})(Call);
exports.RegexWithInterpolations = RegexWithInterpolations = (function(superClass1) {
extend1(RegexWithInterpolations, superClass1);
function RegexWithInterpolations(args) {
if (args == null) {
args = [];
}
RegexWithInterpolations.__super__.constructor.call(this, new Value(new IdentifierLiteral('RegExp')), args, false);
}
return RegexWithInterpolations;
})(Call);
exports.Extends = Extends = (function(superClass1) {
extend1(Extends, superClass1);
@ -1107,15 +1217,18 @@
Access.prototype.children = ['name'];
Access.prototype.compileToFragments = function(o) {
var name;
var name, node, ref3;
name = this.name.compileToFragments(o);
if (IDENTIFIER.test(fragmentsToText(name))) {
name.unshift(this.makeCode("."));
node = this.name.unwrap();
if (node instanceof IdentifierLiteral) {
if (ref3 = node.value, indexOf.call(JS_FORBIDDEN, ref3) >= 0) {
return [this.makeCode('["')].concat(slice.call(name), [this.makeCode('"]')]);
} else {
return [this.makeCode('.')].concat(slice.call(name));
}
} else {
name.unshift(this.makeCode("["));
name.push(this.makeCode("]"));
return [this.makeCode('[')].concat(slice.call(name), [this.makeCode(']')]);
}
return name;
};
Access.prototype.isComplex = NO;
@ -1158,7 +1271,7 @@
}
Range.prototype.compileVariables = function(o) {
var isComplex, ref3, ref4, ref5, ref6, step;
var isComplex, ref3, ref4, ref5, step;
o = merge(o, {
top: true
});
@ -1168,10 +1281,9 @@
if (step = del(o, 'step')) {
ref5 = this.cacheToCodeFragments(step.cache(o, LEVEL_LIST, isComplex)), this.step = ref5[0], this.stepVar = ref5[1];
}
ref6 = [this.fromVar.match(NUMBER), this.toVar.match(NUMBER)], this.fromNum = ref6[0], this.toNum = ref6[1];
if (this.stepVar) {
return this.stepNum = this.stepVar.match(NUMBER);
}
this.fromNum = this.from.isNumber() ? Number(this.fromVar) : null;
this.toNum = this.to.isNumber() ? Number(this.toVar) : null;
return this.stepNum = (step != null ? step.isNumber() : void 0) ? Number(this.stepVar) : null;
};
Range.prototype.compileNode = function(o) {
@ -1182,7 +1294,7 @@
if (!o.index) {
return this.compileArray(o);
}
known = this.fromNum && this.toNum;
known = (this.fromNum != null) && (this.toNum != null);
idx = del(o, 'index');
idxName = del(o, 'name');
namedIndex = idxName && idxName !== idx;
@ -1194,7 +1306,7 @@
varPart += ", " + this.step;
}
ref3 = [idx + " <" + this.equals, idx + " >" + this.equals], lt = ref3[0], gt = ref3[1];
condPart = this.stepNum ? parseNum(this.stepNum[0]) > 0 ? lt + " " + this.toVar : gt + " " + this.toVar : known ? ((ref4 = [parseNum(this.fromNum[0]), parseNum(this.toNum[0])], from = ref4[0], to = ref4[1], ref4), from <= to ? lt + " " + to : gt + " " + to) : (cond = this.stepVar ? this.stepVar + " > 0" : this.fromVar + " <= " + this.toVar, cond + " ? " + lt + " " + this.toVar + " : " + gt + " " + this.toVar);
condPart = this.stepNum != null ? this.stepNum > 0 ? lt + " " + this.toVar : gt + " " + this.toVar : known ? ((ref4 = [this.fromNum, this.toNum], from = ref4[0], to = ref4[1], ref4), from <= to ? lt + " " + to : gt + " " + to) : (cond = this.stepVar ? this.stepVar + " > 0" : this.fromVar + " <= " + this.toVar, cond + " ? " + lt + " " + this.toVar + " : " + gt + " " + this.toVar);
stepPart = this.stepVar ? idx + " += " + this.stepVar : known ? namedIndex ? from <= to ? "++" + idx : "--" + idx : from <= to ? idx + "++" : idx + "--" : namedIndex ? cond + " ? ++" + idx + " : --" + idx : cond + " ? " + idx + "++ : " + idx + "--";
if (namedIndex) {
varPart = idxName + " = " + varPart;
@ -1206,11 +1318,12 @@
};
Range.prototype.compileArray = function(o) {
var args, body, cond, hasArgs, i, idt, j, post, pre, range, ref3, ref4, result, results, vars;
if (this.fromNum && this.toNum && Math.abs(this.fromNum - this.toNum) <= 20) {
var args, body, cond, hasArgs, i, idt, j, known, post, pre, range, ref3, ref4, result, results, vars;
known = (this.fromNum != null) && (this.toNum != null);
if (known && Math.abs(this.fromNum - this.toNum) <= 20) {
range = (function() {
results = [];
for (var j = ref3 = +this.fromNum, ref4 = +this.toNum; ref3 <= ref4 ? j <= ref4 : j >= ref4; ref3 <= ref4 ? j++ : j--){ results.push(j); }
for (var j = ref3 = this.fromNum, ref4 = this.toNum; ref3 <= ref4 ? j <= ref4 : j >= ref4; ref3 <= ref4 ? j++ : j--){ results.push(j); }
return results;
}).apply(this);
if (this.exclusive) {
@ -1224,7 +1337,7 @@
});
result = o.scope.freeVariable('results');
pre = "\n" + idt + result + " = [];";
if (this.fromNum && this.toNum) {
if (known) {
o.index = i;
body = fragmentsToText(this.compileNode(o));
} else {
@ -1264,7 +1377,7 @@
compiled = to.compileToFragments(o, LEVEL_PAREN);
compiledText = fragmentsToText(compiled);
if (!(!this.range.exclusive && +compiledText === -1)) {
toStr = ', ' + (this.range.exclusive ? compiledText : SIMPLENUM.test(compiledText) ? "" + (+compiledText + 1) : (compiled = to.compileToFragments(o, LEVEL_ACCESS), "+" + (fragmentsToText(compiled)) + " + 1 || 9e9"));
toStr = ', ' + (this.range.exclusive ? compiledText : to.isNumber() ? "" + (+compiledText + 1) : (compiled = to.compileToFragments(o, LEVEL_ACCESS), "+" + (fragmentsToText(compiled)) + " + 1 || 9e9"));
}
}
return [this.makeCode(".slice(" + (fragmentsToText(fromCompiled)) + (toStr || '') + ")")];
@ -1347,7 +1460,7 @@
} else {
ref3 = prop.base.cache(o), key = ref3[0], value = ref3[1];
}
prop = new Assign(new Value(new Literal(oref), [new Access(key)]), value);
prop = new Assign(new Value(new IdentifierLiteral(oref), [new Access(key)]), value);
}
}
if (indent) {
@ -1464,17 +1577,24 @@
Class.prototype.children = ['variable', 'parent', 'body'];
Class.prototype.defaultClassVariableName = '_Class';
Class.prototype.determineName = function() {
var decl, ref3, tail;
var name, node, ref3, tail;
if (!this.variable) {
return null;
return this.defaultClassVariableName;
}
ref3 = this.variable.properties, tail = ref3[ref3.length - 1];
decl = tail ? tail instanceof Access && tail.name.value : this.variable.base.value;
if (indexOf.call(STRICT_PROSCRIBED, decl) >= 0) {
this.variable.error("class variable name may not be " + decl);
node = tail ? tail instanceof Access && tail.name : this.variable.base;
if (!(node instanceof IdentifierLiteral)) {
return this.defaultClassVariableName;
}
name = node.value;
if (indexOf.call(JS_FORBIDDEN, name) >= 0) {
return "_" + name;
} else {
return name;
}
return decl && (decl = IDENTIFIER.test(decl) && decl);
};
Class.prototype.setContext = function(name) {
@ -1482,7 +1602,7 @@
if (node.classBody) {
return false;
}
if (node instanceof Literal && node.value === 'this') {
if (node instanceof ThisLiteral) {
return node.value = name;
} else if (node instanceof Code) {
if (node.bound) {
@ -1497,7 +1617,7 @@
ref3 = this.boundFuncs;
for (j = 0, len1 = ref3.length; j < len1; j++) {
bvar = ref3[j];
lhs = (new Value(new Literal("this"), [new Access(bvar)])).compile(o);
lhs = (new Value(new ThisLiteral, [new Access(bvar)])).compile(o);
this.ctor.body.unshift(new Literal(lhs + " = " + (utility('bind', o)) + "(" + lhs + ", this)"));
}
};
@ -1524,14 +1644,14 @@
assign = this.ctor = func;
} else {
this.externalCtor = o.classScope.freeVariable('class');
assign = new Assign(new Literal(this.externalCtor), func);
assign = new Assign(new IdentifierLiteral(this.externalCtor), func);
}
} else {
if (assign.variable["this"]) {
func["static"] = true;
} else {
acc = base.isComplex() ? new Index(base) : new Access(base);
assign.variable = new Value(new Literal(name), [new Access(new Literal('prototype')), acc]);
assign.variable = new Value(new IdentifierLiteral(name), [new Access(new IdentifierLiteral('prototype')), acc]);
if (func instanceof Code && func.bound) {
this.boundFuncs.push(base);
func.bound = false;
@ -1606,11 +1726,8 @@
if (argumentsNode = this.body.contains(isLiteralArguments)) {
argumentsNode.error("Class bodies shouldn't reference arguments");
}
name = this.determineName() || '_Class';
if (name.reserved) {
name = "_" + name;
}
lname = new Literal(name);
name = this.determineName();
lname = new IdentifierLiteral(name);
func = new Code([], Block.wrap([this.body]));
args = [];
o.classScope = func.makeScope(o.scope);
@ -1622,7 +1739,7 @@
this.body.spaced = true;
this.body.expressions.push(lname);
if (this.parent) {
superClass = new Literal(o.classScope.freeVariable('superClass', {
superClass = new IdentifierLiteral(o.classScope.freeVariable('superClass', {
reserve: false
}));
this.body.expressions.unshift(new Extends(lname, superClass));
@ -1674,7 +1791,7 @@
};
Assign.prototype.compileNode = function(o) {
var answer, compiledName, isValue, j, name, properties, prototype, ref3, ref4, ref5, ref6, ref7, val, varBase;
var answer, compiledName, isValue, j, name, properties, prototype, ref3, ref4, ref5, ref6, ref7, ref8, val, varBase;
if (isValue = this.variable instanceof Value) {
if (this.variable.isArray() || this.variable.isObject()) {
return this.compilePatternMatch(o);
@ -1722,6 +1839,10 @@
}
compiledName = this.variable.compileToFragments(o, LEVEL_LIST);
if (this.context === 'object') {
if (ref8 = fragmentsToText(compiledName), indexOf.call(JS_FORBIDDEN, ref8) >= 0) {
compiledName.unshift(this.makeCode('"'));
compiledName.push(this.makeCode('"'));
}
return compiledName.concat(this.makeCode(": "), val);
}
answer = compiledName.concat(this.makeCode(" " + (this.context || '=') + " "), val);
@ -1763,9 +1884,9 @@
defaultValue = obj.value;
obj = obj.variable;
}
idx = isObject ? obj["this"] ? obj.properties[0].name : obj : new Literal(0);
idx = isObject ? obj["this"] ? obj.properties[0].name : obj : new NumberLiteral(0);
}
acc = IDENTIFIER.test(idx.unwrap().value);
acc = idx.unwrap() instanceof IdentifierLiteral;
value = new Value(value);
value.properties.push(new (acc ? Access : Index)(idx));
if (ref5 = obj.unwrap().value, indexOf.call(RESERVED, ref5) >= 0) {
@ -1782,7 +1903,7 @@
vvarText = fragmentsToText(vvar);
assigns = [];
expandedIdx = false;
if (!IDENTIFIER.test(vvarText) || this.variable.assigns(vvarText)) {
if (!(value.unwrap() instanceof IdentifierLiteral) || this.variable.assigns(vvarText)) {
assigns.push([this.makeCode((ref = o.scope.freeVariable('ref')) + " = ")].concat(slice.call(vvar)));
vvar = [this.makeCode(ref)];
vvarText = ref;
@ -1837,7 +1958,7 @@
idx = isObject ? obj["this"] ? obj.properties[0].name : obj : new Literal(expandedIdx || idx);
}
name = obj.unwrap().value;
acc = IDENTIFIER.test(idx.unwrap().value);
acc = idx.unwrap() instanceof IdentifierLiteral;
val = new Value(new Literal(vvarText), [new (acc ? Access : Index)(idx)]);
if (defaultValue) {
val = new Op('?', val, defaultValue);
@ -1865,7 +1986,7 @@
Assign.prototype.compileConditional = function(o) {
var fragments, left, ref3, right;
ref3 = this.variable.cacheReference(o), left = ref3[0], right = ref3[1];
if (!left.properties.length && left.base instanceof Literal && left.base.value !== "this" && !o.scope.check(left.base.value)) {
if (!left.properties.length && left.base instanceof Literal && !(left.base instanceof ThisLiteral) && !o.scope.check(left.base.value)) {
this.variable.error("the variable \"" + left.base.value + "\" can't be assigned with " + this.context + " because it has not been declared before");
}
if (indexOf.call(this.context, "?") >= 0) {
@ -1899,7 +2020,7 @@
fromDecl = fromRef = '0';
}
if (to) {
if (from instanceof Value && from.isSimpleNumber() && to instanceof Value && to.isSimpleNumber()) {
if ((from != null ? from.isNumber() : void 0) && to.isNumber()) {
to = to.compile(o) - fromRef;
if (!exclusive) {
to += 1;
@ -1957,8 +2078,8 @@
}
if (this.bound && !this.context) {
this.context = '_this';
wrapper = new Code([new Param(new Literal(this.context))], new Block([this]));
boundfunc = new Call(wrapper, [new Literal('this')]);
wrapper = new Code([new Param(new IdentifierLiteral(this.context))], new Block([this]));
boundfunc = new Call(wrapper, [new ThisLiteral]);
boundfunc.updateLocationDataIfMissing(this.locationData);
return boundfunc.compileNode(o);
}
@ -1998,7 +2119,7 @@
results.push(p.asReference(o));
}
return results;
}).call(this))), new Value(new Literal('arguments')));
}).call(this))), new Value(new IdentifierLiteral('arguments')));
break;
}
ref7 = this.params;
@ -2129,12 +2250,12 @@
node = this.name;
if (node["this"]) {
name = node.properties[0].name.value;
if (name.reserved) {
if (indexOf.call(JS_FORBIDDEN, name) >= 0) {
name = "_" + name;
}
node = new Literal(o.scope.freeVariable(name));
node = new IdentifierLiteral(o.scope.freeVariable(name));
} else if (node.isComplex()) {
node = new Literal(o.scope.freeVariable('arg'));
node = new IdentifierLiteral(o.scope.freeVariable('arg'));
}
node = new Value(node);
if (this.splat) {
@ -2347,7 +2468,7 @@
}
if (this.guard) {
if (body.expressions.length > 1) {
body.expressions.unshift(new If((new Parens(this.guard)).invert(), new Literal("continue")));
body.expressions.unshift(new If((new Parens(this.guard)).invert(), new StatementLiteral("continue")));
} else {
if (this.guard) {
body = Block.wrap([new If(this.guard, body)]);
@ -2408,7 +2529,10 @@
Op.prototype.children = ['first', 'second'];
Op.prototype.isSimpleNumber = NO;
Op.prototype.isNumber = function() {
var ref3;
return this.isUnary() && ((ref3 = this.operator) === '+' || ref3 === '-') && this.first instanceof Value && this.first.isNumber();
};
Op.prototype.isYield = function() {
var ref3;
@ -2420,8 +2544,7 @@
};
Op.prototype.isComplex = function() {
var ref3;
return !(this.isUnary() && ((ref3 = this.operator) === '+' || ref3 === '-') && this.first instanceof Value && this.first.isSimpleNumber());
return !this.isNumber();
};
Op.prototype.isChainable = function() {
@ -2540,7 +2663,7 @@
Op.prototype.compileExistence = function(o) {
var fst, ref;
if (this.first.isComplex()) {
ref = new Literal(o.scope.freeVariable('ref'));
ref = new IdentifierLiteral(o.scope.freeVariable('ref'));
fst = new Parens(new Assign(ref, this.first));
} else {
fst = this.first;
@ -2606,13 +2729,13 @@
Op.prototype.compilePower = function(o) {
var pow;
pow = new Value(new Literal('Math'), [new Access(new Literal('pow'))]);
pow = new Value(new IdentifierLiteral('Math'), [new Access(new IdentifierLiteral('pow'))]);
return new Call(pow, [this.first, this.second]).compileToFragments(o);
};
Op.prototype.compileFloorDivision = function(o) {
var div, floor;
floor = new Value(new Literal('Math'), [new Access(new Literal('floor'))]);
floor = new Value(new IdentifierLiteral('Math'), [new Access(new IdentifierLiteral('floor'))]);
div = new Op('/', this.first, this.second);
return new Call(floor, [div]).compileToFragments(o);
};
@ -2740,7 +2863,7 @@
tryPart = this.attempt.compileToFragments(o, LEVEL_TOP);
catchPart = this.recovery ? (generatedErrorVariableName = o.scope.freeVariable('error', {
reserve: false
}), placeholder = new Literal(generatedErrorVariableName), this.errorVariable ? this.recovery.unshift(new Assign(this.errorVariable, placeholder)) : void 0, [].concat(this.makeCode(" catch ("), placeholder.compileToFragments(o), this.makeCode(") {\n"), this.recovery.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab + "}"))) : !(this.ensure || this.recovery) ? (generatedErrorVariableName = o.scope.freeVariable('error', {
}), placeholder = new IdentifierLiteral(generatedErrorVariableName), this.errorVariable ? this.recovery.unshift(new Assign(this.errorVariable, placeholder)) : void 0, [].concat(this.makeCode(" catch ("), placeholder.compileToFragments(o), this.makeCode(") {\n"), this.recovery.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab + "}"))) : !(this.ensure || this.recovery) ? (generatedErrorVariableName = o.scope.freeVariable('error', {
reserve: false
}), [this.makeCode(" catch (" + generatedErrorVariableName + ") {}")]) : [];
ensurePart = this.ensure ? [].concat(this.makeCode(" finally {\n"), this.ensure.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab + "}")) : [];
@ -2789,7 +2912,7 @@
var cmp, cnj, code, ref3;
this.expression.front = this.front;
code = this.expression.compile(o, LEVEL_OP);
if (IDENTIFIER.test(code) && !o.scope.check(code)) {
if (this.expression.unwrap() instanceof IdentifierLiteral && !o.scope.check(code)) {
ref3 = this.negated ? ['===', '||'] : ['!==', '&&'], cmp = ref3[0], cnj = ref3[1];
code = "typeof " + code + " " + cmp + " \"undefined\" " + cnj + " " + code + " " + cmp + " null";
} else {
@ -2839,6 +2962,17 @@
})(Base);
exports.StringWithInterpolations = StringWithInterpolations = (function(superClass1) {
extend1(StringWithInterpolations, superClass1);
function StringWithInterpolations() {
return StringWithInterpolations.__super__.constructor.apply(this, arguments);
}
return StringWithInterpolations;
})(Parens);
exports.For = For = (function(superClass1) {
extend1(For, superClass1);
@ -2899,7 +3033,9 @@
kvarAssign = kvar !== ivar ? kvar + " = " : "";
if (this.step && !this.range) {
ref4 = this.cacheToCodeFragments(this.step.cache(o, LEVEL_LIST, isComplexOrAssignable)), step = ref4[0], stepVar = ref4[1];
stepNum = stepVar.match(NUMBER);
if (this.step.isNumber()) {
stepNum = Number(stepVar);
}
}
if (this.pattern) {
name = ivar;
@ -2917,7 +3053,7 @@
}));
} else {
svar = this.source.compile(o, LEVEL_LIST);
if ((name || this.own) && !IDENTIFIER.test(svar)) {
if ((name || this.own) && !(this.source.unwrap() instanceof IdentifierLiteral)) {
defPart += "" + this.tab + (ref = scope.freeVariable('ref')) + " = " + svar + ";\n";
svar = ref;
}
@ -2928,7 +3064,8 @@
if (step !== stepVar) {
defPart += "" + this.tab + step + ";\n";
}
if (!(this.step && stepNum && (down = parseNum(stepNum[0]) < 0))) {
down = stepNum < 0;
if (!(this.step && (stepNum != null) && down)) {
lvar = scope.freeVariable('len');
}
declare = "" + kvarAssign + ivar + " = 0, " + lvar + " = " + svar + ".length";
@ -2936,7 +3073,7 @@
compare = ivar + " < " + lvar;
compareDown = ivar + " >= 0";
if (this.step) {
if (stepNum) {
if (stepNum != null) {
if (down) {
compare = compareDown;
declare = declareDown;
@ -2959,7 +3096,7 @@
}
if (this.guard) {
if (body.expressions.length > 1) {
body.expressions.unshift(new If((new Parens(this.guard)).invert(), new Literal("continue")));
body.expressions.unshift(new If((new Parens(this.guard)).invert(), new StatementLiteral("continue")));
} else {
if (this.guard) {
body = Block.wrap([new If(this.guard, body)]);
@ -3003,7 +3140,7 @@
continue;
}
fn = ((ref8 = val.base) != null ? ref8.unwrapAll() : void 0) || val;
ref = new Literal(o.scope.freeVariable('fn'));
ref = new IdentifierLiteral(o.scope.freeVariable('fn'));
base = new Value(ref);
if (val.base) {
ref9 = [base, val], val.base = ref9[0], base = ref9[1];
@ -3264,18 +3401,8 @@
TAB = ' ';
IDENTIFIER = /^(?!\d)[$\w\x7f-\uffff]+$/;
SIMPLENUM = /^[+-]?\d+$/;
HEXNUM = /^[+-]?0x[\da-f]+/i;
NUMBER = /^[+-]?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)$/i;
IS_STRING = /^['"]/;
IS_REGEX = /^\//;
utility = function(name, o) {
var ref, root;
root = o.scope.root;
@ -3293,22 +3420,12 @@
return code.replace(/\s+$/, '');
};
parseNum = function(x) {
if (x == null) {
return 0;
} else if (x.match(HEXNUM)) {
return parseInt(x, 16);
} else {
return parseFloat(x);
}
};
isLiteralArguments = function(node) {
return node instanceof Literal && node.value === 'arguments' && !node.asKey;
};
isLiteralThis = function(node) {
return (node instanceof Literal && node.value === 'this' && !node.asKey) || (node instanceof Code && node.bound) || (node instanceof Call && node.isSuper);
return (node instanceof ThisLiteral && !node.asKey) || (node instanceof Code && node.bound) || node instanceof SuperCall;
};
isComplexOrAssignable = function(node) {

File diff suppressed because one or more lines are too long

View File

@ -487,7 +487,7 @@
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS'];
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'STRING_START', 'JS', 'REGEX', 'REGEX_START', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'];
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'INFINITY', 'STRING', 'STRING_START', 'JS', 'REGEX', 'REGEX_START', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'];
IMPLICIT_UNSPACED_CALL = ['+', '-'];

View File

@ -251,7 +251,7 @@ parser.yy.parseError = (message, {token}) ->
'end of input'
when errorTag in ['INDENT', 'OUTDENT']
'indentation'
when errorTag in ['IDENTIFIER', 'NUMBER', 'STRING', 'STRING_START', 'REGEX', 'REGEX_START']
when errorTag in ['IDENTIFIER', 'NUMBER', 'INFINITY', 'STRING', 'STRING_START', 'REGEX', 'REGEX_START']
errorTag.replace(/_START$/, '').toLowerCase()
else
helpers.nameWhitespaceCharacter errorText

View File

@ -96,7 +96,7 @@ grammar =
Statement: [
o 'Return'
o 'Comment'
o 'STATEMENT', -> new Literal $1
o 'STATEMENT', -> new StatementLiteral $1
]
# All the different types of expressions in our language. The basic unit of
@ -135,36 +135,36 @@ grammar =
# A literal identifier, a variable name or property.
Identifier: [
o 'IDENTIFIER', -> new Literal $1
o 'IDENTIFIER', -> new IdentifierLiteral $1
]
# Alphanumerics are separated from the other **Literal** matchers because
# they can also serve as keys in object literals.
AlphaNumeric: [
o 'NUMBER', -> new Literal $1
o 'NUMBER', -> new NumberLiteral $1
o 'INFINITY', -> new InfinityLiteral $1
o 'String'
]
String: [
o 'STRING', -> new Literal $1
o 'STRING_START Body STRING_END', -> new Parens $2
o 'STRING', -> new StringLiteral $1
o 'STRING_START Body STRING_END', -> new StringWithInterpolations $2
]
Regex: [
o 'REGEX', -> new Literal $1
o 'REGEX_START Invocation REGEX_END', -> $2
o 'REGEX', -> new RegexLiteral $1
o 'REGEX_START Invocation REGEX_END', -> new RegexWithInterpolations $2.args
]
# All of our immediate values. Generally these can be passed straight
# through and printed to JavaScript.
Literal: [
o 'AlphaNumeric'
o 'JS', -> new Literal $1
o 'JS', -> new PassthroughLiteral $1
o 'Regex'
o 'DEBUGGER', -> new Literal $1
o 'UNDEFINED', -> new Undefined
o 'NULL', -> new Null
o 'BOOL', -> new Bool $1
o 'UNDEFINED', -> new UndefinedLiteral
o 'NULL', -> new NullLiteral
o 'BOOL', -> new BooleanLiteral $1
]
# Assignment of a variable, property, or index to a value.
@ -299,9 +299,9 @@ grammar =
Accessor: [
o '. Identifier', -> new Access $2
o '?. Identifier', -> new Access $2, 'soak'
o ':: Identifier', -> [LOC(1)(new Access new Literal('prototype')), LOC(2)(new Access $2)]
o '?:: Identifier', -> [LOC(1)(new Access new Literal('prototype'), 'soak'), LOC(2)(new Access $2)]
o '::', -> new Access new Literal 'prototype'
o ':: Identifier', -> [LOC(1)(new Access new IdentifierLiteral('prototype')), LOC(2)(new Access $2)]
o '?:: Identifier', -> [LOC(1)(new Access new IdentifierLiteral('prototype'), 'soak'), LOC(2)(new Access $2)]
o '::', -> new Access new IdentifierLiteral 'prototype'
o 'Index'
]
@ -348,8 +348,12 @@ grammar =
Invocation: [
o 'Value OptFuncExist Arguments', -> new Call $1, $3, $2
o 'Invocation OptFuncExist Arguments', -> new Call $1, $3, $2
o 'SUPER', -> new Call 'super', [new Splat new Literal 'arguments']
o 'SUPER Arguments', -> new Call 'super', $2
o 'Super'
]
Super: [
o 'SUPER', -> new SuperCall
o 'SUPER Arguments', -> new SuperCall $2
]
# An optional existence check on a function.
@ -366,13 +370,13 @@ grammar =
# A reference to the *this* current object.
This: [
o 'THIS', -> new Value new Literal 'this'
o '@', -> new Value new Literal 'this'
o 'THIS', -> new Value new ThisLiteral
o '@', -> new Value new ThisLiteral
]
# A reference to a property on *this*.
ThisProperty: [
o '@ Identifier', -> new Value LOC(1)(new Literal('this')), [LOC(2)(new Access($2))], 'this'
o '@ Identifier', -> new Value LOC(1)(new ThisLiteral), [LOC(2)(new Access($2))], 'this'
]
# The array literal.
@ -473,8 +477,8 @@ grammar =
]
Loop: [
o 'LOOP Block', -> new While(LOC(1) new Literal 'true').addBody $2
o 'LOOP Expression', -> new While(LOC(1) new Literal 'true').addBody LOC(2) Block.wrap [$2]
o 'LOOP Block', -> new While(LOC(1) new BooleanLiteral 'true').addBody $2
o 'LOOP Expression', -> new While(LOC(1) new BooleanLiteral 'true').addBody LOC(2) Block.wrap [$2]
]
# Array, object, and range comprehensions, at the most generic level.

View File

@ -142,8 +142,6 @@ exports.Lexer = class Lexer
if id in JS_FORBIDDEN
if forcedIdentifier
tag = 'IDENTIFIER'
id = new String id
id.reserved = yes
else if id in RESERVED
@error "reserved word '#{id}'", length: id.length
@ -156,7 +154,8 @@ exports.Lexer = class Lexer
when '==', '!=' then 'COMPARE'
when '&&', '||' then 'LOGIC'
when 'true', 'false' then 'BOOL'
when 'break', 'continue' then 'STATEMENT'
when 'break', 'continue', \
'debugger' then 'STATEMENT'
else tag
tagToken = @token tag, id, 0, idLength
@ -187,10 +186,15 @@ exports.Lexer = class Lexer
else if /^0\d+/.test number
@error "octal literal '#{number}' must be prefixed with '0o'", length: lexedLength
if octalLiteral = /^0o([0-7]+)/.exec number
number = '0x' + parseInt(octalLiteral[1], 8).toString 16
if binaryLiteral = /^0b([01]+)/.exec number
number = '0x' + parseInt(binaryLiteral[1], 2).toString 16
@token 'NUMBER', number, 0, lexedLength
numberValue = parseInt(octalLiteral[1], 8)
number = "0x#{numberValue.toString 16}"
else if binaryLiteral = /^0b([01]+)/.exec number
numberValue = parseInt(binaryLiteral[1], 2)
number = "0x#{numberValue.toString 16}"
else
numberValue = parseFloat(number)
tag = if numberValue is Infinity then 'INFINITY' else 'NUMBER'
@token tag, number, 0, lexedLength
lexedLength
# Matches strings, including multi-line strings, as well as heredocs, with or without
@ -408,7 +412,7 @@ exports.Lexer = class Lexer
tag = value
[..., prev] = @tokens
if value is '=' and prev
if not prev[1].reserved and prev[1] in JS_FORBIDDEN
if prev.variable and prev[1] in JS_FORBIDDEN
prev = prev.origin if prev.origin
@error "reserved word '#{prev[1]}' can't be assigned", prev[2]
if prev[1] in ['||', '&&']
@ -786,6 +790,7 @@ JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED)
exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS).concat(STRICT_PROSCRIBED)
exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED
exports.JS_FORBIDDEN = JS_FORBIDDEN
# The character code of the nasty Microsoft madness otherwise known as the BOM.
BOM = 65279
@ -918,7 +923,7 @@ BOOL = ['TRUE', 'FALSE']
# of a function invocation or indexing operation.
CALLABLE = ['IDENTIFIER', ')', ']', '?', '@', 'THIS', 'SUPER']
INDEXABLE = CALLABLE.concat [
'NUMBER', 'STRING', 'STRING_END', 'REGEX', 'REGEX_END'
'NUMBER', 'INFINITY', 'STRING', 'STRING_END', 'REGEX', 'REGEX_END'
'BOOL', 'NULL', 'UNDEFINED', '}', '::'
]

View File

@ -6,7 +6,7 @@
Error.stackTraceLimit = Infinity
{Scope} = require './scope'
{RESERVED, STRICT_PROSCRIBED} = require './lexer'
{RESERVED, STRICT_PROSCRIBED, JS_FORBIDDEN} = require './lexer'
# Import the helpers we plan to use.
{compact, flatten, extend, merge, del, starts, ends, some,
@ -82,13 +82,13 @@ exports.Base = class Base
func = new Code [], Block.wrap [this]
args = []
if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis
args = [new Literal 'this']
args = [new ThisLiteral]
if argumentsNode
meth = 'apply'
args.push new Literal 'arguments'
args.push new IdentifierLiteral 'arguments'
else
meth = 'call'
func = new Value func, [new Access new Literal meth]
func = new Value func, [new Access new IdentifierLiteral meth]
parts = (new Call func, args).compileNode o
if func.isGenerator or func.base?.isGenerator
parts.unshift @makeCode "(yield* "
@ -105,7 +105,7 @@ exports.Base = class Base
cache: (o, level, isComplex) ->
complex = if isComplex? then isComplex this else @isComplex()
if complex
ref = new Literal o.scope.freeVariable 'ref'
ref = new IdentifierLiteral o.scope.freeVariable 'ref'
sub = new Assign ref, this
if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref]
else
@ -181,6 +181,7 @@ exports.Base = class Base
isComplex : YES
isChainable : NO
isAssignable : NO
isNumber : NO
unwrap : THIS
unfoldSoak : NO
@ -380,59 +381,70 @@ exports.Block = class Block extends Base
#### Literal
# Literals are static values that can be passed through directly into
# JavaScript without translation, such as: strings, numbers,
# `Literal` is a base class for static values that can be passed through
# directly into JavaScript without translation, such as: strings, numbers,
# `true`, `false`, `null`...
exports.Literal = class Literal extends Base
constructor: (@value) ->
makeReturn: ->
if @isStatement() then this else super
isAssignable: ->
IDENTIFIER.test @value
isStatement: ->
@value in ['break', 'continue', 'debugger']
isComplex: NO
assigns: (name) ->
name is @value
compileNode: (o) ->
[@makeCode @value]
toString: ->
" #{if @isStatement() then super else @constructor.name}: #{@value}"
exports.NumberLiteral = class NumberLiteral extends Literal
exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
compileNode: ->
[@makeCode 'Infinity']
exports.StringLiteral = class StringLiteral extends Literal
exports.RegexLiteral = class RegexLiteral extends Literal
exports.PassthroughLiteral = class PassthroughLiteral extends Literal
exports.IdentifierLiteral = class IdentifierLiteral extends Literal
isAssignable: -> @value not in RESERVED
exports.StatementLiteral = class StatementLiteral extends Literal
isStatement: YES
makeReturn: THIS
jumps: (o) ->
return this if @value is 'break' and not (o?.loop or o?.block)
return this if @value is 'continue' and not o?.loop
compileNode: (o) ->
code = if @value is 'this'
if o.scope.method?.bound then o.scope.method.context else @value
else if @value.reserved
"\"#{@value}\""
else
@value
answer = if @isStatement() then "#{@tab}#{code};" else code
[@makeCode answer]
[@makeCode "#{@tab}#{@value};"]
toString: ->
' "' + @value + '"'
exports.ThisLiteral = class ThisLiteral extends Literal
constructor: ->
super 'this'
compileNode: (o) ->
code = if o.scope.method?.bound then o.scope.method.context else @value
[@makeCode code]
exports.UndefinedLiteral = class UndefinedLiteral extends Literal
constructor: ->
super 'undefined'
class exports.Undefined extends Base
isAssignable: NO
isComplex: NO
compileNode: (o) ->
[@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0']
class exports.Null extends Base
isAssignable: NO
isComplex: NO
compileNode: -> [@makeCode "null"]
exports.NullLiteral = class NullLiteral extends Literal
constructor: ->
super 'null'
class exports.Bool extends Base
isAssignable: NO
isComplex: NO
compileNode: -> [@makeCode @val]
constructor: (@val) ->
exports.BooleanLiteral = class BooleanLiteral extends Literal
#### Return
@ -498,16 +510,20 @@ exports.Value = class Value extends Base
isRange : -> @bareLiteral(Range)
isComplex : -> @hasProperties() or @base.isComplex()
isAssignable : -> @hasProperties() or @base.isAssignable()
isSimpleNumber : -> @bareLiteral(Literal) and SIMPLENUM.test @base.value
isString : -> @bareLiteral(Literal) and IS_STRING.test @base.value
isRegex : -> @bareLiteral(Literal) and IS_REGEX.test @base.value
isNumber : -> @bareLiteral(NumberLiteral)
isString : -> @bareLiteral(StringLiteral)
isRegex : -> @bareLiteral(RegexLiteral)
isUndefined : -> @bareLiteral(UndefinedLiteral)
isNull : -> @bareLiteral(NullLiteral)
isBoolean : -> @bareLiteral(BooleanLiteral)
isAtomic : ->
for node in @properties.concat @base
return no if node.soak or node instanceof Call
yes
isNotCallable : -> @isSimpleNumber() or @isString() or @isRegex() or
@isArray() or @isRange() or @isSplice() or @isObject()
isNotCallable : -> @isNumber() or @isString() or @isRegex() or
@isArray() or @isRange() or @isSplice() or @isObject() or
@isUndefined() or @isNull() or @isBoolean()
isStatement : (o) -> not @properties.length and @base.isStatement o
assigns : (name) -> not @properties.length and @base.assigns name
@ -539,11 +555,11 @@ exports.Value = class Value extends Base
return [this, this] # `a` `a.b`
base = new Value @base, @properties[...-1]
if base.isComplex() # `a().b`
bref = new Literal o.scope.freeVariable 'base'
bref = new IdentifierLiteral o.scope.freeVariable 'base'
base = new Value new Parens new Assign bref, base
return [base, bref] unless name # `a()`
if name.isComplex() # `a[b()]`
nref = new Literal o.scope.freeVariable 'name'
nref = new IdentifierLiteral o.scope.freeVariable 'name'
name = new Index new Assign nref, name.index
nref = new Index nref
[base.add(name), new Value(bref or base.base, [nref or name])]
@ -556,7 +572,7 @@ exports.Value = class Value extends Base
@base.front = @front
props = @properties
fragments = @base.compileToFragments o, (if props.length then LEVEL_ACCESS else null)
if (@base instanceof Parens or props.length) and SIMPLENUM.test fragmentsToText fragments
if props.length and SIMPLENUM.test fragmentsToText fragments
fragments.push @makeCode '.'
for prop in props
fragments.push (prop.compileToFragments o)...
@ -573,7 +589,7 @@ exports.Value = class Value extends Base
fst = new Value @base, @properties[...i]
snd = new Value @base, @properties[i..]
if fst.isComplex()
ref = new Literal o.scope.freeVariable 'ref'
ref = new IdentifierLiteral o.scope.freeVariable 'ref'
fst = new Parens new Assign ref, fst
snd.base = ref
return new If new Existence(fst), snd, soak: on
@ -597,15 +613,12 @@ exports.Comment = class Comment extends Base
#### Call
# Node for a function invocation. Takes care of converting `super()` calls into
# calls against the prototype's function of the same name.
# Node for a function invocation.
exports.Call = class Call extends Base
constructor: (variable, @args = [], @soak) ->
constructor: (@variable, @args = [], @soak) ->
@isNew = false
@isSuper = variable is 'super'
@variable = if @isSuper then null else variable
if variable instanceof Value and variable.isNotCallable()
variable.error "literal is not a function"
if @variable instanceof Value and @variable.isNotCallable()
@variable.error "literal is not a function"
children: ['variable', 'args']
@ -618,45 +631,15 @@ exports.Call = class Call extends Base
@isNew = true
this
# Grab the reference to the superclass's implementation of the current
# method.
superReference: (o) ->
method = o.scope.namedMethod()
if method?.klass
{klass, name, variable} = method
if klass.isComplex()
bref = new Literal o.scope.parent.freeVariable 'base'
base = new Value new Parens new Assign bref, klass
variable.base = base
variable.properties.splice 0, klass.properties.length
if name.isComplex() or (name instanceof Index and name.index.isAssignable())
nref = new Literal o.scope.parent.freeVariable 'name'
name = new Index new Assign nref, name.index
variable.properties.pop()
variable.properties.push name
accesses = [new Access new Literal '__super__']
accesses.push new Access new Literal 'constructor' if method.static
accesses.push if nref? then new Index nref else name
(new Value bref ? klass, accesses).compile o
else if method?.ctor
"#{method.name}.__super__.constructor"
else
@error 'cannot call super outside of an instance method.'
# The appropriate `this` value for a `super` call.
superThis : (o) ->
method = o.scope.method
(method and not method.klass and method.context) or "this"
# Soaked chained invocations unfold into if/else ternary structures.
unfoldSoak: (o) ->
if @soak
if @variable
return ifn if ifn = unfoldSoak o, this, 'variable'
[left, rite] = new Value(@variable).cacheReference o
else
if this instanceof SuperCall
left = new Literal @superReference o
rite = new Value left
else
return ifn if ifn = unfoldSoak o, this, 'variable'
[left, rite] = new Value(@variable).cacheReference o
rite = new Call rite, @args
rite.isNew = @isNew
left = new Literal "typeof #{ left.compile o } === \"function\""
@ -692,7 +675,7 @@ exports.Call = class Call extends Base
compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)...
fragments = []
if @isSuper
if this instanceof SuperCall
preface = @superReference(o) + ".call(#{@superThis(o)}"
if compiledArgs.length then preface += ", "
fragments.push @makeCode preface
@ -711,7 +694,7 @@ exports.Call = class Call extends Base
#
# splatArgs is an array of CodeFragments to put into the 'apply'.
compileSplat: (o, splatArgs) ->
if @isSuper
if this instanceof SuperCall
return [].concat @makeCode("#{ @superReference o }.apply(#{@superThis(o)}, "),
splatArgs, @makeCode(")")
@ -745,6 +728,54 @@ exports.Call = class Call extends Base
answer = answer.concat fun
answer = answer.concat @makeCode(".apply(#{ref}, "), splatArgs, @makeCode(")")
#### Super
# Takes care of converting `super()` calls into calls against the prototype's
# function of the same name.
exports.SuperCall = class SuperCall extends Call
constructor: (args) ->
super null, args ? [new Splat new IdentifierLiteral 'arguments']
# Allow to recognize a bare `super` call without parentheses and arguments.
@isBare = args?
# Grab the reference to the superclass's implementation of the current
# method.
superReference: (o) ->
method = o.scope.namedMethod()
if method?.klass
{klass, name, variable} = method
if klass.isComplex()
bref = new IdentifierLiteral o.scope.parent.freeVariable 'base'
base = new Value new Parens new Assign bref, klass
variable.base = base
variable.properties.splice 0, klass.properties.length
if name.isComplex() or (name instanceof Index and name.index.isAssignable())
nref = new IdentifierLiteral o.scope.parent.freeVariable 'name'
name = new Index new Assign nref, name.index
variable.properties.pop()
variable.properties.push name
accesses = [new Access new IdentifierLiteral '__super__']
accesses.push new Access new IdentifierLiteral 'constructor' if method.static
accesses.push if nref? then new Index nref else name
(new Value bref ? klass, accesses).compile o
else if method?.ctor
"#{method.name}.__super__.constructor"
else
@error 'cannot call super outside of an instance method.'
# The appropriate `this` value for a `super` call.
superThis : (o) ->
method = o.scope.method
(method and not method.klass and method.context) or "this"
#### RegexWithInterpolations
# Regexes with interpolations are in fact just a variation of a `Call` (a
# `RegExp()` call to be precise) with a `StringWithInterpolations` inside.
exports.RegexWithInterpolations = class RegexWithInterpolations extends Call
constructor: (args = []) ->
super (new Value new IdentifierLiteral 'RegExp'), args, false
#### Extends
# Node to extend an object's prototype with an ancestor object.
@ -772,12 +803,14 @@ exports.Access = class Access extends Base
compileToFragments: (o) ->
name = @name.compileToFragments o
if IDENTIFIER.test fragmentsToText name
name.unshift @makeCode "."
node = @name.unwrap()
if node instanceof IdentifierLiteral
if node.value in JS_FORBIDDEN
[@makeCode('["'), name..., @makeCode('"]')]
else
[@makeCode('.'), name...]
else
name.unshift @makeCode "["
name.push @makeCode "]"
name
[@makeCode('['), name..., @makeCode(']')]
isComplex: NO
@ -818,8 +851,9 @@ exports.Range = class Range extends Base
[@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST, isComplex
[@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST, isComplex
[@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST, isComplex if step = del o, 'step'
[@fromNum, @toNum] = [@fromVar.match(NUMBER), @toVar.match(NUMBER)]
@stepNum = @stepVar.match(NUMBER) if @stepVar
@fromNum = if @from.isNumber() then Number @fromVar else null
@toNum = if @to.isNumber() then Number @toVar else null
@stepNum = if step?.isNumber() then Number @stepVar else null
# When compiled normally, the range returns the contents of the *for loop*
# needed to iterate over the values in the range. Used by comprehensions.
@ -828,7 +862,7 @@ exports.Range = class Range extends Base
return @compileArray(o) unless o.index
# Set up endpoints.
known = @fromNum and @toNum
known = @fromNum? and @toNum?
idx = del o, 'index'
idxName = del o, 'name'
namedIndex = idxName and idxName isnt idx
@ -838,10 +872,10 @@ exports.Range = class Range extends Base
[lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]
# Generate the condition.
condPart = if @stepNum
if parseNum(@stepNum[0]) > 0 then "#{lt} #{@toVar}" else "#{gt} #{@toVar}"
condPart = if @stepNum?
if @stepNum > 0 then "#{lt} #{@toVar}" else "#{gt} #{@toVar}"
else if known
[from, to] = [parseNum(@fromNum[0]), parseNum(@toNum[0])]
[from, to] = [@fromNum, @toNum]
if from <= to then "#{lt} #{to}" else "#{gt} #{to}"
else
cond = if @stepVar then "#{@stepVar} > 0" else "#{@fromVar} <= #{@toVar}"
@ -870,15 +904,16 @@ exports.Range = class Range extends Base
# When used as a value, expand the range into the equivalent array.
compileArray: (o) ->
if @fromNum and @toNum and Math.abs(@fromNum - @toNum) <= 20
range = [+@fromNum..+@toNum]
known = @fromNum? and @toNum?
if known and Math.abs(@fromNum - @toNum) <= 20
range = [@fromNum..@toNum]
range.pop() if @exclusive
return [@makeCode "[#{ range.join(', ') }]"]
idt = @tab + TAB
i = o.scope.freeVariable 'i', single: true
result = o.scope.freeVariable 'results'
pre = "\n#{idt}#{result} = [];"
if @fromNum and @toNum
if known
o.index = i
body = fragmentsToText @compileNode o
else
@ -915,7 +950,7 @@ exports.Slice = class Slice extends Base
if not (not @range.exclusive and +compiledText is -1)
toStr = ', ' + if @range.exclusive
compiledText
else if SIMPLENUM.test compiledText
else if to.isNumber()
"#{+compiledText + 1}"
else
compiled = to.compileToFragments o, LEVEL_ACCESS
@ -975,7 +1010,7 @@ exports.Obj = class Obj extends Base
value = prop.value
else
[key, value] = prop.base.cache o
prop = new Assign (new Value (new Literal oref), [new Access key]), value
prop = new Assign (new Value (new IdentifierLiteral oref), [new Access key]), value
if indent then answer.push @makeCode indent
answer.push prop.compileToFragments(o, LEVEL_TOP)...
if join then answer.push @makeCode join
@ -1034,24 +1069,26 @@ exports.Class = class Class extends Base
children: ['variable', 'parent', 'body']
defaultClassVariableName: '_Class'
# Figure out the appropriate name for the constructor function of this class.
determineName: ->
return null unless @variable
return @defaultClassVariableName unless @variable
[..., tail] = @variable.properties
decl = if tail
tail instanceof Access and tail.name.value
node = if tail
tail instanceof Access and tail.name
else
@variable.base.value
if decl in STRICT_PROSCRIBED
@variable.error "class variable name may not be #{decl}"
decl and= IDENTIFIER.test(decl) and decl
@variable.base
return @defaultClassVariableName unless node instanceof IdentifierLiteral
name = node.value
if name in JS_FORBIDDEN then "_#{name}" else name
# For all `this`-references and bound functions in the class definition,
# `this` is the Class being constructed.
setContext: (name) ->
@body.traverseChildren false, (node) ->
return false if node.classBody
if node instanceof Literal and node.value is 'this'
if node instanceof ThisLiteral
node.value = name
else if node instanceof Code
node.context = name if node.bound
@ -1060,7 +1097,7 @@ exports.Class = class Class extends Base
# constructor.
addBoundFunctions: (o) ->
for bvar in @boundFuncs
lhs = (new Value (new Literal "this"), [new Access bvar]).compile o
lhs = (new Value (new ThisLiteral), [new Access bvar]).compile o
@ctor.body.unshift new Literal "#{lhs} = #{utility 'bind', o}(#{lhs}, this)"
return
@ -1082,13 +1119,13 @@ exports.Class = class Class extends Base
assign = @ctor = func
else
@externalCtor = o.classScope.freeVariable 'class'
assign = new Assign new Literal(@externalCtor), func
assign = new Assign new IdentifierLiteral(@externalCtor), func
else
if assign.variable.this
func.static = yes
else
acc = if base.isComplex() then new Index base else new Access base
assign.variable = new Value(new Literal(name), [(new Access new Literal 'prototype'), acc])
assign.variable = new Value(new IdentifierLiteral(name), [(new Access new IdentifierLiteral 'prototype'), acc])
if func instanceof Code and func.bound
@boundFuncs.push base
func.bound = no
@ -1145,9 +1182,8 @@ exports.Class = class Class extends Base
if argumentsNode = @body.contains isLiteralArguments
argumentsNode.error "Class bodies shouldn't reference arguments"
name = @determineName() or '_Class'
name = "_#{name}" if name.reserved
lname = new Literal name
name = @determineName()
lname = new IdentifierLiteral name
func = new Code [], Block.wrap [@body]
args = []
o.classScope = func.makeScope o.scope
@ -1161,7 +1197,7 @@ exports.Class = class Class extends Base
@body.expressions.push lname
if @parent
superClass = new Literal o.classScope.freeVariable 'superClass', reserve: no
superClass = new IdentifierLiteral o.classScope.freeVariable 'superClass', reserve: no
@body.expressions.unshift new Extends lname, superClass
func.params.push new Param superClass
args.push @parent
@ -1227,7 +1263,13 @@ exports.Assign = class Assign extends Base
val = @value.compileToFragments o, LEVEL_LIST
@variable.front = true if isValue and @variable.base instanceof Obj
compiledName = @variable.compileToFragments o, LEVEL_LIST
return (compiledName.concat @makeCode(": "), val) if @context is 'object'
if @context is 'object'
if fragmentsToText(compiledName) in JS_FORBIDDEN
compiledName.unshift @makeCode '"'
compiledName.push @makeCode '"'
return compiledName.concat @makeCode(": "), val
answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
if o.level <= LEVEL_LIST then answer else @wrapInBraces answer
@ -1263,8 +1305,8 @@ exports.Assign = class Assign extends Base
if obj.this then obj.properties[0].name else obj
else
# A regular array pattern-match.
new Literal 0
acc = IDENTIFIER.test idx.unwrap().value
new NumberLiteral 0
acc = idx.unwrap() instanceof IdentifierLiteral
value = new Value value
value.properties.push new (if acc then Access else Index) idx
if obj.unwrap().value in RESERVED
@ -1276,7 +1318,7 @@ exports.Assign = class Assign extends Base
assigns = []
expandedIdx = false
# Make vvar into a simple variable if it isn't already.
if not IDENTIFIER.test(vvarText) or @variable.assigns(vvarText)
if value.unwrap() not instanceof IdentifierLiteral or @variable.assigns(vvarText)
assigns.push [@makeCode("#{ ref = o.scope.freeVariable 'ref' } = "), vvar...]
vvar = [@makeCode ref]
vvarText = ref
@ -1324,7 +1366,7 @@ exports.Assign = class Assign extends Base
# A regular array pattern-match.
new Literal expandedIdx or idx
name = obj.unwrap().value
acc = IDENTIFIER.test idx.unwrap().value
acc = idx.unwrap() instanceof IdentifierLiteral
val = new Value new Literal(vvarText), [new (if acc then Access else Index) idx]
val = new Op '?', val, defaultValue if defaultValue
if name? and name in RESERVED
@ -1341,7 +1383,7 @@ exports.Assign = class Assign extends Base
[left, right] = @variable.cacheReference o
# Disallow conditional assignment of undefined variables.
if not left.properties.length and left.base instanceof Literal and
left.base.value != "this" and not o.scope.check left.base.value
left.base not instanceof ThisLiteral and not o.scope.check left.base.value
@variable.error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been declared before"
if "?" in @context
o.isExistentialEquals = true
@ -1366,8 +1408,7 @@ exports.Assign = class Assign extends Base
else
fromDecl = fromRef = '0'
if to
if from instanceof Value and from.isSimpleNumber() and
to instanceof Value and to.isSimpleNumber()
if from?.isNumber() and to.isNumber()
to = to.compile(o) - fromRef
to += 1 unless exclusive
else
@ -1413,8 +1454,8 @@ exports.Code = class Code extends Base
# Handle bound functions early.
if @bound and not @context
@context = '_this'
wrapper = new Code [new Param new Literal @context], new Block [this]
boundfunc = new Call(wrapper, [new Literal 'this'])
wrapper = new Code [new Param new IdentifierLiteral @context], new Block [this]
boundfunc = new Call(wrapper, [new ThisLiteral])
boundfunc.updateLocationDataIfMissing @locationData
return boundfunc.compileNode(o)
@ -1431,7 +1472,7 @@ exports.Code = class Code extends Base
for p in @params when p not instanceof Expansion and p.name.value
o.scope.add p.name.value, 'var', yes
splats = new Assign new Value(new Arr(p.asReference o for p in @params)),
new Value new Literal 'arguments'
new Value new IdentifierLiteral 'arguments'
break
for param in @params
if param.isComplex()
@ -1502,10 +1543,10 @@ exports.Param = class Param extends Base
node = @name
if node.this
name = node.properties[0].name.value
name = "_#{name}" if name.reserved
node = new Literal o.scope.freeVariable name
name = "_#{name}" if name in JS_FORBIDDEN
node = new IdentifierLiteral o.scope.freeVariable name
else if node.isComplex()
node = new Literal o.scope.freeVariable 'arg'
node = new IdentifierLiteral o.scope.freeVariable 'arg'
node = new Value node
node = new Splat node if @splat
node.updateLocationDataIfMissing @locationData
@ -1663,7 +1704,7 @@ exports.While = class While extends Base
set = "#{@tab}#{rvar} = [];\n"
if @guard
if body.expressions.length > 1
body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
body.expressions.unshift new If (new Parens @guard).invert(), new StatementLiteral "continue"
else
body = Block.wrap [new If @guard, body] if @guard
body = [].concat @makeCode("\n"), (body.compileToFragments o, LEVEL_TOP), @makeCode("\n#{@tab}")
@ -1705,7 +1746,9 @@ exports.Op = class Op extends Base
children: ['first', 'second']
isSimpleNumber: NO
isNumber: ->
@isUnary() and @operator in ['+', '-'] and
@first instanceof Value and @first.isNumber()
isYield: ->
@operator in ['yield', 'yield*']
@ -1714,8 +1757,7 @@ exports.Op = class Op extends Base
not @second
isComplex: ->
not (@isUnary() and @operator in ['+', '-'] and
@first instanceof Value and @first.isSimpleNumber())
not @isNumber()
# Am I capable of
# [Python-style comparison chaining](http://docs.python.org/reference/expressions.html#notin)?
@ -1806,7 +1848,7 @@ exports.Op = class Op extends Base
# Keep reference to the left expression, unless this an existential assignment
compileExistence: (o) ->
if @first.isComplex()
ref = new Literal o.scope.freeVariable 'ref'
ref = new IdentifierLiteral o.scope.freeVariable 'ref'
fst = new Parens new Assign ref, @first
else
fst = @first
@ -1849,11 +1891,11 @@ exports.Op = class Op extends Base
compilePower: (o) ->
# Make a Math.pow call
pow = new Value new Literal('Math'), [new Access new Literal 'pow']
pow = new Value new IdentifierLiteral('Math'), [new Access new IdentifierLiteral 'pow']
new Call(pow, [@first, @second]).compileToFragments o
compileFloorDivision: (o) ->
floor = new Value new Literal('Math'), [new Access new Literal 'floor']
floor = new Value new IdentifierLiteral('Math'), [new Access new IdentifierLiteral 'floor']
div = new Op '/', @first, @second
new Call(floor, [div]).compileToFragments o
@ -1926,7 +1968,7 @@ exports.Try = class Try extends Base
catchPart = if @recovery
generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
placeholder = new Literal generatedErrorVariableName
placeholder = new IdentifierLiteral generatedErrorVariableName
@recovery.unshift new Assign @errorVariable, placeholder if @errorVariable
[].concat @makeCode(" catch ("), placeholder.compileToFragments(o), @makeCode(") {\n"),
@recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}")
@ -1975,7 +2017,7 @@ exports.Existence = class Existence extends Base
compileNode: (o) ->
@expression.front = @front
code = @expression.compile o, LEVEL_OP
if IDENTIFIER.test(code) and not o.scope.check code
if @expression.unwrap() instanceof IdentifierLiteral and not o.scope.check code
[cmp, cnj] = if @negated then ['===', '||'] else ['!==', '&&']
code = "typeof #{code} #{cmp} \"undefined\" #{cnj} #{code} #{cmp} null"
else
@ -2008,6 +2050,13 @@ exports.Parens = class Parens extends Base
(expr instanceof For and expr.returns))
if bare then fragments else @wrapInBraces fragments
#### StringWithInterpolations
# Strings with interpolations are in fact just a variation of `Parens` with
# string concatenation inside.
exports.StringWithInterpolations = class StringWithInterpolations extends Parens
#### For
# CoffeeScript's replacement for the *for* loop is our array and object
@ -2054,7 +2103,7 @@ exports.For = class For extends While
kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
if @step and not @range
[step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST, isComplexOrAssignable
stepNum = stepVar.match NUMBER
stepNum = Number stepVar if @step.isNumber()
name = ivar if @pattern
varPart = ''
guardPart = ''
@ -2065,20 +2114,21 @@ exports.For = class For extends While
{index: ivar, name, @step, isComplex: isComplexOrAssignable}
else
svar = @source.compile o, LEVEL_LIST
if (name or @own) and not IDENTIFIER.test svar
if (name or @own) and @source.unwrap() not instanceof IdentifierLiteral
defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
svar = ref
if name and not @pattern
namePart = "#{name} = #{svar}[#{kvar}]"
if not @object
defPart += "#{@tab}#{step};\n" if step isnt stepVar
lvar = scope.freeVariable 'len' unless @step and stepNum and down = (parseNum(stepNum[0]) < 0)
down = stepNum < 0
lvar = scope.freeVariable 'len' unless @step and stepNum? and down
declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
compare = "#{ivar} < #{lvar}"
compareDown = "#{ivar} >= 0"
if @step
if stepNum
if stepNum?
if down
compare = compareDown
declare = declareDown
@ -2095,7 +2145,7 @@ exports.For = class For extends While
body.makeReturn rvar
if @guard
if body.expressions.length > 1
body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
body.expressions.unshift new If (new Parens @guard).invert(), new StatementLiteral "continue"
else
body = Block.wrap [new If @guard, body] if @guard
if @pattern
@ -2124,7 +2174,7 @@ exports.For = class For extends While
val.properties.length is 1 and
val.properties[0].name?.value in ['call', 'apply'])
fn = val.base?.unwrapAll() or val
ref = new Literal o.scope.freeVariable 'fn'
ref = new IdentifierLiteral o.scope.freeVariable 'fn'
base = new Value ref
if val.base
[val.base, base] = [base, val]
@ -2317,17 +2367,7 @@ LEVEL_ACCESS = 6 # ...[0]
# Tabs are two spaces for pretty printing.
TAB = ' '
IDENTIFIER = /// ^ (?!\d) [$\w\x7f-\uffff]+ $ ///
SIMPLENUM = /^[+-]?\d+$/
HEXNUM = /^[+-]?0x[\da-f]+/i
NUMBER = ///^[+-]?(?:
0x[\da-f]+ | # hex
\d*\.?\d+ (?:e[+-]?\d+)? # decimal
)$///i
# Is a literal value a string/regex?
IS_STRING = /^['"]/
IS_REGEX = /^\//
SIMPLENUM = /^[+-]?\d+$/
# Helper Functions
# ----------------
@ -2346,23 +2386,13 @@ multident = (code, tab) ->
code = code.replace /\n/g, '$&' + tab
code.replace /\s+$/, ''
# Parse a number (+- decimal/hexadecimal)
# Examples: 0, -1, 1, 2e3, 2e-3, -0xfe, 0xfe
parseNum = (x) ->
if not x?
0
else if x.match HEXNUM
parseInt x, 16
else
parseFloat x
isLiteralArguments = (node) ->
node instanceof Literal and node.value is 'arguments' and not node.asKey
isLiteralThis = (node) ->
(node instanceof Literal and node.value is 'this' and not node.asKey) or
(node instanceof ThisLiteral and not node.asKey) or
(node instanceof Code and node.bound) or
(node instanceof Call and node.isSuper)
node instanceof SuperCall
isComplexOrAssignable = (node) -> node.isComplex() or node.isAssignable?()

View File

@ -483,7 +483,7 @@ IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@
# If preceded by an `IMPLICIT_FUNC`, indicates a function invocation.
IMPLICIT_CALL = [
'IDENTIFIER', 'NUMBER', 'STRING', 'STRING_START', 'JS', 'REGEX', 'REGEX_START'
'IDENTIFIER', 'NUMBER', 'INFINITY', 'STRING', 'STRING_START', 'JS', 'REGEX', 'REGEX_START'
'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL'
'UNDEFINED', 'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW'
'@', '->', '=>', '[', '(', '{', '--', '++'

View File

@ -832,14 +832,14 @@ test "#4130: unassignable in destructured param", ->
}) ->
console.log "Oh hello!"
''', '''
[stdin]:2:12: error: "null" cannot be assigned
[stdin]:2:12: error: assignment to a reserved word: null
@param : null
^^^^
'''
assertErrorFormat '''
({a: null}) ->
''', '''
[stdin]:1:6: error: "null" cannot be assigned
[stdin]:1:6: error: assignment to a reserved word: null
({a: null}) ->
^^^^
'''

View File

@ -86,6 +86,15 @@ test "self-referencing functions", ->
changeMe()
eq changeMe, 2
test "#2009: don't touch `` `this` ``", ->
nonceA = {}
nonceB = {}
fn = null
(->
fn = => this is nonceA and `this` is nonceB
).call nonceA
ok fn.call nonceB
# Parameter List Features

View File

@ -29,6 +29,8 @@ eq Number::toString, 42['toString']
eq Number::toString, 42.toString
eq Number::toString, 2e308['toString'] # Infinity
# Non-Integer Literals
@ -74,3 +76,10 @@ test "#2224: hex literals with 0b or B or E", ->
eq 176, 0x0b0
eq 177, 0x0B1
eq 225, 0xE1
test "Infinity", ->
eq Infinity, CoffeeScript.eval "0b#{Array(1024 + 1).join('1')}"
eq Infinity, CoffeeScript.eval "0o#{Array(342 + 1).join('7')}"
eq Infinity, CoffeeScript.eval "0x#{Array(256 + 1).join('f')}"
eq Infinity, CoffeeScript.eval Array(500 + 1).join('9')
eq Infinity, 2e308