mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-05-03 03:00:14 -04:00
* destructuring optimization * refactor * minor improvement, fix errors * minor refactoring * improvements * Update output * Update output
This commit is contained in:
@@ -11,7 +11,8 @@
|
||||
// format that can be fed directly into [Jison](https://github.com/zaach/jison). These
|
||||
// are read by jison in the `parser.lexer` function defined in coffeescript.coffee.
|
||||
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARABLE_LEFT_SIDE, COMPARE, COMPOUND_ASSIGN, CSX_ATTRIBUTE, CSX_FRAGMENT_IDENTIFIER, CSX_IDENTIFIER, CSX_INTERPOLATION, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_OMIT, HERE_JSTOKEN, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INSIDE_CSX, INVERSES, JSTOKEN, JS_KEYWORDS, LEADING_BLANK_LINE, LINE_BREAK, LINE_CONTINUER, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, REGEX_INVALID_ESCAPE, RELATION, RESERVED, Rewriter, SHIFT, SIMPLE_STRING_OMIT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_INVALID_ESCAPE, STRING_OMIT, STRING_SINGLE, STRING_START, TRAILING_BLANK_LINE, TRAILING_SPACES, UNARY, UNARY_MATH, UNFINISHED, UNICODE_CODE_POINT_ESCAPE, VALID_FLAGS, WHITESPACE, attachCommentsToNode, compact, count, invertLiterate, isForFrom, isUnassignable, key, locationDataToString, merge, repeat, starts, throwSyntaxError,
|
||||
indexOf = [].indexOf;
|
||||
indexOf = [].indexOf,
|
||||
slice = [].slice;
|
||||
|
||||
({Rewriter, INVERSES} = require('./rewriter'));
|
||||
|
||||
@@ -1050,7 +1051,7 @@
|
||||
if (braceInterpolator) {
|
||||
// Turn the leading and trailing `{` and `}` into parentheses. Unnecessary
|
||||
// parentheses will be removed later.
|
||||
open = nested[0], close = nested[nested.length - 1];
|
||||
[open] = nested, [close] = slice.call(nested, -1);
|
||||
open[0] = open[1] = '(';
|
||||
close[0] = close[1] = ')';
|
||||
close.origin = ['', 'end of interpolation', close[2]];
|
||||
@@ -1075,7 +1076,7 @@
|
||||
length: delimiter.length
|
||||
});
|
||||
}
|
||||
firstToken = tokens[0], lastToken = tokens[tokens.length - 1];
|
||||
[firstToken] = tokens, [lastToken] = slice.call(tokens, -1);
|
||||
firstToken[2].first_column -= delimiter.length;
|
||||
if (lastToken[1].substr(-1) === '\n') {
|
||||
lastToken[2].last_line += 1;
|
||||
@@ -1176,7 +1177,7 @@
|
||||
this.tokens.push(...tokensToPush);
|
||||
}
|
||||
if (lparen) {
|
||||
lastToken = tokens[tokens.length - 1];
|
||||
[lastToken] = slice.call(tokens, -1);
|
||||
lparen.origin = [
|
||||
'STRING',
|
||||
null,
|
||||
@@ -1202,7 +1203,7 @@
|
||||
// correctly balanced throughout the course of the token stream.
|
||||
pair(tag) {
|
||||
var lastIndent, prev, ref, ref1, wanted;
|
||||
ref = this.ends, prev = ref[ref.length - 1];
|
||||
ref = this.ends, [prev] = slice.call(ref, -1);
|
||||
if (tag !== (wanted = prev != null ? prev.tag : void 0)) {
|
||||
if ('OUTDENT' !== wanted) {
|
||||
this.error(`unmatched ${tag}`);
|
||||
@@ -1212,7 +1213,7 @@
|
||||
// el.click((event) ->
|
||||
// el.hide())
|
||||
|
||||
ref1 = this.indents, lastIndent = ref1[ref1.length - 1];
|
||||
ref1 = this.indents, [lastIndent] = slice.call(ref1, -1);
|
||||
this.outdentToken(lastIndent, true);
|
||||
return this.pair(tag);
|
||||
}
|
||||
@@ -1238,7 +1239,7 @@
|
||||
lineCount = count(string, '\n');
|
||||
column = this.chunkColumn;
|
||||
if (lineCount > 0) {
|
||||
ref = string.split('\n'), lastLine = ref[ref.length - 1];
|
||||
ref = string.split('\n'), [lastLine] = slice.call(ref, -1);
|
||||
column = lastLine.length;
|
||||
} else {
|
||||
column += string.length;
|
||||
@@ -1279,14 +1280,14 @@
|
||||
// Peek at the last tag in the token stream.
|
||||
tag() {
|
||||
var ref, token;
|
||||
ref = this.tokens, token = ref[ref.length - 1];
|
||||
ref = this.tokens, [token] = slice.call(ref, -1);
|
||||
return token != null ? token[0] : void 0;
|
||||
}
|
||||
|
||||
// Peek at the last value in the token stream.
|
||||
value(useOrigin = false) {
|
||||
var ref, ref1, token;
|
||||
ref = this.tokens, token = ref[ref.length - 1];
|
||||
ref = this.tokens, [token] = slice.call(ref, -1);
|
||||
if (useOrigin && ((token != null ? token.origin : void 0) != null)) {
|
||||
return (ref1 = token.origin) != null ? ref1[1] : void 0;
|
||||
} else {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, CSXTag, Call, Class, Code, CodeFragment, ComputedPropertyName, Elision, ExecutableClassBody, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, FuncGlyph, HereComment, HoistTarget, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, LineComment, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, Super, SuperCall, Switch, TAB, THIS, TaggedTemplateCall, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addDataToNode, attachCommentsToNode, compact, del, ends, extend, flatten, fragmentsToText, hasLineComments, indentInitial, isLiteralArguments, isLiteralThis, isUnassignable, locationDataToString, merge, moveComments, multident, shouldCacheOrIsAssignable, some, starts, throwSyntaxError, unfoldSoak, unshiftAfterComments, utility,
|
||||
indexOf = [].indexOf,
|
||||
splice = [].splice,
|
||||
slice = [].slice;
|
||||
slice1 = [].slice;
|
||||
|
||||
Error.stackTraceLimit = 2e308;
|
||||
|
||||
@@ -721,7 +721,7 @@
|
||||
fragments = node.compileToFragments(o);
|
||||
if (!node.isStatement(o)) {
|
||||
fragments = indentInitial(fragments, this);
|
||||
lastFragment = fragments[fragments.length - 1];
|
||||
[lastFragment] = slice1.call(fragments, -1);
|
||||
if (!(lastFragment.code === '' || lastFragment.isComment)) {
|
||||
fragments.push(this.makeCode(';'));
|
||||
}
|
||||
@@ -1380,7 +1380,7 @@
|
||||
|
||||
isSplice() {
|
||||
var lastProp, ref1;
|
||||
ref1 = this.properties, lastProp = ref1[ref1.length - 1];
|
||||
ref1 = this.properties, [lastProp] = slice1.call(ref1, -1);
|
||||
return lastProp instanceof Slice;
|
||||
}
|
||||
|
||||
@@ -1404,7 +1404,7 @@
|
||||
// `a()[b()] ?= c` -> `(_base = a())[_name = b()] ? _base[_name] = c`
|
||||
cacheReference(o) {
|
||||
var base, bref, name, nref, ref1;
|
||||
ref1 = this.properties, name = ref1[ref1.length - 1];
|
||||
ref1 = this.properties, [name] = slice1.call(ref1, -1);
|
||||
if (this.properties.length < 2 && !this.base.shouldCache() && !(name != null ? name.shouldCache() : void 0)) {
|
||||
return [this, this]; // `a` `a.b`
|
||||
}
|
||||
@@ -2615,7 +2615,7 @@
|
||||
if (!this.variable) {
|
||||
return null;
|
||||
}
|
||||
ref1 = this.variable.properties, tail = ref1[ref1.length - 1];
|
||||
ref1 = this.variable.properties, [tail] = slice1.call(ref1, -1);
|
||||
node = tail ? tail instanceof Access && tail.name : this.variable.base;
|
||||
if (!(node instanceof IdentifierLiteral || node instanceof PropertyName)) {
|
||||
return null;
|
||||
@@ -3234,7 +3234,7 @@
|
||||
// we've been assigned to, for correct internal references. If the variable
|
||||
// has not been seen yet within the current scope, declare it.
|
||||
compileNode(o) {
|
||||
var answer, compiledName, hasSplat, isValue, j, name, objDestructAnswer, properties, prototype, ref1, ref2, ref3, ref4, ref5, val, varBase;
|
||||
var answer, compiledName, hasSplat, isValue, name, objDestructAnswer, properties, prototype, ref1, ref2, ref3, ref4, ref5, val, varBase;
|
||||
isValue = this.variable instanceof Value;
|
||||
if (isValue) {
|
||||
// When compiling `@variable`, remember if it is part of a function parameter.
|
||||
@@ -3317,7 +3317,7 @@
|
||||
if (this.value.isStatic) {
|
||||
this.value.name = this.variable.properties[0];
|
||||
} else if (((ref3 = this.variable.properties) != null ? ref3.length : void 0) >= 2) {
|
||||
ref4 = this.variable.properties, properties = 3 <= ref4.length ? slice.call(ref4, 0, j = ref4.length - 2) : (j = 0, []), prototype = ref4[j++], name = ref4[j++];
|
||||
ref4 = this.variable.properties, [...properties] = ref4, [prototype, name] = splice.call(properties, -2);
|
||||
if (((ref5 = prototype.name) != null ? ref5.value : void 0) === 'prototype') {
|
||||
this.value.name = name;
|
||||
}
|
||||
@@ -3464,7 +3464,7 @@
|
||||
// Brief implementation of recursive pattern matching, when assigning array or
|
||||
// object literals to a value. Peeks at their properties to assign inner names.
|
||||
compileDestructuring(o) {
|
||||
var acc, assigns, code, defaultValue, expandedIdx, fragments, i, idx, isObject, ivar, j, len1, message, name, obj, objects, olen, ref, rest, top, val, value, vvar, vvarText;
|
||||
var assignObjects, assigns, code, compSlice, compSplice, complexObjects, expIdx, expans, fragments, hasObjAssigns, hasObjSpreads, i, isArray, isExpans, isObject, isSplat, leftObjs, loopObjects, obj, objIsUnassignable, objects, olen, processObjects, ref, refExp, restVar, rightObjs, slicer, splats, splatsAndExpans, top, value, vvar, vvarText;
|
||||
top = o.level === LEVEL_TOP;
|
||||
({value} = this);
|
||||
({objects} = this.variable.base);
|
||||
@@ -3484,53 +3484,45 @@
|
||||
if (olen === 1 && obj instanceof Expansion) {
|
||||
obj.error('Destructuring assignment has no target');
|
||||
}
|
||||
isObject = this.variable.isObject();
|
||||
// Special case for when there's only one thing destructured off of
|
||||
// something. `{a} = b`, `[a] = b`, `{a: b} = c`
|
||||
if (top && olen === 1 && !(obj instanceof Splat)) {
|
||||
// Pick the property straight off the value when there’s just one to pick
|
||||
// (no need to cache the value into a variable).
|
||||
defaultValue = void 0;
|
||||
if (obj instanceof Assign && obj.context === 'object') {
|
||||
({
|
||||
// A regular object pattern-match.
|
||||
variable: {
|
||||
base: idx
|
||||
},
|
||||
value: obj
|
||||
} = obj);
|
||||
if (obj instanceof Assign) {
|
||||
defaultValue = obj.value;
|
||||
obj = obj.variable;
|
||||
// Count all `Splats`: [a, b, c..., d, e]
|
||||
splats = (function() {
|
||||
var j, len1, results;
|
||||
results = [];
|
||||
for (i = j = 0, len1 = objects.length; j < len1; i = ++j) {
|
||||
obj = objects[i];
|
||||
if (obj instanceof Splat) {
|
||||
results.push(i);
|
||||
}
|
||||
} else {
|
||||
if (obj instanceof Assign) {
|
||||
defaultValue = obj.value;
|
||||
obj = obj.variable;
|
||||
}
|
||||
return results;
|
||||
})();
|
||||
// Count all `Expansions`: [a, b, ..., c, d]
|
||||
expans = (function() {
|
||||
var j, len1, results;
|
||||
results = [];
|
||||
for (i = j = 0, len1 = objects.length; j < len1; i = ++j) {
|
||||
obj = objects[i];
|
||||
if (obj instanceof Expansion) {
|
||||
results.push(i);
|
||||
}
|
||||
// A shorthand `{a, b, @c} = val` pattern-match.
|
||||
// A regular array pattern-match.
|
||||
idx = isObject ? obj.this ? obj.properties[0].name : new PropertyName(obj.unwrap().value) : new NumberLiteral(0);
|
||||
}
|
||||
acc = idx.unwrap() instanceof PropertyName;
|
||||
value = new Value(value);
|
||||
value.properties.push(new (acc ? Access : Index)(idx));
|
||||
message = isUnassignable(obj.unwrap().value);
|
||||
if (message) {
|
||||
obj.error(message);
|
||||
}
|
||||
if (defaultValue) {
|
||||
defaultValue.isDefaultValue = true;
|
||||
value = new Op('?', value, defaultValue);
|
||||
}
|
||||
return new Assign(obj, value, null, {
|
||||
param: this.param
|
||||
}).compileToFragments(o, LEVEL_TOP);
|
||||
return results;
|
||||
})();
|
||||
// Combine splats and expansions.
|
||||
splatsAndExpans = [...splats, ...expans];
|
||||
// Show error if there is more than one `Splat`, or `Expansion`.
|
||||
// Examples: [a, b, c..., d, e, f...], [a, b, ..., c, d, ...], [a, b, ..., c, d, e...]
|
||||
if (splatsAndExpans.length > 1) {
|
||||
// Sort 'splatsAndExpans' so we can show error at first disallowed token.
|
||||
objects[splatsAndExpans.sort()[1]].error("multiple splats/expansions are disallowed in an assignment");
|
||||
}
|
||||
isSplat = splats.length;
|
||||
isExpans = expans.length;
|
||||
isObject = this.variable.isObject();
|
||||
isArray = this.variable.isArray();
|
||||
vvar = value.compileToFragments(o, LEVEL_LIST);
|
||||
vvarText = fragmentsToText(vvar);
|
||||
assigns = [];
|
||||
expandedIdx = false;
|
||||
// At this point, there are several things to destructure. So the `fn()` in
|
||||
// `{a, b} = fn()` must be cached, for example. Make vvar into a simple
|
||||
// variable if it isn’t already.
|
||||
@@ -3540,100 +3532,176 @@
|
||||
vvar = [this.makeCode(ref)];
|
||||
vvarText = ref;
|
||||
}
|
||||
// And here comes the big loop that handles all of these cases:
|
||||
// `[a, b] = c`
|
||||
// `[a..., b] = c`
|
||||
// `[..., a, b] = c`
|
||||
// `[@a, b] = c`
|
||||
// `[a = 1, b] = c`
|
||||
// `{a, b} = c`
|
||||
// `{@a, b} = c`
|
||||
// `{a = 1, b} = c`
|
||||
// etc.
|
||||
for (i = j = 0, len1 = objects.length; j < len1; i = ++j) {
|
||||
obj = objects[i];
|
||||
idx = i;
|
||||
if (!expandedIdx && obj instanceof Splat) {
|
||||
name = obj.name.unwrap().value;
|
||||
obj = obj.unwrap();
|
||||
val = `${olen} <= ${vvarText}.length ? ${utility('slice', o)}.call(${vvarText}, ${i}`;
|
||||
rest = olen - i - 1;
|
||||
if (rest !== 0) {
|
||||
ivar = o.scope.freeVariable('i', {
|
||||
single: true
|
||||
});
|
||||
val += `, ${ivar} = ${vvarText}.length - ${rest}) : (${ivar} = ${i}, [])`;
|
||||
} else {
|
||||
val += ") : []";
|
||||
slicer = function(type) {
|
||||
return function(vvar, start, end = false) {
|
||||
var args, slice;
|
||||
args = [new IdentifierLiteral(vvar), new NumberLiteral(start)];
|
||||
if (end) {
|
||||
args.push(new NumberLiteral(end));
|
||||
}
|
||||
val = new Literal(val);
|
||||
expandedIdx = `${ivar}++`;
|
||||
} else if (!expandedIdx && obj instanceof Expansion) {
|
||||
rest = olen - i - 1;
|
||||
if (rest !== 0) {
|
||||
if (rest === 1) {
|
||||
expandedIdx = `${vvarText}.length - 1`;
|
||||
} else {
|
||||
ivar = o.scope.freeVariable('i', {
|
||||
single: true
|
||||
});
|
||||
val = new Literal(`${ivar} = ${vvarText}.length - ${rest}`);
|
||||
expandedIdx = `${ivar}++`;
|
||||
assigns.push(val.compileToFragments(o, LEVEL_LIST));
|
||||
}
|
||||
slice = new Value(new IdentifierLiteral(utility(type, o)), [new Access(new PropertyName('call'))]);
|
||||
return new Value(new Call(slice, args));
|
||||
};
|
||||
};
|
||||
// Helper which outputs `[].slice` code.
|
||||
compSlice = slicer("slice");
|
||||
// Helper which outputs `[].splice` code.
|
||||
compSplice = slicer("splice");
|
||||
// Check if `objects` array contains object spread (`{a, r...}`), e.g. `[a, b, {c, r...}]`.
|
||||
hasObjSpreads = function(objs) {
|
||||
var j, len1, results;
|
||||
results = [];
|
||||
for (i = j = 0, len1 = objs.length; j < len1; i = ++j) {
|
||||
obj = objs[i];
|
||||
if (obj.base instanceof Obj && obj.base.hasSplat()) {
|
||||
results.push(i);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
if (obj instanceof Splat || obj instanceof Expansion) {
|
||||
obj.error("multiple splats/expansions are disallowed in an assignment");
|
||||
}
|
||||
return results;
|
||||
};
|
||||
// Check if `objects` array contains any instance of `Assign`, e.g. {a:1}.
|
||||
hasObjAssigns = function(objs) {
|
||||
var j, len1, results;
|
||||
results = [];
|
||||
for (i = j = 0, len1 = objs.length; j < len1; i = ++j) {
|
||||
obj = objs[i];
|
||||
if (obj instanceof Assign && obj.context === 'object') {
|
||||
results.push(i);
|
||||
}
|
||||
defaultValue = void 0;
|
||||
}
|
||||
return results;
|
||||
};
|
||||
// Check if `objects` array contains any unassignable object.
|
||||
objIsUnassignable = function(objs) {
|
||||
var j, len1;
|
||||
for (j = 0, len1 = objs.length; j < len1; j++) {
|
||||
obj = objs[j];
|
||||
if (!obj.isAssignable()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// `objects` are complex when there is object spread ({a...}), object assign ({a:1}),
|
||||
// unassignable object, or just a single node.
|
||||
complexObjects = function(objs) {
|
||||
return hasObjSpreads(objs).length || hasObjAssigns(objs).length || objIsUnassignable(objs) || olen === 1;
|
||||
};
|
||||
// "Complex" `objects` are processed in a loop.
|
||||
// Examples: [a, b, {c, r...}, d], [a, ..., {b, r...}, c, d]
|
||||
loopObjects = (objs, vvarTxt) => {
|
||||
var acc, idx, j, len1, message, objSpreads, results, vval;
|
||||
objSpreads = hasObjSpreads(objs);
|
||||
results = [];
|
||||
for (i = j = 0, len1 = objs.length; j < len1; i = ++j) {
|
||||
obj = objs[i];
|
||||
if (obj instanceof Elision) {
|
||||
// `Elision` can be skipped.
|
||||
continue;
|
||||
}
|
||||
// If `obj` is {a: 1}
|
||||
if (obj instanceof Assign && obj.context === 'object') {
|
||||
({
|
||||
// A regular object pattern-match.
|
||||
variable: {
|
||||
base: idx
|
||||
},
|
||||
value: obj
|
||||
value: vvar
|
||||
} = obj);
|
||||
if (obj instanceof Assign) {
|
||||
defaultValue = obj.value;
|
||||
obj = obj.variable;
|
||||
if (vvar instanceof Assign) {
|
||||
({
|
||||
variable: vvar
|
||||
} = vvar);
|
||||
}
|
||||
idx = vvar.this ? vvar.properties[0].name : new PropertyName(vvar.unwrap().value);
|
||||
acc = idx.unwrap() instanceof PropertyName;
|
||||
vval = new Value(value, [new (acc ? Access : Index)(idx)]);
|
||||
} else {
|
||||
if (obj instanceof Assign) {
|
||||
defaultValue = obj.value;
|
||||
obj = obj.variable;
|
||||
}
|
||||
// A shorthand `{a, b, @c} = val` pattern-match.
|
||||
// A regular array pattern-match.
|
||||
idx = isObject ? obj.this ? obj.properties[0].name : new PropertyName(obj.unwrap().value) : new Literal(expandedIdx || idx);
|
||||
// `obj` is [a...], {a...} or a
|
||||
vvar = (function() {
|
||||
switch (false) {
|
||||
case !(obj instanceof Splat):
|
||||
return new Value(obj.name);
|
||||
case indexOf.call(objSpreads, i) < 0:
|
||||
return new Value(obj.base);
|
||||
default:
|
||||
return obj;
|
||||
}
|
||||
})();
|
||||
vval = (function() {
|
||||
switch (false) {
|
||||
case !(obj instanceof Splat):
|
||||
return compSlice(vvarTxt, i);
|
||||
default:
|
||||
return new Value(new Literal(vvarTxt), [new Index(new NumberLiteral(i))]);
|
||||
}
|
||||
})();
|
||||
}
|
||||
name = obj.unwrap().value;
|
||||
acc = idx.unwrap() instanceof PropertyName;
|
||||
val = new Value(new Literal(vvarText), [new (acc ? Access : Index)(idx)]);
|
||||
if (defaultValue) {
|
||||
defaultValue.isDefaultValue = true;
|
||||
val = new Op('?', val, defaultValue);
|
||||
}
|
||||
}
|
||||
if (name != null) {
|
||||
message = isUnassignable(name);
|
||||
message = isUnassignable(vvar.unwrap().value);
|
||||
if (message) {
|
||||
obj.error(message);
|
||||
vvar.error(message);
|
||||
}
|
||||
}
|
||||
if (!(obj instanceof Elision)) {
|
||||
assigns.push(new Assign(obj, val, null, {
|
||||
results.push(assigns.push(new Assign(vvar, vval, null, {
|
||||
param: this.param,
|
||||
subpattern: true
|
||||
}).compileToFragments(o, LEVEL_LIST));
|
||||
} else {
|
||||
if (expandedIdx) {
|
||||
// Output `Elision` only if `idx` is `i++`, e.g. expandedIdx.
|
||||
assigns.push(idx.compileToFragments(o, LEVEL_LIST));
|
||||
}
|
||||
}).compileToFragments(o, LEVEL_LIST)));
|
||||
}
|
||||
return results;
|
||||
};
|
||||
// "Simple" `objects` can be split and compiled to arrays, [a, b, c] = arr, [a, b, c...] = arr
|
||||
assignObjects = (objs, vvarTxt) => {
|
||||
var vval;
|
||||
vvar = new Value(new Arr(objs, true));
|
||||
vval = vvarTxt instanceof Value ? vvarTxt : new Value(new Literal(vvarTxt));
|
||||
return assigns.push(new Assign(vvar, vval, null, {
|
||||
param: this.param,
|
||||
subpattern: true
|
||||
}).compileToFragments(o, LEVEL_LIST));
|
||||
};
|
||||
processObjects = function(objs, vvarTxt) {
|
||||
if (complexObjects(objs)) {
|
||||
return loopObjects(objs, vvarTxt);
|
||||
} else {
|
||||
return assignObjects(objs, vvarTxt);
|
||||
}
|
||||
};
|
||||
// In case there is `Splat` or `Expansion` in `objects`,
|
||||
// we can split array in two simple subarrays.
|
||||
// `Splat` [a, b, c..., d, e] can be split into [a, b, c...] and [d, e].
|
||||
// `Expansion` [a, b, ..., c, d] can be split into [a, b] and [c, d].
|
||||
// Examples:
|
||||
// a) `Splat`
|
||||
// CS: [a, b, c..., d, e] = arr
|
||||
// JS: [a, b, ...c] = arr, [d, e] = splice.call(c, -2)
|
||||
// b) `Expansion`
|
||||
// CS: [a, b, ..., d, e] = arr
|
||||
// JS: [a, b] = arr, [d, e] = slice.call(arr, -2)
|
||||
if (splatsAndExpans.length) {
|
||||
expIdx = splatsAndExpans[0];
|
||||
leftObjs = objects.slice(0, expIdx + (isSplat ? 1 : 0));
|
||||
rightObjs = objects.slice(expIdx + 1);
|
||||
if (leftObjs.length !== 0) {
|
||||
processObjects(leftObjs, vvarText);
|
||||
}
|
||||
if (rightObjs.length !== 0) {
|
||||
// Slice or splice `objects`.
|
||||
refExp = (function() {
|
||||
switch (false) {
|
||||
case !isSplat:
|
||||
return compSplice(objects[expIdx].unwrapAll().value, rightObjs.length * -1);
|
||||
case !isExpans:
|
||||
return compSlice(vvarText, rightObjs.length * -1);
|
||||
}
|
||||
})();
|
||||
if (complexObjects(rightObjs)) {
|
||||
restVar = refExp;
|
||||
refExp = o.scope.freeVariable('ref');
|
||||
assigns.push([this.makeCode(refExp + ' = '), ...restVar.compileToFragments(o, LEVEL_LIST)]);
|
||||
}
|
||||
processObjects(rightObjs, refExp);
|
||||
}
|
||||
} else {
|
||||
// There is no `Splat` or `Expansion` in `objects`.
|
||||
processObjects(objects, vvarText);
|
||||
}
|
||||
if (!(top || this.subpattern)) {
|
||||
assigns.push(vvar);
|
||||
@@ -5293,7 +5361,7 @@
|
||||
compileNode(o) {
|
||||
var body, bodyFragments, compare, compareDown, declare, declareDown, defPart, down, forPartFragments, fragments, guardPart, idt1, increment, index, ivar, kvar, kvarAssign, last, lvar, name, namePart, ref, ref1, resultPart, returnResult, rvar, scope, source, step, stepNum, stepVar, svar, varPart;
|
||||
body = Block.wrap([this.body]);
|
||||
ref1 = body.expressions, last = ref1[ref1.length - 1];
|
||||
ref1 = body.expressions, [last] = slice1.call(ref1, -1);
|
||||
if ((last != null ? last.jumps() : void 0) instanceof Return) {
|
||||
this.returns = false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Generated by CoffeeScript 2.1.1
|
||||
(function() {
|
||||
var LONG_FLAG, MULTI_FLAG, OPTIONAL, OptionParser, SHORT_FLAG, buildRule, buildRules, normalizeArguments, repeat,
|
||||
slice = [].slice;
|
||||
splice = [].splice;
|
||||
|
||||
({repeat} = require('./helpers'));
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
};
|
||||
|
||||
normalizeArguments = function(args, flagDict) {
|
||||
var arg, argIndex, flag, i, innerOpts, j, k, lastOpt, len, len1, multiFlags, multiOpts, needsArgOpt, positional, ref, rule, rules, singleRule, withArg;
|
||||
var arg, argIndex, flag, i, innerOpts, j, lastOpt, len, len1, multiFlags, multiOpts, needsArgOpt, positional, ref, rule, rules, singleRule, withArg;
|
||||
rules = [];
|
||||
positional = [];
|
||||
needsArgOpt = null;
|
||||
@@ -187,9 +187,9 @@
|
||||
return {rule, flag};
|
||||
});
|
||||
// Only the last flag in a multi-flag may have an argument.
|
||||
innerOpts = 2 <= multiOpts.length ? slice.call(multiOpts, 0, j = multiOpts.length - 1) : (j = 0, []), lastOpt = multiOpts[j++];
|
||||
for (k = 0, len1 = innerOpts.length; k < len1; k++) {
|
||||
({rule, flag} = innerOpts[k]);
|
||||
[...innerOpts] = multiOpts, [lastOpt] = splice.call(innerOpts, -1);
|
||||
for (j = 0, len1 = innerOpts.length; j < len1; j++) {
|
||||
({rule, flag} = innerOpts[j]);
|
||||
if (rule.hasArgument) {
|
||||
throw new Error(`cannot use option ${flag} in multi-flag ${arg} except as the last option, because it needs an argument`);
|
||||
}
|
||||
|
||||
224
src/nodes.coffee
224
src/nodes.coffee
@@ -2351,47 +2351,26 @@ exports.Assign = class Assign extends Base
|
||||
if olen is 1 and obj instanceof Expansion
|
||||
obj.error 'Destructuring assignment has no target'
|
||||
|
||||
isObject = @variable.isObject()
|
||||
# Count all `Splats`: [a, b, c..., d, e]
|
||||
splats = (i for obj, i in objects when obj instanceof Splat)
|
||||
# Count all `Expansions`: [a, b, ..., c, d]
|
||||
expans = (i for obj, i in objects when obj instanceof Expansion)
|
||||
# Combine splats and expansions.
|
||||
splatsAndExpans = [splats..., expans...]
|
||||
# Show error if there is more than one `Splat`, or `Expansion`.
|
||||
# Examples: [a, b, c..., d, e, f...], [a, b, ..., c, d, ...], [a, b, ..., c, d, e...]
|
||||
if splatsAndExpans.length > 1
|
||||
# Sort 'splatsAndExpans' so we can show error at first disallowed token.
|
||||
objects[splatsAndExpans.sort()[1]].error "multiple splats/expansions are disallowed in an assignment"
|
||||
|
||||
# Special case for when there's only one thing destructured off of
|
||||
# something. `{a} = b`, `[a] = b`, `{a: b} = c`
|
||||
if top and olen is 1 and obj not instanceof Splat
|
||||
# Pick the property straight off the value when there’s just one to pick
|
||||
# (no need to cache the value into a variable).
|
||||
defaultValue = undefined
|
||||
if obj instanceof Assign and obj.context is 'object'
|
||||
# A regular object pattern-match.
|
||||
{variable: {base: idx}, value: obj} = obj
|
||||
if obj instanceof Assign
|
||||
defaultValue = obj.value
|
||||
obj = obj.variable
|
||||
else
|
||||
if obj instanceof Assign
|
||||
defaultValue = obj.value
|
||||
obj = obj.variable
|
||||
idx = if isObject
|
||||
# A shorthand `{a, b, @c} = val` pattern-match.
|
||||
if obj.this
|
||||
obj.properties[0].name
|
||||
else
|
||||
new PropertyName obj.unwrap().value
|
||||
else
|
||||
# A regular array pattern-match.
|
||||
new NumberLiteral 0
|
||||
acc = idx.unwrap() instanceof PropertyName
|
||||
value = new Value value
|
||||
value.properties.push new (if acc then Access else Index) idx
|
||||
message = isUnassignable obj.unwrap().value
|
||||
obj.error message if message
|
||||
if defaultValue
|
||||
defaultValue.isDefaultValue = yes
|
||||
value = new Op '?', value, defaultValue
|
||||
return new Assign(obj, value, null, param: @param).compileToFragments o, LEVEL_TOP
|
||||
isSplat = splats.length
|
||||
isExpans = expans.length
|
||||
isObject = @variable.isObject()
|
||||
isArray = @variable.isArray()
|
||||
|
||||
vvar = value.compileToFragments o, LEVEL_LIST
|
||||
vvarText = fragmentsToText vvar
|
||||
assigns = []
|
||||
expandedIdx = false
|
||||
|
||||
# At this point, there are several things to destructure. So the `fn()` in
|
||||
# `{a, b} = fn()` must be cached, for example. Make vvar into a simple
|
||||
@@ -2402,79 +2381,108 @@ exports.Assign = class Assign extends Base
|
||||
vvar = [@makeCode ref]
|
||||
vvarText = ref
|
||||
|
||||
# And here comes the big loop that handles all of these cases:
|
||||
# `[a, b] = c`
|
||||
# `[a..., b] = c`
|
||||
# `[..., a, b] = c`
|
||||
# `[@a, b] = c`
|
||||
# `[a = 1, b] = c`
|
||||
# `{a, b} = c`
|
||||
# `{@a, b} = c`
|
||||
# `{a = 1, b} = c`
|
||||
# etc.
|
||||
for obj, i in objects
|
||||
idx = i
|
||||
if not expandedIdx and obj instanceof Splat
|
||||
name = obj.name.unwrap().value
|
||||
obj = obj.unwrap()
|
||||
val = "#{olen} <= #{vvarText}.length ? #{utility 'slice', o}.call(#{vvarText}, #{i}"
|
||||
rest = olen - i - 1
|
||||
if rest isnt 0
|
||||
ivar = o.scope.freeVariable 'i', single: true
|
||||
val += ", #{ivar} = #{vvarText}.length - #{rest}) : (#{ivar} = #{i}, [])"
|
||||
else
|
||||
val += ") : []"
|
||||
val = new Literal val
|
||||
expandedIdx = "#{ivar}++"
|
||||
else if not expandedIdx and obj instanceof Expansion
|
||||
rest = olen - i - 1
|
||||
if rest isnt 0
|
||||
if rest is 1
|
||||
expandedIdx = "#{vvarText}.length - 1"
|
||||
else
|
||||
ivar = o.scope.freeVariable 'i', single: true
|
||||
val = new Literal "#{ivar} = #{vvarText}.length - #{rest}"
|
||||
expandedIdx = "#{ivar}++"
|
||||
assigns.push val.compileToFragments o, LEVEL_LIST
|
||||
continue
|
||||
else
|
||||
if obj instanceof Splat or obj instanceof Expansion
|
||||
obj.error "multiple splats/expansions are disallowed in an assignment"
|
||||
defaultValue = undefined
|
||||
if obj instanceof Assign and obj.context is 'object'
|
||||
# A regular object pattern-match.
|
||||
{variable: {base: idx}, value: obj} = obj
|
||||
if obj instanceof Assign
|
||||
defaultValue = obj.value
|
||||
obj = obj.variable
|
||||
else
|
||||
if obj instanceof Assign
|
||||
defaultValue = obj.value
|
||||
obj = obj.variable
|
||||
idx = if isObject
|
||||
# A shorthand `{a, b, @c} = val` pattern-match.
|
||||
if obj.this
|
||||
obj.properties[0].name
|
||||
else
|
||||
new PropertyName obj.unwrap().value
|
||||
else
|
||||
# A regular array pattern-match.
|
||||
new Literal expandedIdx or idx
|
||||
name = obj.unwrap().value
|
||||
acc = idx.unwrap() instanceof PropertyName
|
||||
val = new Value new Literal(vvarText), [new (if acc then Access else Index) idx]
|
||||
if defaultValue
|
||||
defaultValue.isDefaultValue = yes
|
||||
val = new Op '?', val, defaultValue
|
||||
if name?
|
||||
message = isUnassignable name
|
||||
obj.error message if message
|
||||
unless obj instanceof Elision
|
||||
assigns.push new Assign(obj, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
|
||||
else
|
||||
# Output `Elision` only if `idx` is `i++`, e.g. expandedIdx.
|
||||
assigns.push idx.compileToFragments o, LEVEL_LIST if expandedIdx
|
||||
slicer = (type) -> (vvar, start, end = no) ->
|
||||
args = [new IdentifierLiteral(vvar), new NumberLiteral(start)]
|
||||
args.push new NumberLiteral end if end
|
||||
slice = new Value (new IdentifierLiteral utility type, o), [new Access new PropertyName 'call']
|
||||
new Value new Call slice, args
|
||||
|
||||
# Helper which outputs `[].slice` code.
|
||||
compSlice = slicer "slice"
|
||||
|
||||
# Helper which outputs `[].splice` code.
|
||||
compSplice = slicer "splice"
|
||||
|
||||
# Check if `objects` array contains object spread (`{a, r...}`), e.g. `[a, b, {c, r...}]`.
|
||||
hasObjSpreads = (objs) ->
|
||||
(i for obj, i in objs when obj.base instanceof Obj and obj.base.hasSplat())
|
||||
|
||||
# Check if `objects` array contains any instance of `Assign`, e.g. {a:1}.
|
||||
hasObjAssigns = (objs) ->
|
||||
(i for obj, i in objs when obj instanceof Assign and obj.context is 'object')
|
||||
|
||||
# Check if `objects` array contains any unassignable object.
|
||||
objIsUnassignable = (objs) ->
|
||||
return yes for obj in objs when not obj.isAssignable()
|
||||
no
|
||||
|
||||
# `objects` are complex when there is object spread ({a...}), object assign ({a:1}),
|
||||
# unassignable object, or just a single node.
|
||||
complexObjects = (objs) ->
|
||||
hasObjSpreads(objs).length or hasObjAssigns(objs).length or objIsUnassignable(objs) or olen is 1
|
||||
|
||||
# "Complex" `objects` are processed in a loop.
|
||||
# Examples: [a, b, {c, r...}, d], [a, ..., {b, r...}, c, d]
|
||||
loopObjects = (objs, vvarTxt) =>
|
||||
objSpreads = hasObjSpreads objs
|
||||
for obj, i in objs
|
||||
# `Elision` can be skipped.
|
||||
continue if obj instanceof Elision
|
||||
# If `obj` is {a: 1}
|
||||
if obj instanceof Assign and obj.context is 'object'
|
||||
{variable: {base: idx}, value: vvar} = obj
|
||||
{variable: vvar} = vvar if vvar instanceof Assign
|
||||
idx =
|
||||
if vvar.this
|
||||
vvar.properties[0].name
|
||||
else
|
||||
new PropertyName vvar.unwrap().value
|
||||
acc = idx.unwrap() instanceof PropertyName
|
||||
vval = new Value value, [new (if acc then Access else Index) idx]
|
||||
else
|
||||
# `obj` is [a...], {a...} or a
|
||||
vvar = switch
|
||||
when obj instanceof Splat then new Value obj.name
|
||||
when i in objSpreads then new Value obj.base
|
||||
else obj
|
||||
vval = switch
|
||||
when obj instanceof Splat then compSlice(vvarTxt, i)
|
||||
else new Value new Literal(vvarTxt), [new Index new NumberLiteral i]
|
||||
message = isUnassignable vvar.unwrap().value
|
||||
vvar.error message if message
|
||||
assigns.push new Assign(vvar, vval, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
|
||||
|
||||
# "Simple" `objects` can be split and compiled to arrays, [a, b, c] = arr, [a, b, c...] = arr
|
||||
assignObjects = (objs, vvarTxt) =>
|
||||
vvar = new Value new Arr(objs, yes)
|
||||
vval = if vvarTxt instanceof Value then vvarTxt else new Value new Literal(vvarTxt)
|
||||
assigns.push new Assign(vvar, vval, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
|
||||
|
||||
processObjects = (objs, vvarTxt) ->
|
||||
if complexObjects objs
|
||||
loopObjects objs, vvarTxt
|
||||
else
|
||||
assignObjects objs, vvarTxt
|
||||
|
||||
# In case there is `Splat` or `Expansion` in `objects`,
|
||||
# we can split array in two simple subarrays.
|
||||
# `Splat` [a, b, c..., d, e] can be split into [a, b, c...] and [d, e].
|
||||
# `Expansion` [a, b, ..., c, d] can be split into [a, b] and [c, d].
|
||||
# Examples:
|
||||
# a) `Splat`
|
||||
# CS: [a, b, c..., d, e] = arr
|
||||
# JS: [a, b, ...c] = arr, [d, e] = splice.call(c, -2)
|
||||
# b) `Expansion`
|
||||
# CS: [a, b, ..., d, e] = arr
|
||||
# JS: [a, b] = arr, [d, e] = slice.call(arr, -2)
|
||||
if splatsAndExpans.length
|
||||
expIdx = splatsAndExpans[0]
|
||||
leftObjs = objects.slice 0, expIdx + (if isSplat then 1 else 0)
|
||||
rightObjs = objects.slice expIdx + 1
|
||||
processObjects leftObjs, vvarText if leftObjs.length isnt 0
|
||||
if rightObjs.length isnt 0
|
||||
# Slice or splice `objects`.
|
||||
refExp = switch
|
||||
when isSplat then compSplice objects[expIdx].unwrapAll().value, rightObjs.length * -1
|
||||
when isExpans then compSlice vvarText, rightObjs.length * -1
|
||||
if complexObjects rightObjs
|
||||
restVar = refExp
|
||||
refExp = o.scope.freeVariable 'ref'
|
||||
assigns.push [@makeCode(refExp + ' = '), restVar.compileToFragments(o, LEVEL_LIST)...]
|
||||
processObjects rightObjs, refExp
|
||||
else
|
||||
# There is no `Splat` or `Expansion` in `objects`.
|
||||
processObjects objects, vvarText
|
||||
assigns.push vvar unless top or @subpattern
|
||||
fragments = @joinFragmentArrays assigns, ', '
|
||||
if o.level < LEVEL_LIST then fragments else @wrapInParentheses fragments
|
||||
|
||||
Reference in New Issue
Block a user