Merging in Tesco and rofrankel's soaks for function calls. A soaked check that fails will return undefined.

This commit is contained in:
Jeremy Ashkenas
2010-08-28 09:00:04 -04:00
10 changed files with 217 additions and 153 deletions

View File

@@ -267,10 +267,17 @@
}) })
], ],
Invocation: [ Invocation: [
o("Value Arguments", function() { o("Value OptFuncExist Arguments", function() {
return new CallNode($1, $2); return new CallNode($1, $3, $2);
}), o("Invocation Arguments", function() { }), o("Invocation OptFuncExist Arguments", function() {
return new CallNode($1, $2); return new CallNode($1, $3, $2);
})
],
OptFuncExist: [
o("", function() {
return false;
}), o("FUNC_EXIST", function() {
return true;
}) })
], ],
Arguments: [ Arguments: [

View File

@@ -317,7 +317,7 @@
return true; return true;
}; };
Lexer.prototype.literalToken = function() { Lexer.prototype.literalToken = function() {
var _d, match, space, spaced, tag, value; var _d, match, prev, space, spaced, tag, value;
match = this.chunk.match(OPERATOR); match = this.chunk.match(OPERATOR);
value = match && match[1]; value = match && match[1];
space = match && match[2]; space = match && match[2];
@@ -326,14 +326,14 @@
} }
value || (value = this.chunk.substr(0, 1)); value || (value = this.chunk.substr(0, 1));
this.i += value.length; this.i += value.length;
spaced = this.prev() && this.prev().spaced; spaced = (prev = this.prev()) && prev.spaced;
tag = value; tag = value;
if (value === '=') { if (value === '=') {
if (include(JS_FORBIDDEN, this.value())) { if (include(JS_FORBIDDEN, this.value())) {
this.assignmentError(); this.assignmentError();
} }
if (('or' === (_d = this.value()) || 'and' === _d)) { if (('or' === (_d = this.value()) || 'and' === _d)) {
this.tokens.splice(this.tokens.length - 1, 1, ['COMPOUND_ASSIGN', CONVERSIONS[this.value()] + '=', this.prev()[2]]); this.tokens.splice(this.tokens.length - 1, 1, ['COMPOUND_ASSIGN', CONVERSIONS[this.value()] + '=', prev[2]]);
return true; return true;
} }
} }
@@ -353,6 +353,9 @@
tag = 'SHIFT'; tag = 'SHIFT';
} else if (include(CALLABLE, this.tag()) && !spaced) { } else if (include(CALLABLE, this.tag()) && !spaced) {
if (value === '(') { if (value === '(') {
if (prev[0] === '?') {
prev[0] = 'FUNC_EXIST';
}
tag = 'CALL_START'; tag = 'CALL_START';
} else if (value === '[') { } else if (value === '[') {
tag = 'INDEX_START'; tag = 'INDEX_START';

View File

@@ -443,13 +443,15 @@
return CommentNode; return CommentNode;
})(); })();
exports.CallNode = (function() { exports.CallNode = (function() {
CallNode = function(variable, _b) { CallNode = function(variable, _b, _c) {
this.exist = _c;
this.args = _b; this.args = _b;
CallNode.__super__.constructor.call(this); CallNode.__super__.constructor.call(this);
this.isNew = false; this.isNew = false;
this.isSuper = variable === 'super'; this.isSuper = variable === 'super';
this.variable = this.isSuper ? null : variable; this.variable = this.isSuper ? null : variable;
this.args || (this.args = []); this.args || (this.args = []);
this.first = (this.last = '');
this.compileSplatArguments = function(o) { this.compileSplatArguments = function(o) {
return SplatNode.compileSplattedArray.call(this, this.args, o); return SplatNode.compileSplattedArray.call(this, this.args, o);
}; };
@@ -479,30 +481,41 @@
})()); })());
}; };
CallNode.prototype.compileNode = function(o) { CallNode.prototype.compileNode = function(o) {
var _b, _c, _d, _e, _f, _g, _h, arg, args, compilation; var _b, _c, _d, _e, _f, _g, _h, _i, arg, args, compilation;
if (!(o.chainRoot)) { if (!(o.chainRoot)) {
o.chainRoot = this; o.chainRoot = this;
} }
_c = this.args; if (this.exist) {
for (_b = 0, _d = _c.length; _b < _d; _b++) { _b = this.variable.compileReference(o, {
arg = _c[_b]; precompile: true
});
this.first = _b[0];
this.meth = _b[1];
this.first = ("(typeof " + (this.first) + " === \"function\" ? ");
this.last = " : undefined)";
} else if (this.variable) {
this.meth = this.variable.compile(o);
}
_d = this.args;
for (_c = 0, _e = _d.length; _c < _e; _c++) {
arg = _d[_c];
if (arg instanceof SplatNode) { if (arg instanceof SplatNode) {
compilation = this.compileSplat(o); compilation = this.compileSplat(o);
} }
} }
if (!compilation) { if (!compilation) {
args = (function() { args = (function() {
_e = []; _g = this.args; _f = []; _h = this.args;
for (_f = 0, _h = _g.length; _f < _h; _f++) { for (_g = 0, _i = _h.length; _g < _i; _g++) {
arg = _g[_f]; arg = _h[_g];
_e.push((function() { _f.push((function() {
arg.parenthetical = true; arg.parenthetical = true;
return arg.compile(o); return arg.compile(o);
})()); })());
} }
return _e; return _f;
}).call(this); }).call(this);
compilation = this.isSuper ? this.compileSuper(args.join(', '), o) : ("" + (this.prefix()) + (this.variable.compile(o)) + "(" + (args.join(', ')) + ")"); compilation = this.isSuper ? this.compileSuper(args.join(', '), o) : ("" + (this.first) + (this.prefix()) + (this.meth) + "(" + (args.join(', ')) + ")" + (this.last));
} }
return compilation; return compilation;
}; };
@@ -511,7 +524,7 @@
}; };
CallNode.prototype.compileSplat = function(o) { CallNode.prototype.compileSplat = function(o) {
var meth, obj, temp; var meth, obj, temp;
meth = this.variable ? this.variable.compile(o) : this.superReference(o); meth = this.meth || this.superReference(o);
obj = this.variable && this.variable.source || 'this'; obj = this.variable && this.variable.source || 'this';
if (obj.match(/\(/)) { if (obj.match(/\(/)) {
temp = o.scope.freeVariable(); temp = o.scope.freeVariable();
@@ -520,9 +533,9 @@
} }
if (this.isNew) { if (this.isNew) {
utility('extends'); utility('extends');
return "(function() {\n" + (this.idt(1)) + "var ctor = function(){};\n" + (this.idt(1)) + "__extends(ctor, " + (meth) + ");\n" + (this.idt(1)) + "return " + (meth) + ".apply(new ctor, " + (this.compileSplatArguments(o)) + ");\n" + (this.tab) + "}).call(this)"; return "" + (this.first) + "(function() {\n" + (this.idt(1)) + "var ctor = function(){};\n" + (this.idt(1)) + "__extends(ctor, " + (meth) + ");\n" + (this.idt(1)) + "return " + (meth) + ".apply(new ctor, " + (this.compileSplatArguments(o)) + ");\n" + (this.tab) + "}).call(this)" + (this.last);
} else { } else {
return "" + (this.prefix()) + (meth) + ".apply(" + (obj) + ", " + (this.compileSplatArguments(o)) + ")"; return "" + (this.first) + (this.prefix()) + (meth) + ".apply(" + (obj) + ", " + (this.compileSplatArguments(o)) + ")" + (this.last);
} }
}; };
return CallNode; return CallNode;

File diff suppressed because one or more lines are too long

View File

@@ -197,7 +197,10 @@
if (include(LINEBREAKS, token[0])) { if (include(LINEBREAKS, token[0])) {
classLine = false; classLine = false;
} }
if (prev && (prev.spaced && include(IMPLICIT_FUNC, prev[0]) && include(IMPLICIT_CALL, token[0]) && !(token[0] === 'UNARY' && (('IN' === (_c = this.tag(i + 1)) || 'OF' === _c || 'INSTANCEOF' === _c)))) || callObject) { if (prev && !prev.spaced && token[0] === '?') {
token.call = true;
}
if (prev && (prev.spaced && (include(IMPLICIT_FUNC, prev[0]) || prev.call) && include(IMPLICIT_CALL, token[0]) && !(token[0] === 'UNARY' && (('IN' === (_c = this.tag(i + 1)) || 'OF' === _c || 'INSTANCEOF' === _c)))) || callObject) {
this.tokens.splice(i, 0, ['CALL_START', '(', token[2]]); this.tokens.splice(i, 0, ['CALL_START', '(', token[2]]);
condition = function(token, i) { condition = function(token, i) {
return (!token.generated && this.tokens[i - 1][0] !== ',' && include(IMPLICIT_END, token[0]) && !(token[0] === 'INDENT' && (include(IMPLICIT_BLOCK, this.tag(i - 1)) || this.tag(i - 2) === 'CLASS'))) || token[0] === 'PROPERTY_ACCESS' && this.tag(i - 1) === 'OUTDENT'; return (!token.generated && this.tokens[i - 1][0] !== ',' && include(IMPLICIT_END, token[0]) && !(token[0] === 'INDENT' && (include(IMPLICIT_BLOCK, this.tag(i - 1)) || this.tag(i - 2) === 'CLASS'))) || token[0] === 'PROPERTY_ACCESS' && this.tag(i - 1) === 'OUTDENT';
@@ -207,6 +210,9 @@
return this.tokens.splice(idx, 0, ['CALL_END', ')', token[2]]); return this.tokens.splice(idx, 0, ['CALL_END', ')', token[2]]);
}; };
this.detectEnd(i + idx, condition, action); this.detectEnd(i + idx, condition, action);
if (prev[0] === '?') {
prev[0] = 'FUNC_EXIST';
}
return 2; return 2;
} }
return 1; return 1;

View File

@@ -313,8 +313,14 @@ grammar =
# Ordinary function invocation, or a chained series of calls. # Ordinary function invocation, or a chained series of calls.
Invocation: [ Invocation: [
o "Value Arguments", -> new CallNode $1, $2 o "Value OptFuncExist Arguments", -> new CallNode $1, $3, $2
o "Invocation Arguments", -> new CallNode $1, $2 o "Invocation OptFuncExist Arguments", -> new CallNode $1, $3, $2
]
# An optional existence check on a function.
OptFuncExist: [
o "", -> no
o "FUNC_EXIST", -> yes
] ]
# The list of arguments to a function call. # The list of arguments to a function call.

View File

@@ -272,12 +272,12 @@ exports.Lexer = class Lexer
@tagParameters() if value and value.match CODE @tagParameters() if value and value.match CODE
value or= @chunk.substr 0, 1 value or= @chunk.substr 0, 1
@i += value.length @i += value.length
spaced = @prev() and @prev().spaced spaced = (prev = @prev()) and prev.spaced
tag = value tag = value
if value is '=' if value is '='
@assignmentError() if include JS_FORBIDDEN, @value() @assignmentError() if include JS_FORBIDDEN, @value()
if @value() in ['or', 'and'] if @value() in ['or', 'and']
@tokens.splice(@tokens.length - 1, 1, ['COMPOUND_ASSIGN', CONVERSIONS[@value()] + '=', @prev()[2]]) @tokens.splice(@tokens.length - 1, 1, ['COMPOUND_ASSIGN', CONVERSIONS[@value()] + '=', prev[2]])
return true return true
if value is ';' then tag = 'TERMINATOR' if value is ';' then tag = 'TERMINATOR'
else if include(LOGIC, value) then tag = 'LOGIC' else if include(LOGIC, value) then tag = 'LOGIC'
@@ -288,6 +288,7 @@ exports.Lexer = class Lexer
else if include(SHIFT, value) then tag = 'SHIFT' else if include(SHIFT, value) then tag = 'SHIFT'
else if include(CALLABLE, @tag()) and not spaced else if include(CALLABLE, @tag()) and not spaced
if value is '(' if value is '('
prev[0] = 'FUNC_EXIST' if prev[0] is '?'
tag = 'CALL_START' tag = 'CALL_START'
else if value is '[' else if value is '['
tag = 'INDEX_START' tag = 'INDEX_START'

View File

@@ -410,12 +410,13 @@ exports.CallNode = class CallNode extends BaseNode
class: 'CallNode' class: 'CallNode'
children: ['variable', 'args'] children: ['variable', 'args']
constructor: (variable, @args) -> constructor: (variable, @args, @exist) ->
super() super()
@isNew = false @isNew = false
@isSuper = variable is 'super' @isSuper = variable is 'super'
@variable = if @isSuper then null else variable @variable = if @isSuper then null else variable
@args or= [] @args or= []
@first = @last = ''
@compileSplatArguments = (o) -> @compileSplatArguments = (o) ->
SplatNode.compileSplattedArray.call(this, @args, o) SplatNode.compileSplattedArray.call(this, @args, o)
@@ -439,6 +440,11 @@ exports.CallNode = class CallNode extends BaseNode
# Compile a vanilla function call. # Compile a vanilla function call.
compileNode: (o) -> compileNode: (o) ->
o.chainRoot = this unless o.chainRoot o.chainRoot = this unless o.chainRoot
if @exist
[@first, @meth] = @variable.compileReference o, precompile: yes
@first = "(typeof #{@first} === \"function\" ? "
@last = " : undefined)"
else if @variable then @meth = @variable.compile o
for arg in @args when arg instanceof SplatNode for arg in @args when arg instanceof SplatNode
compilation = @compileSplat(o) compilation = @compileSplat(o)
if not compilation if not compilation
@@ -448,7 +454,7 @@ exports.CallNode = class CallNode extends BaseNode
compilation = if @isSuper compilation = if @isSuper
@compileSuper(args.join(', '), o) @compileSuper(args.join(', '), o)
else else
"#{@prefix()}#{@variable.compile(o)}(#{ args.join(', ') })" "#{@first}#{@prefix()}#{@meth}(#{ args.join(', ') })#{@last}"
compilation compilation
# `super()` is converted into a call against the superclass's implementation # `super()` is converted into a call against the superclass's implementation
@@ -461,7 +467,7 @@ exports.CallNode = class CallNode extends BaseNode
# If it's a constructor, then things get real tricky. We have to inject an # If it's a constructor, then things get real tricky. We have to inject an
# inner constructor in order to be able to pass the varargs. # inner constructor in order to be able to pass the varargs.
compileSplat: (o) -> compileSplat: (o) ->
meth = if @variable then @variable.compile(o) else @superReference(o) meth = @meth or @superReference(o)
obj = @variable and @variable.source or 'this' obj = @variable and @variable.source or 'this'
if obj.match(/\(/) if obj.match(/\(/)
temp = o.scope.freeVariable() temp = o.scope.freeVariable()
@@ -470,14 +476,14 @@ exports.CallNode = class CallNode extends BaseNode
if @isNew if @isNew
utility 'extends' utility 'extends'
""" """
(function() { #{@first}(function() {
#{@idt(1)}var ctor = function(){}; #{@idt(1)}var ctor = function(){};
#{@idt(1)}__extends(ctor, #{meth}); #{@idt(1)}__extends(ctor, #{meth});
#{@idt(1)}return #{meth}.apply(new ctor, #{ @compileSplatArguments(o) }); #{@idt(1)}return #{meth}.apply(new ctor, #{ @compileSplatArguments(o) });
#{@tab}}).call(this) #{@tab}}).call(this)#{@last}
""" """
else else
"#{@prefix()}#{meth}.apply(#{obj}, #{ @compileSplatArguments(o) })" "#{@first}#{@prefix()}#{meth}.apply(#{obj}, #{ @compileSplatArguments(o) })#{@last}"
#### ExtendsNode #### ExtendsNode

View File

@@ -148,7 +148,6 @@ exports.Rewriter = class Rewriter
return 2 return 2
return 1 return 1
# Methods may be optionally called without parentheses, for simple cases. # Methods may be optionally called without parentheses, for simple cases.
# Insert the implicit parentheses here, so that the parser doesn't have to # Insert the implicit parentheses here, so that the parser doesn't have to
# deal with them. # deal with them.
@@ -162,7 +161,8 @@ exports.Rewriter = class Rewriter
callObject = not classLine and token[0] is 'INDENT' and next and next.generated and next[0] is '{' and prev and include(IMPLICIT_FUNC, prev[0]) callObject = not classLine and token[0] is 'INDENT' and next and next.generated and next[0] is '{' and prev and include(IMPLICIT_FUNC, prev[0])
idx = 2 if callObject idx = 2 if callObject
classLine = no if include(LINEBREAKS, token[0]) classLine = no if include(LINEBREAKS, token[0])
if prev and (prev.spaced and include(IMPLICIT_FUNC, prev[0]) and include(IMPLICIT_CALL, token[0]) and token.call = yes if prev and not prev.spaced and token[0] is '?'
if prev and (prev.spaced and (include(IMPLICIT_FUNC, prev[0]) or prev.call) and include(IMPLICIT_CALL, token[0]) and
not (token[0] is 'UNARY' and (@tag(i + 1) in ['IN', 'OF', 'INSTANCEOF']))) or callObject not (token[0] is 'UNARY' and (@tag(i + 1) in ['IN', 'OF', 'INSTANCEOF']))) or callObject
@tokens.splice i, 0, ['CALL_START', '(', token[2]] @tokens.splice i, 0, ['CALL_START', '(', token[2]]
condition = (token, i) -> condition = (token, i) ->
@@ -173,6 +173,7 @@ exports.Rewriter = class Rewriter
idx = if token[0] is 'OUTDENT' then i + 1 else i idx = if token[0] is 'OUTDENT' then i + 1 else i
@tokens.splice idx, 0, ['CALL_END', ')', token[2]] @tokens.splice idx, 0, ['CALL_END', ')', token[2]]
@detectEnd i + idx, condition, action @detectEnd i + idx, condition, action
prev[0] = 'FUNC_EXIST' if prev[0] is '?'
return 2 return 2
return 1 return 1

View File

@@ -109,3 +109,20 @@ ok x is - 1
# Things that compile to ternaries should force parentheses, like operators do. # Things that compile to ternaries should force parentheses, like operators do.
duration = if options?.animated then 150 else 0 duration = if options?.animated then 150 else 0
ok duration is 0 ok duration is 0
# function soak
plus1 = (x) -> x + 1
ok plus1?(41) is 42
ok (plus1? 41) is 42
ok plus2?(41) is undefined
ok (plus2? 41) is undefined
maybe_close = (f, arg) -> if typeof f is 'function' then () -> f(arg) else -1
ok maybe_close(plus1, 41)?() is 42
ok (maybe_close plus1, 41)?() is 42
ok (maybe_close 'string', 41)?() is undefined
ok 2?(3) is undefined