Skip to content

Commit

Permalink
splats in function invocation
Browse files Browse the repository at this point in the history
  • Loading branch information
alongubkin committed Nov 14, 2014
1 parent 8f68c49 commit 86cc7fb
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 74 deletions.
1 change: 1 addition & 0 deletions lib/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ module.exports = {
NewExpression: require('./ast/expressions/NewExpression').NewExpression,
ThisExpression: require('./ast/expressions/ThisExpression').ThisExpression,
SuperExpression: require('./ast/expressions/SuperExpression').SuperExpression,
SplatExpression: require('./ast/expressions/SplatExpression').SplatExpression,
BlockStatement: require('./ast/statements/BlockStatement').BlockStatement,
ExpressionStatement: require('./ast/statements/ExpressionStatement').ExpressionStatement,
IfStatement: require('./ast/statements/IfStatement').IfStatement,
Expand Down
92 changes: 79 additions & 13 deletions lib/ast/expressions/CallExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,82 @@ exports.CallExpression.prototype.codegen = function () {
}

var args = this.arguments;
args.forEach(function (arg, i) {
if (!args[i].codeGenerated) {
var splatPositions = [];

args.forEach(function (arg, i) {
if (args[i].type === "SplatExpression" || args[i].__splat) {
splatPositions.push(i);
}

if (!args[i].codeGenerated) {
args[i] = arg.codegen();
}
});

if (splatPositions.length > 0) {
var argsClone = args.slice(0);
args.length = 0;
args.push({
"type": "Literal",
"value": null
});

if (argsClone.length === 1) {
args.push(argsClone[0].arguments[0]);
} else {
args.push({
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"computed": false,
"object": splatPositions[0] === 0 ? argsClone[0] : {
"type": "ArrayExpression",
"elements": argsClone.slice(0, splatPositions[0])
},
"property": {
"type": "Identifier",
"name": "concat"
}
},
"arguments": argsClone.slice(splatPositions[0] === 0 ? 1 : splatPositions[0])
.map(function (arg, i) {
if (splatPositions.indexOf(i + (splatPositions[0] === 0 ? 1 : splatPositions[0])) !== -1) {
return arg;
}

return {
"type": "ArrayExpression",
"elements": [arg]
};
})
});
}
}

// If we are null propagating (?.), then turn this
// into a condition and add the null propagating condition.
if (this.callee.type === 'ConditionalExpression' &&
(calleeType === 'NullPropagatingExpression' || calleeType === 'MemberExpression')) {
var parent = this.parent;

var consequent = {
type: 'CallExpression',
callee: this.callee.consequent,
arguments: this.arguments
};

if (splatPositions.length > 0) {
this.callee.consequent = {
"type": "MemberExpression",
"computed": false,
"object": this.callee.consequent,
"property": {
"type": "Identifier",
"name": "apply"
}
};
}

// If we're inside a statement, then turn this into
// a normal if statement.
if (parent.type === 'ExpressionStatement') {
Expand All @@ -54,26 +118,28 @@ exports.CallExpression.prototype.codegen = function () {
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'CallExpression',
callee: this.callee.consequent,
arguments: this.arguments
}
expression: consequent
}
]
};
parent.alternate = null;
} else {
// Otherwise, it should be a conditional expression (?:).
this.type = 'ConditionalExpression';
this.type = 'ConditionalExpression';
this.test = this.callee.test;
this.consequent = {
type: 'CallExpression',
callee: this.callee.consequent,
arguments: this.arguments
};
this.consequent = consequent;
this.alternate = this.callee.alternate;
}
} else if (splatPositions.length > 0) {
this.callee = {
"type": "MemberExpression",
"computed": false,
"object": this.callee,
"property": {
"type": "Identifier",
"name": "apply"
}
};
}

return this;
Expand Down
5 changes: 5 additions & 0 deletions lib/ast/expressions/NullCheckCallExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,13 @@ exports.NullCheckCallExpression.prototype.codegen = function () {

var args = this.args;
args.forEach(function (arg, i) {
var isSplat = args[i].type === "SplatExpression";
args[i] = arg.codegen();
args[i].codeGenerated = true;

if (isSplat) {
args[i].__splat = true;
}
});

// If the callee has a function call (e.g: a().b)
Expand Down
49 changes: 49 additions & 0 deletions lib/ast/expressions/SplatExpression.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
var Node = require('../Node').Node;

exports.SplatExpression = function (expression) {
Node.call(this);

this.type = 'SplatExpression';

this.expression = expression;
this.expression.parent = this;
};

exports.SplatExpression.prototype = Object.create(Node);

exports.SplatExpression.prototype.codegen = function () {
if (!Node.prototype.codegen.call(this)) {
return;
}

this.expression = this.expression.codegen();

return {
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "ArrayExpression",
"elements": []
},
"property": {
"type": "Identifier",
"name": "slice"
}
},
"property": {
"type": "Identifier",
"name": "call"
}
},
"arguments": [this.expression]
};
};

exports.SplatExpression.prototype.hasCallExpression = function () {
return true;
};
Loading

0 comments on commit 86cc7fb

Please sign in to comment.