mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
0b0a9ef2c4
* 2.0.1 changelog * Version bump to 2.0.1
5697 lines
206 KiB
JavaScript
5697 lines
206 KiB
JavaScript
// Generated by CoffeeScript 2.0.1
|
||
(function() {
|
||
// `nodes.coffee` contains all of the node classes for the syntax tree. Most
|
||
// nodes are created as the result of actions in the [grammar](grammar.html),
|
||
// but some are created by other nodes as a method of code generation. To convert
|
||
// the syntax tree into a string of JavaScript code, call `compile()` on the root.
|
||
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, CSXTag, Call, Class, Code, CodeFragment, 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;
|
||
|
||
Error.stackTraceLimit = 2e308;
|
||
|
||
({Scope} = require('./scope'));
|
||
|
||
({isUnassignable, JS_FORBIDDEN} = require('./lexer'));
|
||
|
||
// Import the helpers we plan to use.
|
||
({compact, flatten, extend, merge, del, starts, ends, some, addDataToNode, attachCommentsToNode, locationDataToString, throwSyntaxError} = require('./helpers'));
|
||
|
||
// Functions required by parser.
|
||
exports.extend = extend;
|
||
|
||
exports.addDataToNode = addDataToNode;
|
||
|
||
// Constant functions for nodes that don’t need customization.
|
||
YES = function() {
|
||
return true;
|
||
};
|
||
|
||
NO = function() {
|
||
return false;
|
||
};
|
||
|
||
THIS = function() {
|
||
return this;
|
||
};
|
||
|
||
NEGATE = function() {
|
||
this.negated = !this.negated;
|
||
return this;
|
||
};
|
||
|
||
//### CodeFragment
|
||
|
||
// The various nodes defined below all compile to a collection of **CodeFragment** objects.
|
||
// A CodeFragments is a block of generated code, and the location in the source file where the code
|
||
// came from. CodeFragments can be assembled together into working code just by catting together
|
||
// all the CodeFragments' `code` snippets, in order.
|
||
exports.CodeFragment = CodeFragment = class CodeFragment {
|
||
constructor(parent, code) {
|
||
var ref1;
|
||
this.code = `${code}`;
|
||
this.type = (parent != null ? (ref1 = parent.constructor) != null ? ref1.name : void 0 : void 0) || 'unknown';
|
||
this.locationData = parent != null ? parent.locationData : void 0;
|
||
this.comments = parent != null ? parent.comments : void 0;
|
||
}
|
||
|
||
toString() {
|
||
// This is only intended for debugging.
|
||
return `${this.code}${(this.locationData ? ": " + locationDataToString(this.locationData) : '')}`;
|
||
}
|
||
|
||
};
|
||
|
||
// Convert an array of CodeFragments into a string.
|
||
fragmentsToText = function(fragments) {
|
||
var fragment;
|
||
return ((function() {
|
||
var j, len1, results;
|
||
results = [];
|
||
for (j = 0, len1 = fragments.length; j < len1; j++) {
|
||
fragment = fragments[j];
|
||
results.push(fragment.code);
|
||
}
|
||
return results;
|
||
})()).join('');
|
||
};
|
||
|
||
//### Base
|
||
|
||
// The **Base** is the abstract base class for all nodes in the syntax tree.
|
||
// Each subclass implements the `compileNode` method, which performs the
|
||
// code generation for that node. To compile a node to JavaScript,
|
||
// call `compile` on it, which wraps `compileNode` in some generic extra smarts,
|
||
// to know when the generated code needs to be wrapped up in a closure.
|
||
// An options hash is passed and cloned throughout, containing information about
|
||
// the environment from higher in the tree (such as if a returned value is
|
||
// being requested by the surrounding function), information about the current
|
||
// scope, and indentation level.
|
||
exports.Base = Base = (function() {
|
||
class Base {
|
||
compile(o, lvl) {
|
||
return fragmentsToText(this.compileToFragments(o, lvl));
|
||
}
|
||
|
||
// Occasionally a node is compiled multiple times, for example to get the name
|
||
// of a variable to add to scope tracking. When we know that a “premature”
|
||
// compilation won’t result in comments being output, set those comments aside
|
||
// so that they’re preserved for a later `compile` call that will result in
|
||
// the comments being included in the output.
|
||
compileWithoutComments(o, lvl, method = 'compile') {
|
||
var fragments, unwrapped;
|
||
if (this.comments) {
|
||
this.ignoreTheseCommentsTemporarily = this.comments;
|
||
delete this.comments;
|
||
}
|
||
unwrapped = this.unwrapAll();
|
||
if (unwrapped.comments) {
|
||
unwrapped.ignoreTheseCommentsTemporarily = unwrapped.comments;
|
||
delete unwrapped.comments;
|
||
}
|
||
fragments = this[method](o, lvl);
|
||
if (this.ignoreTheseCommentsTemporarily) {
|
||
this.comments = this.ignoreTheseCommentsTemporarily;
|
||
delete this.ignoreTheseCommentsTemporarily;
|
||
}
|
||
if (unwrapped.ignoreTheseCommentsTemporarily) {
|
||
unwrapped.comments = unwrapped.ignoreTheseCommentsTemporarily;
|
||
delete unwrapped.ignoreTheseCommentsTemporarily;
|
||
}
|
||
return fragments;
|
||
}
|
||
|
||
compileNodeWithoutComments(o, lvl) {
|
||
return this.compileWithoutComments(o, lvl, 'compileNode');
|
||
}
|
||
|
||
// Common logic for determining whether to wrap this node in a closure before
|
||
// compiling it, or to compile directly. We need to wrap if this node is a
|
||
// *statement*, and it's not a *pureStatement*, and we're not at
|
||
// the top level of a block (which would be unnecessary), and we haven't
|
||
// already been asked to return the result (because statements know how to
|
||
// return results).
|
||
compileToFragments(o, lvl) {
|
||
var fragments, node;
|
||
o = extend({}, o);
|
||
if (lvl) {
|
||
o.level = lvl;
|
||
}
|
||
node = this.unfoldSoak(o) || this;
|
||
node.tab = o.indent;
|
||
fragments = o.level === LEVEL_TOP || !node.isStatement(o) ? node.compileNode(o) : node.compileClosure(o);
|
||
this.compileCommentFragments(o, node, fragments);
|
||
return fragments;
|
||
}
|
||
|
||
compileToFragmentsWithoutComments(o, lvl) {
|
||
return this.compileWithoutComments(o, lvl, 'compileToFragments');
|
||
}
|
||
|
||
// Statements converted into expressions via closure-wrapping share a scope
|
||
// object with their parent closure, to preserve the expected lexical scope.
|
||
compileClosure(o) {
|
||
var args, argumentsNode, func, jumpNode, meth, parts, ref1, ref2;
|
||
if (jumpNode = this.jumps()) {
|
||
jumpNode.error('cannot use a pure statement in an expression');
|
||
}
|
||
o.sharedScope = true;
|
||
func = new Code([], Block.wrap([this]));
|
||
args = [];
|
||
if (this.contains((function(node) {
|
||
return node instanceof SuperCall;
|
||
}))) {
|
||
func.bound = true;
|
||
} else if ((argumentsNode = this.contains(isLiteralArguments)) || this.contains(isLiteralThis)) {
|
||
args = [new ThisLiteral];
|
||
if (argumentsNode) {
|
||
meth = 'apply';
|
||
args.push(new IdentifierLiteral('arguments'));
|
||
} else {
|
||
meth = 'call';
|
||
}
|
||
func = new Value(func, [new Access(new PropertyName(meth))]);
|
||
}
|
||
parts = (new Call(func, args)).compileNode(o);
|
||
switch (false) {
|
||
case !(func.isGenerator || ((ref1 = func.base) != null ? ref1.isGenerator : void 0)):
|
||
parts.unshift(this.makeCode("(yield* "));
|
||
parts.push(this.makeCode(")"));
|
||
break;
|
||
case !(func.isAsync || ((ref2 = func.base) != null ? ref2.isAsync : void 0)):
|
||
parts.unshift(this.makeCode("(await "));
|
||
parts.push(this.makeCode(")"));
|
||
}
|
||
return parts;
|
||
}
|
||
|
||
compileCommentFragments(o, node, fragments) {
|
||
var base1, base2, comment, commentFragment, j, len1, ref1, unshiftCommentFragment;
|
||
if (!node.comments) {
|
||
return fragments;
|
||
}
|
||
// This is where comments, that are attached to nodes as a `comments`
|
||
// property, become `CodeFragment`s. “Inline block comments,” e.g.
|
||
// `/* */`-delimited comments that are interspersed within code on a line,
|
||
// are added to the current `fragments` stream. All other fragments are
|
||
// attached as properties to the nearest preceding or following fragment,
|
||
// to remain stowaways until they get properly output in `compileComments`
|
||
// later on.
|
||
unshiftCommentFragment = function(commentFragment) {
|
||
var precedingFragment;
|
||
if (commentFragment.unshift) {
|
||
// Find the first non-comment fragment and insert `commentFragment`
|
||
// before it.
|
||
return unshiftAfterComments(fragments, commentFragment);
|
||
} else {
|
||
if (fragments.length !== 0) {
|
||
precedingFragment = fragments[fragments.length - 1];
|
||
if (commentFragment.newLine && precedingFragment.code !== '' && !/\n\s*$/.test(precedingFragment.code)) {
|
||
commentFragment.code = `\n${commentFragment.code}`;
|
||
}
|
||
}
|
||
return fragments.push(commentFragment);
|
||
}
|
||
};
|
||
ref1 = node.comments;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
comment = ref1[j];
|
||
if (!(indexOf.call(this.compiledComments, comment) < 0)) {
|
||
continue;
|
||
}
|
||
this.compiledComments.push(comment); // Don’t output this comment twice.
|
||
// For block/here comments, denoted by `###`, that are inline comments
|
||
// like `1 + ### comment ### 2`, create fragments and insert them into
|
||
// the fragments array.
|
||
// Otherwise attach comment fragments to their closest fragment for now,
|
||
// so they can be inserted into the output later after all the newlines
|
||
// have been added.
|
||
if (comment.here) { // Block comment, delimited by `###`.
|
||
commentFragment = new HereComment(comment).compileNode(o); // Line comment, delimited by `#`.
|
||
} else {
|
||
commentFragment = new LineComment(comment).compileNode(o);
|
||
}
|
||
if ((commentFragment.isHereComment && !commentFragment.newLine) || node.includeCommentFragments()) {
|
||
// Inline block comments, like `1 + /* comment */ 2`, or a node whose
|
||
// `compileToFragments` method has logic for outputting comments.
|
||
unshiftCommentFragment(commentFragment);
|
||
} else {
|
||
if (commentFragment.unshift) {
|
||
if ((base1 = fragments[0]).precedingComments == null) {
|
||
base1.precedingComments = [];
|
||
}
|
||
fragments[0].precedingComments.push(commentFragment);
|
||
} else {
|
||
if ((base2 = fragments[fragments.length - 1]).followingComments == null) {
|
||
base2.followingComments = [];
|
||
}
|
||
fragments[fragments.length - 1].followingComments.push(commentFragment);
|
||
}
|
||
}
|
||
}
|
||
return fragments;
|
||
}
|
||
|
||
// If the code generation wishes to use the result of a complex expression
|
||
// in multiple places, ensure that the expression is only ever evaluated once,
|
||
// by assigning it to a temporary variable. Pass a level to precompile.
|
||
|
||
// If `level` is passed, then returns `[val, ref]`, where `val` is the compiled value, and `ref`
|
||
// is the compiled reference. If `level` is not passed, this returns `[val, ref]` where
|
||
// the two values are raw nodes which have not been compiled.
|
||
cache(o, level, shouldCache) {
|
||
var complex, ref, sub;
|
||
complex = shouldCache != null ? shouldCache(this) : this.shouldCache();
|
||
if (complex) {
|
||
ref = new IdentifierLiteral(o.scope.freeVariable('ref'));
|
||
sub = new Assign(ref, this);
|
||
if (level) {
|
||
return [sub.compileToFragments(o, level), [this.makeCode(ref.value)]];
|
||
} else {
|
||
return [sub, ref];
|
||
}
|
||
} else {
|
||
ref = level ? this.compileToFragments(o, level) : this;
|
||
return [ref, ref];
|
||
}
|
||
}
|
||
|
||
// Occasionally it may be useful to make an expression behave as if it was 'hoisted', whereby the
|
||
// result of the expression is available before its location in the source, but the expression's
|
||
// variable scope corresponds the source position. This is used extensively to deal with executable
|
||
// class bodies in classes.
|
||
|
||
// Calling this method mutates the node, proxying the `compileNode` and `compileToFragments`
|
||
// methods to store their result for later replacing the `target` node, which is returned by the
|
||
// call.
|
||
hoist() {
|
||
var compileNode, compileToFragments, target;
|
||
this.hoisted = true;
|
||
target = new HoistTarget(this);
|
||
compileNode = this.compileNode;
|
||
compileToFragments = this.compileToFragments;
|
||
this.compileNode = function(o) {
|
||
return target.update(compileNode, o);
|
||
};
|
||
this.compileToFragments = function(o) {
|
||
return target.update(compileToFragments, o);
|
||
};
|
||
return target;
|
||
}
|
||
|
||
cacheToCodeFragments(cacheValues) {
|
||
return [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])];
|
||
}
|
||
|
||
// Construct a node that returns the current node's result.
|
||
// Note that this is overridden for smarter behavior for
|
||
// many statement nodes (e.g. If, For)...
|
||
makeReturn(res) {
|
||
var me;
|
||
me = this.unwrapAll();
|
||
if (res) {
|
||
return new Call(new Literal(`${res}.push`), [me]);
|
||
} else {
|
||
return new Return(me);
|
||
}
|
||
}
|
||
|
||
// Does this node, or any of its children, contain a node of a certain kind?
|
||
// Recursively traverses down the *children* nodes and returns the first one
|
||
// that verifies `pred`. Otherwise return undefined. `contains` does not cross
|
||
// scope boundaries.
|
||
contains(pred) {
|
||
var node;
|
||
node = void 0;
|
||
this.traverseChildren(false, function(n) {
|
||
if (pred(n)) {
|
||
node = n;
|
||
return false;
|
||
}
|
||
});
|
||
return node;
|
||
}
|
||
|
||
// Pull out the last node of a node list.
|
||
lastNode(list) {
|
||
if (list.length === 0) {
|
||
return null;
|
||
} else {
|
||
return list[list.length - 1];
|
||
}
|
||
}
|
||
|
||
// `toString` representation of the node, for inspecting the parse tree.
|
||
// This is what `coffee --nodes` prints out.
|
||
toString(idt = '', name = this.constructor.name) {
|
||
var tree;
|
||
tree = '\n' + idt + name;
|
||
if (this.soak) {
|
||
tree += '?';
|
||
}
|
||
this.eachChild(function(node) {
|
||
return tree += node.toString(idt + TAB);
|
||
});
|
||
return tree;
|
||
}
|
||
|
||
// Passes each child to a function, breaking when the function returns `false`.
|
||
eachChild(func) {
|
||
var attr, child, j, k, len1, len2, ref1, ref2;
|
||
if (!this.children) {
|
||
return this;
|
||
}
|
||
ref1 = this.children;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
attr = ref1[j];
|
||
if (this[attr]) {
|
||
ref2 = flatten([this[attr]]);
|
||
for (k = 0, len2 = ref2.length; k < len2; k++) {
|
||
child = ref2[k];
|
||
if (func(child) === false) {
|
||
return this;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return this;
|
||
}
|
||
|
||
traverseChildren(crossScope, func) {
|
||
return this.eachChild(function(child) {
|
||
var recur;
|
||
recur = func(child);
|
||
if (recur !== false) {
|
||
return child.traverseChildren(crossScope, func);
|
||
}
|
||
});
|
||
}
|
||
|
||
// `replaceInContext` will traverse children looking for a node for which `match` returns
|
||
// true. Once found, the matching node will be replaced by the result of calling `replacement`.
|
||
replaceInContext(match, replacement) {
|
||
var attr, child, children, i, j, k, len1, len2, ref1, ref2;
|
||
if (!this.children) {
|
||
return false;
|
||
}
|
||
ref1 = this.children;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
attr = ref1[j];
|
||
if (children = this[attr]) {
|
||
if (Array.isArray(children)) {
|
||
for (i = k = 0, len2 = children.length; k < len2; i = ++k) {
|
||
child = children[i];
|
||
if (match(child)) {
|
||
splice.apply(children, [i, i - i + 1].concat(ref2 = replacement(child, this))), ref2;
|
||
return true;
|
||
} else {
|
||
if (child.replaceInContext(match, replacement)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
} else if (match(children)) {
|
||
this[attr] = replacement(children, this);
|
||
return true;
|
||
} else {
|
||
if (children.replaceInContext(match, replacement)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
invert() {
|
||
return new Op('!', this);
|
||
}
|
||
|
||
unwrapAll() {
|
||
var node;
|
||
node = this;
|
||
while (node !== (node = node.unwrap())) {
|
||
continue;
|
||
}
|
||
return node;
|
||
}
|
||
|
||
// For this node and all descendents, set the location data to `locationData`
|
||
// if the location data is not already set.
|
||
updateLocationDataIfMissing(locationData) {
|
||
if (this.locationData && !this.forceUpdateLocation) {
|
||
return this;
|
||
}
|
||
delete this.forceUpdateLocation;
|
||
this.locationData = locationData;
|
||
return this.eachChild(function(child) {
|
||
return child.updateLocationDataIfMissing(locationData);
|
||
});
|
||
}
|
||
|
||
// Throw a SyntaxError associated with this node’s location.
|
||
error(message) {
|
||
return throwSyntaxError(message, this.locationData);
|
||
}
|
||
|
||
makeCode(code) {
|
||
return new CodeFragment(this, code);
|
||
}
|
||
|
||
wrapInParentheses(fragments) {
|
||
return [this.makeCode('('), ...fragments, this.makeCode(')')];
|
||
}
|
||
|
||
wrapInBraces(fragments) {
|
||
return [this.makeCode('{'), ...fragments, this.makeCode('}')];
|
||
}
|
||
|
||
// `fragmentsList` is an array of arrays of fragments. Each array in fragmentsList will be
|
||
// concatenated together, with `joinStr` added in between each, to produce a final flat array
|
||
// of fragments.
|
||
joinFragmentArrays(fragmentsList, joinStr) {
|
||
var answer, fragments, i, j, len1;
|
||
answer = [];
|
||
for (i = j = 0, len1 = fragmentsList.length; j < len1; i = ++j) {
|
||
fragments = fragmentsList[i];
|
||
if (i) {
|
||
answer.push(this.makeCode(joinStr));
|
||
}
|
||
answer = answer.concat(fragments);
|
||
}
|
||
return answer;
|
||
}
|
||
|
||
};
|
||
|
||
// Default implementations of the common node properties and methods. Nodes
|
||
// will override these with custom logic, if needed.
|
||
|
||
// `children` are the properties to recurse into when tree walking. The
|
||
// `children` list *is* the structure of the AST. The `parent` pointer, and
|
||
// the pointer to the `children` are how you can traverse the tree.
|
||
Base.prototype.children = [];
|
||
|
||
// `isStatement` has to do with “everything is an expression”. A few things
|
||
// can’t be expressions, such as `break`. Things that `isStatement` returns
|
||
// `true` for are things that can’t be used as expressions. There are some
|
||
// error messages that come from `nodes.coffee` due to statements ending up
|
||
// in expression position.
|
||
Base.prototype.isStatement = NO;
|
||
|
||
// Track comments that have been compiled into fragments, to avoid outputting
|
||
// them twice.
|
||
Base.prototype.compiledComments = [];
|
||
|
||
// `includeCommentFragments` lets `compileCommentFragments` know whether this node
|
||
// has special awareness of how to handle comments within its output.
|
||
Base.prototype.includeCommentFragments = NO;
|
||
|
||
// `jumps` tells you if an expression, or an internal part of an expression
|
||
// has a flow control construct (like `break`, or `continue`, or `return`,
|
||
// or `throw`) that jumps out of the normal flow of control and can’t be
|
||
// used as a value. This is important because things like this make no sense;
|
||
// we have to disallow them.
|
||
Base.prototype.jumps = NO;
|
||
|
||
// If `node.shouldCache() is false`, it is safe to use `node` more than once.
|
||
// Otherwise you need to store the value of `node` in a variable and output
|
||
// that variable several times instead. Kind of like this: `5` need not be
|
||
// cached. `returnFive()`, however, could have side effects as a result of
|
||
// evaluating it more than once, and therefore we need to cache it. The
|
||
// parameter is named `shouldCache` rather than `mustCache` because there are
|
||
// also cases where we might not need to cache but where we want to, for
|
||
// example a long expression that may well be idempotent but we want to cache
|
||
// for brevity.
|
||
Base.prototype.shouldCache = YES;
|
||
|
||
Base.prototype.isChainable = NO;
|
||
|
||
Base.prototype.isAssignable = NO;
|
||
|
||
Base.prototype.isNumber = NO;
|
||
|
||
Base.prototype.unwrap = THIS;
|
||
|
||
Base.prototype.unfoldSoak = NO;
|
||
|
||
// Is this node used to assign a certain variable?
|
||
Base.prototype.assigns = NO;
|
||
|
||
return Base;
|
||
|
||
})();
|
||
|
||
//### HoistTarget
|
||
|
||
// A **HoistTargetNode** represents the output location in the node tree for a hoisted node.
|
||
// See Base#hoist.
|
||
exports.HoistTarget = HoistTarget = class HoistTarget extends Base {
|
||
// Expands hoisted fragments in the given array
|
||
static expand(fragments) {
|
||
var fragment, i, j, ref1;
|
||
for (i = j = fragments.length - 1; j >= 0; i = j += -1) {
|
||
fragment = fragments[i];
|
||
if (fragment.fragments) {
|
||
splice.apply(fragments, [i, i - i + 1].concat(ref1 = this.expand(fragment.fragments))), ref1;
|
||
}
|
||
}
|
||
return fragments;
|
||
}
|
||
|
||
constructor(source1) {
|
||
super();
|
||
this.source = source1;
|
||
// Holds presentational options to apply when the source node is compiled.
|
||
this.options = {};
|
||
// Placeholder fragments to be replaced by the source node’s compilation.
|
||
this.targetFragments = {
|
||
fragments: []
|
||
};
|
||
}
|
||
|
||
isStatement(o) {
|
||
return this.source.isStatement(o);
|
||
}
|
||
|
||
// Update the target fragments with the result of compiling the source.
|
||
// Calls the given compile function with the node and options (overriden with the target
|
||
// presentational options).
|
||
update(compile, o) {
|
||
return this.targetFragments.fragments = compile.call(this.source, merge(o, this.options));
|
||
}
|
||
|
||
// Copies the target indent and level, and returns the placeholder fragments
|
||
compileToFragments(o, level) {
|
||
this.options.indent = o.indent;
|
||
this.options.level = level != null ? level : o.level;
|
||
return [this.targetFragments];
|
||
}
|
||
|
||
compileNode(o) {
|
||
return this.compileToFragments(o);
|
||
}
|
||
|
||
compileClosure(o) {
|
||
return this.compileToFragments(o);
|
||
}
|
||
|
||
};
|
||
|
||
//### Block
|
||
|
||
// The block is the list of expressions that forms the body of an
|
||
// indented block of code -- the implementation of a function, a clause in an
|
||
// `if`, `switch`, or `try`, and so on...
|
||
exports.Block = Block = (function() {
|
||
class Block extends Base {
|
||
constructor(nodes) {
|
||
super();
|
||
this.expressions = compact(flatten(nodes || []));
|
||
}
|
||
|
||
// Tack an expression on to the end of this expression list.
|
||
push(node) {
|
||
this.expressions.push(node);
|
||
return this;
|
||
}
|
||
|
||
// Remove and return the last expression of this expression list.
|
||
pop() {
|
||
return this.expressions.pop();
|
||
}
|
||
|
||
// Add an expression at the beginning of this expression list.
|
||
unshift(node) {
|
||
this.expressions.unshift(node);
|
||
return this;
|
||
}
|
||
|
||
// If this Block consists of just a single node, unwrap it by pulling
|
||
// it back out.
|
||
unwrap() {
|
||
if (this.expressions.length === 1) {
|
||
return this.expressions[0];
|
||
} else {
|
||
return this;
|
||
}
|
||
}
|
||
|
||
// Is this an empty block of code?
|
||
isEmpty() {
|
||
return !this.expressions.length;
|
||
}
|
||
|
||
isStatement(o) {
|
||
var exp, j, len1, ref1;
|
||
ref1 = this.expressions;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
exp = ref1[j];
|
||
if (exp.isStatement(o)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
jumps(o) {
|
||
var exp, j, jumpNode, len1, ref1;
|
||
ref1 = this.expressions;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
exp = ref1[j];
|
||
if (jumpNode = exp.jumps(o)) {
|
||
return jumpNode;
|
||
}
|
||
}
|
||
}
|
||
|
||
// A Block node does not return its entire body, rather it
|
||
// ensures that the final expression is returned.
|
||
makeReturn(res) {
|
||
var expr, len;
|
||
len = this.expressions.length;
|
||
while (len--) {
|
||
expr = this.expressions[len];
|
||
this.expressions[len] = expr.makeReturn(res);
|
||
if (expr instanceof Return && !expr.expression) {
|
||
this.expressions.splice(len, 1);
|
||
}
|
||
break;
|
||
}
|
||
return this;
|
||
}
|
||
|
||
// A **Block** is the only node that can serve as the root.
|
||
compileToFragments(o = {}, level) {
|
||
if (o.scope) {
|
||
return super.compileToFragments(o, level);
|
||
} else {
|
||
return this.compileRoot(o);
|
||
}
|
||
}
|
||
|
||
// Compile all expressions within the **Block** body. If we need to return
|
||
// the result, and it’s an expression, simply return it. If it’s a statement,
|
||
// ask the statement to do so.
|
||
compileNode(o) {
|
||
var answer, compiledNodes, fragments, index, j, lastFragment, len1, node, ref1, top;
|
||
this.tab = o.indent;
|
||
top = o.level === LEVEL_TOP;
|
||
compiledNodes = [];
|
||
ref1 = this.expressions;
|
||
for (index = j = 0, len1 = ref1.length; j < len1; index = ++j) {
|
||
node = ref1[index];
|
||
node = node.unwrapAll();
|
||
node = node.unfoldSoak(o) || node;
|
||
if (node instanceof Block) {
|
||
// This is a nested block. We don’t do anything special here like
|
||
// enclose it in a new scope; we just compile the statements in this
|
||
// block along with our own.
|
||
compiledNodes.push(node.compileNode(o));
|
||
} else if (node.hoisted) {
|
||
// This is a hoisted expression.
|
||
// We want to compile this and ignore the result.
|
||
node.compileToFragments(o);
|
||
} else if (top) {
|
||
node.front = true;
|
||
fragments = node.compileToFragments(o);
|
||
if (!node.isStatement(o)) {
|
||
fragments = indentInitial(fragments, this);
|
||
lastFragment = fragments[fragments.length - 1];
|
||
if (!(lastFragment.code === '' || lastFragment.isComment)) {
|
||
fragments.push(this.makeCode(';'));
|
||
}
|
||
}
|
||
compiledNodes.push(fragments);
|
||
} else {
|
||
compiledNodes.push(node.compileToFragments(o, LEVEL_LIST));
|
||
}
|
||
}
|
||
if (top) {
|
||
if (this.spaced) {
|
||
return [].concat(this.joinFragmentArrays(compiledNodes, '\n\n'), this.makeCode('\n'));
|
||
} else {
|
||
return this.joinFragmentArrays(compiledNodes, '\n');
|
||
}
|
||
}
|
||
if (compiledNodes.length) {
|
||
answer = this.joinFragmentArrays(compiledNodes, ', ');
|
||
} else {
|
||
answer = [this.makeCode('void 0')];
|
||
}
|
||
if (compiledNodes.length > 1 && o.level >= LEVEL_LIST) {
|
||
return this.wrapInParentheses(answer);
|
||
} else {
|
||
return answer;
|
||
}
|
||
}
|
||
|
||
// If we happen to be the top-level **Block**, wrap everything in a safety
|
||
// closure, unless requested not to. It would be better not to generate them
|
||
// in the first place, but for now, clean up obvious double-parentheses.
|
||
compileRoot(o) {
|
||
var fragments, j, len1, name, ref1, ref2;
|
||
o.indent = o.bare ? '' : TAB;
|
||
o.level = LEVEL_TOP;
|
||
this.spaced = true;
|
||
o.scope = new Scope(null, this, null, (ref1 = o.referencedVars) != null ? ref1 : []);
|
||
ref2 = o.locals || [];
|
||
for (j = 0, len1 = ref2.length; j < len1; j++) {
|
||
name = ref2[j];
|
||
// Mark given local variables in the root scope as parameters so they don’t
|
||
// end up being declared on this block.
|
||
o.scope.parameter(name);
|
||
}
|
||
fragments = this.compileWithDeclarations(o);
|
||
HoistTarget.expand(fragments);
|
||
fragments = this.compileComments(fragments);
|
||
if (o.bare) {
|
||
return fragments;
|
||
}
|
||
return [].concat(this.makeCode("(function() {\n"), fragments, this.makeCode("\n}).call(this);\n"));
|
||
}
|
||
|
||
// Compile the expressions body for the contents of a function, with
|
||
// declarations of all inner variables pushed up to the top.
|
||
compileWithDeclarations(o) {
|
||
var assigns, declars, exp, fragments, i, j, len1, post, ref1, rest, scope, spaced;
|
||
fragments = [];
|
||
post = [];
|
||
ref1 = this.expressions;
|
||
for (i = j = 0, len1 = ref1.length; j < len1; i = ++j) {
|
||
exp = ref1[i];
|
||
exp = exp.unwrap();
|
||
if (!(exp instanceof Literal)) {
|
||
break;
|
||
}
|
||
}
|
||
o = merge(o, {
|
||
level: LEVEL_TOP
|
||
});
|
||
if (i) {
|
||
rest = this.expressions.splice(i, 9e9);
|
||
[spaced, this.spaced] = [this.spaced, false];
|
||
[fragments, this.spaced] = [this.compileNode(o), spaced];
|
||
this.expressions = rest;
|
||
}
|
||
post = this.compileNode(o);
|
||
({scope} = o);
|
||
if (scope.expressions === this) {
|
||
declars = o.scope.hasDeclarations();
|
||
assigns = scope.hasAssignments;
|
||
if (declars || assigns) {
|
||
if (i) {
|
||
fragments.push(this.makeCode('\n'));
|
||
}
|
||
fragments.push(this.makeCode(`${this.tab}var `));
|
||
if (declars) {
|
||
fragments.push(this.makeCode(scope.declaredVariables().join(', ')));
|
||
}
|
||
if (assigns) {
|
||
if (declars) {
|
||
fragments.push(this.makeCode(`,\n${this.tab + TAB}`));
|
||
}
|
||
fragments.push(this.makeCode(scope.assignedVariables().join(`,\n${this.tab + TAB}`)));
|
||
}
|
||
fragments.push(this.makeCode(`;\n${(this.spaced ? '\n' : '')}`));
|
||
} else if (fragments.length && post.length) {
|
||
fragments.push(this.makeCode("\n"));
|
||
}
|
||
}
|
||
return fragments.concat(post);
|
||
}
|
||
|
||
compileComments(fragments) {
|
||
var code, commentFragment, fragment, fragmentIndent, fragmentIndex, indent, j, k, l, len1, len2, len3, newLineIndex, onNextLine, pastFragment, pastFragmentIndex, q, r, ref1, ref2, ref3, ref4, trail, upcomingFragment, upcomingFragmentIndex;
|
||
for (fragmentIndex = j = 0, len1 = fragments.length; j < len1; fragmentIndex = ++j) {
|
||
fragment = fragments[fragmentIndex];
|
||
// Insert comments into the output at the next or previous newline.
|
||
// If there are no newlines at which to place comments, create them.
|
||
if (fragment.precedingComments) {
|
||
// Determine the indentation level of the fragment that we are about
|
||
// to insert comments before, and use that indentation level for our
|
||
// inserted comments. At this point, the fragments’ `code` property
|
||
// is the generated output JavaScript, and CoffeeScript always
|
||
// generates output indented by two spaces; so all we need to do is
|
||
// search for a `code` property that begins with at least two spaces.
|
||
fragmentIndent = '';
|
||
ref1 = fragments.slice(0, fragmentIndex + 1);
|
||
for (k = ref1.length - 1; k >= 0; k += -1) {
|
||
pastFragment = ref1[k];
|
||
indent = /^ {2,}/m.exec(pastFragment.code);
|
||
if (indent) {
|
||
fragmentIndent = indent[0];
|
||
break;
|
||
} else if (indexOf.call(pastFragment.code, '\n') >= 0) {
|
||
break;
|
||
}
|
||
}
|
||
code = `\n${fragmentIndent}` + ((function() {
|
||
var l, len2, ref2, results;
|
||
ref2 = fragment.precedingComments;
|
||
results = [];
|
||
for (l = 0, len2 = ref2.length; l < len2; l++) {
|
||
commentFragment = ref2[l];
|
||
if (commentFragment.isHereComment && commentFragment.multiline) {
|
||
results.push(multident(commentFragment.code, fragmentIndent, false));
|
||
} else {
|
||
results.push(commentFragment.code);
|
||
}
|
||
}
|
||
return results;
|
||
})()).join(`\n${fragmentIndent}`).replace(/^(\s*)$/gm, '');
|
||
ref2 = fragments.slice(0, fragmentIndex + 1);
|
||
for (pastFragmentIndex = l = ref2.length - 1; l >= 0; pastFragmentIndex = l += -1) {
|
||
pastFragment = ref2[pastFragmentIndex];
|
||
newLineIndex = pastFragment.code.lastIndexOf('\n');
|
||
if (newLineIndex === -1) {
|
||
// Keep searching previous fragments until we can’t go back any
|
||
// further, either because there are no fragments left or we’ve
|
||
// discovered that we’re in a code block that is interpolated
|
||
// inside a string.
|
||
if (pastFragmentIndex === 0) {
|
||
pastFragment.code = '\n' + pastFragment.code;
|
||
newLineIndex = 0;
|
||
} else if (pastFragment.isStringWithInterpolations && pastFragment.code === '{') {
|
||
code = code.slice(1) + '\n'; // Move newline to end.
|
||
newLineIndex = 1;
|
||
} else {
|
||
continue;
|
||
}
|
||
}
|
||
delete fragment.precedingComments;
|
||
pastFragment.code = pastFragment.code.slice(0, newLineIndex) + code + pastFragment.code.slice(newLineIndex);
|
||
break;
|
||
}
|
||
}
|
||
// Yes, this is awfully similar to the previous `if` block, but if you
|
||
// look closely you’ll find lots of tiny differences that make this
|
||
// confusing if it were abstracted into a function that both blocks share.
|
||
if (fragment.followingComments) {
|
||
// Does the first trailing comment follow at the end of a line of code,
|
||
// like `; // Comment`, or does it start a new line after a line of code?
|
||
trail = fragment.followingComments[0].trail;
|
||
fragmentIndent = '';
|
||
// Find the indent of the next line of code, if we have any non-trailing
|
||
// comments to output. We need to first find the next newline, as these
|
||
// comments will be output after that; and then the indent of the line
|
||
// that follows the next newline.
|
||
if (!(trail && fragment.followingComments.length === 1)) {
|
||
onNextLine = false;
|
||
ref3 = fragments.slice(fragmentIndex);
|
||
for (q = 0, len2 = ref3.length; q < len2; q++) {
|
||
upcomingFragment = ref3[q];
|
||
if (!onNextLine) {
|
||
if (indexOf.call(upcomingFragment.code, '\n') >= 0) {
|
||
onNextLine = true;
|
||
} else {
|
||
continue;
|
||
}
|
||
} else {
|
||
indent = /^ {2,}/m.exec(upcomingFragment.code);
|
||
if (indent) {
|
||
fragmentIndent = indent[0];
|
||
break;
|
||
} else if (indexOf.call(upcomingFragment.code, '\n') >= 0) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// Is this comment following the indent inserted by bare mode?
|
||
// If so, there’s no need to indent this further.
|
||
code = fragmentIndex === 1 && /^\s+$/.test(fragments[0].code) ? '' : trail ? ' ' : `\n${fragmentIndent}`;
|
||
// Assemble properly indented comments.
|
||
code += ((function() {
|
||
var len3, r, ref4, results;
|
||
ref4 = fragment.followingComments;
|
||
results = [];
|
||
for (r = 0, len3 = ref4.length; r < len3; r++) {
|
||
commentFragment = ref4[r];
|
||
if (commentFragment.isHereComment && commentFragment.multiline) {
|
||
results.push(multident(commentFragment.code, fragmentIndent, false));
|
||
} else {
|
||
results.push(commentFragment.code);
|
||
}
|
||
}
|
||
return results;
|
||
})()).join(`\n${fragmentIndent}`).replace(/^(\s*)$/gm, '');
|
||
ref4 = fragments.slice(fragmentIndex);
|
||
for (upcomingFragmentIndex = r = 0, len3 = ref4.length; r < len3; upcomingFragmentIndex = ++r) {
|
||
upcomingFragment = ref4[upcomingFragmentIndex];
|
||
newLineIndex = upcomingFragment.code.indexOf('\n');
|
||
if (newLineIndex === -1) {
|
||
// Keep searching upcoming fragments until we can’t go any
|
||
// further, either because there are no fragments left or we’ve
|
||
// discovered that we’re in a code block that is interpolated
|
||
// inside a string.
|
||
if (upcomingFragmentIndex === fragments.length - 1) {
|
||
upcomingFragment.code = upcomingFragment.code + '\n';
|
||
newLineIndex = upcomingFragment.code.length;
|
||
} else if (upcomingFragment.isStringWithInterpolations && upcomingFragment.code === '}') {
|
||
code = `${code}\n`;
|
||
newLineIndex = 0;
|
||
} else {
|
||
continue;
|
||
}
|
||
}
|
||
delete fragment.followingComments;
|
||
if (upcomingFragment.code === '\n') {
|
||
// Avoid inserting extra blank lines.
|
||
code = code.replace(/^\n/, '');
|
||
}
|
||
upcomingFragment.code = upcomingFragment.code.slice(0, newLineIndex) + code + upcomingFragment.code.slice(newLineIndex);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return fragments;
|
||
}
|
||
|
||
// Wrap up the given nodes as a **Block**, unless it already happens
|
||
// to be one.
|
||
static wrap(nodes) {
|
||
if (nodes.length === 1 && nodes[0] instanceof Block) {
|
||
return nodes[0];
|
||
}
|
||
return new Block(nodes);
|
||
}
|
||
|
||
};
|
||
|
||
Block.prototype.children = ['expressions'];
|
||
|
||
return Block;
|
||
|
||
})();
|
||
|
||
//### Literal
|
||
|
||
// `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 = Literal = (function() {
|
||
class Literal extends Base {
|
||
constructor(value1) {
|
||
super();
|
||
this.value = value1;
|
||
}
|
||
|
||
assigns(name) {
|
||
return name === this.value;
|
||
}
|
||
|
||
compileNode(o) {
|
||
return [this.makeCode(this.value)];
|
||
}
|
||
|
||
toString() {
|
||
// This is only intended for debugging.
|
||
return ` ${(this.isStatement() ? super.toString() : this.constructor.name)}: ${this.value}`;
|
||
}
|
||
|
||
};
|
||
|
||
Literal.prototype.shouldCache = NO;
|
||
|
||
return Literal;
|
||
|
||
})();
|
||
|
||
exports.NumberLiteral = NumberLiteral = class NumberLiteral extends Literal {};
|
||
|
||
exports.InfinityLiteral = InfinityLiteral = class InfinityLiteral extends NumberLiteral {
|
||
compileNode() {
|
||
return [this.makeCode('2e308')];
|
||
}
|
||
|
||
};
|
||
|
||
exports.NaNLiteral = NaNLiteral = class NaNLiteral extends NumberLiteral {
|
||
constructor() {
|
||
super('NaN');
|
||
}
|
||
|
||
compileNode(o) {
|
||
var code;
|
||
code = [this.makeCode('0/0')];
|
||
if (o.level >= LEVEL_OP) {
|
||
return this.wrapInParentheses(code);
|
||
} else {
|
||
return code;
|
||
}
|
||
}
|
||
|
||
};
|
||
|
||
exports.StringLiteral = StringLiteral = class StringLiteral extends Literal {
|
||
compileNode(o) {
|
||
var res;
|
||
return res = this.csx ? [this.makeCode(this.unquote(true, true))] : super.compileNode();
|
||
}
|
||
|
||
unquote(doubleQuote = false, newLine = false) {
|
||
var unquoted;
|
||
unquoted = this.value.slice(1, -1);
|
||
if (doubleQuote) {
|
||
unquoted = unquoted.replace(/\\"/g, '"');
|
||
}
|
||
if (newLine) {
|
||
unquoted = unquoted.replace(/\\n/g, '\n');
|
||
}
|
||
return unquoted;
|
||
}
|
||
|
||
};
|
||
|
||
exports.RegexLiteral = RegexLiteral = class RegexLiteral extends Literal {};
|
||
|
||
exports.PassthroughLiteral = PassthroughLiteral = class PassthroughLiteral extends Literal {};
|
||
|
||
exports.IdentifierLiteral = IdentifierLiteral = (function() {
|
||
class IdentifierLiteral extends Literal {
|
||
eachName(iterator) {
|
||
return iterator(this);
|
||
}
|
||
|
||
};
|
||
|
||
IdentifierLiteral.prototype.isAssignable = YES;
|
||
|
||
return IdentifierLiteral;
|
||
|
||
})();
|
||
|
||
exports.CSXTag = CSXTag = class CSXTag extends IdentifierLiteral {};
|
||
|
||
exports.PropertyName = PropertyName = (function() {
|
||
class PropertyName extends Literal {};
|
||
|
||
PropertyName.prototype.isAssignable = YES;
|
||
|
||
return PropertyName;
|
||
|
||
})();
|
||
|
||
exports.StatementLiteral = StatementLiteral = (function() {
|
||
class StatementLiteral extends Literal {
|
||
jumps(o) {
|
||
if (this.value === 'break' && !((o != null ? o.loop : void 0) || (o != null ? o.block : void 0))) {
|
||
return this;
|
||
}
|
||
if (this.value === 'continue' && !(o != null ? o.loop : void 0)) {
|
||
return this;
|
||
}
|
||
}
|
||
|
||
compileNode(o) {
|
||
return [this.makeCode(`${this.tab}${this.value};`)];
|
||
}
|
||
|
||
};
|
||
|
||
StatementLiteral.prototype.isStatement = YES;
|
||
|
||
StatementLiteral.prototype.makeReturn = THIS;
|
||
|
||
return StatementLiteral;
|
||
|
||
})();
|
||
|
||
exports.ThisLiteral = ThisLiteral = class ThisLiteral extends Literal {
|
||
constructor() {
|
||
super('this');
|
||
}
|
||
|
||
compileNode(o) {
|
||
var code, ref1;
|
||
code = ((ref1 = o.scope.method) != null ? ref1.bound : void 0) ? o.scope.method.context : this.value;
|
||
return [this.makeCode(code)];
|
||
}
|
||
|
||
};
|
||
|
||
exports.UndefinedLiteral = UndefinedLiteral = class UndefinedLiteral extends Literal {
|
||
constructor() {
|
||
super('undefined');
|
||
}
|
||
|
||
compileNode(o) {
|
||
return [this.makeCode(o.level >= LEVEL_ACCESS ? '(void 0)' : 'void 0')];
|
||
}
|
||
|
||
};
|
||
|
||
exports.NullLiteral = NullLiteral = class NullLiteral extends Literal {
|
||
constructor() {
|
||
super('null');
|
||
}
|
||
|
||
};
|
||
|
||
exports.BooleanLiteral = BooleanLiteral = class BooleanLiteral extends Literal {};
|
||
|
||
//### Return
|
||
|
||
// A `return` is a *pureStatement*—wrapping it in a closure wouldn’t make sense.
|
||
exports.Return = Return = (function() {
|
||
class Return extends Base {
|
||
constructor(expression1) {
|
||
super();
|
||
this.expression = expression1;
|
||
}
|
||
|
||
compileToFragments(o, level) {
|
||
var expr, ref1;
|
||
expr = (ref1 = this.expression) != null ? ref1.makeReturn() : void 0;
|
||
if (expr && !(expr instanceof Return)) {
|
||
return expr.compileToFragments(o, level);
|
||
} else {
|
||
return super.compileToFragments(o, level);
|
||
}
|
||
}
|
||
|
||
compileNode(o) {
|
||
var answer, fragment, j, len1;
|
||
answer = [];
|
||
// TODO: If we call `expression.compile()` here twice, we’ll sometimes
|
||
// get back different results!
|
||
if (this.expression) {
|
||
answer = this.expression.compileToFragments(o, LEVEL_PAREN);
|
||
unshiftAfterComments(answer, this.makeCode(`${this.tab}return `));
|
||
// Since the `return` got indented by `@tab`, preceding comments that are
|
||
// multiline need to be indented.
|
||
for (j = 0, len1 = answer.length; j < len1; j++) {
|
||
fragment = answer[j];
|
||
if (fragment.isHereComment && indexOf.call(fragment.code, '\n') >= 0) {
|
||
fragment.code = multident(fragment.code, this.tab);
|
||
} else if (fragment.isLineComment) {
|
||
fragment.code = `${this.tab}${fragment.code}`;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
answer.push(this.makeCode(`${this.tab}return`));
|
||
}
|
||
answer.push(this.makeCode(';'));
|
||
return answer;
|
||
}
|
||
|
||
};
|
||
|
||
Return.prototype.children = ['expression'];
|
||
|
||
Return.prototype.isStatement = YES;
|
||
|
||
Return.prototype.makeReturn = THIS;
|
||
|
||
Return.prototype.jumps = THIS;
|
||
|
||
return Return;
|
||
|
||
})();
|
||
|
||
// `yield return` works exactly like `return`, except that it turns the function
|
||
// into a generator.
|
||
exports.YieldReturn = YieldReturn = class YieldReturn extends Return {
|
||
compileNode(o) {
|
||
if (o.scope.parent == null) {
|
||
this.error('yield can only occur inside functions');
|
||
}
|
||
return super.compileNode(o);
|
||
}
|
||
|
||
};
|
||
|
||
exports.AwaitReturn = AwaitReturn = class AwaitReturn extends Return {
|
||
compileNode(o) {
|
||
if (o.scope.parent == null) {
|
||
this.error('await can only occur inside functions');
|
||
}
|
||
return super.compileNode(o);
|
||
}
|
||
|
||
};
|
||
|
||
//### Value
|
||
|
||
// A value, variable or literal or parenthesized, indexed or dotted into,
|
||
// or vanilla.
|
||
exports.Value = Value = (function() {
|
||
class Value extends Base {
|
||
constructor(base, props, tag, isDefaultValue = false) {
|
||
var ref1, ref2;
|
||
super();
|
||
if (!props && base instanceof Value) {
|
||
return base;
|
||
}
|
||
this.base = base;
|
||
this.properties = props || [];
|
||
if (tag) {
|
||
this[tag] = true;
|
||
}
|
||
this.isDefaultValue = isDefaultValue;
|
||
if (((ref1 = this.base) != null ? ref1.comments : void 0) && this.base instanceof ThisLiteral && (((ref2 = this.properties[0]) != null ? ref2.name : void 0) != null)) {
|
||
moveComments(this.base, this.properties[0].name);
|
||
}
|
||
}
|
||
|
||
// Add a property (or *properties* ) `Access` to the list.
|
||
add(props) {
|
||
this.properties = this.properties.concat(props);
|
||
this.forceUpdateLocation = true;
|
||
return this;
|
||
}
|
||
|
||
hasProperties() {
|
||
return this.properties.length !== 0;
|
||
}
|
||
|
||
bareLiteral(type) {
|
||
return !this.properties.length && this.base instanceof type;
|
||
}
|
||
|
||
// Some boolean checks for the benefit of other nodes.
|
||
isArray() {
|
||
return this.bareLiteral(Arr);
|
||
}
|
||
|
||
isRange() {
|
||
return this.bareLiteral(Range);
|
||
}
|
||
|
||
shouldCache() {
|
||
return this.hasProperties() || this.base.shouldCache();
|
||
}
|
||
|
||
isAssignable() {
|
||
return this.hasProperties() || this.base.isAssignable();
|
||
}
|
||
|
||
isNumber() {
|
||
return this.bareLiteral(NumberLiteral);
|
||
}
|
||
|
||
isString() {
|
||
return this.bareLiteral(StringLiteral);
|
||
}
|
||
|
||
isRegex() {
|
||
return this.bareLiteral(RegexLiteral);
|
||
}
|
||
|
||
isUndefined() {
|
||
return this.bareLiteral(UndefinedLiteral);
|
||
}
|
||
|
||
isNull() {
|
||
return this.bareLiteral(NullLiteral);
|
||
}
|
||
|
||
isBoolean() {
|
||
return this.bareLiteral(BooleanLiteral);
|
||
}
|
||
|
||
isAtomic() {
|
||
var j, len1, node, ref1;
|
||
ref1 = this.properties.concat(this.base);
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
node = ref1[j];
|
||
if (node.soak || node instanceof Call) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
isNotCallable() {
|
||
return this.isNumber() || this.isString() || this.isRegex() || this.isArray() || this.isRange() || this.isSplice() || this.isObject() || this.isUndefined() || this.isNull() || this.isBoolean();
|
||
}
|
||
|
||
isStatement(o) {
|
||
return !this.properties.length && this.base.isStatement(o);
|
||
}
|
||
|
||
assigns(name) {
|
||
return !this.properties.length && this.base.assigns(name);
|
||
}
|
||
|
||
jumps(o) {
|
||
return !this.properties.length && this.base.jumps(o);
|
||
}
|
||
|
||
isObject(onlyGenerated) {
|
||
if (this.properties.length) {
|
||
return false;
|
||
}
|
||
return (this.base instanceof Obj) && (!onlyGenerated || this.base.generated);
|
||
}
|
||
|
||
isSplice() {
|
||
var lastProp, ref1;
|
||
ref1 = this.properties, lastProp = ref1[ref1.length - 1];
|
||
return lastProp instanceof Slice;
|
||
}
|
||
|
||
looksStatic(className) {
|
||
var ref1;
|
||
return (this.this || this.base instanceof ThisLiteral || this.base.value === className) && this.properties.length === 1 && ((ref1 = this.properties[0].name) != null ? ref1.value : void 0) !== 'prototype';
|
||
}
|
||
|
||
// The value can be unwrapped as its inner node, if there are no attached
|
||
// properties.
|
||
unwrap() {
|
||
if (this.properties.length) {
|
||
return this;
|
||
} else {
|
||
return this.base;
|
||
}
|
||
}
|
||
|
||
// A reference has base part (`this` value) and name part.
|
||
// We cache them separately for compiling complex expressions.
|
||
// `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];
|
||
if (this.properties.length < 2 && !this.base.shouldCache() && !(name != null ? name.shouldCache() : void 0)) {
|
||
return [this, this]; // `a` `a.b`
|
||
}
|
||
base = new Value(this.base, this.properties.slice(0, -1));
|
||
if (base.shouldCache()) { // `a().b`
|
||
bref = new IdentifierLiteral(o.scope.freeVariable('base'));
|
||
base = new Value(new Parens(new Assign(bref, base)));
|
||
}
|
||
if (!name) { // `a()`
|
||
return [base, bref];
|
||
}
|
||
if (name.shouldCache()) { // `a[b()]`
|
||
nref = new IdentifierLiteral(o.scope.freeVariable('name'));
|
||
name = new Index(new Assign(nref, name.index));
|
||
nref = new Index(nref);
|
||
}
|
||
return [base.add(name), new Value(bref || base.base, [nref || name])];
|
||
}
|
||
|
||
// We compile a value to JavaScript by compiling and joining each property.
|
||
// Things get much more interesting if the chain of properties has *soak*
|
||
// operators `?.` interspersed. Then we have to take care not to accidentally
|
||
// evaluate anything twice when building the soak chain.
|
||
compileNode(o) {
|
||
var fragments, j, len1, prop, props;
|
||
this.base.front = this.front;
|
||
props = this.properties;
|
||
fragments = this.base.compileToFragments(o, (props.length ? LEVEL_ACCESS : null));
|
||
if (props.length && SIMPLENUM.test(fragmentsToText(fragments))) {
|
||
fragments.push(this.makeCode('.'));
|
||
}
|
||
for (j = 0, len1 = props.length; j < len1; j++) {
|
||
prop = props[j];
|
||
fragments.push(...(prop.compileToFragments(o)));
|
||
}
|
||
return fragments;
|
||
}
|
||
|
||
// Unfold a soak into an `If`: `a?.b` -> `a.b if a?`
|
||
unfoldSoak(o) {
|
||
return this.unfoldedSoak != null ? this.unfoldedSoak : this.unfoldedSoak = (() => {
|
||
var fst, i, ifn, j, len1, prop, ref, ref1, snd;
|
||
ifn = this.base.unfoldSoak(o);
|
||
if (ifn) {
|
||
ifn.body.properties.push(...this.properties);
|
||
return ifn;
|
||
}
|
||
ref1 = this.properties;
|
||
for (i = j = 0, len1 = ref1.length; j < len1; i = ++j) {
|
||
prop = ref1[i];
|
||
if (!prop.soak) {
|
||
continue;
|
||
}
|
||
prop.soak = false;
|
||
fst = new Value(this.base, this.properties.slice(0, i));
|
||
snd = new Value(this.base, this.properties.slice(i));
|
||
if (fst.shouldCache()) {
|
||
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: true
|
||
});
|
||
}
|
||
return false;
|
||
})();
|
||
}
|
||
|
||
eachName(iterator) {
|
||
if (this.hasProperties()) {
|
||
return iterator(this);
|
||
} else if (this.base.isAssignable()) {
|
||
return this.base.eachName(iterator);
|
||
} else {
|
||
return this.error('tried to assign to unassignable value');
|
||
}
|
||
}
|
||
|
||
};
|
||
|
||
Value.prototype.children = ['base', 'properties'];
|
||
|
||
return Value;
|
||
|
||
})();
|
||
|
||
//### HereComment
|
||
|
||
// Comment delimited by `###` (becoming `/* */`).
|
||
exports.HereComment = HereComment = class HereComment extends Base {
|
||
constructor({
|
||
content: content1,
|
||
newLine: newLine1,
|
||
unshift: unshift
|
||
}) {
|
||
super();
|
||
this.content = content1;
|
||
this.newLine = newLine1;
|
||
this.unshift = unshift;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var fragment, hasLeadingMarks, j, largestIndent, leadingWhitespace, len1, line, multiline, ref1;
|
||
multiline = indexOf.call(this.content, '\n') >= 0;
|
||
hasLeadingMarks = /\n\s*[#|\*]/.test(this.content);
|
||
if (hasLeadingMarks) {
|
||
this.content = this.content.replace(/^([ \t]*)#(?=\s)/gm, ' *');
|
||
}
|
||
// Unindent multiline comments. They will be reindented later.
|
||
if (multiline) {
|
||
largestIndent = '';
|
||
ref1 = this.content.split('\n');
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
line = ref1[j];
|
||
leadingWhitespace = /^\s*/.exec(line)[0];
|
||
if (leadingWhitespace.length > largestIndent.length) {
|
||
largestIndent = leadingWhitespace;
|
||
}
|
||
}
|
||
this.content = this.content.replace(RegExp(`^(${leadingWhitespace})`, "gm"), '');
|
||
}
|
||
this.content = `/*${this.content}${(hasLeadingMarks ? ' ' : '')}*/`;
|
||
fragment = this.makeCode(this.content);
|
||
fragment.newLine = this.newLine;
|
||
fragment.unshift = this.unshift;
|
||
fragment.multiline = multiline;
|
||
// Don’t rely on `fragment.type`, which can break when the compiler is minified.
|
||
fragment.isComment = fragment.isHereComment = true;
|
||
return fragment;
|
||
}
|
||
|
||
};
|
||
|
||
//### LineComment
|
||
|
||
// Comment running from `#` to the end of a line (becoming `//`).
|
||
exports.LineComment = LineComment = class LineComment extends Base {
|
||
constructor({
|
||
content: content1,
|
||
newLine: newLine1,
|
||
unshift: unshift
|
||
}) {
|
||
super();
|
||
this.content = content1;
|
||
this.newLine = newLine1;
|
||
this.unshift = unshift;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var fragment;
|
||
fragment = this.makeCode(/^\s*$/.test(this.content) ? '' : `//${this.content}`);
|
||
fragment.newLine = this.newLine;
|
||
fragment.unshift = this.unshift;
|
||
fragment.trail = !this.newLine && !this.unshift;
|
||
// Don’t rely on `fragment.type`, which can break when the compiler is minified.
|
||
fragment.isComment = fragment.isLineComment = true;
|
||
return fragment;
|
||
}
|
||
|
||
};
|
||
|
||
//### Call
|
||
|
||
// Node for a function invocation.
|
||
exports.Call = Call = (function() {
|
||
class Call extends Base {
|
||
constructor(variable1, args1 = [], soak1, token1) {
|
||
var ref1;
|
||
super();
|
||
this.variable = variable1;
|
||
this.args = args1;
|
||
this.soak = soak1;
|
||
this.token = token1;
|
||
this.isNew = false;
|
||
if (this.variable instanceof Value && this.variable.isNotCallable()) {
|
||
this.variable.error("literal is not a function");
|
||
}
|
||
this.csx = this.variable.base instanceof CSXTag;
|
||
if (((ref1 = this.variable.base) != null ? ref1.value : void 0) === 'RegExp' && this.args.length !== 0) {
|
||
moveComments(this.variable, this.args[0]);
|
||
}
|
||
}
|
||
|
||
// When setting the location, we sometimes need to update the start location to
|
||
// account for a newly-discovered `new` operator to the left of us. This
|
||
// expands the range on the left, but not the right.
|
||
updateLocationDataIfMissing(locationData) {
|
||
var base, ref1;
|
||
if (this.locationData && this.needsUpdatedStartLocation) {
|
||
this.locationData.first_line = locationData.first_line;
|
||
this.locationData.first_column = locationData.first_column;
|
||
base = ((ref1 = this.variable) != null ? ref1.base : void 0) || this.variable;
|
||
if (base.needsUpdatedStartLocation) {
|
||
this.variable.locationData.first_line = locationData.first_line;
|
||
this.variable.locationData.first_column = locationData.first_column;
|
||
base.updateLocationDataIfMissing(locationData);
|
||
}
|
||
delete this.needsUpdatedStartLocation;
|
||
}
|
||
return super.updateLocationDataIfMissing(locationData);
|
||
}
|
||
|
||
// Tag this invocation as creating a new instance.
|
||
newInstance() {
|
||
var base, ref1;
|
||
base = ((ref1 = this.variable) != null ? ref1.base : void 0) || this.variable;
|
||
if (base instanceof Call && !base.isNew) {
|
||
base.newInstance();
|
||
} else {
|
||
this.isNew = true;
|
||
}
|
||
this.needsUpdatedStartLocation = true;
|
||
return this;
|
||
}
|
||
|
||
// Soaked chained invocations unfold into if/else ternary structures.
|
||
unfoldSoak(o) {
|
||
var call, ifn, j, left, len1, list, ref1, rite;
|
||
if (this.soak) {
|
||
if (this.variable instanceof Super) {
|
||
left = new Literal(this.variable.compile(o));
|
||
rite = new Value(left);
|
||
if (this.variable.accessor == null) {
|
||
this.variable.error("Unsupported reference to 'super'");
|
||
}
|
||
} else {
|
||
if (ifn = unfoldSoak(o, this, 'variable')) {
|
||
return ifn;
|
||
}
|
||
[left, rite] = new Value(this.variable).cacheReference(o);
|
||
}
|
||
rite = new Call(rite, this.args);
|
||
rite.isNew = this.isNew;
|
||
left = new Literal(`typeof ${left.compile(o)} === "function"`);
|
||
return new If(left, new Value(rite), {
|
||
soak: true
|
||
});
|
||
}
|
||
call = this;
|
||
list = [];
|
||
while (true) {
|
||
if (call.variable instanceof Call) {
|
||
list.push(call);
|
||
call = call.variable;
|
||
continue;
|
||
}
|
||
if (!(call.variable instanceof Value)) {
|
||
break;
|
||
}
|
||
list.push(call);
|
||
if (!((call = call.variable.base) instanceof Call)) {
|
||
break;
|
||
}
|
||
}
|
||
ref1 = list.reverse();
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
call = ref1[j];
|
||
if (ifn) {
|
||
if (call.variable instanceof Call) {
|
||
call.variable = ifn;
|
||
} else {
|
||
call.variable.base = ifn;
|
||
}
|
||
}
|
||
ifn = unfoldSoak(o, call, 'variable');
|
||
}
|
||
return ifn;
|
||
}
|
||
|
||
// Compile a vanilla function call.
|
||
compileNode(o) {
|
||
var arg, argIndex, compiledArgs, fragments, j, len1, ref1, ref2;
|
||
if (this.csx) {
|
||
return this.compileCSX(o);
|
||
}
|
||
if ((ref1 = this.variable) != null) {
|
||
ref1.front = this.front;
|
||
}
|
||
compiledArgs = [];
|
||
ref2 = this.args;
|
||
for (argIndex = j = 0, len1 = ref2.length; j < len1; argIndex = ++j) {
|
||
arg = ref2[argIndex];
|
||
if (argIndex) {
|
||
compiledArgs.push(this.makeCode(", "));
|
||
}
|
||
compiledArgs.push(...(arg.compileToFragments(o, LEVEL_LIST)));
|
||
}
|
||
fragments = [];
|
||
if (this.isNew) {
|
||
if (this.variable instanceof Super) {
|
||
this.variable.error("Unsupported reference to 'super'");
|
||
}
|
||
fragments.push(this.makeCode('new '));
|
||
}
|
||
fragments.push(...this.variable.compileToFragments(o, LEVEL_ACCESS));
|
||
fragments.push(this.makeCode('('), ...compiledArgs, this.makeCode(')'));
|
||
return fragments;
|
||
}
|
||
|
||
compileCSX(o) {
|
||
var attr, attrProps, attributes, content, fragments, j, len1, obj, ref1, tag;
|
||
[attributes, content] = this.args;
|
||
attributes.base.csx = true;
|
||
if (content != null) {
|
||
content.base.csx = true;
|
||
}
|
||
fragments = [this.makeCode('<')];
|
||
fragments.push(...(tag = this.variable.compileToFragments(o, LEVEL_ACCESS)));
|
||
if (attributes.base instanceof Arr) {
|
||
ref1 = attributes.base.objects;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
obj = ref1[j];
|
||
attr = obj.base;
|
||
attrProps = (attr != null ? attr.properties : void 0) || [];
|
||
// Catch invalid CSX attributes: <div {a:"b", props} {props} "value" />
|
||
if (!(attr instanceof Obj || attr instanceof IdentifierLiteral) || (attr instanceof Obj && !attr.generated && (attrProps.length > 1 || !(attrProps[0] instanceof Splat)))) {
|
||
obj.error("Unexpected token. Allowed CSX attributes are: id=\"val\", src={source}, {props...} or attribute.");
|
||
}
|
||
if (obj.base instanceof Obj) {
|
||
obj.base.csx = true;
|
||
}
|
||
fragments.push(this.makeCode(' '));
|
||
fragments.push(...obj.compileToFragments(o, LEVEL_PAREN));
|
||
}
|
||
}
|
||
if (content) {
|
||
fragments.push(this.makeCode('>'));
|
||
fragments.push(...content.compileNode(o, LEVEL_LIST));
|
||
fragments.push(...[this.makeCode('</'), ...tag, this.makeCode('>')]);
|
||
} else {
|
||
fragments.push(this.makeCode(' />'));
|
||
}
|
||
return fragments;
|
||
}
|
||
|
||
};
|
||
|
||
Call.prototype.children = ['variable', 'args'];
|
||
|
||
return Call;
|
||
|
||
})();
|
||
|
||
//### Super
|
||
|
||
// Takes care of converting `super()` calls into calls against the prototype's
|
||
// function of the same name.
|
||
// When `expressions` are set the call will be compiled in such a way that the
|
||
// expressions are evaluated without altering the return value of the `SuperCall`
|
||
// expression.
|
||
exports.SuperCall = SuperCall = (function() {
|
||
class SuperCall extends Call {
|
||
isStatement(o) {
|
||
var ref1;
|
||
return ((ref1 = this.expressions) != null ? ref1.length : void 0) && o.level === LEVEL_TOP;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var ref, ref1, replacement, superCall;
|
||
if (!((ref1 = this.expressions) != null ? ref1.length : void 0)) {
|
||
return super.compileNode(o);
|
||
}
|
||
superCall = new Literal(fragmentsToText(super.compileNode(o)));
|
||
replacement = new Block(this.expressions.slice());
|
||
if (o.level > LEVEL_TOP) {
|
||
// If we might be in an expression we need to cache and return the result
|
||
[superCall, ref] = superCall.cache(o, null, YES);
|
||
replacement.push(ref);
|
||
}
|
||
replacement.unshift(superCall);
|
||
return replacement.compileToFragments(o, o.level === LEVEL_TOP ? o.level : LEVEL_LIST);
|
||
}
|
||
|
||
};
|
||
|
||
SuperCall.prototype.children = Call.prototype.children.concat(['expressions']);
|
||
|
||
return SuperCall;
|
||
|
||
})();
|
||
|
||
exports.Super = Super = (function() {
|
||
class Super extends Base {
|
||
constructor(accessor) {
|
||
super();
|
||
this.accessor = accessor;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var fragments, method, name, nref, ref1, ref2, salvagedComments, variable;
|
||
method = o.scope.namedMethod();
|
||
if (!(method != null ? method.isMethod : void 0)) {
|
||
this.error('cannot use super outside of an instance method');
|
||
}
|
||
if (!((method.ctor != null) || (this.accessor != null))) {
|
||
({name, variable} = method);
|
||
if (name.shouldCache() || (name instanceof Index && name.index.isAssignable())) {
|
||
nref = new IdentifierLiteral(o.scope.parent.freeVariable('name'));
|
||
name.index = new Assign(nref, name.index);
|
||
}
|
||
this.accessor = nref != null ? new Index(nref) : name;
|
||
}
|
||
if ((ref1 = this.accessor) != null ? (ref2 = ref1.name) != null ? ref2.comments : void 0 : void 0) {
|
||
// A `super()` call gets compiled to e.g. `super.method()`, which means
|
||
// the `method` property name gets compiled for the first time here, and
|
||
// again when the `method:` property of the class gets compiled. Since
|
||
// this compilation happens first, comments attached to `method:` would
|
||
// get incorrectly output near `super.method()`, when we want them to
|
||
// get output on the second pass when `method:` is output. So set them
|
||
// aside during this compilation pass, and put them back on the object so
|
||
// that they’re there for the later compilation.
|
||
salvagedComments = this.accessor.name.comments;
|
||
delete this.accessor.name.comments;
|
||
}
|
||
fragments = (new Value(new Literal('super'), this.accessor ? [this.accessor] : [])).compileToFragments(o);
|
||
if (salvagedComments) {
|
||
attachCommentsToNode(salvagedComments, this.accessor.name);
|
||
}
|
||
return fragments;
|
||
}
|
||
|
||
};
|
||
|
||
Super.prototype.children = ['accessor'];
|
||
|
||
return Super;
|
||
|
||
})();
|
||
|
||
//### 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 = RegexWithInterpolations = class RegexWithInterpolations extends Call {
|
||
constructor(args = []) {
|
||
super(new Value(new IdentifierLiteral('RegExp')), args, false);
|
||
}
|
||
|
||
};
|
||
|
||
//### TaggedTemplateCall
|
||
exports.TaggedTemplateCall = TaggedTemplateCall = class TaggedTemplateCall extends Call {
|
||
constructor(variable, arg, soak) {
|
||
if (arg instanceof StringLiteral) {
|
||
arg = new StringWithInterpolations(Block.wrap([new Value(arg)]));
|
||
}
|
||
super(variable, [arg], soak);
|
||
}
|
||
|
||
compileNode(o) {
|
||
return this.variable.compileToFragments(o, LEVEL_ACCESS).concat(this.args[0].compileToFragments(o, LEVEL_LIST));
|
||
}
|
||
|
||
};
|
||
|
||
//### Extends
|
||
|
||
// Node to extend an object's prototype with an ancestor object.
|
||
// After `goog.inherits` from the
|
||
// [Closure Library](https://github.com/google/closure-library/blob/master/closure/goog/base.js).
|
||
exports.Extends = Extends = (function() {
|
||
class Extends extends Base {
|
||
constructor(child1, parent1) {
|
||
super();
|
||
this.child = child1;
|
||
this.parent = parent1;
|
||
}
|
||
|
||
// Hooks one constructor into another's prototype chain.
|
||
compileToFragments(o) {
|
||
return new Call(new Value(new Literal(utility('extend', o))), [this.child, this.parent]).compileToFragments(o);
|
||
}
|
||
|
||
};
|
||
|
||
Extends.prototype.children = ['child', 'parent'];
|
||
|
||
return Extends;
|
||
|
||
})();
|
||
|
||
//### Access
|
||
|
||
// A `.` access into a property of a value, or the `::` shorthand for
|
||
// an access into the object's prototype.
|
||
exports.Access = Access = (function() {
|
||
class Access extends Base {
|
||
constructor(name1, tag) {
|
||
super();
|
||
this.name = name1;
|
||
this.soak = tag === 'soak';
|
||
}
|
||
|
||
compileToFragments(o) {
|
||
var name, node;
|
||
name = this.name.compileToFragments(o);
|
||
node = this.name.unwrap();
|
||
if (node instanceof PropertyName) {
|
||
return [this.makeCode('.'), ...name];
|
||
} else {
|
||
return [this.makeCode('['), ...name, this.makeCode(']')];
|
||
}
|
||
}
|
||
|
||
};
|
||
|
||
Access.prototype.children = ['name'];
|
||
|
||
Access.prototype.shouldCache = NO;
|
||
|
||
return Access;
|
||
|
||
})();
|
||
|
||
//### Index
|
||
|
||
// A `[ ... ]` indexed access into an array or object.
|
||
exports.Index = Index = (function() {
|
||
class Index extends Base {
|
||
constructor(index1) {
|
||
super();
|
||
this.index = index1;
|
||
}
|
||
|
||
compileToFragments(o) {
|
||
return [].concat(this.makeCode("["), this.index.compileToFragments(o, LEVEL_PAREN), this.makeCode("]"));
|
||
}
|
||
|
||
shouldCache() {
|
||
return this.index.shouldCache();
|
||
}
|
||
|
||
};
|
||
|
||
Index.prototype.children = ['index'];
|
||
|
||
return Index;
|
||
|
||
})();
|
||
|
||
//### Range
|
||
|
||
// A range literal. Ranges can be used to extract portions (slices) of arrays,
|
||
// to specify a range for comprehensions, or as a value, to be expanded into the
|
||
// corresponding array of integers at runtime.
|
||
exports.Range = Range = (function() {
|
||
class Range extends Base {
|
||
constructor(from1, to1, tag) {
|
||
super();
|
||
this.from = from1;
|
||
this.to = to1;
|
||
this.exclusive = tag === 'exclusive';
|
||
this.equals = this.exclusive ? '' : '=';
|
||
}
|
||
|
||
// Compiles the range's source variables -- where it starts and where it ends.
|
||
// But only if they need to be cached to avoid double evaluation.
|
||
compileVariables(o) {
|
||
var shouldCache, step;
|
||
o = merge(o, {
|
||
top: true
|
||
});
|
||
shouldCache = del(o, 'shouldCache');
|
||
[this.fromC, this.fromVar] = this.cacheToCodeFragments(this.from.cache(o, LEVEL_LIST, shouldCache));
|
||
[this.toC, this.toVar] = this.cacheToCodeFragments(this.to.cache(o, LEVEL_LIST, shouldCache));
|
||
if (step = del(o, 'step')) {
|
||
[this.step, this.stepVar] = this.cacheToCodeFragments(step.cache(o, LEVEL_LIST, shouldCache));
|
||
}
|
||
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;
|
||
}
|
||
|
||
// When compiled normally, the range returns the contents of the *for loop*
|
||
// needed to iterate over the values in the range. Used by comprehensions.
|
||
compileNode(o) {
|
||
var cond, condPart, from, gt, idx, idxName, known, lt, namedIndex, stepPart, to, varPart;
|
||
if (!this.fromVar) {
|
||
this.compileVariables(o);
|
||
}
|
||
if (!o.index) {
|
||
return this.compileArray(o);
|
||
}
|
||
// Set up endpoints.
|
||
known = (this.fromNum != null) && (this.toNum != null);
|
||
idx = del(o, 'index');
|
||
idxName = del(o, 'name');
|
||
namedIndex = idxName && idxName !== idx;
|
||
varPart = `${idx} = ${this.fromC}`;
|
||
if (this.toC !== this.toVar) {
|
||
varPart += `, ${this.toC}`;
|
||
}
|
||
if (this.step !== this.stepVar) {
|
||
varPart += `, ${this.step}`;
|
||
}
|
||
[lt, gt] = [`${idx} <${this.equals}`, `${idx} >${this.equals}`];
|
||
// Generate the condition.
|
||
condPart = this.stepNum != null ? this.stepNum > 0 ? `${lt} ${this.toVar}` : `${gt} ${this.toVar}` : known ? ([from, to] = [this.fromNum, this.toNum], from <= to ? `${lt} ${to}` : `${gt} ${to}`) : (cond = this.stepVar ? `${this.stepVar} > 0` : `${this.fromVar} <= ${this.toVar}`, `${cond} ? ${lt} ${this.toVar} : ${gt} ${this.toVar}`);
|
||
// Generate the step.
|
||
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}`;
|
||
}
|
||
if (namedIndex) {
|
||
stepPart = `${idxName} = ${stepPart}`;
|
||
}
|
||
// The final loop body.
|
||
return [this.makeCode(`${varPart}; ${condPart}; ${stepPart}`)];
|
||
}
|
||
|
||
// When used as a value, expand the range into the equivalent array.
|
||
compileArray(o) {
|
||
var args, body, cond, hasArgs, i, idt, j, known, post, pre, range, ref1, ref2, 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 = ref1 = this.fromNum, ref2 = this.toNum; ref1 <= ref2 ? j <= ref2 : j >= ref2; ref1 <= ref2 ? j++ : j--){ results.push(j); }
|
||
return results;
|
||
}).apply(this);
|
||
if (this.exclusive) {
|
||
range.pop();
|
||
}
|
||
return [this.makeCode(`[${range.join(', ')}]`)];
|
||
}
|
||
idt = this.tab + TAB;
|
||
i = o.scope.freeVariable('i', {
|
||
single: true
|
||
});
|
||
result = o.scope.freeVariable('results');
|
||
pre = `\n${idt}${result} = [];`;
|
||
if (known) {
|
||
o.index = i;
|
||
body = fragmentsToText(this.compileNode(o));
|
||
} else {
|
||
vars = `${i} = ${this.fromC}` + (this.toC !== this.toVar ? `, ${this.toC}` : '');
|
||
cond = `${this.fromVar} <= ${this.toVar}`;
|
||
body = `var ${vars}; ${cond} ? ${i} <${this.equals} ${this.toVar} : ${i} >${this.equals} ${this.toVar}; ${cond} ? ${i}++ : ${i}--`;
|
||
}
|
||
post = `{ ${result}.push(${i}); }\n${idt}return ${result};\n${o.indent}`;
|
||
hasArgs = function(node) {
|
||
return node != null ? node.contains(isLiteralArguments) : void 0;
|
||
};
|
||
if (hasArgs(this.from) || hasArgs(this.to)) {
|
||
args = ', arguments';
|
||
}
|
||
return [this.makeCode(`(function() {${pre}\n${idt}for (${body})${post}}).apply(this${args != null ? args : ''})`)];
|
||
}
|
||
|
||
};
|
||
|
||
Range.prototype.children = ['from', 'to'];
|
||
|
||
return Range;
|
||
|
||
})();
|
||
|
||
//### Slice
|
||
|
||
// An array slice literal. Unlike JavaScript's `Array#slice`, the second parameter
|
||
// specifies the index of the end of the slice, just as the first parameter
|
||
// is the index of the beginning.
|
||
exports.Slice = Slice = (function() {
|
||
class Slice extends Base {
|
||
constructor(range1) {
|
||
super();
|
||
this.range = range1;
|
||
}
|
||
|
||
// We have to be careful when trying to slice through the end of the array,
|
||
// `9e9` is used because not all implementations respect `undefined` or `1/0`.
|
||
// `9e9` should be safe because `9e9` > `2**32`, the max array length.
|
||
compileNode(o) {
|
||
var compiled, compiledText, from, fromCompiled, to, toStr;
|
||
({to, from} = this.range);
|
||
fromCompiled = from && from.compileToFragments(o, LEVEL_PAREN) || [this.makeCode('0')];
|
||
// TODO: jwalton - move this into the 'if'?
|
||
if (to) {
|
||
compiled = to.compileToFragments(o, LEVEL_PAREN);
|
||
compiledText = fragmentsToText(compiled);
|
||
if (!(!this.range.exclusive && +compiledText === -1)) {
|
||
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 || ''})`)];
|
||
}
|
||
|
||
};
|
||
|
||
Slice.prototype.children = ['range'];
|
||
|
||
return Slice;
|
||
|
||
})();
|
||
|
||
//### Obj
|
||
|
||
// An object literal, nothing fancy.
|
||
exports.Obj = Obj = (function() {
|
||
class Obj extends Base {
|
||
constructor(props, generated = false, lhs1 = false) {
|
||
super();
|
||
this.generated = generated;
|
||
this.lhs = lhs1;
|
||
this.objects = this.properties = props || [];
|
||
}
|
||
|
||
isAssignable() {
|
||
var j, len1, message, prop, ref1;
|
||
ref1 = this.properties;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
prop = ref1[j];
|
||
// Check for reserved words.
|
||
message = isUnassignable(prop.unwrapAll().value);
|
||
if (message) {
|
||
prop.error(message);
|
||
}
|
||
if (prop instanceof Assign && prop.context === 'object') {
|
||
prop = prop.value;
|
||
}
|
||
if (!prop.isAssignable()) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
shouldCache() {
|
||
return !this.isAssignable();
|
||
}
|
||
|
||
// Check if object contains splat.
|
||
hasSplat() {
|
||
var j, len1, prop, ref1;
|
||
ref1 = this.properties;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
prop = ref1[j];
|
||
if (prop instanceof Splat) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var answer, i, idt, indent, isCompact, j, join, k, key, l, lastNode, len1, len2, len3, len4, node, prop, props, q, ref1, unwrappedVal, value;
|
||
props = this.properties;
|
||
if (this.generated) {
|
||
for (j = 0, len1 = props.length; j < len1; j++) {
|
||
node = props[j];
|
||
if (node instanceof Value) {
|
||
node.error('cannot have an implicit value in an implicit object');
|
||
}
|
||
}
|
||
}
|
||
if (this.hasSplat() && !this.csx) {
|
||
// Object spread properties. https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md
|
||
return this.compileSpread(o);
|
||
}
|
||
idt = o.indent += TAB;
|
||
lastNode = this.lastNode(this.properties);
|
||
if (this.csx) {
|
||
// CSX attributes <div id="val" attr={aaa} {props...} />
|
||
return this.compileCSXAttributes(o);
|
||
}
|
||
// If this object is the left-hand side of an assignment, all its children
|
||
// are too.
|
||
if (this.lhs) {
|
||
for (k = 0, len2 = props.length; k < len2; k++) {
|
||
prop = props[k];
|
||
if (!(prop instanceof Assign)) {
|
||
continue;
|
||
}
|
||
({value} = prop);
|
||
unwrappedVal = value.unwrapAll();
|
||
if (unwrappedVal instanceof Arr || unwrappedVal instanceof Obj) {
|
||
unwrappedVal.lhs = true;
|
||
} else if (unwrappedVal instanceof Assign) {
|
||
unwrappedVal.nestedLhs = true;
|
||
}
|
||
}
|
||
}
|
||
isCompact = true;
|
||
ref1 = this.properties;
|
||
for (l = 0, len3 = ref1.length; l < len3; l++) {
|
||
prop = ref1[l];
|
||
if (prop instanceof Assign && prop.context === 'object') {
|
||
isCompact = false;
|
||
}
|
||
}
|
||
answer = [];
|
||
answer.push(this.makeCode(isCompact ? '' : '\n'));
|
||
for (i = q = 0, len4 = props.length; q < len4; i = ++q) {
|
||
prop = props[i];
|
||
join = i === props.length - 1 ? '' : isCompact ? ', ' : prop === lastNode ? '\n' : ',\n';
|
||
indent = isCompact ? '' : idt;
|
||
key = prop instanceof Assign && prop.context === 'object' ? prop.variable : prop instanceof Assign ? (!this.lhs ? prop.operatorToken.error(`unexpected ${prop.operatorToken.value}`) : void 0, prop.variable) : prop;
|
||
if (key instanceof Value && key.hasProperties()) {
|
||
if (prop.context === 'object' || !key.this) {
|
||
key.error('invalid object key');
|
||
}
|
||
key = key.properties[0].name;
|
||
prop = new Assign(key, prop, 'object');
|
||
}
|
||
if (key === prop) {
|
||
if (prop.shouldCache()) {
|
||
[key, value] = prop.base.cache(o);
|
||
if (key instanceof IdentifierLiteral) {
|
||
key = new PropertyName(key.value);
|
||
}
|
||
prop = new Assign(key, value, 'object');
|
||
} else if (!(typeof prop.bareLiteral === "function" ? prop.bareLiteral(IdentifierLiteral) : void 0)) {
|
||
prop = new Assign(prop, prop, 'object');
|
||
}
|
||
}
|
||
if (indent) {
|
||
answer.push(this.makeCode(indent));
|
||
}
|
||
answer.push(...prop.compileToFragments(o, LEVEL_TOP));
|
||
if (join) {
|
||
answer.push(this.makeCode(join));
|
||
}
|
||
}
|
||
answer.push(this.makeCode(isCompact ? '' : `\n${this.tab}`));
|
||
answer = this.wrapInBraces(answer);
|
||
if (this.front) {
|
||
return this.wrapInParentheses(answer);
|
||
} else {
|
||
return answer;
|
||
}
|
||
}
|
||
|
||
assigns(name) {
|
||
var j, len1, prop, ref1;
|
||
ref1 = this.properties;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
prop = ref1[j];
|
||
if (prop.assigns(name)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
eachName(iterator) {
|
||
var j, len1, prop, ref1, results;
|
||
ref1 = this.properties;
|
||
results = [];
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
prop = ref1[j];
|
||
if (prop instanceof Assign && prop.context === 'object') {
|
||
prop = prop.value;
|
||
}
|
||
prop = prop.unwrapAll();
|
||
if (prop.eachName != null) {
|
||
results.push(prop.eachName(iterator));
|
||
} else {
|
||
results.push(void 0);
|
||
}
|
||
}
|
||
return results;
|
||
}
|
||
|
||
// Object spread properties. https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md
|
||
// `obj2 = {a: 1, obj..., c: 3, d: 4}` → `obj2 = _extends({}, {a: 1}, obj, {c: 3, d: 4})`
|
||
compileSpread(o) {
|
||
var _extends, addSlice, j, len1, prop, propSlices, props, slices, splatSlice;
|
||
props = this.properties;
|
||
// Store object spreads.
|
||
splatSlice = [];
|
||
propSlices = [];
|
||
slices = [];
|
||
addSlice = function() {
|
||
if (propSlices.length) {
|
||
slices.push(new Obj(propSlices));
|
||
}
|
||
if (splatSlice.length) {
|
||
slices.push(...splatSlice);
|
||
}
|
||
splatSlice = [];
|
||
return propSlices = [];
|
||
};
|
||
for (j = 0, len1 = props.length; j < len1; j++) {
|
||
prop = props[j];
|
||
if (prop instanceof Splat) {
|
||
splatSlice.push(new Value(prop.name));
|
||
addSlice();
|
||
} else {
|
||
propSlices.push(prop);
|
||
}
|
||
}
|
||
addSlice();
|
||
if (!(slices[0] instanceof Obj)) {
|
||
slices.unshift(new Obj);
|
||
}
|
||
_extends = new Value(new Literal(utility('_extends', o)));
|
||
return (new Call(_extends, slices)).compileToFragments(o);
|
||
}
|
||
|
||
compileCSXAttributes(o) {
|
||
var answer, i, j, join, len1, prop, props;
|
||
props = this.properties;
|
||
answer = [];
|
||
for (i = j = 0, len1 = props.length; j < len1; i = ++j) {
|
||
prop = props[i];
|
||
prop.csx = true;
|
||
join = i === props.length - 1 ? '' : ' ';
|
||
if (prop instanceof Splat) {
|
||
prop = new Literal(`{${prop.compile(o)}}`);
|
||
}
|
||
answer.push(...prop.compileToFragments(o, LEVEL_TOP));
|
||
answer.push(this.makeCode(join));
|
||
}
|
||
if (this.front) {
|
||
return this.wrapInParentheses(answer);
|
||
} else {
|
||
return answer;
|
||
}
|
||
}
|
||
|
||
};
|
||
|
||
Obj.prototype.children = ['properties'];
|
||
|
||
return Obj;
|
||
|
||
})();
|
||
|
||
//### Arr
|
||
|
||
// An array literal.
|
||
exports.Arr = Arr = (function() {
|
||
class Arr extends Base {
|
||
constructor(objs, lhs1 = false) {
|
||
super();
|
||
this.lhs = lhs1;
|
||
this.objects = objs || [];
|
||
}
|
||
|
||
isAssignable() {
|
||
var i, j, len1, obj, ref1;
|
||
if (!this.objects.length) {
|
||
return false;
|
||
}
|
||
ref1 = this.objects;
|
||
for (i = j = 0, len1 = ref1.length; j < len1; i = ++j) {
|
||
obj = ref1[i];
|
||
if (obj instanceof Splat && i + 1 !== this.objects.length) {
|
||
return false;
|
||
}
|
||
if (!(obj.isAssignable() && (!obj.isAtomic || obj.isAtomic()))) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
shouldCache() {
|
||
return !this.isAssignable();
|
||
}
|
||
|
||
compileNode(o) {
|
||
var answer, compiledObjs, fragment, fragmentIndex, fragments, includesLineCommentsOnNonFirstElement, index, j, k, l, len1, len2, len3, len4, len5, obj, objIndex, q, r, ref1, unwrappedObj;
|
||
if (!this.objects.length) {
|
||
return [this.makeCode('[]')];
|
||
}
|
||
o.indent += TAB;
|
||
answer = [];
|
||
ref1 = this.objects;
|
||
for (objIndex = j = 0, len1 = ref1.length; j < len1; objIndex = ++j) {
|
||
obj = ref1[objIndex];
|
||
unwrappedObj = obj.unwrapAll();
|
||
// Let `compileCommentFragments` know to intersperse block comments
|
||
// into the fragments created when compiling this array.
|
||
if (unwrappedObj.comments && unwrappedObj.comments.filter(function(comment) {
|
||
return !comment.here;
|
||
}).length === 0) {
|
||
unwrappedObj.includeCommentFragments = YES;
|
||
}
|
||
// If this array is the left-hand side of an assignment, all its children
|
||
// are too.
|
||
if (this.lhs) {
|
||
if (unwrappedObj instanceof Arr || unwrappedObj instanceof Obj) {
|
||
unwrappedObj.lhs = true;
|
||
}
|
||
}
|
||
}
|
||
compiledObjs = (function() {
|
||
var k, len2, ref2, results;
|
||
ref2 = this.objects;
|
||
results = [];
|
||
for (k = 0, len2 = ref2.length; k < len2; k++) {
|
||
obj = ref2[k];
|
||
results.push(obj.compileToFragments(o, LEVEL_LIST));
|
||
}
|
||
return results;
|
||
}).call(this);
|
||
// If `compiledObjs` includes newlines, we will output this as a multiline
|
||
// array (i.e. with a newline and indentation after the `[`). If an element
|
||
// contains line comments, that should also trigger multiline output since
|
||
// by definition line comments will introduce newlines into our output.
|
||
// The exception is if only the first element has line comments; in that
|
||
// case, output as the compact form if we otherwise would have, so that the
|
||
// first element’s line comments get output before or after the array.
|
||
includesLineCommentsOnNonFirstElement = false;
|
||
for (index = k = 0, len2 = compiledObjs.length; k < len2; index = ++k) {
|
||
fragments = compiledObjs[index];
|
||
for (l = 0, len3 = fragments.length; l < len3; l++) {
|
||
fragment = fragments[l];
|
||
if (fragment.isHereComment) {
|
||
fragment.code = fragment.code.trim();
|
||
} else if (index !== 0 && includesLineCommentsOnNonFirstElement === false && hasLineComments(fragment)) {
|
||
includesLineCommentsOnNonFirstElement = true;
|
||
}
|
||
}
|
||
if (index !== 0) {
|
||
answer.push(this.makeCode(', '));
|
||
}
|
||
answer.push(...fragments);
|
||
}
|
||
if (includesLineCommentsOnNonFirstElement || indexOf.call(fragmentsToText(answer), '\n') >= 0) {
|
||
for (fragmentIndex = q = 0, len4 = answer.length; q < len4; fragmentIndex = ++q) {
|
||
fragment = answer[fragmentIndex];
|
||
if (fragment.isHereComment) {
|
||
fragment.code = `${multident(fragment.code, o.indent, false)}\n${o.indent}`;
|
||
} else if (fragment.code === ', ') {
|
||
fragment.code = `,\n${o.indent}`;
|
||
}
|
||
}
|
||
answer.unshift(this.makeCode(`[\n${o.indent}`));
|
||
answer.push(this.makeCode(`\n${this.tab}]`));
|
||
} else {
|
||
for (r = 0, len5 = answer.length; r < len5; r++) {
|
||
fragment = answer[r];
|
||
if (fragment.isHereComment) {
|
||
fragment.code = `${fragment.code} `;
|
||
}
|
||
}
|
||
answer.unshift(this.makeCode('['));
|
||
answer.push(this.makeCode(']'));
|
||
}
|
||
return answer;
|
||
}
|
||
|
||
assigns(name) {
|
||
var j, len1, obj, ref1;
|
||
ref1 = this.objects;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
obj = ref1[j];
|
||
if (obj.assigns(name)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
eachName(iterator) {
|
||
var j, len1, obj, ref1, results;
|
||
ref1 = this.objects;
|
||
results = [];
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
obj = ref1[j];
|
||
obj = obj.unwrapAll();
|
||
results.push(obj.eachName(iterator));
|
||
}
|
||
return results;
|
||
}
|
||
|
||
};
|
||
|
||
Arr.prototype.children = ['objects'];
|
||
|
||
return Arr;
|
||
|
||
})();
|
||
|
||
//### Class
|
||
|
||
// The CoffeeScript class definition.
|
||
// Initialize a **Class** with its name, an optional superclass, and a body.
|
||
exports.Class = Class = (function() {
|
||
class Class extends Base {
|
||
constructor(variable1, parent1, body1 = new Block) {
|
||
super();
|
||
this.variable = variable1;
|
||
this.parent = parent1;
|
||
this.body = body1;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var executableBody, node, parentName;
|
||
this.name = this.determineName();
|
||
executableBody = this.walkBody();
|
||
if (this.parent instanceof Value && !this.parent.hasProperties()) {
|
||
// Special handling to allow `class expr.A extends A` declarations
|
||
parentName = this.parent.base.value;
|
||
}
|
||
this.hasNameClash = (this.name != null) && this.name === parentName;
|
||
node = this;
|
||
if (executableBody || this.hasNameClash) {
|
||
node = new ExecutableClassBody(node, executableBody);
|
||
} else if ((this.name == null) && o.level === LEVEL_TOP) {
|
||
// Anonymous classes are only valid in expressions
|
||
node = new Parens(node);
|
||
}
|
||
if (this.boundMethods.length && this.parent) {
|
||
if (this.variable == null) {
|
||
this.variable = new IdentifierLiteral(o.scope.freeVariable('_class'));
|
||
}
|
||
if (this.variableRef == null) {
|
||
[this.variable, this.variableRef] = this.variable.cache(o);
|
||
}
|
||
}
|
||
if (this.variable) {
|
||
node = new Assign(this.variable, node, null, {moduleDeclaration: this.moduleDeclaration});
|
||
}
|
||
this.compileNode = this.compileClassDeclaration;
|
||
try {
|
||
return node.compileToFragments(o);
|
||
} finally {
|
||
delete this.compileNode;
|
||
}
|
||
}
|
||
|
||
compileClassDeclaration(o) {
|
||
var ref1, result;
|
||
if (this.externalCtor || this.boundMethods.length) {
|
||
if (this.ctor == null) {
|
||
this.ctor = this.makeDefaultConstructor();
|
||
}
|
||
}
|
||
if ((ref1 = this.ctor) != null) {
|
||
ref1.noReturn = true;
|
||
}
|
||
if (this.boundMethods.length) {
|
||
this.proxyBoundMethods();
|
||
}
|
||
o.indent += TAB;
|
||
result = [];
|
||
result.push(this.makeCode("class "));
|
||
if (this.name) {
|
||
result.push(this.makeCode(`${this.name} `));
|
||
}
|
||
if (this.parent) {
|
||
result.push(this.makeCode('extends '), ...this.parent.compileToFragments(o), this.makeCode(' '));
|
||
}
|
||
result.push(this.makeCode('{'));
|
||
if (!this.body.isEmpty()) {
|
||
this.body.spaced = true;
|
||
result.push(this.makeCode('\n'));
|
||
result.push(...this.body.compileToFragments(o, LEVEL_TOP));
|
||
result.push(this.makeCode(`\n${this.tab}`));
|
||
}
|
||
result.push(this.makeCode('}'));
|
||
return result;
|
||
}
|
||
|
||
// Figure out the appropriate name for this class
|
||
determineName() {
|
||
var message, name, node, ref1, tail;
|
||
if (!this.variable) {
|
||
return null;
|
||
}
|
||
ref1 = this.variable.properties, tail = ref1[ref1.length - 1];
|
||
node = tail ? tail instanceof Access && tail.name : this.variable.base;
|
||
if (!(node instanceof IdentifierLiteral || node instanceof PropertyName)) {
|
||
return null;
|
||
}
|
||
name = node.value;
|
||
if (!tail) {
|
||
message = isUnassignable(name);
|
||
if (message) {
|
||
this.variable.error(message);
|
||
}
|
||
}
|
||
if (indexOf.call(JS_FORBIDDEN, name) >= 0) {
|
||
return `_${name}`;
|
||
} else {
|
||
return name;
|
||
}
|
||
}
|
||
|
||
walkBody() {
|
||
var assign, end, executableBody, expression, expressions, exprs, i, initializer, initializerExpression, j, k, len1, len2, method, properties, pushSlice, ref1, start;
|
||
this.ctor = null;
|
||
this.boundMethods = [];
|
||
executableBody = null;
|
||
initializer = [];
|
||
({expressions} = this.body);
|
||
i = 0;
|
||
ref1 = expressions.slice();
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
expression = ref1[j];
|
||
if (expression instanceof Value && expression.isObject(true)) {
|
||
({properties} = expression.base);
|
||
exprs = [];
|
||
end = 0;
|
||
start = 0;
|
||
pushSlice = function() {
|
||
if (end > start) {
|
||
return exprs.push(new Value(new Obj(properties.slice(start, end), true)));
|
||
}
|
||
};
|
||
while (assign = properties[end]) {
|
||
if (initializerExpression = this.addInitializerExpression(assign)) {
|
||
pushSlice();
|
||
exprs.push(initializerExpression);
|
||
initializer.push(initializerExpression);
|
||
start = end + 1;
|
||
}
|
||
end++;
|
||
}
|
||
pushSlice();
|
||
splice.apply(expressions, [i, i - i + 1].concat(exprs)), exprs;
|
||
i += exprs.length;
|
||
} else {
|
||
if (initializerExpression = this.addInitializerExpression(expression)) {
|
||
initializer.push(initializerExpression);
|
||
expressions[i] = initializerExpression;
|
||
}
|
||
i += 1;
|
||
}
|
||
}
|
||
for (k = 0, len2 = initializer.length; k < len2; k++) {
|
||
method = initializer[k];
|
||
if (method instanceof Code) {
|
||
if (method.ctor) {
|
||
if (this.ctor) {
|
||
method.error('Cannot define more than one constructor in a class');
|
||
}
|
||
this.ctor = method;
|
||
} else if (method.isStatic && method.bound) {
|
||
method.context = this.name;
|
||
} else if (method.bound) {
|
||
this.boundMethods.push(method);
|
||
}
|
||
}
|
||
}
|
||
if (initializer.length !== expressions.length) {
|
||
this.body.expressions = (function() {
|
||
var l, len3, results;
|
||
results = [];
|
||
for (l = 0, len3 = initializer.length; l < len3; l++) {
|
||
expression = initializer[l];
|
||
results.push(expression.hoist());
|
||
}
|
||
return results;
|
||
})();
|
||
return new Block(expressions);
|
||
}
|
||
}
|
||
|
||
// Add an expression to the class initializer
|
||
|
||
// This is the key method for determining whether an expression in a class
|
||
// body should appear in the initializer or the executable body. If the given
|
||
// `node` is valid in a class body the method will return a (new, modified,
|
||
// or identical) node for inclusion in the class initializer, otherwise
|
||
// nothing will be returned and the node will appear in the executable body.
|
||
|
||
// At time of writing, only methods (instance and static) are valid in ES
|
||
// class initializers. As new ES class features (such as class fields) reach
|
||
// Stage 4, this method will need to be updated to support them. We
|
||
// additionally allow `PassthroughLiteral`s (backticked expressions) in the
|
||
// initializer as an escape hatch for ES features that are not implemented
|
||
// (e.g. getters and setters defined via the `get` and `set` keywords as
|
||
// opposed to the `Object.defineProperty` method).
|
||
addInitializerExpression(node) {
|
||
if (node.unwrapAll() instanceof PassthroughLiteral) {
|
||
return node;
|
||
} else if (this.validInitializerMethod(node)) {
|
||
return this.addInitializerMethod(node);
|
||
} else {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// Checks if the given node is a valid ES class initializer method.
|
||
validInitializerMethod(node) {
|
||
if (!(node instanceof Assign && node.value instanceof Code)) {
|
||
return false;
|
||
}
|
||
if (node.context === 'object' && !node.variable.hasProperties()) {
|
||
return true;
|
||
}
|
||
return node.variable.looksStatic(this.name) && (this.name || !node.value.bound);
|
||
}
|
||
|
||
// Returns a configured class initializer method
|
||
addInitializerMethod(assign) {
|
||
var method, methodName, variable;
|
||
({
|
||
variable,
|
||
value: method
|
||
} = assign);
|
||
method.isMethod = true;
|
||
method.isStatic = variable.looksStatic(this.name);
|
||
if (method.isStatic) {
|
||
method.name = variable.properties[0];
|
||
} else {
|
||
methodName = variable.base;
|
||
method.name = new (methodName.shouldCache() ? Index : Access)(methodName);
|
||
method.name.updateLocationDataIfMissing(methodName.locationData);
|
||
if (methodName.value === 'constructor') {
|
||
method.ctor = (this.parent ? 'derived' : 'base');
|
||
}
|
||
if (method.bound && method.ctor) {
|
||
method.error('Cannot define a constructor as a bound (fat arrow) function');
|
||
}
|
||
}
|
||
return method;
|
||
}
|
||
|
||
makeDefaultConstructor() {
|
||
var applyArgs, applyCtor, ctor;
|
||
ctor = this.addInitializerMethod(new Assign(new Value(new PropertyName('constructor')), new Code));
|
||
this.body.unshift(ctor);
|
||
if (this.parent) {
|
||
ctor.body.push(new SuperCall(new Super, [new Splat(new IdentifierLiteral('arguments'))]));
|
||
}
|
||
if (this.externalCtor) {
|
||
applyCtor = new Value(this.externalCtor, [new Access(new PropertyName('apply'))]);
|
||
applyArgs = [new ThisLiteral, new IdentifierLiteral('arguments')];
|
||
ctor.body.push(new Call(applyCtor, applyArgs));
|
||
ctor.body.makeReturn();
|
||
}
|
||
return ctor;
|
||
}
|
||
|
||
proxyBoundMethods() {
|
||
var method, name;
|
||
this.ctor.thisAssignments = (function() {
|
||
var j, len1, ref1, results;
|
||
ref1 = this.boundMethods;
|
||
results = [];
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
method = ref1[j];
|
||
if (this.parent) {
|
||
method.classVariable = this.variableRef;
|
||
}
|
||
name = new Value(new ThisLiteral, [method.name]);
|
||
results.push(new Assign(name, new Call(new Value(name, [new Access(new PropertyName('bind'))]), [new ThisLiteral])));
|
||
}
|
||
return results;
|
||
}).call(this);
|
||
return null;
|
||
}
|
||
|
||
};
|
||
|
||
Class.prototype.children = ['variable', 'parent', 'body'];
|
||
|
||
return Class;
|
||
|
||
})();
|
||
|
||
exports.ExecutableClassBody = ExecutableClassBody = (function() {
|
||
class ExecutableClassBody extends Base {
|
||
constructor(_class, body1 = new Block) {
|
||
super();
|
||
this.class = _class;
|
||
this.body = body1;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var args, argumentsNode, directives, externalCtor, ident, jumpNode, klass, params, parent, ref1, wrapper;
|
||
if (jumpNode = this.body.jumps()) {
|
||
jumpNode.error('Class bodies cannot contain pure statements');
|
||
}
|
||
if (argumentsNode = this.body.contains(isLiteralArguments)) {
|
||
argumentsNode.error("Class bodies shouldn't reference arguments");
|
||
}
|
||
this.name = (ref1 = this.class.name) != null ? ref1 : this.defaultClassVariableName;
|
||
directives = this.walkBody();
|
||
this.setContext();
|
||
ident = new IdentifierLiteral(this.name);
|
||
params = [];
|
||
args = [];
|
||
wrapper = new Code(params, this.body);
|
||
klass = new Parens(new Call(wrapper, args));
|
||
this.body.spaced = true;
|
||
o.classScope = wrapper.makeScope(o.scope);
|
||
if (this.class.hasNameClash) {
|
||
parent = new IdentifierLiteral(o.classScope.freeVariable('superClass'));
|
||
wrapper.params.push(new Param(parent));
|
||
args.push(this.class.parent);
|
||
this.class.parent = parent;
|
||
}
|
||
if (this.externalCtor) {
|
||
externalCtor = new IdentifierLiteral(o.classScope.freeVariable('ctor', {
|
||
reserve: false
|
||
}));
|
||
this.class.externalCtor = externalCtor;
|
||
this.externalCtor.variable.base = externalCtor;
|
||
}
|
||
if (this.name !== this.class.name) {
|
||
this.body.expressions.unshift(new Assign(new IdentifierLiteral(this.name), this.class));
|
||
} else {
|
||
this.body.expressions.unshift(this.class);
|
||
}
|
||
this.body.expressions.unshift(...directives);
|
||
this.body.push(ident);
|
||
return klass.compileToFragments(o);
|
||
}
|
||
|
||
// Traverse the class's children and:
|
||
// - Hoist valid ES properties into `@properties`
|
||
// - Hoist static assignments into `@properties`
|
||
// - Convert invalid ES properties into class or prototype assignments
|
||
walkBody() {
|
||
var directives, expr, index;
|
||
directives = [];
|
||
index = 0;
|
||
while (expr = this.body.expressions[index]) {
|
||
if (!(expr instanceof Value && expr.isString())) {
|
||
break;
|
||
}
|
||
if (expr.hoisted) {
|
||
index++;
|
||
} else {
|
||
directives.push(...this.body.expressions.splice(index, 1));
|
||
}
|
||
}
|
||
this.traverseChildren(false, (child) => {
|
||
var cont, i, j, len1, node, ref1;
|
||
if (child instanceof Class || child instanceof HoistTarget) {
|
||
return false;
|
||
}
|
||
cont = true;
|
||
if (child instanceof Block) {
|
||
ref1 = child.expressions;
|
||
for (i = j = 0, len1 = ref1.length; j < len1; i = ++j) {
|
||
node = ref1[i];
|
||
if (node instanceof Value && node.isObject(true)) {
|
||
cont = false;
|
||
child.expressions[i] = this.addProperties(node.base.properties);
|
||
} else if (node instanceof Assign && node.variable.looksStatic(this.name)) {
|
||
node.value.isStatic = true;
|
||
}
|
||
}
|
||
child.expressions = flatten(child.expressions);
|
||
}
|
||
return cont;
|
||
});
|
||
return directives;
|
||
}
|
||
|
||
setContext() {
|
||
return this.body.traverseChildren(false, (node) => {
|
||
if (node instanceof ThisLiteral) {
|
||
return node.value = this.name;
|
||
} else if (node instanceof Code && node.bound && node.isStatic) {
|
||
return node.context = this.name;
|
||
}
|
||
});
|
||
}
|
||
|
||
// Make class/prototype assignments for invalid ES properties
|
||
addProperties(assigns) {
|
||
var assign, base, name, prototype, result, value, variable;
|
||
result = (function() {
|
||
var j, len1, results;
|
||
results = [];
|
||
for (j = 0, len1 = assigns.length; j < len1; j++) {
|
||
assign = assigns[j];
|
||
variable = assign.variable;
|
||
base = variable != null ? variable.base : void 0;
|
||
value = assign.value;
|
||
delete assign.context;
|
||
if (base.value === 'constructor') {
|
||
if (value instanceof Code) {
|
||
base.error('constructors must be defined at the top level of a class body');
|
||
}
|
||
// The class scope is not available yet, so return the assignment to update later
|
||
assign = this.externalCtor = new Assign(new Value, value);
|
||
} else if (!assign.variable.this) {
|
||
name = new (base.shouldCache() ? Index : Access)(base);
|
||
prototype = new Access(new PropertyName('prototype'));
|
||
variable = new Value(new ThisLiteral(), [prototype, name]);
|
||
assign.variable = variable;
|
||
} else if (assign.value instanceof Code) {
|
||
assign.value.isStatic = true;
|
||
}
|
||
results.push(assign);
|
||
}
|
||
return results;
|
||
}).call(this);
|
||
return compact(result);
|
||
}
|
||
|
||
};
|
||
|
||
ExecutableClassBody.prototype.children = ['class', 'body'];
|
||
|
||
ExecutableClassBody.prototype.defaultClassVariableName = '_Class';
|
||
|
||
return ExecutableClassBody;
|
||
|
||
})();
|
||
|
||
//### Import and Export
|
||
exports.ModuleDeclaration = ModuleDeclaration = (function() {
|
||
class ModuleDeclaration extends Base {
|
||
constructor(clause, source1) {
|
||
super();
|
||
this.clause = clause;
|
||
this.source = source1;
|
||
this.checkSource();
|
||
}
|
||
|
||
checkSource() {
|
||
if ((this.source != null) && this.source instanceof StringWithInterpolations) {
|
||
return this.source.error('the name of the module to be imported from must be an uninterpolated string');
|
||
}
|
||
}
|
||
|
||
checkScope(o, moduleDeclarationType) {
|
||
if (o.indent.length !== 0) {
|
||
return this.error(`${moduleDeclarationType} statements must be at top-level scope`);
|
||
}
|
||
}
|
||
|
||
};
|
||
|
||
ModuleDeclaration.prototype.children = ['clause', 'source'];
|
||
|
||
ModuleDeclaration.prototype.isStatement = YES;
|
||
|
||
ModuleDeclaration.prototype.jumps = THIS;
|
||
|
||
ModuleDeclaration.prototype.makeReturn = THIS;
|
||
|
||
return ModuleDeclaration;
|
||
|
||
})();
|
||
|
||
exports.ImportDeclaration = ImportDeclaration = class ImportDeclaration extends ModuleDeclaration {
|
||
compileNode(o) {
|
||
var code, ref1;
|
||
this.checkScope(o, 'import');
|
||
o.importedSymbols = [];
|
||
code = [];
|
||
code.push(this.makeCode(`${this.tab}import `));
|
||
if (this.clause != null) {
|
||
code.push(...this.clause.compileNode(o));
|
||
}
|
||
if (((ref1 = this.source) != null ? ref1.value : void 0) != null) {
|
||
if (this.clause !== null) {
|
||
code.push(this.makeCode(' from '));
|
||
}
|
||
code.push(this.makeCode(this.source.value));
|
||
}
|
||
code.push(this.makeCode(';'));
|
||
return code;
|
||
}
|
||
|
||
};
|
||
|
||
exports.ImportClause = ImportClause = (function() {
|
||
class ImportClause extends Base {
|
||
constructor(defaultBinding, namedImports) {
|
||
super();
|
||
this.defaultBinding = defaultBinding;
|
||
this.namedImports = namedImports;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var code;
|
||
code = [];
|
||
if (this.defaultBinding != null) {
|
||
code.push(...this.defaultBinding.compileNode(o));
|
||
if (this.namedImports != null) {
|
||
code.push(this.makeCode(', '));
|
||
}
|
||
}
|
||
if (this.namedImports != null) {
|
||
code.push(...this.namedImports.compileNode(o));
|
||
}
|
||
return code;
|
||
}
|
||
|
||
};
|
||
|
||
ImportClause.prototype.children = ['defaultBinding', 'namedImports'];
|
||
|
||
return ImportClause;
|
||
|
||
})();
|
||
|
||
exports.ExportDeclaration = ExportDeclaration = class ExportDeclaration extends ModuleDeclaration {
|
||
compileNode(o) {
|
||
var code, ref1;
|
||
this.checkScope(o, 'export');
|
||
code = [];
|
||
code.push(this.makeCode(`${this.tab}export `));
|
||
if (this instanceof ExportDefaultDeclaration) {
|
||
code.push(this.makeCode('default '));
|
||
}
|
||
if (!(this instanceof ExportDefaultDeclaration) && (this.clause instanceof Assign || this.clause instanceof Class)) {
|
||
// Prevent exporting an anonymous class; all exported members must be named
|
||
if (this.clause instanceof Class && !this.clause.variable) {
|
||
this.clause.error('anonymous classes cannot be exported');
|
||
}
|
||
code.push(this.makeCode('var '));
|
||
this.clause.moduleDeclaration = 'export';
|
||
}
|
||
if ((this.clause.body != null) && this.clause.body instanceof Block) {
|
||
code = code.concat(this.clause.compileToFragments(o, LEVEL_TOP));
|
||
} else {
|
||
code = code.concat(this.clause.compileNode(o));
|
||
}
|
||
if (((ref1 = this.source) != null ? ref1.value : void 0) != null) {
|
||
code.push(this.makeCode(` from ${this.source.value}`));
|
||
}
|
||
code.push(this.makeCode(';'));
|
||
return code;
|
||
}
|
||
|
||
};
|
||
|
||
exports.ExportNamedDeclaration = ExportNamedDeclaration = class ExportNamedDeclaration extends ExportDeclaration {};
|
||
|
||
exports.ExportDefaultDeclaration = ExportDefaultDeclaration = class ExportDefaultDeclaration extends ExportDeclaration {};
|
||
|
||
exports.ExportAllDeclaration = ExportAllDeclaration = class ExportAllDeclaration extends ExportDeclaration {};
|
||
|
||
exports.ModuleSpecifierList = ModuleSpecifierList = (function() {
|
||
class ModuleSpecifierList extends Base {
|
||
constructor(specifiers) {
|
||
super();
|
||
this.specifiers = specifiers;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var code, compiledList, fragments, index, j, len1, specifier;
|
||
code = [];
|
||
o.indent += TAB;
|
||
compiledList = (function() {
|
||
var j, len1, ref1, results;
|
||
ref1 = this.specifiers;
|
||
results = [];
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
specifier = ref1[j];
|
||
results.push(specifier.compileToFragments(o, LEVEL_LIST));
|
||
}
|
||
return results;
|
||
}).call(this);
|
||
if (this.specifiers.length !== 0) {
|
||
code.push(this.makeCode(`{\n${o.indent}`));
|
||
for (index = j = 0, len1 = compiledList.length; j < len1; index = ++j) {
|
||
fragments = compiledList[index];
|
||
if (index) {
|
||
code.push(this.makeCode(`,\n${o.indent}`));
|
||
}
|
||
code.push(...fragments);
|
||
}
|
||
code.push(this.makeCode("\n}"));
|
||
} else {
|
||
code.push(this.makeCode('{}'));
|
||
}
|
||
return code;
|
||
}
|
||
|
||
};
|
||
|
||
ModuleSpecifierList.prototype.children = ['specifiers'];
|
||
|
||
return ModuleSpecifierList;
|
||
|
||
})();
|
||
|
||
exports.ImportSpecifierList = ImportSpecifierList = class ImportSpecifierList extends ModuleSpecifierList {};
|
||
|
||
exports.ExportSpecifierList = ExportSpecifierList = class ExportSpecifierList extends ModuleSpecifierList {};
|
||
|
||
exports.ModuleSpecifier = ModuleSpecifier = (function() {
|
||
class ModuleSpecifier extends Base {
|
||
constructor(original, alias, moduleDeclarationType1) {
|
||
var ref1, ref2;
|
||
super();
|
||
this.original = original;
|
||
this.alias = alias;
|
||
this.moduleDeclarationType = moduleDeclarationType1;
|
||
if (this.original.comments || ((ref1 = this.alias) != null ? ref1.comments : void 0)) {
|
||
this.comments = [];
|
||
if (this.original.comments) {
|
||
this.comments.push(...this.original.comments);
|
||
}
|
||
if ((ref2 = this.alias) != null ? ref2.comments : void 0) {
|
||
this.comments.push(...this.alias.comments);
|
||
}
|
||
}
|
||
// The name of the variable entering the local scope
|
||
this.identifier = this.alias != null ? this.alias.value : this.original.value;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var code;
|
||
o.scope.find(this.identifier, this.moduleDeclarationType);
|
||
code = [];
|
||
code.push(this.makeCode(this.original.value));
|
||
if (this.alias != null) {
|
||
code.push(this.makeCode(` as ${this.alias.value}`));
|
||
}
|
||
return code;
|
||
}
|
||
|
||
};
|
||
|
||
ModuleSpecifier.prototype.children = ['original', 'alias'];
|
||
|
||
return ModuleSpecifier;
|
||
|
||
})();
|
||
|
||
exports.ImportSpecifier = ImportSpecifier = class ImportSpecifier extends ModuleSpecifier {
|
||
constructor(imported, local) {
|
||
super(imported, local, 'import');
|
||
}
|
||
|
||
compileNode(o) {
|
||
var ref1;
|
||
// Per the spec, symbols can’t be imported multiple times
|
||
// (e.g. `import { foo, foo } from 'lib'` is invalid)
|
||
if ((ref1 = this.identifier, indexOf.call(o.importedSymbols, ref1) >= 0) || o.scope.check(this.identifier)) {
|
||
this.error(`'${this.identifier}' has already been declared`);
|
||
} else {
|
||
o.importedSymbols.push(this.identifier);
|
||
}
|
||
return super.compileNode(o);
|
||
}
|
||
|
||
};
|
||
|
||
exports.ImportDefaultSpecifier = ImportDefaultSpecifier = class ImportDefaultSpecifier extends ImportSpecifier {};
|
||
|
||
exports.ImportNamespaceSpecifier = ImportNamespaceSpecifier = class ImportNamespaceSpecifier extends ImportSpecifier {};
|
||
|
||
exports.ExportSpecifier = ExportSpecifier = class ExportSpecifier extends ModuleSpecifier {
|
||
constructor(local, exported) {
|
||
super(local, exported, 'export');
|
||
}
|
||
|
||
};
|
||
|
||
//### Assign
|
||
|
||
// The **Assign** is used to assign a local variable to value, or to set the
|
||
// property of an object -- including within object literals.
|
||
exports.Assign = Assign = (function() {
|
||
class Assign extends Base {
|
||
constructor(variable1, value1, context1, options = {}) {
|
||
super();
|
||
this.variable = variable1;
|
||
this.value = value1;
|
||
this.context = context1;
|
||
({param: this.param, subpattern: this.subpattern, operatorToken: this.operatorToken, moduleDeclaration: this.moduleDeclaration} = options);
|
||
}
|
||
|
||
isStatement(o) {
|
||
return (o != null ? o.level : void 0) === LEVEL_TOP && (this.context != null) && (this.moduleDeclaration || indexOf.call(this.context, "?") >= 0);
|
||
}
|
||
|
||
checkAssignability(o, varBase) {
|
||
if (Object.prototype.hasOwnProperty.call(o.scope.positions, varBase.value) && o.scope.variables[o.scope.positions[varBase.value]].type === 'import') {
|
||
return varBase.error(`'${varBase.value}' is read-only`);
|
||
}
|
||
}
|
||
|
||
assigns(name) {
|
||
return this[this.context === 'object' ? 'value' : 'variable'].assigns(name);
|
||
}
|
||
|
||
unfoldSoak(o) {
|
||
return unfoldSoak(o, this, 'variable');
|
||
}
|
||
|
||
// Compile an assignment, delegating to `compileDestructuring` or
|
||
// `compileSplice` if appropriate. Keep track of the name of the base object
|
||
// 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, isValue, j, 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.
|
||
this.variable.param = this.param;
|
||
// If `@variable` is an array or an object, we’re destructuring;
|
||
// if it’s also `isAssignable()`, the destructuring syntax is supported
|
||
// in ES and we can output it as is; otherwise we `@compileDestructuring`
|
||
// and convert this ES-unsupported destructuring into acceptable output.
|
||
if (this.variable.isArray() || this.variable.isObject()) {
|
||
// This is the left-hand side of an assignment; let `Arr` and `Obj`
|
||
// know that, so that those nodes know that they’re assignable as
|
||
// destructured variables.
|
||
this.variable.base.lhs = true;
|
||
if (!this.variable.isAssignable()) {
|
||
return this.compileDestructuring(o);
|
||
}
|
||
if (this.variable.isObject() && this.variable.contains(function(node) {
|
||
return node instanceof Obj && node.hasSplat();
|
||
})) {
|
||
// Object destructuring. Can be removed once ES proposal hits Stage 4.
|
||
objDestructAnswer = this.compileObjectDestruct(o);
|
||
}
|
||
if (objDestructAnswer) {
|
||
return objDestructAnswer;
|
||
}
|
||
}
|
||
if (this.variable.isSplice()) {
|
||
return this.compileSplice(o);
|
||
}
|
||
if ((ref1 = this.context) === '||=' || ref1 === '&&=' || ref1 === '?=') {
|
||
return this.compileConditional(o);
|
||
}
|
||
if ((ref2 = this.context) === '**=' || ref2 === '//=' || ref2 === '%%=') {
|
||
return this.compileSpecialMath(o);
|
||
}
|
||
}
|
||
if (!this.context) {
|
||
varBase = this.variable.unwrapAll();
|
||
if (!varBase.isAssignable()) {
|
||
this.variable.error(`'${this.variable.compile(o)}' can't be assigned`);
|
||
}
|
||
varBase.eachName((name) => {
|
||
var message;
|
||
if (typeof name.hasProperties === "function" ? name.hasProperties() : void 0) {
|
||
return;
|
||
}
|
||
message = isUnassignable(name.value);
|
||
if (message) {
|
||
name.error(message);
|
||
}
|
||
// `moduleDeclaration` can be `'import'` or `'export'`
|
||
this.checkAssignability(o, name);
|
||
if (this.moduleDeclaration) {
|
||
return o.scope.add(name.value, this.moduleDeclaration);
|
||
} else if (this.param) {
|
||
return o.scope.add(name.value, this.param === 'alwaysDeclare' ? 'var' : 'param');
|
||
} else {
|
||
return o.scope.find(name.value);
|
||
}
|
||
});
|
||
}
|
||
if (this.value instanceof Code) {
|
||
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++];
|
||
if (((ref5 = prototype.name) != null ? ref5.value : void 0) === 'prototype') {
|
||
this.value.name = name;
|
||
}
|
||
}
|
||
}
|
||
if (this.csx) {
|
||
this.value.base.csxAttribute = true;
|
||
}
|
||
val = this.value.compileToFragments(o, LEVEL_LIST);
|
||
compiledName = this.variable.compileToFragments(o, LEVEL_LIST);
|
||
if (this.context === 'object') {
|
||
if (this.variable.shouldCache()) {
|
||
compiledName.unshift(this.makeCode('['));
|
||
compiledName.push(this.makeCode(']'));
|
||
}
|
||
return compiledName.concat(this.makeCode(this.csx ? '=' : ': '), val);
|
||
}
|
||
answer = compiledName.concat(this.makeCode(` ${this.context || '='} `), val);
|
||
// Per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration,
|
||
// if we’re destructuring without declaring, the destructuring assignment must be wrapped in parentheses.
|
||
if (o.level > LEVEL_LIST || o.level === LEVEL_TOP && isValue && this.variable.base instanceof Obj && !this.nestedLhs && !(this.param === true)) {
|
||
return this.wrapInParentheses(answer);
|
||
} else {
|
||
return answer;
|
||
}
|
||
}
|
||
|
||
// Check object destructuring variable for rest elements;
|
||
// can be removed once ES proposal hits Stage 4.
|
||
compileObjectDestruct(o) {
|
||
var fragments, getPropKey, getPropName, j, len1, restElement, restElements, result, traverseRest, value, valueRef, valueRefTemp;
|
||
// Returns a safe (cached) reference to the key for a given property
|
||
getPropKey = function(prop) {
|
||
var key;
|
||
if (prop instanceof Assign) {
|
||
[prop.variable, key] = prop.variable.cache(o);
|
||
return key;
|
||
} else {
|
||
return prop;
|
||
}
|
||
};
|
||
// Returns the name of a given property for use with excludeProps
|
||
// Property names are quoted (e.g. `a: b` -> 'a'), and everything else uses the key reference
|
||
// (e.g. `'a': b -> 'a'`, `"#{a}": b` -> <cached>`)
|
||
getPropName = function(prop) {
|
||
var cached, key;
|
||
key = getPropKey(prop);
|
||
cached = prop instanceof Assign && prop.variable !== key;
|
||
if (cached || !key.isAssignable()) {
|
||
return key;
|
||
} else {
|
||
return new Literal(`'${key.compileWithoutComments(o)}'`);
|
||
}
|
||
};
|
||
// Recursive function for searching and storing rest elements in objects.
|
||
// e.g. `{[properties...]} = source`.
|
||
traverseRest = (properties, source) => {
|
||
var base1, index, j, len1, nestedProperties, nestedSource, nestedSourceDefault, p, prop, restElements, restIndex;
|
||
restElements = [];
|
||
restIndex = void 0;
|
||
if (source.properties == null) {
|
||
source = new Value(source);
|
||
}
|
||
for (index = j = 0, len1 = properties.length; j < len1; index = ++j) {
|
||
prop = properties[index];
|
||
nestedSourceDefault = nestedSource = nestedProperties = null;
|
||
if (prop instanceof Assign) {
|
||
if (typeof (base1 = prop.value).isObject === "function" ? base1.isObject() : void 0) {
|
||
if (prop.context !== 'object') {
|
||
// prop is `k = {...} `
|
||
continue;
|
||
}
|
||
// prop is `k: {...}`
|
||
nestedProperties = prop.value.base.properties;
|
||
} else if (prop.value instanceof Assign && prop.value.variable.isObject()) {
|
||
// prop is `k: {...} = default`
|
||
nestedProperties = prop.value.variable.base.properties;
|
||
[prop.value.value, nestedSourceDefault] = prop.value.value.cache(o);
|
||
}
|
||
if (nestedProperties) {
|
||
nestedSource = new Value(source.base, source.properties.concat([new Access(getPropKey(prop))]));
|
||
if (nestedSourceDefault) {
|
||
nestedSource = new Value(new Op('?', nestedSource, nestedSourceDefault));
|
||
}
|
||
restElements.push(...traverseRest(nestedProperties, nestedSource));
|
||
}
|
||
} else if (prop instanceof Splat) {
|
||
if (restIndex != null) {
|
||
prop.error("multiple rest elements are disallowed in object destructuring");
|
||
}
|
||
restIndex = index;
|
||
restElements.push({
|
||
name: prop.name.unwrapAll(),
|
||
source,
|
||
excludeProps: new Arr((function() {
|
||
var k, len2, results;
|
||
results = [];
|
||
for (k = 0, len2 = properties.length; k < len2; k++) {
|
||
p = properties[k];
|
||
if (p !== prop) {
|
||
results.push(getPropName(p));
|
||
}
|
||
}
|
||
return results;
|
||
})())
|
||
});
|
||
}
|
||
}
|
||
if (restIndex != null) {
|
||
// Remove rest element from the properties after iteration
|
||
properties.splice(restIndex, 1);
|
||
}
|
||
return restElements;
|
||
};
|
||
// Cache the value for reuse with rest elements.
|
||
valueRefTemp = this.value.shouldCache() ? new IdentifierLiteral(o.scope.freeVariable('ref', {
|
||
reserve: false
|
||
})) : this.value.base;
|
||
// Find all rest elements.
|
||
restElements = traverseRest(this.variable.base.properties, valueRefTemp);
|
||
if (!(restElements && restElements.length > 0)) {
|
||
return false;
|
||
}
|
||
[this.value, valueRef] = this.value.cache(o);
|
||
result = new Block([this]);
|
||
for (j = 0, len1 = restElements.length; j < len1; j++) {
|
||
restElement = restElements[j];
|
||
value = new Call(new Value(new Literal(utility('objectWithoutKeys', o))), [restElement.source, restElement.excludeProps]);
|
||
result.push(new Assign(new Value(restElement.name), value, null, {
|
||
param: this.param ? 'alwaysDeclare' : null
|
||
}));
|
||
}
|
||
fragments = result.compileToFragments(o);
|
||
if (o.level === LEVEL_TOP) {
|
||
// Remove leading tab and trailing semicolon
|
||
fragments.shift();
|
||
fragments.pop();
|
||
}
|
||
return fragments;
|
||
}
|
||
|
||
// 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;
|
||
top = o.level === LEVEL_TOP;
|
||
({value} = this);
|
||
({objects} = this.variable.base);
|
||
olen = objects.length;
|
||
// Special-case for `{} = a` and `[] = a` (empty patterns).
|
||
// Compile to simply `a`.
|
||
if (olen === 0) {
|
||
code = value.compileToFragments(o);
|
||
if (o.level >= LEVEL_OP) {
|
||
return this.wrapInParentheses(code);
|
||
} else {
|
||
return code;
|
||
}
|
||
}
|
||
[obj] = objects;
|
||
// Disallow `[...] = a` for some reason. (Could be equivalent to `[] = a`?)
|
||
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;
|
||
}
|
||
} 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 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);
|
||
}
|
||
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.
|
||
if (!(value.unwrap() instanceof IdentifierLiteral) || this.variable.assigns(vvarText)) {
|
||
ref = o.scope.freeVariable('ref');
|
||
assigns.push([this.makeCode(ref + ' = '), ...vvar]);
|
||
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 += ") : []";
|
||
}
|
||
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));
|
||
}
|
||
}
|
||
continue;
|
||
} else {
|
||
if (obj instanceof Splat || obj instanceof Expansion) {
|
||
obj.error("multiple splats/expansions are disallowed in an assignment");
|
||
}
|
||
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;
|
||
}
|
||
} 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);
|
||
}
|
||
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);
|
||
if (message) {
|
||
obj.error(message);
|
||
}
|
||
}
|
||
assigns.push(new Assign(obj, val, null, {
|
||
param: this.param,
|
||
subpattern: true
|
||
}).compileToFragments(o, LEVEL_LIST));
|
||
}
|
||
if (!(top || this.subpattern)) {
|
||
assigns.push(vvar);
|
||
}
|
||
fragments = this.joinFragmentArrays(assigns, ', ');
|
||
if (o.level < LEVEL_LIST) {
|
||
return fragments;
|
||
} else {
|
||
return this.wrapInParentheses(fragments);
|
||
}
|
||
}
|
||
|
||
// When compiling a conditional assignment, take care to ensure that the
|
||
// operands are only evaluated once, even though we have to reference them
|
||
// more than once.
|
||
compileConditional(o) {
|
||
var fragments, left, right;
|
||
[left, right] = this.variable.cacheReference(o);
|
||
// Disallow conditional assignment of undefined variables.
|
||
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) {
|
||
o.isExistentialEquals = true;
|
||
return new If(new Existence(left), right, {
|
||
type: 'if'
|
||
}).addElse(new Assign(right, this.value, '=')).compileToFragments(o);
|
||
} else {
|
||
fragments = new Op(this.context.slice(0, -1), left, new Assign(right, this.value, '=')).compileToFragments(o);
|
||
if (o.level <= LEVEL_LIST) {
|
||
return fragments;
|
||
} else {
|
||
return this.wrapInParentheses(fragments);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Convert special math assignment operators like `a **= b` to the equivalent
|
||
// extended form `a = a ** b` and then compiles that.
|
||
compileSpecialMath(o) {
|
||
var left, right;
|
||
[left, right] = this.variable.cacheReference(o);
|
||
return new Assign(left, new Op(this.context.slice(0, -1), right, this.value)).compileToFragments(o);
|
||
}
|
||
|
||
// Compile the assignment from an array splice literal, using JavaScript's
|
||
// `Array#splice` method.
|
||
compileSplice(o) {
|
||
var answer, exclusive, from, fromDecl, fromRef, name, to, unwrappedVar, valDef, valRef;
|
||
({
|
||
range: {from, to, exclusive}
|
||
} = this.variable.properties.pop());
|
||
unwrappedVar = this.variable.unwrapAll();
|
||
if (unwrappedVar.comments) {
|
||
moveComments(unwrappedVar, this);
|
||
delete this.variable.comments;
|
||
}
|
||
name = this.variable.compile(o);
|
||
if (from) {
|
||
[fromDecl, fromRef] = this.cacheToCodeFragments(from.cache(o, LEVEL_OP));
|
||
} else {
|
||
fromDecl = fromRef = '0';
|
||
}
|
||
if (to) {
|
||
if ((from != null ? from.isNumber() : void 0) && to.isNumber()) {
|
||
to = to.compile(o) - fromRef;
|
||
if (!exclusive) {
|
||
to += 1;
|
||
}
|
||
} else {
|
||
to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef;
|
||
if (!exclusive) {
|
||
to += ' + 1';
|
||
}
|
||
}
|
||
} else {
|
||
to = "9e9";
|
||
}
|
||
[valDef, valRef] = this.value.cache(o, LEVEL_LIST);
|
||
answer = [].concat(this.makeCode(`${utility('splice', o)}.apply(${name}, [${fromDecl}, ${to}].concat(`), valDef, this.makeCode(")), "), valRef);
|
||
if (o.level > LEVEL_TOP) {
|
||
return this.wrapInParentheses(answer);
|
||
} else {
|
||
return answer;
|
||
}
|
||
}
|
||
|
||
eachName(iterator) {
|
||
return this.variable.unwrapAll().eachName(iterator);
|
||
}
|
||
|
||
};
|
||
|
||
Assign.prototype.children = ['variable', 'value'];
|
||
|
||
Assign.prototype.isAssignable = YES;
|
||
|
||
return Assign;
|
||
|
||
})();
|
||
|
||
//### FuncGlyph
|
||
exports.FuncGlyph = FuncGlyph = class FuncGlyph extends Base {
|
||
constructor(glyph) {
|
||
super();
|
||
this.glyph = glyph;
|
||
}
|
||
|
||
};
|
||
|
||
//### Code
|
||
|
||
// A function definition. This is the only node that creates a new Scope.
|
||
// When for the purposes of walking the contents of a function body, the Code
|
||
// has no *children* -- they're within the inner scope.
|
||
exports.Code = Code = (function() {
|
||
class Code extends Base {
|
||
constructor(params, body, funcGlyph) {
|
||
var ref1;
|
||
super();
|
||
this.funcGlyph = funcGlyph;
|
||
this.params = params || [];
|
||
this.body = body || new Block;
|
||
this.bound = ((ref1 = this.funcGlyph) != null ? ref1.glyph : void 0) === '=>';
|
||
this.isGenerator = false;
|
||
this.isAsync = false;
|
||
this.isMethod = false;
|
||
this.body.traverseChildren(false, (node) => {
|
||
if ((node instanceof Op && node.isYield()) || node instanceof YieldReturn) {
|
||
this.isGenerator = true;
|
||
}
|
||
if ((node instanceof Op && node.isAwait()) || node instanceof AwaitReturn) {
|
||
this.isAsync = true;
|
||
}
|
||
if (this.isGenerator && this.isAsync) {
|
||
return node.error("function can't contain both yield and await");
|
||
}
|
||
});
|
||
}
|
||
|
||
isStatement() {
|
||
return this.isMethod;
|
||
}
|
||
|
||
makeScope(parentScope) {
|
||
return new Scope(parentScope, this.body, this);
|
||
}
|
||
|
||
// Compilation creates a new scope unless explicitly asked to share with the
|
||
// outer scope. Handles splat parameters in the parameter list by setting
|
||
// such parameters to be the final parameter in the function definition, as
|
||
// required per the ES2015 spec. If the CoffeeScript function definition had
|
||
// parameters after the splat, they are declared via expressions in the
|
||
// function body.
|
||
compileNode(o) {
|
||
var answer, body, boundMethodCheck, comment, condition, exprs, generatedVariables, haveBodyParam, haveSplatParam, i, ifTrue, j, k, l, len1, len2, len3, m, methodScope, modifiers, name, param, paramNames, paramToAddToScope, params, paramsAfterSplat, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, scopeVariablesCount, signature, splatParamName, thisAssignments, wasEmpty;
|
||
if (this.ctor) {
|
||
if (this.isAsync) {
|
||
this.name.error('Class constructor may not be async');
|
||
}
|
||
if (this.isGenerator) {
|
||
this.name.error('Class constructor may not be a generator');
|
||
}
|
||
}
|
||
if (this.bound) {
|
||
if ((ref1 = o.scope.method) != null ? ref1.bound : void 0) {
|
||
this.context = o.scope.method.context;
|
||
}
|
||
if (!this.context) {
|
||
this.context = 'this';
|
||
}
|
||
}
|
||
o.scope = del(o, 'classScope') || this.makeScope(o.scope);
|
||
o.scope.shared = del(o, 'sharedScope');
|
||
o.indent += TAB;
|
||
delete o.bare;
|
||
delete o.isExistentialEquals;
|
||
params = [];
|
||
exprs = [];
|
||
thisAssignments = (ref2 = (ref3 = this.thisAssignments) != null ? ref3.slice() : void 0) != null ? ref2 : [];
|
||
paramsAfterSplat = [];
|
||
haveSplatParam = false;
|
||
haveBodyParam = false;
|
||
// Check for duplicate parameters and separate `this` assignments.
|
||
paramNames = [];
|
||
this.eachParamName(function(name, node, param) {
|
||
var target;
|
||
if (indexOf.call(paramNames, name) >= 0) {
|
||
node.error(`multiple parameters named '${name}'`);
|
||
}
|
||
paramNames.push(name);
|
||
if (node.this) {
|
||
name = node.properties[0].name.value;
|
||
if (indexOf.call(JS_FORBIDDEN, name) >= 0) {
|
||
name = `_${name}`;
|
||
}
|
||
target = new IdentifierLiteral(o.scope.freeVariable(name));
|
||
param.renameParam(node, target);
|
||
return thisAssignments.push(new Assign(node, target));
|
||
}
|
||
});
|
||
ref4 = this.params;
|
||
// Parse the parameters, adding them to the list of parameters to put in the
|
||
// function definition; and dealing with splats or expansions, including
|
||
// adding expressions to the function body to declare all parameter
|
||
// variables that would have been after the splat/expansion parameter.
|
||
// If we encounter a parameter that needs to be declared in the function
|
||
// body for any reason, for example it’s destructured with `this`, also
|
||
// declare and assign all subsequent parameters in the function body so that
|
||
// any non-idempotent parameters are evaluated in the correct order.
|
||
for (i = j = 0, len1 = ref4.length; j < len1; i = ++j) {
|
||
param = ref4[i];
|
||
// Was `...` used with this parameter? (Only one such parameter is allowed
|
||
// per function.) Splat/expansion parameters cannot have default values,
|
||
// so we need not worry about that.
|
||
if (param.splat || param instanceof Expansion) {
|
||
if (haveSplatParam) {
|
||
param.error('only one splat or expansion parameter is allowed per function definition');
|
||
} else if (param instanceof Expansion && this.params.length === 1) {
|
||
param.error('an expansion parameter cannot be the only parameter in a function definition');
|
||
}
|
||
haveSplatParam = true;
|
||
if (param.splat) {
|
||
if (param.name instanceof Arr) {
|
||
// Splat arrays are treated oddly by ES; deal with them the legacy
|
||
// way in the function body. TODO: Should this be handled in the
|
||
// function parameter list, and if so, how?
|
||
splatParamName = o.scope.freeVariable('arg');
|
||
params.push(ref = new Value(new IdentifierLiteral(splatParamName)));
|
||
exprs.push(new Assign(new Value(param.name), ref));
|
||
} else {
|
||
params.push(ref = param.asReference(o));
|
||
splatParamName = fragmentsToText(ref.compileNodeWithoutComments(o));
|
||
}
|
||
if (param.shouldCache()) {
|
||
exprs.push(new Assign(new Value(param.name), ref)); // `param` is an Expansion
|
||
}
|
||
} else {
|
||
splatParamName = o.scope.freeVariable('args');
|
||
params.push(new Value(new IdentifierLiteral(splatParamName)));
|
||
}
|
||
o.scope.parameter(splatParamName);
|
||
} else {
|
||
// Parse all other parameters; if a splat paramater has not yet been
|
||
// encountered, add these other parameters to the list to be output in
|
||
// the function definition.
|
||
if (param.shouldCache() || haveBodyParam) {
|
||
param.assignedInBody = true;
|
||
haveBodyParam = true;
|
||
// This parameter cannot be declared or assigned in the parameter
|
||
// list. So put a reference in the parameter list and add a statement
|
||
// to the function body assigning it, e.g.
|
||
// `(arg) => { var a = arg.a; }`, with a default value if it has one.
|
||
if (param.value != null) {
|
||
condition = new Op('===', param, new UndefinedLiteral);
|
||
ifTrue = new Assign(new Value(param.name), param.value);
|
||
exprs.push(new If(condition, ifTrue));
|
||
} else {
|
||
exprs.push(new Assign(new Value(param.name), param.asReference(o), null, {
|
||
param: 'alwaysDeclare'
|
||
}));
|
||
}
|
||
}
|
||
// If this parameter comes before the splat or expansion, it will go
|
||
// in the function definition parameter list.
|
||
if (!haveSplatParam) {
|
||
// If this parameter has a default value, and it hasn’t already been
|
||
// set by the `shouldCache()` block above, define it as a statement in
|
||
// the function body. This parameter comes after the splat parameter,
|
||
// so we can’t define its default value in the parameter list.
|
||
if (param.shouldCache()) {
|
||
ref = param.asReference(o);
|
||
} else {
|
||
if ((param.value != null) && !param.assignedInBody) {
|
||
ref = new Assign(new Value(param.name), param.value, null, {
|
||
param: true
|
||
});
|
||
} else {
|
||
ref = param;
|
||
}
|
||
}
|
||
// Add this parameter’s reference(s) to the function scope.
|
||
if (param.name instanceof Arr || param.name instanceof Obj) {
|
||
// This parameter is destructured.
|
||
param.name.lhs = true;
|
||
// Compile `foo({a, b...}) ->` to `foo(arg) -> {a, b...} = arg`.
|
||
// Can be removed once ES proposal hits Stage 4.
|
||
if (param.name instanceof Obj && param.name.hasSplat()) {
|
||
splatParamName = o.scope.freeVariable('arg');
|
||
o.scope.parameter(splatParamName);
|
||
ref = new Value(new IdentifierLiteral(splatParamName));
|
||
exprs.push(new Assign(new Value(param.name), ref, null, {
|
||
param: 'alwaysDeclare'
|
||
}));
|
||
// Compile `foo({a, b...} = {}) ->` to `foo(arg = {}) -> {a, b...} = arg`.
|
||
if ((param.value != null) && !param.assignedInBody) {
|
||
ref = new Assign(ref, param.value, null, {
|
||
param: true
|
||
});
|
||
}
|
||
} else if (!param.shouldCache()) {
|
||
param.name.eachName(function(prop) {
|
||
return o.scope.parameter(prop.value);
|
||
});
|
||
}
|
||
} else {
|
||
// This compilation of the parameter is only to get its name to add
|
||
// to the scope name tracking; since the compilation output here
|
||
// isn’t kept for eventual output, don’t include comments in this
|
||
// compilation, so that they get output the “real” time this param
|
||
// is compiled.
|
||
paramToAddToScope = param.value != null ? param : ref;
|
||
o.scope.parameter(fragmentsToText(paramToAddToScope.compileToFragmentsWithoutComments(o)));
|
||
}
|
||
params.push(ref);
|
||
} else {
|
||
paramsAfterSplat.push(param);
|
||
// If this parameter had a default value, since it’s no longer in the
|
||
// function parameter list we need to assign its default value
|
||
// (if necessary) as an expression in the body.
|
||
if ((param.value != null) && !param.shouldCache()) {
|
||
condition = new Op('===', param, new UndefinedLiteral);
|
||
ifTrue = new Assign(new Value(param.name), param.value);
|
||
exprs.push(new If(condition, ifTrue));
|
||
}
|
||
if (((ref5 = param.name) != null ? ref5.value : void 0) != null) {
|
||
// Add this parameter to the scope, since it wouldn’t have been added
|
||
// yet since it was skipped earlier.
|
||
o.scope.add(param.name.value, 'var', true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// If there were parameters after the splat or expansion parameter, those
|
||
// parameters need to be assigned in the body of the function.
|
||
if (paramsAfterSplat.length !== 0) {
|
||
// Create a destructured assignment, e.g. `[a, b, c] = [args..., b, c]`
|
||
exprs.unshift(new Assign(new Value(new Arr([
|
||
new Splat(new IdentifierLiteral(splatParamName)),
|
||
...((function() {
|
||
var k, len2, results;
|
||
results = [];
|
||
for (k = 0, len2 = paramsAfterSplat.length; k < len2; k++) {
|
||
param = paramsAfterSplat[k];
|
||
results.push(param.asReference(o));
|
||
}
|
||
return results;
|
||
})())
|
||
])), new Value(new IdentifierLiteral(splatParamName))));
|
||
}
|
||
// Add new expressions to the function body
|
||
wasEmpty = this.body.isEmpty();
|
||
if (!this.expandCtorSuper(thisAssignments)) {
|
||
this.body.expressions.unshift(...thisAssignments);
|
||
}
|
||
this.body.expressions.unshift(...exprs);
|
||
if (this.isMethod && this.bound && !this.isStatic && this.classVariable) {
|
||
boundMethodCheck = new Value(new Literal(utility('boundMethodCheck', o)));
|
||
this.body.expressions.unshift(new Call(boundMethodCheck, [new Value(new ThisLiteral), this.classVariable]));
|
||
}
|
||
if (!(wasEmpty || this.noReturn)) {
|
||
this.body.makeReturn();
|
||
}
|
||
// Assemble the output
|
||
modifiers = [];
|
||
if (this.isMethod && this.isStatic) {
|
||
modifiers.push('static');
|
||
}
|
||
if (this.isAsync) {
|
||
modifiers.push('async');
|
||
}
|
||
if (!(this.isMethod || this.bound)) {
|
||
modifiers.push(`function${(this.isGenerator ? '*' : '')}`);
|
||
} else if (this.isGenerator) {
|
||
modifiers.push('*');
|
||
}
|
||
signature = [this.makeCode('(')];
|
||
for (i = k = 0, len2 = params.length; k < len2; i = ++k) {
|
||
param = params[i];
|
||
if (i !== 0) {
|
||
signature.push(this.makeCode(', '));
|
||
}
|
||
if (haveSplatParam && i === params.length - 1) {
|
||
signature.push(this.makeCode('...'));
|
||
}
|
||
// Compile this parameter, but if any generated variables get created
|
||
// (e.g. `ref`), shift those into the parent scope since we can’t put a
|
||
// `var` line inside a function parameter list.
|
||
scopeVariablesCount = o.scope.variables.length;
|
||
signature.push(...param.compileToFragments(o));
|
||
if (scopeVariablesCount !== o.scope.variables.length) {
|
||
generatedVariables = o.scope.variables.splice(scopeVariablesCount);
|
||
o.scope.parent.variables.push(...generatedVariables);
|
||
}
|
||
}
|
||
signature.push(this.makeCode(')'));
|
||
// Block comments between `)` and `->`/`=>` get output between `)` and `{`.
|
||
if (((ref6 = this.funcGlyph) != null ? ref6.comments : void 0) != null) {
|
||
ref7 = this.funcGlyph.comments;
|
||
for (l = 0, len3 = ref7.length; l < len3; l++) {
|
||
comment = ref7[l];
|
||
comment.unshift = false;
|
||
}
|
||
this.compileCommentFragments(o, this.funcGlyph, signature);
|
||
}
|
||
if (!this.body.isEmpty()) {
|
||
body = this.body.compileWithDeclarations(o);
|
||
}
|
||
// We need to compile the body before method names to ensure `super`
|
||
// references are handled.
|
||
if (this.isMethod) {
|
||
[methodScope, o.scope] = [o.scope, o.scope.parent];
|
||
name = this.name.compileToFragments(o);
|
||
if (name[0].code === '.') {
|
||
name.shift();
|
||
}
|
||
o.scope = methodScope;
|
||
}
|
||
answer = this.joinFragmentArrays((function() {
|
||
var len4, q, results;
|
||
results = [];
|
||
for (q = 0, len4 = modifiers.length; q < len4; q++) {
|
||
m = modifiers[q];
|
||
results.push(this.makeCode(m));
|
||
}
|
||
return results;
|
||
}).call(this), ' ');
|
||
if (modifiers.length && name) {
|
||
answer.push(this.makeCode(' '));
|
||
}
|
||
if (name) {
|
||
answer.push(...name);
|
||
}
|
||
answer.push(...signature);
|
||
if (this.bound && !this.isMethod) {
|
||
answer.push(this.makeCode(' =>'));
|
||
}
|
||
answer.push(this.makeCode(' {'));
|
||
if (body != null ? body.length : void 0) {
|
||
answer.push(this.makeCode('\n'), ...body, this.makeCode(`\n${this.tab}`));
|
||
}
|
||
answer.push(this.makeCode('}'));
|
||
if (this.isMethod) {
|
||
return indentInitial(answer, this);
|
||
}
|
||
if (this.front || (o.level >= LEVEL_ACCESS)) {
|
||
return this.wrapInParentheses(answer);
|
||
} else {
|
||
return answer;
|
||
}
|
||
}
|
||
|
||
eachParamName(iterator) {
|
||
var j, len1, param, ref1, results;
|
||
ref1 = this.params;
|
||
results = [];
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
param = ref1[j];
|
||
results.push(param.eachName(iterator));
|
||
}
|
||
return results;
|
||
}
|
||
|
||
// Short-circuit `traverseChildren` method to prevent it from crossing scope
|
||
// boundaries unless `crossScope` is `true`.
|
||
traverseChildren(crossScope, func) {
|
||
if (crossScope) {
|
||
return super.traverseChildren(crossScope, func);
|
||
}
|
||
}
|
||
|
||
// Short-circuit `replaceInContext` method to prevent it from crossing context boundaries. Bound
|
||
// functions have the same context.
|
||
replaceInContext(child, replacement) {
|
||
if (this.bound) {
|
||
return super.replaceInContext(child, replacement);
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
expandCtorSuper(thisAssignments) {
|
||
var haveThisParam, param, ref1, seenSuper;
|
||
if (!this.ctor) {
|
||
return false;
|
||
}
|
||
this.eachSuperCall(Block.wrap(this.params), function(superCall) {
|
||
return superCall.error("'super' is not allowed in constructor parameter defaults");
|
||
});
|
||
seenSuper = this.eachSuperCall(this.body, (superCall) => {
|
||
if (this.ctor === 'base') {
|
||
superCall.error("'super' is only allowed in derived class constructors");
|
||
}
|
||
return superCall.expressions = thisAssignments;
|
||
});
|
||
haveThisParam = thisAssignments.length && thisAssignments.length !== ((ref1 = this.thisAssignments) != null ? ref1.length : void 0);
|
||
if (this.ctor === 'derived' && !seenSuper && haveThisParam) {
|
||
param = thisAssignments[0].variable;
|
||
param.error("Can't use @params in derived class constructors without calling super");
|
||
}
|
||
return seenSuper;
|
||
}
|
||
|
||
// Find all super calls in the given context node
|
||
// Returns `true` if `iterator` is called
|
||
eachSuperCall(context, iterator) {
|
||
var seenSuper;
|
||
seenSuper = false;
|
||
context.traverseChildren(true, (child) => {
|
||
if (child instanceof SuperCall) {
|
||
seenSuper = true;
|
||
iterator(child);
|
||
} else if (child instanceof ThisLiteral && this.ctor === 'derived' && !seenSuper) {
|
||
child.error("Can't reference 'this' before calling super in derived class constructors");
|
||
}
|
||
// `super` has the same target in bound (arrow) functions, so check them too
|
||
return !(child instanceof SuperCall) && (!(child instanceof Code) || child.bound);
|
||
});
|
||
return seenSuper;
|
||
}
|
||
|
||
};
|
||
|
||
Code.prototype.children = ['params', 'body'];
|
||
|
||
Code.prototype.jumps = NO;
|
||
|
||
return Code;
|
||
|
||
})();
|
||
|
||
//### Param
|
||
|
||
// A parameter in a function definition. Beyond a typical JavaScript parameter,
|
||
// these parameters can also attach themselves to the context of the function,
|
||
// as well as be a splat, gathering up a group of parameters into an array.
|
||
exports.Param = Param = (function() {
|
||
class Param extends Base {
|
||
constructor(name1, value1, splat) {
|
||
var message, token;
|
||
super();
|
||
this.name = name1;
|
||
this.value = value1;
|
||
this.splat = splat;
|
||
message = isUnassignable(this.name.unwrapAll().value);
|
||
if (message) {
|
||
this.name.error(message);
|
||
}
|
||
if (this.name instanceof Obj && this.name.generated) {
|
||
token = this.name.objects[0].operatorToken;
|
||
token.error(`unexpected ${token.value}`);
|
||
}
|
||
}
|
||
|
||
compileToFragments(o) {
|
||
return this.name.compileToFragments(o, LEVEL_LIST);
|
||
}
|
||
|
||
compileToFragmentsWithoutComments(o) {
|
||
return this.name.compileToFragmentsWithoutComments(o, LEVEL_LIST);
|
||
}
|
||
|
||
asReference(o) {
|
||
var name, node;
|
||
if (this.reference) {
|
||
return this.reference;
|
||
}
|
||
node = this.name;
|
||
if (node.this) {
|
||
name = node.properties[0].name.value;
|
||
if (indexOf.call(JS_FORBIDDEN, name) >= 0) {
|
||
name = `_${name}`;
|
||
}
|
||
node = new IdentifierLiteral(o.scope.freeVariable(name));
|
||
} else if (node.shouldCache()) {
|
||
node = new IdentifierLiteral(o.scope.freeVariable('arg'));
|
||
}
|
||
node = new Value(node);
|
||
node.updateLocationDataIfMissing(this.locationData);
|
||
return this.reference = node;
|
||
}
|
||
|
||
shouldCache() {
|
||
return this.name.shouldCache();
|
||
}
|
||
|
||
// Iterates the name or names of a `Param`.
|
||
// In a sense, a destructured parameter represents multiple JS parameters. This
|
||
// method allows to iterate them all.
|
||
// The `iterator` function will be called as `iterator(name, node)` where
|
||
// `name` is the name of the parameter and `node` is the AST node corresponding
|
||
// to that name.
|
||
eachName(iterator, name = this.name) {
|
||
var atParam, j, len1, node, obj, ref1, ref2;
|
||
atParam = (obj) => {
|
||
return iterator(`@${obj.properties[0].name.value}`, obj, this);
|
||
};
|
||
if (name instanceof Literal) {
|
||
// * simple literals `foo`
|
||
return iterator(name.value, name, this);
|
||
}
|
||
if (name instanceof Value) {
|
||
// * at-params `@foo`
|
||
return atParam(name);
|
||
}
|
||
ref2 = (ref1 = name.objects) != null ? ref1 : [];
|
||
for (j = 0, len1 = ref2.length; j < len1; j++) {
|
||
obj = ref2[j];
|
||
// * destructured parameter with default value
|
||
if (obj instanceof Assign && (obj.context == null)) {
|
||
obj = obj.variable;
|
||
}
|
||
// * assignments within destructured parameters `{foo:bar}`
|
||
if (obj instanceof Assign) {
|
||
// ... possibly with a default value
|
||
if (obj.value instanceof Assign) {
|
||
obj = obj.value.variable;
|
||
} else {
|
||
obj = obj.value;
|
||
}
|
||
this.eachName(iterator, obj.unwrap());
|
||
// * splats within destructured parameters `[xs...]`
|
||
} else if (obj instanceof Splat) {
|
||
node = obj.name.unwrap();
|
||
iterator(node.value, node, this);
|
||
} else if (obj instanceof Value) {
|
||
// * destructured parameters within destructured parameters `[{a}]`
|
||
if (obj.isArray() || obj.isObject()) {
|
||
this.eachName(iterator, obj.base);
|
||
// * at-params within destructured parameters `{@foo}`
|
||
} else if (obj.this) {
|
||
atParam(obj);
|
||
} else {
|
||
// * simple destructured parameters {foo}
|
||
iterator(obj.base.value, obj.base, this);
|
||
}
|
||
} else if (!(obj instanceof Expansion)) {
|
||
obj.error(`illegal parameter ${obj.compile()}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Rename a param by replacing the given AST node for a name with a new node.
|
||
// This needs to ensure that the the source for object destructuring does not change.
|
||
renameParam(node, newNode) {
|
||
var isNode, replacement;
|
||
isNode = function(candidate) {
|
||
return candidate === node;
|
||
};
|
||
replacement = (node, parent) => {
|
||
var key;
|
||
if (parent instanceof Obj) {
|
||
key = node;
|
||
if (node.this) {
|
||
key = node.properties[0].name;
|
||
}
|
||
return new Assign(new Value(key), newNode, 'object');
|
||
} else {
|
||
return newNode;
|
||
}
|
||
};
|
||
return this.replaceInContext(isNode, replacement);
|
||
}
|
||
|
||
};
|
||
|
||
Param.prototype.children = ['name', 'value'];
|
||
|
||
return Param;
|
||
|
||
})();
|
||
|
||
//### Splat
|
||
|
||
// A splat, either as a parameter to a function, an argument to a call,
|
||
// or as part of a destructuring assignment.
|
||
exports.Splat = Splat = (function() {
|
||
class Splat extends Base {
|
||
constructor(name) {
|
||
super();
|
||
this.name = name.compile ? name : new Literal(name);
|
||
}
|
||
|
||
isAssignable() {
|
||
return this.name.isAssignable() && (!this.name.isAtomic || this.name.isAtomic());
|
||
}
|
||
|
||
assigns(name) {
|
||
return this.name.assigns(name);
|
||
}
|
||
|
||
compileNode(o) {
|
||
return [this.makeCode('...'), ...this.name.compileToFragments(o, LEVEL_OP)];
|
||
}
|
||
|
||
unwrap() {
|
||
return this.name;
|
||
}
|
||
|
||
};
|
||
|
||
Splat.prototype.children = ['name'];
|
||
|
||
return Splat;
|
||
|
||
})();
|
||
|
||
//### Expansion
|
||
|
||
// Used to skip values inside an array destructuring (pattern matching) or
|
||
// parameter list.
|
||
exports.Expansion = Expansion = (function() {
|
||
class Expansion extends Base {
|
||
compileNode(o) {
|
||
return this.error('Expansion must be used inside a destructuring assignment or parameter list');
|
||
}
|
||
|
||
asReference(o) {
|
||
return this;
|
||
}
|
||
|
||
eachName(iterator) {}
|
||
|
||
};
|
||
|
||
Expansion.prototype.shouldCache = NO;
|
||
|
||
return Expansion;
|
||
|
||
})();
|
||
|
||
//### While
|
||
|
||
// A while loop, the only sort of low-level loop exposed by CoffeeScript. From
|
||
// it, all other loops can be manufactured. Useful in cases where you need more
|
||
// flexibility or more speed than a comprehension can provide.
|
||
exports.While = While = (function() {
|
||
class While extends Base {
|
||
constructor(condition, options) {
|
||
super();
|
||
this.condition = (options != null ? options.invert : void 0) ? condition.invert() : condition;
|
||
this.guard = options != null ? options.guard : void 0;
|
||
}
|
||
|
||
makeReturn(res) {
|
||
if (res) {
|
||
return super.makeReturn(res);
|
||
} else {
|
||
this.returns = !this.jumps();
|
||
return this;
|
||
}
|
||
}
|
||
|
||
addBody(body1) {
|
||
this.body = body1;
|
||
return this;
|
||
}
|
||
|
||
jumps() {
|
||
var expressions, j, jumpNode, len1, node;
|
||
({expressions} = this.body);
|
||
if (!expressions.length) {
|
||
return false;
|
||
}
|
||
for (j = 0, len1 = expressions.length; j < len1; j++) {
|
||
node = expressions[j];
|
||
if (jumpNode = node.jumps({
|
||
loop: true
|
||
})) {
|
||
return jumpNode;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// The main difference from a JavaScript *while* is that the CoffeeScript
|
||
// *while* can be used as a part of a larger expression -- while loops may
|
||
// return an array containing the computed result of each iteration.
|
||
compileNode(o) {
|
||
var answer, body, rvar, set;
|
||
o.indent += TAB;
|
||
set = '';
|
||
({body} = this);
|
||
if (body.isEmpty()) {
|
||
body = this.makeCode('');
|
||
} else {
|
||
if (this.returns) {
|
||
body.makeReturn(rvar = o.scope.freeVariable('results'));
|
||
set = `${this.tab}${rvar} = [];\n`;
|
||
}
|
||
if (this.guard) {
|
||
if (body.expressions.length > 1) {
|
||
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)]);
|
||
}
|
||
}
|
||
}
|
||
body = [].concat(this.makeCode("\n"), body.compileToFragments(o, LEVEL_TOP), this.makeCode(`\n${this.tab}`));
|
||
}
|
||
answer = [].concat(this.makeCode(set + this.tab + "while ("), this.condition.compileToFragments(o, LEVEL_PAREN), this.makeCode(") {"), body, this.makeCode("}"));
|
||
if (this.returns) {
|
||
answer.push(this.makeCode(`\n${this.tab}return ${rvar};`));
|
||
}
|
||
return answer;
|
||
}
|
||
|
||
};
|
||
|
||
While.prototype.children = ['condition', 'guard', 'body'];
|
||
|
||
While.prototype.isStatement = YES;
|
||
|
||
return While;
|
||
|
||
})();
|
||
|
||
//### Op
|
||
|
||
// Simple Arithmetic and logical operations. Performs some conversion from
|
||
// CoffeeScript operations into their JavaScript equivalents.
|
||
exports.Op = Op = (function() {
|
||
var CONVERSIONS, INVERSIONS;
|
||
|
||
class Op extends Base {
|
||
constructor(op, first, second, flip) {
|
||
var firstCall;
|
||
super();
|
||
if (op === 'in') {
|
||
return new In(first, second);
|
||
}
|
||
if (op === 'do') {
|
||
return Op.prototype.generateDo(first);
|
||
}
|
||
if (op === 'new') {
|
||
if ((firstCall = first.unwrap()) instanceof Call && !firstCall.do && !firstCall.isNew) {
|
||
return firstCall.newInstance();
|
||
}
|
||
if (first instanceof Code && first.bound || first.do) {
|
||
first = new Parens(first);
|
||
}
|
||
}
|
||
this.operator = CONVERSIONS[op] || op;
|
||
this.first = first;
|
||
this.second = second;
|
||
this.flip = !!flip;
|
||
return this;
|
||
}
|
||
|
||
isNumber() {
|
||
var ref1;
|
||
return this.isUnary() && ((ref1 = this.operator) === '+' || ref1 === '-') && this.first instanceof Value && this.first.isNumber();
|
||
}
|
||
|
||
isAwait() {
|
||
return this.operator === 'await';
|
||
}
|
||
|
||
isYield() {
|
||
var ref1;
|
||
return (ref1 = this.operator) === 'yield' || ref1 === 'yield*';
|
||
}
|
||
|
||
isUnary() {
|
||
return !this.second;
|
||
}
|
||
|
||
shouldCache() {
|
||
return !this.isNumber();
|
||
}
|
||
|
||
// Am I capable of
|
||
// [Python-style comparison chaining](https://docs.python.org/3/reference/expressions.html#not-in)?
|
||
isChainable() {
|
||
var ref1;
|
||
return (ref1 = this.operator) === '<' || ref1 === '>' || ref1 === '>=' || ref1 === '<=' || ref1 === '===' || ref1 === '!==';
|
||
}
|
||
|
||
invert() {
|
||
var allInvertable, curr, fst, op, ref1;
|
||
if (this.isChainable() && this.first.isChainable()) {
|
||
allInvertable = true;
|
||
curr = this;
|
||
while (curr && curr.operator) {
|
||
allInvertable && (allInvertable = curr.operator in INVERSIONS);
|
||
curr = curr.first;
|
||
}
|
||
if (!allInvertable) {
|
||
return new Parens(this).invert();
|
||
}
|
||
curr = this;
|
||
while (curr && curr.operator) {
|
||
curr.invert = !curr.invert;
|
||
curr.operator = INVERSIONS[curr.operator];
|
||
curr = curr.first;
|
||
}
|
||
return this;
|
||
} else if (op = INVERSIONS[this.operator]) {
|
||
this.operator = op;
|
||
if (this.first.unwrap() instanceof Op) {
|
||
this.first.invert();
|
||
}
|
||
return this;
|
||
} else if (this.second) {
|
||
return new Parens(this).invert();
|
||
} else if (this.operator === '!' && (fst = this.first.unwrap()) instanceof Op && ((ref1 = fst.operator) === '!' || ref1 === 'in' || ref1 === 'instanceof')) {
|
||
return fst;
|
||
} else {
|
||
return new Op('!', this);
|
||
}
|
||
}
|
||
|
||
unfoldSoak(o) {
|
||
var ref1;
|
||
return ((ref1 = this.operator) === '++' || ref1 === '--' || ref1 === 'delete') && unfoldSoak(o, this, 'first');
|
||
}
|
||
|
||
generateDo(exp) {
|
||
var call, func, j, len1, param, passedParams, ref, ref1;
|
||
passedParams = [];
|
||
func = exp instanceof Assign && (ref = exp.value.unwrap()) instanceof Code ? ref : exp;
|
||
ref1 = func.params || [];
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
param = ref1[j];
|
||
if (param.value) {
|
||
passedParams.push(param.value);
|
||
delete param.value;
|
||
} else {
|
||
passedParams.push(param);
|
||
}
|
||
}
|
||
call = new Call(exp, passedParams);
|
||
call.do = true;
|
||
return call;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var answer, isChain, lhs, message, ref1, rhs;
|
||
isChain = this.isChainable() && this.first.isChainable();
|
||
if (!isChain) {
|
||
// In chains, there's no need to wrap bare obj literals in parens,
|
||
// as the chained expression is wrapped.
|
||
this.first.front = this.front;
|
||
}
|
||
if (this.operator === 'delete' && o.scope.check(this.first.unwrapAll().value)) {
|
||
this.error('delete operand may not be argument or var');
|
||
}
|
||
if ((ref1 = this.operator) === '--' || ref1 === '++') {
|
||
message = isUnassignable(this.first.unwrapAll().value);
|
||
if (message) {
|
||
this.first.error(message);
|
||
}
|
||
}
|
||
if (this.isYield() || this.isAwait()) {
|
||
return this.compileContinuation(o);
|
||
}
|
||
if (this.isUnary()) {
|
||
return this.compileUnary(o);
|
||
}
|
||
if (isChain) {
|
||
return this.compileChain(o);
|
||
}
|
||
switch (this.operator) {
|
||
case '?':
|
||
return this.compileExistence(o, this.second.isDefaultValue);
|
||
case '**':
|
||
return this.compilePower(o);
|
||
case '//':
|
||
return this.compileFloorDivision(o);
|
||
case '%%':
|
||
return this.compileModulo(o);
|
||
default:
|
||
lhs = this.first.compileToFragments(o, LEVEL_OP);
|
||
rhs = this.second.compileToFragments(o, LEVEL_OP);
|
||
answer = [].concat(lhs, this.makeCode(` ${this.operator} `), rhs);
|
||
if (o.level <= LEVEL_OP) {
|
||
return answer;
|
||
} else {
|
||
return this.wrapInParentheses(answer);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Mimic Python's chained comparisons when multiple comparison operators are
|
||
// used sequentially. For example:
|
||
|
||
// bin/coffee -e 'console.log 50 < 65 > 10'
|
||
// true
|
||
compileChain(o) {
|
||
var fragments, fst, shared;
|
||
[this.first.second, shared] = this.first.second.cache(o);
|
||
fst = this.first.compileToFragments(o, LEVEL_OP);
|
||
fragments = fst.concat(this.makeCode(` ${(this.invert ? '&&' : '||')} `), shared.compileToFragments(o), this.makeCode(` ${this.operator} `), this.second.compileToFragments(o, LEVEL_OP));
|
||
return this.wrapInParentheses(fragments);
|
||
}
|
||
|
||
// Keep reference to the left expression, unless this an existential assignment
|
||
compileExistence(o, checkOnlyUndefined) {
|
||
var fst, ref;
|
||
if (this.first.shouldCache()) {
|
||
ref = new IdentifierLiteral(o.scope.freeVariable('ref'));
|
||
fst = new Parens(new Assign(ref, this.first));
|
||
} else {
|
||
fst = this.first;
|
||
ref = fst;
|
||
}
|
||
return new If(new Existence(fst, checkOnlyUndefined), ref, {
|
||
type: 'if'
|
||
}).addElse(this.second).compileToFragments(o);
|
||
}
|
||
|
||
// Compile a unary **Op**.
|
||
compileUnary(o) {
|
||
var op, parts, plusMinus;
|
||
parts = [];
|
||
op = this.operator;
|
||
parts.push([this.makeCode(op)]);
|
||
if (op === '!' && this.first instanceof Existence) {
|
||
this.first.negated = !this.first.negated;
|
||
return this.first.compileToFragments(o);
|
||
}
|
||
if (o.level >= LEVEL_ACCESS) {
|
||
return (new Parens(this)).compileToFragments(o);
|
||
}
|
||
plusMinus = op === '+' || op === '-';
|
||
if ((op === 'new' || op === 'typeof' || op === 'delete') || plusMinus && this.first instanceof Op && this.first.operator === op) {
|
||
parts.push([this.makeCode(' ')]);
|
||
}
|
||
if ((plusMinus && this.first instanceof Op) || (op === 'new' && this.first.isStatement(o))) {
|
||
this.first = new Parens(this.first);
|
||
}
|
||
parts.push(this.first.compileToFragments(o, LEVEL_OP));
|
||
if (this.flip) {
|
||
parts.reverse();
|
||
}
|
||
return this.joinFragmentArrays(parts, '');
|
||
}
|
||
|
||
compileContinuation(o) {
|
||
var op, parts, ref1, ref2;
|
||
parts = [];
|
||
op = this.operator;
|
||
if (o.scope.parent == null) {
|
||
this.error(`${this.operator} can only occur inside functions`);
|
||
}
|
||
if (((ref1 = o.scope.method) != null ? ref1.bound : void 0) && o.scope.method.isGenerator) {
|
||
this.error('yield cannot occur inside bound (fat arrow) functions');
|
||
}
|
||
if (indexOf.call(Object.keys(this.first), 'expression') >= 0 && !(this.first instanceof Throw)) {
|
||
if (this.first.expression != null) {
|
||
parts.push(this.first.expression.compileToFragments(o, LEVEL_OP));
|
||
}
|
||
} else {
|
||
if (o.level >= LEVEL_PAREN) {
|
||
parts.push([this.makeCode("(")]);
|
||
}
|
||
parts.push([this.makeCode(op)]);
|
||
if (((ref2 = this.first.base) != null ? ref2.value : void 0) !== '') {
|
||
parts.push([this.makeCode(" ")]);
|
||
}
|
||
parts.push(this.first.compileToFragments(o, LEVEL_OP));
|
||
if (o.level >= LEVEL_PAREN) {
|
||
parts.push([this.makeCode(")")]);
|
||
}
|
||
}
|
||
return this.joinFragmentArrays(parts, '');
|
||
}
|
||
|
||
compilePower(o) {
|
||
var pow;
|
||
// Make a Math.pow call
|
||
pow = new Value(new IdentifierLiteral('Math'), [new Access(new PropertyName('pow'))]);
|
||
return new Call(pow, [this.first, this.second]).compileToFragments(o);
|
||
}
|
||
|
||
compileFloorDivision(o) {
|
||
var div, floor, second;
|
||
floor = new Value(new IdentifierLiteral('Math'), [new Access(new PropertyName('floor'))]);
|
||
second = this.second.shouldCache() ? new Parens(this.second) : this.second;
|
||
div = new Op('/', this.first, second);
|
||
return new Call(floor, [div]).compileToFragments(o);
|
||
}
|
||
|
||
compileModulo(o) {
|
||
var mod;
|
||
mod = new Value(new Literal(utility('modulo', o)));
|
||
return new Call(mod, [this.first, this.second]).compileToFragments(o);
|
||
}
|
||
|
||
toString(idt) {
|
||
return super.toString(idt, this.constructor.name + ' ' + this.operator);
|
||
}
|
||
|
||
};
|
||
|
||
// The map of conversions from CoffeeScript to JavaScript symbols.
|
||
CONVERSIONS = {
|
||
'==': '===',
|
||
'!=': '!==',
|
||
'of': 'in',
|
||
'yieldfrom': 'yield*'
|
||
};
|
||
|
||
// The map of invertible operators.
|
||
INVERSIONS = {
|
||
'!==': '===',
|
||
'===': '!=='
|
||
};
|
||
|
||
Op.prototype.children = ['first', 'second'];
|
||
|
||
return Op;
|
||
|
||
})();
|
||
|
||
//### In
|
||
exports.In = In = (function() {
|
||
class In extends Base {
|
||
constructor(object, array) {
|
||
super();
|
||
this.object = object;
|
||
this.array = array;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var hasSplat, j, len1, obj, ref1;
|
||
if (this.array instanceof Value && this.array.isArray() && this.array.base.objects.length) {
|
||
ref1 = this.array.base.objects;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
obj = ref1[j];
|
||
if (!(obj instanceof Splat)) {
|
||
continue;
|
||
}
|
||
hasSplat = true;
|
||
break;
|
||
}
|
||
if (!hasSplat) {
|
||
// `compileOrTest` only if we have an array literal with no splats
|
||
return this.compileOrTest(o);
|
||
}
|
||
}
|
||
return this.compileLoopTest(o);
|
||
}
|
||
|
||
compileOrTest(o) {
|
||
var cmp, cnj, i, item, j, len1, ref, ref1, sub, tests;
|
||
[sub, ref] = this.object.cache(o, LEVEL_OP);
|
||
[cmp, cnj] = this.negated ? [' !== ', ' && '] : [' === ', ' || '];
|
||
tests = [];
|
||
ref1 = this.array.base.objects;
|
||
for (i = j = 0, len1 = ref1.length; j < len1; i = ++j) {
|
||
item = ref1[i];
|
||
if (i) {
|
||
tests.push(this.makeCode(cnj));
|
||
}
|
||
tests = tests.concat((i ? ref : sub), this.makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS));
|
||
}
|
||
if (o.level < LEVEL_OP) {
|
||
return tests;
|
||
} else {
|
||
return this.wrapInParentheses(tests);
|
||
}
|
||
}
|
||
|
||
compileLoopTest(o) {
|
||
var fragments, ref, sub;
|
||
[sub, ref] = this.object.cache(o, LEVEL_LIST);
|
||
fragments = [].concat(this.makeCode(utility('indexOf', o) + ".call("), this.array.compileToFragments(o, LEVEL_LIST), this.makeCode(", "), ref, this.makeCode(") " + (this.negated ? '< 0' : '>= 0')));
|
||
if (fragmentsToText(sub) === fragmentsToText(ref)) {
|
||
return fragments;
|
||
}
|
||
fragments = sub.concat(this.makeCode(', '), fragments);
|
||
if (o.level < LEVEL_LIST) {
|
||
return fragments;
|
||
} else {
|
||
return this.wrapInParentheses(fragments);
|
||
}
|
||
}
|
||
|
||
toString(idt) {
|
||
return super.toString(idt, this.constructor.name + (this.negated ? '!' : ''));
|
||
}
|
||
|
||
};
|
||
|
||
In.prototype.children = ['object', 'array'];
|
||
|
||
In.prototype.invert = NEGATE;
|
||
|
||
return In;
|
||
|
||
})();
|
||
|
||
//### Try
|
||
|
||
// A classic *try/catch/finally* block.
|
||
exports.Try = Try = (function() {
|
||
class Try extends Base {
|
||
constructor(attempt, errorVariable, recovery, ensure) {
|
||
super();
|
||
this.attempt = attempt;
|
||
this.errorVariable = errorVariable;
|
||
this.recovery = recovery;
|
||
this.ensure = ensure;
|
||
}
|
||
|
||
jumps(o) {
|
||
var ref1;
|
||
return this.attempt.jumps(o) || ((ref1 = this.recovery) != null ? ref1.jumps(o) : void 0);
|
||
}
|
||
|
||
makeReturn(res) {
|
||
if (this.attempt) {
|
||
this.attempt = this.attempt.makeReturn(res);
|
||
}
|
||
if (this.recovery) {
|
||
this.recovery = this.recovery.makeReturn(res);
|
||
}
|
||
return this;
|
||
}
|
||
|
||
// Compilation is more or less as you would expect -- the *finally* clause
|
||
// is optional, the *catch* is not.
|
||
compileNode(o) {
|
||
var catchPart, ensurePart, generatedErrorVariableName, message, placeholder, tryPart;
|
||
o.indent += TAB;
|
||
tryPart = this.attempt.compileToFragments(o, LEVEL_TOP);
|
||
catchPart = this.recovery ? (generatedErrorVariableName = o.scope.freeVariable('error', {
|
||
reserve: false
|
||
}), placeholder = new IdentifierLiteral(generatedErrorVariableName), this.errorVariable ? (message = isUnassignable(this.errorVariable.unwrapAll().value), message ? this.errorVariable.error(message) : void 0, 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}}`)) : [];
|
||
return [].concat(this.makeCode(`${this.tab}try {\n`), tryPart, this.makeCode(`\n${this.tab}}`), catchPart, ensurePart);
|
||
}
|
||
|
||
};
|
||
|
||
Try.prototype.children = ['attempt', 'recovery', 'ensure'];
|
||
|
||
Try.prototype.isStatement = YES;
|
||
|
||
return Try;
|
||
|
||
})();
|
||
|
||
//### Throw
|
||
|
||
// Simple node to throw an exception.
|
||
exports.Throw = Throw = (function() {
|
||
class Throw extends Base {
|
||
constructor(expression1) {
|
||
super();
|
||
this.expression = expression1;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var fragments;
|
||
fragments = this.expression.compileToFragments(o, LEVEL_LIST);
|
||
unshiftAfterComments(fragments, this.makeCode('throw '));
|
||
fragments.unshift(this.makeCode(this.tab));
|
||
fragments.push(this.makeCode(';'));
|
||
return fragments;
|
||
}
|
||
|
||
};
|
||
|
||
Throw.prototype.children = ['expression'];
|
||
|
||
Throw.prototype.isStatement = YES;
|
||
|
||
Throw.prototype.jumps = NO;
|
||
|
||
// A **Throw** is already a return, of sorts...
|
||
Throw.prototype.makeReturn = THIS;
|
||
|
||
return Throw;
|
||
|
||
})();
|
||
|
||
//### Existence
|
||
|
||
// Checks a variable for existence -- not `null` and not `undefined`. This is
|
||
// similar to `.nil?` in Ruby, and avoids having to consult a JavaScript truth
|
||
// table. Optionally only check if a variable is not `undefined`.
|
||
exports.Existence = Existence = (function() {
|
||
class Existence extends Base {
|
||
constructor(expression1, onlyNotUndefined = false) {
|
||
var salvagedComments;
|
||
super();
|
||
this.expression = expression1;
|
||
this.comparisonTarget = onlyNotUndefined ? 'undefined' : 'null';
|
||
salvagedComments = [];
|
||
this.expression.eachChild(function(child) {
|
||
var comment, j, k, len1, len2, ref1, ref2, ref3;
|
||
if (child.comments) {
|
||
ref1 = child.comments;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
comment = ref1[j];
|
||
if (indexOf.call(salvagedComments, comment) < 0) {
|
||
salvagedComments.push(comment);
|
||
}
|
||
}
|
||
delete child.comments;
|
||
}
|
||
if ((ref2 = child.name) != null ? ref2.comments : void 0) {
|
||
ref3 = child.name.comments;
|
||
for (k = 0, len2 = ref3.length; k < len2; k++) {
|
||
comment = ref3[k];
|
||
if (indexOf.call(salvagedComments, comment) < 0) {
|
||
salvagedComments.push(comment);
|
||
}
|
||
}
|
||
return delete child.name.comments;
|
||
}
|
||
});
|
||
attachCommentsToNode(salvagedComments, this);
|
||
moveComments(this.expression, this);
|
||
}
|
||
|
||
compileNode(o) {
|
||
var cmp, cnj, code;
|
||
this.expression.front = this.front;
|
||
code = this.expression.compile(o, LEVEL_OP);
|
||
if (this.expression.unwrap() instanceof IdentifierLiteral && !o.scope.check(code)) {
|
||
[cmp, cnj] = this.negated ? ['===', '||'] : ['!==', '&&'];
|
||
code = `typeof ${code} ${cmp} "undefined"` + (this.comparisonTarget !== 'undefined' ? ` ${cnj} ${code} ${cmp} ${this.comparisonTarget}` : '');
|
||
} else {
|
||
// We explicity want to use loose equality (`==`) when comparing against `null`,
|
||
// so that an existence check roughly corresponds to a check for truthiness.
|
||
// Do *not* change this to `===` for `null`, as this will break mountains of
|
||
// existing code. When comparing only against `undefined`, however, we want to
|
||
// use `===` because this use case is for parity with ES2015+ default values,
|
||
// which only get assigned when the variable is `undefined` (but not `null`).
|
||
cmp = this.comparisonTarget === 'null' ? this.negated ? '==' : '!=' : this.negated ? '===' : '!=='; // `undefined`
|
||
code = `${code} ${cmp} ${this.comparisonTarget}`;
|
||
}
|
||
return [this.makeCode(o.level <= LEVEL_COND ? code : `(${code})`)];
|
||
}
|
||
|
||
};
|
||
|
||
Existence.prototype.children = ['expression'];
|
||
|
||
Existence.prototype.invert = NEGATE;
|
||
|
||
return Existence;
|
||
|
||
})();
|
||
|
||
//### Parens
|
||
|
||
// An extra set of parentheses, specified explicitly in the source. At one time
|
||
// we tried to clean up the results by detecting and removing redundant
|
||
// parentheses, but no longer -- you can put in as many as you please.
|
||
|
||
// Parentheses are a good way to force any statement to become an expression.
|
||
exports.Parens = Parens = (function() {
|
||
class Parens extends Base {
|
||
constructor(body1) {
|
||
super();
|
||
this.body = body1;
|
||
}
|
||
|
||
unwrap() {
|
||
return this.body;
|
||
}
|
||
|
||
shouldCache() {
|
||
return this.body.shouldCache();
|
||
}
|
||
|
||
compileNode(o) {
|
||
var bare, expr, fragments;
|
||
expr = this.body.unwrap();
|
||
if (expr instanceof Value && expr.isAtomic() && !this.csxAttribute) {
|
||
expr.front = this.front;
|
||
return expr.compileToFragments(o);
|
||
}
|
||
fragments = expr.compileToFragments(o, LEVEL_PAREN);
|
||
bare = o.level < LEVEL_OP && (expr instanceof Op || expr.unwrap() instanceof Call || (expr instanceof For && expr.returns)) && (o.level < LEVEL_COND || fragments.length <= 3);
|
||
if (this.csxAttribute) {
|
||
return this.wrapInBraces(fragments);
|
||
}
|
||
if (bare) {
|
||
return fragments;
|
||
} else {
|
||
return this.wrapInParentheses(fragments);
|
||
}
|
||
}
|
||
|
||
};
|
||
|
||
Parens.prototype.children = ['body'];
|
||
|
||
return Parens;
|
||
|
||
})();
|
||
|
||
//### StringWithInterpolations
|
||
exports.StringWithInterpolations = StringWithInterpolations = (function() {
|
||
class StringWithInterpolations extends Base {
|
||
constructor(body1) {
|
||
super();
|
||
this.body = body1;
|
||
}
|
||
|
||
// `unwrap` returns `this` to stop ancestor nodes reaching in to grab @body,
|
||
// and using @body.compileNode. `StringWithInterpolations.compileNode` is
|
||
// _the_ custom logic to output interpolated strings as code.
|
||
unwrap() {
|
||
return this;
|
||
}
|
||
|
||
shouldCache() {
|
||
return this.body.shouldCache();
|
||
}
|
||
|
||
compileNode(o) {
|
||
var code, element, elements, expr, fragments, j, len1, salvagedComments, wrapped;
|
||
if (this.csxAttribute) {
|
||
wrapped = new Parens(new StringWithInterpolations(this.body));
|
||
wrapped.csxAttribute = true;
|
||
return wrapped.compileNode(o);
|
||
}
|
||
// Assumes that `expr` is `Value` » `StringLiteral` or `Op`
|
||
expr = this.body.unwrap();
|
||
elements = [];
|
||
salvagedComments = [];
|
||
expr.traverseChildren(false, function(node) {
|
||
var comment, j, k, len1, len2, ref1;
|
||
if (node instanceof StringLiteral) {
|
||
if (node.comments) {
|
||
salvagedComments.push(...node.comments);
|
||
delete node.comments;
|
||
}
|
||
elements.push(node);
|
||
return true;
|
||
} else if (node instanceof Parens) {
|
||
if (salvagedComments.length !== 0) {
|
||
for (j = 0, len1 = salvagedComments.length; j < len1; j++) {
|
||
comment = salvagedComments[j];
|
||
comment.unshift = true;
|
||
comment.newLine = true;
|
||
}
|
||
attachCommentsToNode(salvagedComments, node);
|
||
}
|
||
elements.push(node);
|
||
return false;
|
||
} else if (node.comments) {
|
||
// This node is getting discarded, but salvage its comments.
|
||
if (elements.length !== 0 && !(elements[elements.length - 1] instanceof StringLiteral)) {
|
||
ref1 = node.comments;
|
||
for (k = 0, len2 = ref1.length; k < len2; k++) {
|
||
comment = ref1[k];
|
||
comment.unshift = false;
|
||
comment.newLine = true;
|
||
}
|
||
attachCommentsToNode(node.comments, elements[elements.length - 1]);
|
||
} else {
|
||
salvagedComments.push(...node.comments);
|
||
}
|
||
delete node.comments;
|
||
}
|
||
return true;
|
||
});
|
||
fragments = [];
|
||
if (!this.csx) {
|
||
fragments.push(this.makeCode('`'));
|
||
}
|
||
for (j = 0, len1 = elements.length; j < len1; j++) {
|
||
element = elements[j];
|
||
if (element instanceof StringLiteral) {
|
||
element.value = element.unquote(true, this.csx);
|
||
if (!this.csx) {
|
||
// Backticks and `${` inside template literals must be escaped.
|
||
element.value = element.value.replace(/(\\*)(`|\$\{)/g, function(match, backslashes, toBeEscaped) {
|
||
if (backslashes.length % 2 === 0) {
|
||
return `${backslashes}\\${toBeEscaped}`;
|
||
} else {
|
||
return match;
|
||
}
|
||
});
|
||
}
|
||
fragments.push(...element.compileToFragments(o));
|
||
} else {
|
||
if (!this.csx) {
|
||
fragments.push(this.makeCode('$'));
|
||
}
|
||
code = element.compileToFragments(o, LEVEL_PAREN);
|
||
if (!this.isNestedTag(element) || code.some(function(fragment) {
|
||
return fragment.comments != null;
|
||
})) {
|
||
code = this.wrapInBraces(code);
|
||
// Flag the `{` and `}` fragments as having been generated by this
|
||
// `StringWithInterpolations` node, so that `compileComments` knows
|
||
// to treat them as bounds. Don’t trust `fragment.type`, which can
|
||
// report minified variable names when this compiler is minified.
|
||
code[0].isStringWithInterpolations = true;
|
||
code[code.length - 1].isStringWithInterpolations = true;
|
||
}
|
||
fragments.push(...code);
|
||
}
|
||
}
|
||
if (!this.csx) {
|
||
fragments.push(this.makeCode('`'));
|
||
}
|
||
return fragments;
|
||
}
|
||
|
||
isNestedTag(element) {
|
||
var call, exprs, ref1;
|
||
exprs = (ref1 = element.body) != null ? ref1.expressions : void 0;
|
||
call = exprs != null ? exprs[0].unwrap() : void 0;
|
||
return this.csx && exprs && exprs.length === 1 && call instanceof Call && call.csx;
|
||
}
|
||
|
||
};
|
||
|
||
StringWithInterpolations.prototype.children = ['body'];
|
||
|
||
return StringWithInterpolations;
|
||
|
||
})();
|
||
|
||
//### For
|
||
|
||
// CoffeeScript's replacement for the *for* loop is our array and object
|
||
// comprehensions, that compile into *for* loops here. They also act as an
|
||
// expression, able to return the result of each filtered iteration.
|
||
|
||
// Unlike Python array comprehensions, they can be multi-line, and you can pass
|
||
// the current index of the loop as a second parameter. Unlike Ruby blocks,
|
||
// you can map and filter in a single pass.
|
||
exports.For = For = (function() {
|
||
class For extends While {
|
||
constructor(body, source) {
|
||
var attribute, j, len1, ref1, ref2, ref3;
|
||
super();
|
||
({source: this.source, guard: this.guard, step: this.step, name: this.name, index: this.index} = source);
|
||
this.body = Block.wrap([body]);
|
||
this.own = source.own != null;
|
||
this.object = source.object != null;
|
||
this.from = source.from != null;
|
||
if (this.from && this.index) {
|
||
this.index.error('cannot use index with for-from');
|
||
}
|
||
if (this.own && !this.object) {
|
||
source.ownTag.error(`cannot use own with for-${(this.from ? 'from' : 'in')}`);
|
||
}
|
||
if (this.object) {
|
||
[this.name, this.index] = [this.index, this.name];
|
||
}
|
||
if (((ref1 = this.index) != null ? typeof ref1.isArray === "function" ? ref1.isArray() : void 0 : void 0) || ((ref2 = this.index) != null ? typeof ref2.isObject === "function" ? ref2.isObject() : void 0 : void 0)) {
|
||
this.index.error('index cannot be a pattern matching expression');
|
||
}
|
||
this.range = this.source instanceof Value && this.source.base instanceof Range && !this.source.properties.length && !this.from;
|
||
this.pattern = this.name instanceof Value;
|
||
if (this.range && this.index) {
|
||
this.index.error('indexes do not apply to range loops');
|
||
}
|
||
if (this.range && this.pattern) {
|
||
this.name.error('cannot pattern match over range loops');
|
||
}
|
||
this.returns = false;
|
||
ref3 = ['source', 'guard', 'step', 'name', 'index'];
|
||
// Move up any comments in the “`for` line”, i.e. the line of code with `for`,
|
||
// from any child nodes of that line up to the `for` node itself so that these
|
||
// comments get output, and get output above the `for` loop.
|
||
for (j = 0, len1 = ref3.length; j < len1; j++) {
|
||
attribute = ref3[j];
|
||
if (!this[attribute]) {
|
||
continue;
|
||
}
|
||
this[attribute].traverseChildren(true, (node) => {
|
||
var comment, k, len2, ref4;
|
||
if (node.comments) {
|
||
ref4 = node.comments;
|
||
for (k = 0, len2 = ref4.length; k < len2; k++) {
|
||
comment = ref4[k];
|
||
// These comments are buried pretty deeply, so if they happen to be
|
||
// trailing comments the line they trail will be unrecognizable when
|
||
// we’re done compiling this `for` loop; so just shift them up to
|
||
// output above the `for` line.
|
||
comment.newLine = comment.unshift = true;
|
||
}
|
||
return moveComments(node, this[attribute]);
|
||
}
|
||
});
|
||
moveComments(this[attribute], this);
|
||
}
|
||
}
|
||
|
||
// Welcome to the hairiest method in all of CoffeeScript. Handles the inner
|
||
// loop, filtering, stepping, and result saving for array, object, and range
|
||
// comprehensions. Some of the generated code can be shared in common, and
|
||
// some cannot.
|
||
compileNode(o) {
|
||
var body, bodyFragments, compare, compareDown, declare, declareDown, defPart, defPartFragments, 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];
|
||
if ((last != null ? last.jumps() : void 0) instanceof Return) {
|
||
this.returns = false;
|
||
}
|
||
source = this.range ? this.source.base : this.source;
|
||
scope = o.scope;
|
||
if (!this.pattern) {
|
||
name = this.name && (this.name.compile(o, LEVEL_LIST));
|
||
}
|
||
index = this.index && (this.index.compile(o, LEVEL_LIST));
|
||
if (name && !this.pattern) {
|
||
scope.find(name);
|
||
}
|
||
if (index && !(this.index instanceof Value)) {
|
||
scope.find(index);
|
||
}
|
||
if (this.returns) {
|
||
rvar = scope.freeVariable('results');
|
||
}
|
||
if (this.from) {
|
||
if (this.pattern) {
|
||
ivar = scope.freeVariable('x', {
|
||
single: true
|
||
});
|
||
}
|
||
} else {
|
||
ivar = (this.object && index) || scope.freeVariable('i', {
|
||
single: true
|
||
});
|
||
}
|
||
kvar = ((this.range || this.from) && name) || index || ivar;
|
||
kvarAssign = kvar !== ivar ? `${kvar} = ` : "";
|
||
if (this.step && !this.range) {
|
||
[step, stepVar] = this.cacheToCodeFragments(this.step.cache(o, LEVEL_LIST, shouldCacheOrIsAssignable));
|
||
if (this.step.isNumber()) {
|
||
stepNum = Number(stepVar);
|
||
}
|
||
}
|
||
if (this.pattern) {
|
||
name = ivar;
|
||
}
|
||
varPart = '';
|
||
guardPart = '';
|
||
defPart = '';
|
||
idt1 = this.tab + TAB;
|
||
if (this.range) {
|
||
forPartFragments = source.compileToFragments(merge(o, {
|
||
index: ivar,
|
||
name,
|
||
step: this.step,
|
||
shouldCache: shouldCacheOrIsAssignable
|
||
}));
|
||
} else {
|
||
svar = this.source.compile(o, LEVEL_LIST);
|
||
if ((name || this.own) && !(this.source.unwrap() instanceof IdentifierLiteral)) {
|
||
defPart += `${this.tab}${(ref = scope.freeVariable('ref'))} = ${svar};\n`;
|
||
svar = ref;
|
||
}
|
||
if (name && !this.pattern && !this.from) {
|
||
namePart = `${name} = ${svar}[${kvar}]`;
|
||
}
|
||
if (!this.object && !this.from) {
|
||
if (step !== stepVar) {
|
||
defPart += `${this.tab}${step};\n`;
|
||
}
|
||
down = stepNum < 0;
|
||
if (!(this.step && (stepNum != null) && down)) {
|
||
lvar = scope.freeVariable('len');
|
||
}
|
||
declare = `${kvarAssign}${ivar} = 0, ${lvar} = ${svar}.length`;
|
||
declareDown = `${kvarAssign}${ivar} = ${svar}.length - 1`;
|
||
compare = `${ivar} < ${lvar}`;
|
||
compareDown = `${ivar} >= 0`;
|
||
if (this.step) {
|
||
if (stepNum != null) {
|
||
if (down) {
|
||
compare = compareDown;
|
||
declare = declareDown;
|
||
}
|
||
} else {
|
||
compare = `${stepVar} > 0 ? ${compare} : ${compareDown}`;
|
||
declare = `(${stepVar} > 0 ? (${declare}) : ${declareDown})`;
|
||
}
|
||
increment = `${ivar} += ${stepVar}`;
|
||
} else {
|
||
increment = `${(kvar !== ivar ? `++${ivar}` : `${ivar}++`)}`;
|
||
}
|
||
forPartFragments = [this.makeCode(`${declare}; ${compare}; ${kvarAssign}${increment}`)];
|
||
}
|
||
}
|
||
if (this.returns) {
|
||
resultPart = `${this.tab}${rvar} = [];\n`;
|
||
returnResult = `\n${this.tab}return ${rvar};`;
|
||
body.makeReturn(rvar);
|
||
}
|
||
if (this.guard) {
|
||
if (body.expressions.length > 1) {
|
||
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)]);
|
||
}
|
||
}
|
||
}
|
||
if (this.pattern) {
|
||
body.expressions.unshift(new Assign(this.name, this.from ? new IdentifierLiteral(kvar) : new Literal(`${svar}[${kvar}]`)));
|
||
}
|
||
defPartFragments = [].concat(this.makeCode(defPart), this.pluckDirectCall(o, body));
|
||
if (namePart) {
|
||
varPart = `\n${idt1}${namePart};`;
|
||
}
|
||
if (this.object) {
|
||
forPartFragments = [this.makeCode(`${kvar} in ${svar}`)];
|
||
if (this.own) {
|
||
guardPart = `\n${idt1}if (!${utility('hasProp', o)}.call(${svar}, ${kvar})) continue;`;
|
||
}
|
||
} else if (this.from) {
|
||
forPartFragments = [this.makeCode(`${kvar} of ${svar}`)];
|
||
}
|
||
bodyFragments = body.compileToFragments(merge(o, {
|
||
indent: idt1
|
||
}), LEVEL_TOP);
|
||
if (bodyFragments && bodyFragments.length > 0) {
|
||
bodyFragments = [].concat(this.makeCode('\n'), bodyFragments, this.makeCode('\n'));
|
||
}
|
||
fragments = [];
|
||
if ((defPartFragments != null) && fragmentsToText(defPartFragments) !== '') {
|
||
fragments = fragments.concat(defPartFragments);
|
||
}
|
||
if (resultPart) {
|
||
fragments.push(this.makeCode(resultPart));
|
||
}
|
||
fragments = fragments.concat(this.makeCode(this.tab), this.makeCode('for ('), forPartFragments, this.makeCode(`) {${guardPart}${varPart}`), bodyFragments, this.makeCode(this.tab), this.makeCode('}'));
|
||
if (returnResult) {
|
||
fragments.push(this.makeCode(returnResult));
|
||
}
|
||
return fragments;
|
||
}
|
||
|
||
pluckDirectCall(o, body) {
|
||
var base, defs, expr, fn, idx, j, len1, ref, ref1, ref2, ref3, ref4, ref5, ref6, val;
|
||
defs = [];
|
||
ref1 = body.expressions;
|
||
for (idx = j = 0, len1 = ref1.length; j < len1; idx = ++j) {
|
||
expr = ref1[idx];
|
||
expr = expr.unwrapAll();
|
||
if (!(expr instanceof Call)) {
|
||
continue;
|
||
}
|
||
val = (ref2 = expr.variable) != null ? ref2.unwrapAll() : void 0;
|
||
if (!((val instanceof Code) || (val instanceof Value && ((ref3 = val.base) != null ? ref3.unwrapAll() : void 0) instanceof Code && val.properties.length === 1 && ((ref4 = (ref5 = val.properties[0].name) != null ? ref5.value : void 0) === 'call' || ref4 === 'apply')))) {
|
||
continue;
|
||
}
|
||
fn = ((ref6 = val.base) != null ? ref6.unwrapAll() : void 0) || val;
|
||
ref = new IdentifierLiteral(o.scope.freeVariable('fn'));
|
||
base = new Value(ref);
|
||
if (val.base) {
|
||
[val.base, base] = [base, val];
|
||
}
|
||
body.expressions[idx] = new Call(base, expr.args);
|
||
defs = defs.concat(this.makeCode(this.tab), new Assign(ref, fn).compileToFragments(o, LEVEL_TOP), this.makeCode(';\n'));
|
||
}
|
||
return defs;
|
||
}
|
||
|
||
};
|
||
|
||
For.prototype.children = ['body', 'source', 'guard', 'step'];
|
||
|
||
return For;
|
||
|
||
})();
|
||
|
||
//### Switch
|
||
|
||
// A JavaScript *switch* statement. Converts into a returnable expression on-demand.
|
||
exports.Switch = Switch = (function() {
|
||
class Switch extends Base {
|
||
constructor(subject, cases, otherwise) {
|
||
super();
|
||
this.subject = subject;
|
||
this.cases = cases;
|
||
this.otherwise = otherwise;
|
||
}
|
||
|
||
jumps(o = {
|
||
block: true
|
||
}) {
|
||
var block, conds, j, jumpNode, len1, ref1, ref2;
|
||
ref1 = this.cases;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
[conds, block] = ref1[j];
|
||
if (jumpNode = block.jumps(o)) {
|
||
return jumpNode;
|
||
}
|
||
}
|
||
return (ref2 = this.otherwise) != null ? ref2.jumps(o) : void 0;
|
||
}
|
||
|
||
makeReturn(res) {
|
||
var j, len1, pair, ref1, ref2;
|
||
ref1 = this.cases;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
pair = ref1[j];
|
||
pair[1].makeReturn(res);
|
||
}
|
||
if (res) {
|
||
this.otherwise || (this.otherwise = new Block([new Literal('void 0')]));
|
||
}
|
||
if ((ref2 = this.otherwise) != null) {
|
||
ref2.makeReturn(res);
|
||
}
|
||
return this;
|
||
}
|
||
|
||
compileNode(o) {
|
||
var block, body, cond, conditions, expr, fragments, i, idt1, idt2, j, k, len1, len2, ref1, ref2;
|
||
idt1 = o.indent + TAB;
|
||
idt2 = o.indent = idt1 + TAB;
|
||
fragments = [].concat(this.makeCode(this.tab + "switch ("), (this.subject ? this.subject.compileToFragments(o, LEVEL_PAREN) : this.makeCode("false")), this.makeCode(") {\n"));
|
||
ref1 = this.cases;
|
||
for (i = j = 0, len1 = ref1.length; j < len1; i = ++j) {
|
||
[conditions, block] = ref1[i];
|
||
ref2 = flatten([conditions]);
|
||
for (k = 0, len2 = ref2.length; k < len2; k++) {
|
||
cond = ref2[k];
|
||
if (!this.subject) {
|
||
cond = cond.invert();
|
||
}
|
||
fragments = fragments.concat(this.makeCode(idt1 + "case "), cond.compileToFragments(o, LEVEL_PAREN), this.makeCode(":\n"));
|
||
}
|
||
if ((body = block.compileToFragments(o, LEVEL_TOP)).length > 0) {
|
||
fragments = fragments.concat(body, this.makeCode('\n'));
|
||
}
|
||
if (i === this.cases.length - 1 && !this.otherwise) {
|
||
break;
|
||
}
|
||
expr = this.lastNode(block.expressions);
|
||
if (expr instanceof Return || expr instanceof Throw || (expr instanceof Literal && expr.jumps() && expr.value !== 'debugger')) {
|
||
continue;
|
||
}
|
||
fragments.push(cond.makeCode(idt2 + 'break;\n'));
|
||
}
|
||
if (this.otherwise && this.otherwise.expressions.length) {
|
||
fragments.push(this.makeCode(idt1 + "default:\n"), ...(this.otherwise.compileToFragments(o, LEVEL_TOP)), this.makeCode("\n"));
|
||
}
|
||
fragments.push(this.makeCode(this.tab + '}'));
|
||
return fragments;
|
||
}
|
||
|
||
};
|
||
|
||
Switch.prototype.children = ['subject', 'cases', 'otherwise'];
|
||
|
||
Switch.prototype.isStatement = YES;
|
||
|
||
return Switch;
|
||
|
||
})();
|
||
|
||
//### If
|
||
|
||
// *If/else* statements. Acts as an expression by pushing down requested returns
|
||
// to the last line of each clause.
|
||
|
||
// Single-expression **Ifs** are compiled into conditional operators if possible,
|
||
// because ternaries are already proper expressions, and don’t need conversion.
|
||
exports.If = If = (function() {
|
||
class If extends Base {
|
||
constructor(condition, body1, options = {}) {
|
||
super();
|
||
this.body = body1;
|
||
this.condition = options.type === 'unless' ? condition.invert() : condition;
|
||
this.elseBody = null;
|
||
this.isChain = false;
|
||
({soak: this.soak} = options);
|
||
if (this.condition.comments) {
|
||
moveComments(this.condition, this);
|
||
}
|
||
}
|
||
|
||
bodyNode() {
|
||
var ref1;
|
||
return (ref1 = this.body) != null ? ref1.unwrap() : void 0;
|
||
}
|
||
|
||
elseBodyNode() {
|
||
var ref1;
|
||
return (ref1 = this.elseBody) != null ? ref1.unwrap() : void 0;
|
||
}
|
||
|
||
// Rewrite a chain of **Ifs** to add a default case as the final *else*.
|
||
addElse(elseBody) {
|
||
if (this.isChain) {
|
||
this.elseBodyNode().addElse(elseBody);
|
||
} else {
|
||
this.isChain = elseBody instanceof If;
|
||
this.elseBody = this.ensureBlock(elseBody);
|
||
this.elseBody.updateLocationDataIfMissing(elseBody.locationData);
|
||
}
|
||
return this;
|
||
}
|
||
|
||
// The **If** only compiles into a statement if either of its bodies needs
|
||
// to be a statement. Otherwise a conditional operator is safe.
|
||
isStatement(o) {
|
||
var ref1;
|
||
return (o != null ? o.level : void 0) === LEVEL_TOP || this.bodyNode().isStatement(o) || ((ref1 = this.elseBodyNode()) != null ? ref1.isStatement(o) : void 0);
|
||
}
|
||
|
||
jumps(o) {
|
||
var ref1;
|
||
return this.body.jumps(o) || ((ref1 = this.elseBody) != null ? ref1.jumps(o) : void 0);
|
||
}
|
||
|
||
compileNode(o) {
|
||
if (this.isStatement(o)) {
|
||
return this.compileStatement(o);
|
||
} else {
|
||
return this.compileExpression(o);
|
||
}
|
||
}
|
||
|
||
makeReturn(res) {
|
||
if (res) {
|
||
this.elseBody || (this.elseBody = new Block([new Literal('void 0')]));
|
||
}
|
||
this.body && (this.body = new Block([this.body.makeReturn(res)]));
|
||
this.elseBody && (this.elseBody = new Block([this.elseBody.makeReturn(res)]));
|
||
return this;
|
||
}
|
||
|
||
ensureBlock(node) {
|
||
if (node instanceof Block) {
|
||
return node;
|
||
} else {
|
||
return new Block([node]);
|
||
}
|
||
}
|
||
|
||
// Compile the `If` as a regular *if-else* statement. Flattened chains
|
||
// force inner *else* bodies into statement form.
|
||
compileStatement(o) {
|
||
var answer, body, child, cond, exeq, ifPart, indent;
|
||
child = del(o, 'chainChild');
|
||
exeq = del(o, 'isExistentialEquals');
|
||
if (exeq) {
|
||
return new If(this.condition.invert(), this.elseBodyNode(), {
|
||
type: 'if'
|
||
}).compileToFragments(o);
|
||
}
|
||
indent = o.indent + TAB;
|
||
cond = this.condition.compileToFragments(o, LEVEL_PAREN);
|
||
body = this.ensureBlock(this.body).compileToFragments(merge(o, {indent}));
|
||
ifPart = [].concat(this.makeCode("if ("), cond, this.makeCode(") {\n"), body, this.makeCode(`\n${this.tab}}`));
|
||
if (!child) {
|
||
ifPart.unshift(this.makeCode(this.tab));
|
||
}
|
||
if (!this.elseBody) {
|
||
return ifPart;
|
||
}
|
||
answer = ifPart.concat(this.makeCode(' else '));
|
||
if (this.isChain) {
|
||
o.chainChild = true;
|
||
answer = answer.concat(this.elseBody.unwrap().compileToFragments(o, LEVEL_TOP));
|
||
} else {
|
||
answer = answer.concat(this.makeCode("{\n"), this.elseBody.compileToFragments(merge(o, {indent}), LEVEL_TOP), this.makeCode(`\n${this.tab}}`));
|
||
}
|
||
return answer;
|
||
}
|
||
|
||
// Compile the `If` as a conditional operator.
|
||
compileExpression(o) {
|
||
var alt, body, cond, fragments;
|
||
cond = this.condition.compileToFragments(o, LEVEL_COND);
|
||
body = this.bodyNode().compileToFragments(o, LEVEL_LIST);
|
||
alt = this.elseBodyNode() ? this.elseBodyNode().compileToFragments(o, LEVEL_LIST) : [this.makeCode('void 0')];
|
||
fragments = cond.concat(this.makeCode(" ? "), body, this.makeCode(" : "), alt);
|
||
if (o.level >= LEVEL_COND) {
|
||
return this.wrapInParentheses(fragments);
|
||
} else {
|
||
return fragments;
|
||
}
|
||
}
|
||
|
||
unfoldSoak() {
|
||
return this.soak && this;
|
||
}
|
||
|
||
};
|
||
|
||
If.prototype.children = ['condition', 'body', 'elseBody'];
|
||
|
||
return If;
|
||
|
||
})();
|
||
|
||
// Constants
|
||
// ---------
|
||
UTILITIES = {
|
||
modulo: function() {
|
||
return 'function(a, b) { return (+a % (b = +b) + b) % b; }';
|
||
},
|
||
objectWithoutKeys: function() {
|
||
return "function(o, ks) { var res = {}; for (var k in o) ([].indexOf.call(ks, k) < 0 && {}.hasOwnProperty.call(o, k)) && (res[k] = o[k]); return res; }";
|
||
},
|
||
boundMethodCheck: function() {
|
||
return "function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } }";
|
||
},
|
||
_extends: function() {
|
||
return "Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }";
|
||
},
|
||
// Shortcuts to speed up the lookup time for native functions.
|
||
hasProp: function() {
|
||
return '{}.hasOwnProperty';
|
||
},
|
||
indexOf: function() {
|
||
return '[].indexOf';
|
||
},
|
||
slice: function() {
|
||
return '[].slice';
|
||
},
|
||
splice: function() {
|
||
return '[].splice';
|
||
}
|
||
};
|
||
|
||
// Levels indicate a node's position in the AST. Useful for knowing if
|
||
// parens are necessary or superfluous.
|
||
LEVEL_TOP = 1; // ...;
|
||
|
||
LEVEL_PAREN = 2; // (...)
|
||
|
||
LEVEL_LIST = 3; // [...]
|
||
|
||
LEVEL_COND = 4; // ... ? x : y
|
||
|
||
LEVEL_OP = 5; // !...
|
||
|
||
LEVEL_ACCESS = 6; // ...[0]
|
||
|
||
|
||
// Tabs are two spaces for pretty printing.
|
||
TAB = ' ';
|
||
|
||
SIMPLENUM = /^[+-]?\d+$/;
|
||
|
||
// Helper Functions
|
||
// ----------------
|
||
|
||
// Helper for ensuring that utility functions are assigned at the top level.
|
||
utility = function(name, o) {
|
||
var ref, root;
|
||
({root} = o.scope);
|
||
if (name in root.utilities) {
|
||
return root.utilities[name];
|
||
} else {
|
||
ref = root.freeVariable(name);
|
||
root.assign(ref, UTILITIES[name](o));
|
||
return root.utilities[name] = ref;
|
||
}
|
||
};
|
||
|
||
multident = function(code, tab, includingFirstLine = true) {
|
||
var endsWithNewLine;
|
||
endsWithNewLine = code[code.length - 1] === '\n';
|
||
code = (includingFirstLine ? tab : '') + code.replace(/\n/g, `$&${tab}`);
|
||
code = code.replace(/\s+$/, '');
|
||
if (endsWithNewLine) {
|
||
code = code + '\n';
|
||
}
|
||
return code;
|
||
};
|
||
|
||
// Wherever in CoffeeScript 1 we might’ve inserted a `makeCode "#{@tab}"` to
|
||
// indent a line of code, now we must account for the possibility of comments
|
||
// preceding that line of code. If there are such comments, indent each line of
|
||
// such comments, and _then_ indent the first following line of code.
|
||
indentInitial = function(fragments, node) {
|
||
var fragment, fragmentIndex, j, len1;
|
||
for (fragmentIndex = j = 0, len1 = fragments.length; j < len1; fragmentIndex = ++j) {
|
||
fragment = fragments[fragmentIndex];
|
||
if (fragment.isHereComment) {
|
||
fragment.code = multident(fragment.code, node.tab);
|
||
} else {
|
||
fragments.splice(fragmentIndex, 0, node.makeCode(`${node.tab}`));
|
||
break;
|
||
}
|
||
}
|
||
return fragments;
|
||
};
|
||
|
||
hasLineComments = function(node) {
|
||
var comment, j, len1, ref1;
|
||
if (!node.comments) {
|
||
return false;
|
||
}
|
||
ref1 = node.comments;
|
||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||
comment = ref1[j];
|
||
if (comment.here === false) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
|
||
// Move the `comments` property from one object to another, deleting it from
|
||
// the first object.
|
||
moveComments = function(from, to) {
|
||
if (!(from != null ? from.comments : void 0)) {
|
||
return;
|
||
}
|
||
attachCommentsToNode(from.comments, to);
|
||
return delete from.comments;
|
||
};
|
||
|
||
// Sometimes when compiling a node, we want to insert a fragment at the start
|
||
// of an array of fragments; but if the start has one or more comment fragments,
|
||
// we want to insert this fragment after those but before any non-comments.
|
||
unshiftAfterComments = function(fragments, fragmentToInsert) {
|
||
var fragment, fragmentIndex, inserted, j, len1;
|
||
inserted = false;
|
||
for (fragmentIndex = j = 0, len1 = fragments.length; j < len1; fragmentIndex = ++j) {
|
||
fragment = fragments[fragmentIndex];
|
||
if (!(!fragment.isComment)) {
|
||
continue;
|
||
}
|
||
fragments.splice(fragmentIndex, 0, fragmentToInsert);
|
||
inserted = true;
|
||
break;
|
||
}
|
||
if (!inserted) {
|
||
fragments.push(fragmentToInsert);
|
||
}
|
||
return fragments;
|
||
};
|
||
|
||
isLiteralArguments = function(node) {
|
||
return node instanceof IdentifierLiteral && node.value === 'arguments';
|
||
};
|
||
|
||
isLiteralThis = function(node) {
|
||
return node instanceof ThisLiteral || (node instanceof Code && node.bound);
|
||
};
|
||
|
||
shouldCacheOrIsAssignable = function(node) {
|
||
return node.shouldCache() || (typeof node.isAssignable === "function" ? node.isAssignable() : void 0);
|
||
};
|
||
|
||
// Unfold a node's child if soak, then tuck the node under created `If`
|
||
unfoldSoak = function(o, parent, name) {
|
||
var ifn;
|
||
if (!(ifn = parent[name].unfoldSoak(o))) {
|
||
return;
|
||
}
|
||
parent[name] = ifn.body;
|
||
ifn.body = new Value(parent);
|
||
return ifn;
|
||
};
|
||
|
||
}).call(this);
|