Closed Bug 381113 (expclo) Opened 17 years ago Closed 17 years ago

Implement ES4/JS2 expression closures for JS1.8

Categories

(Core :: JavaScript Engine, defect, P1)

defect

Tracking

()

VERIFIED FIXED
mozilla1.9alpha5

People

(Reporter: brendan, Assigned: brendan)

References

()

Details

Attachments

(2 files, 10 obsolete files)

See the URL.

Still searching for an easy to type, readable shorthand for 'function'. Greek Lambda (u03BB) is allowed as a lexical identifier by ES3, so it could be in use already. And it's not supported by keyboards around this hemisphere, AFAIK.

/be
Interesting thread at:

http://www.nabble.com/Expression-closures---use-cases-for-shortcut-lambda-syntax-(blocks)-t3411894.html

I'm a fan of the "x => x * x" idea, but "function (x) x * x" is definitely an improvement over "function (x) { return x * x; }".
(In reply to comment #1)
> Interesting thread at:
> 
> http://www.nabble.com/Expression-closures---use-cases-for-shortcut-lambda-syntax-(blocks)-t3411894.html
> 
> I'm a fan of the "x => x * x" idea, but "function (x) x * x" is definitely an
> improvement over "function (x) { return x * x; }".

Igor had the last word in that thread, and I thought his arguments were convincing. To recap:

(\ formals ) expr
( formals ) => expr
function ( formals ) expr

ranked from shortest to longest are not the only issue. The examples given, including the Y combinator rewritten to use a named function for applySelf, show that it's not all about terseness.

Also, the bottom up parsing (or simulation of same) required by => is not only bad for implementors -- it strongly suggests (Igor and Lars brought this point out too) that readers will have a hard time grokking any non-trivial use of =>.

Anyway, we're sticking with 'function' being the minimum leading word required for all function forms, for JS2/ES4.

/be
Attached patch proposed implementation (obsolete) — Splinter Review
Try to break this -- it's similar to the let expression parser, but we may have to tweak it for JS2/ES4 (later, and assuming we don't self-host the front end then).

/be
Attachment #265317 - Flags: review?(mrbkap)
Status: NEW → ASSIGNED
Priority: -- → P1
js> function(x) yield x
function (x) {
    return yield x;
}

Should say "generator function returns a value" or something to that effect.
Alias: expclo
js> function ([]) x
Assertion failure: body->pn_type == TOK_LEXICALSCOPE, at jsparse.c:1380

Attached patch proposed implementation, v2 (obsolete) — Splinter Review
The decompiler hates me.

/be
Attachment #265317 - Attachment is obsolete: true
Attachment #265317 - Flags: review?(mrbkap)
js> (function(){ return ({x setter: function(i)i }) })
Assertion failure: rval[strlen(rval)-1] == '}', at jsopcode.c:4217

Not sure if we care, but there are extra parens in this case:

js> (function() { x = function (i) i; })
function () {
    x = (function (i) i);
}
Need parens to prevent object-literal braces from being interpreted as function body braces:

js> function () function (i) ({p:i})               
function () (function (i) {p:i});

(In reply to comment #8)
> Not sure if we care, but there are extra parens in this case:
> 
> js> (function() { x = function (i) i; })
> function () {
>     x = (function (i) i);
> }

See the comment that starts with this sentence: "Always parenthesize expression closures." Hard to fix without separating output precedence from the op that's stacked in order for subsequent dependent ops to (a) inspect the exact bytecode or a format flag; (b) load the precedence for that op.

Thinking about how to fix this, but I'm probably not going to bother.

Thanks for the other finds -- got any more?

/be
Attached patch proposed implementation, v3 (obsolete) — Splinter Review
Attachment #265324 - Attachment is obsolete: true
Attachment #265341 - Flags: review?(mrbkap)
Attached patch proposed implementation, v3a (obsolete) — Splinter Review
Tweak jsparse.c per an old Brian Kernighan chestnut (don't make a fatman's gut of the then clause, with a tiny else below -- invert) combined with my general style rule to handle errors first (in then clause, not else).

/be
Attachment #265341 - Attachment is obsolete: true
Attachment #265342 - Flags: review?(mrbkap)
Attachment #265341 - Flags: review?(mrbkap)
Bogus semicolon:

js> (function() { L: function() 1 })  
function () {
L:
    (function () 1;);
}
Attached patch proposed implementation, v3b (obsolete) — Splinter Review
That's not valid ECMA-262 code, of course, but an extension we support, although JSOPTION_ANONFUNFIX should be used to make the anonymous function statement (and an unlabeled anonymous function declaration) an error.

Fix is to treat anonymous, grammatically non-lambda expression closures as if they were truly grammatical lambdas when decompiling.

/be
Attachment #265342 - Attachment is obsolete: true
Attachment #265345 - Flags: review?(mrbkap)
Attachment #265342 - Flags: review?(mrbkap)
js> (function() { L: function() 1 })  
function () {
L:
    (function () 1);
}
js> options('anonfunfix')

js> options()
anonfunfix
js> (function() { L: function() 1 })  
typein:4: SyntaxError: syntax error:
typein:4: (function() { L: function() 1 })  
typein:4: .........................^
Attached patch proposed implementation, v3c (obsolete) — Splinter Review
Removed a pointless null test.

/be
Attachment #265345 - Attachment is obsolete: true
Attachment #265364 - Flags: review?(mrbkap)
Attachment #265345 - Flags: review?(mrbkap)
Attached patch proposed implementation, v4 (obsolete) — Splinter Review
I need to talk to ES4 folks about this, but for now the parser must parse an AssignExpr for the body of an expression closure, not an Expr (comma expression), or object initialisers such as

let o = {get foo() this._foo, set foo(bar) this._foo = bar};

can't be parsed (the Expr eats the |, set| and then stops, leaving the rest of the initialiser as a syntax error.

/be
Attachment #265364 - Attachment is obsolete: true
Attachment #265368 - Flags: review?(mrbkap)
Attachment #265364 - Flags: review?(mrbkap)
I like it parsing AssignExpr :)  It lets me do things like

x(function(i)i+1, function(i)i*2);

without being surprised by the comma being interpreted as a sequential-evaluation operator.  Few JavaScript developers know about and use the sequential-evaluation operator, and those who do already know to parenthesize to avoid their commas being interpreted as function argument separators.
The decompiler needs to parenthesize comma expressions at the top level of expression closures now:

js> (function() { return {get x() (x, x)}; }) 
function () {
    return {get x() x, x};
}

js> f = (function() { return (function(y) (y, y)); })            
function () {
    return (function (y) y, y);
}

(In reply to comment #18)
> I like it parsing AssignExpr :)  It lets me do things like
> 
> x(function(i)i+1, function(i)i*2);
> 
> without being surprised by the comma being interpreted as a
> sequential-evaluation operator.  Few JavaScript developers know about and use
> the sequential-evaluation operator, and those who do already know to
> parenthesize to avoid their commas being interpreted as function argument
> separators.

IIUC the ES4 idea is to allow unparenthesized comma expressions in expression closure and let bodies where they are not in an argument list, so we should be able to have our cake and eat it. But the formalization is unclear to me, so I'll have to talk to Jeff Dyer before whacking SpiderMonkey harder.

(In reply to comment #19)
> The decompiler needs to parenthesize comma expressions at the top level of
> expression closures now:

Of course -- I hate the decompiler back. Easy to fix, v4a patch later today.

/be
js> f = (function() { ({ get x() (let (w) u), y: 2 }) })
function () {
    ({get x() let (w) u, y:2});
}
js> eval("" + f)
typein:3: SyntaxError: missing } after property list:
typein:3:     ({get x() let (w) u, y:2});
typein:3: ..........................^
Attachment #265364 - Attachment description: proposed fix to xpinstall, v3c → proposed implementation, v3c
Comment on attachment 265368 [details] [diff] [review]
proposed implementation, v4

New patch soon, or my battery died.

/be
Attachment #265368 - Attachment description: proposed fix to xpinstall, v4 → proposed implementation, v4
Attachment #265368 - Attachment is obsolete: true
Attachment #265368 - Flags: review?(mrbkap)
Attached patch proposed implementation, v4a (obsolete) — Splinter Review
I'm not so keen on the proposed ES4/JS2 way of allowing unparenthesized comma expressions as let-expression and expression closure bodies *unless* the let or closure is an item directly embedded in a comma-separated list. But here's the patch that implements that parsing rule. Comments welcome.

/be
Attachment #265774 - Flags: review?(mrbkap)
js> x(function () {1, 2;})
typein:1: SyntaxError: missing ; before statement:
typein:1: x(function () {1, 2;})
typein:1: ................^
Comment on attachment 265774 [details] [diff] [review]
proposed implementation, v4a

In TG1 meeting today we agreed to use AssignExpr for all of let expr, expr closure, and yield (which already uses it), not Expr (comma expression).

/be
Attachment #265774 - Attachment is obsolete: true
Attachment #265774 - Flags: review?(mrbkap)
I'm still seeing the problem in comment 19.
js> f(b[1, 2])
typein:1: SyntaxError: missing ] in index expression:
typein:1: f(b[1, 2])
typein:1: .....^
That patch is broken as well as obsolete, don't bother testing it.

/be
Attached patch proposed implementation, v5 (obsolete) — Splinter Review
I need to run the testsuite, but this is an improvement based on the TG1 decision yesterday to make AssignmentExpression be the non-terminal used for the bodies of expression closures and let expressions, as for yield's operand.

This patch changes the decompiler so that it no longer forces parens around a let expression. It still forces parens around an expression closure, however, in order to help itself decompiler getters and setters.

/be
Attachment #265903 - Flags: review?(mrbkap)
The v5 patch also forces parens around the comma expression operand of return, e.g.

js> (function(){return (x,y)})
function () {
    return (x, y);
}

Necessary without a source note on JSOP_RETURN/JSOP_SETRVAL in order to decompile the corresponding expression closure correctly:

js> (function()(x,y))
function () (x, y)

/be
Attachment #265903 - Flags: review?(mrbkap) → review+
> The v5 patch also forces parens around the comma expression operand of return

Sorta.  It leaves parens that are there, but doesn't put them in:

js> (function(){return x, y})   
function () {
    return x, y;
}

js> (function(){return (x, y)})
function () {
    return (x, y);
}
js>  f = (function() { (yield 2), 3; })   
function () {
    yield 2, 3;
}
js> eval("(" + f + ")")
typein:5: SyntaxError: yield expression must be parenthesized: [...]
> This patch changes the decompiler so that it no longer forces parens 
> around a let expression.

With the patch, I get extra parens around a let expression in this case:

js> (function() { 1 + let (x) x }) 
function () {
    1 + (let (x) x);
}
And I get extra parens around the inner part of some let-expressions:

js> (function() { return let (x) {} }) 
function () {
    return let (x) ({});
}

But I guess that's reasonable, because with the "return", it would need parens to avoid being interpreted as a let-block rather than a let-expression with an object literal.
Extra parens:

js> (function() { f(let (x) x) }) 
function () {
    f((let (x) x));
}
Here too:

js> (function() { [let (x) x] })  
function () {
    [(let (x) x)];
}
js> f = (function() { (let(x) x), 1 })
function () {
    let (x) x, 1;
}
js> eval("(" + f + ")")
typein:5: SyntaxError: missing ; before statement:
typein:5:     let (x) x, 1;
typein:5: .............^

Round-trip paren change.

js> f = (function() { (y && (!z)); })
function () {
    y && (!z);
}
js> eval("(" + f + ")")
function () {
    y && !z;
}
Same thing with the "new" operator:

js> f = (function() { (x && (new y)) })
function () {
    x && (new y);
}
js> eval("(" + f + ")");
function () {
    x && new y;
}
Attached patch proposed implementation, v5a (obsolete) — Splinter Review
Fix precedence levels in jsopcode.tbl, add "nextop" support to Decompile (see the comment before its declaration), and optimize away parens around any single expr in brackets or parens.

/be
Attachment #265903 - Attachment is obsolete: true
Attachment #266509 - Flags: review?(mrbkap)
Comment 31 and comment 33 are still accurate with the latest patch. Comment 34 is overparenthesized no matter how you look at it:

js> (function() { return let (x) {} })
function () {
    return (let (x) ({}));
}

I'll work on this nit, but 31 and 33 are likely to remain accurate.

/be
Attachment #266509 - Flags: review?(mrbkap) → review+
I made a typo in comment 34: I meant "without the return" when I typed "with the return".
js> f = (function() { yield (2, 3); })    
function () {
    yield 2, 3;
}
js> eval("(" + f + ")")
typein:8: SyntaxError: yield expression must be parenthesized:
typein:8:     yield 2, 3;
typein:8: ....^
js> f = (function() { if ((1, 1) for (x in [])) {} } )
function () {
    if (1, 1 for (x in [])) {
    }
}
js> eval("(" + f + ")");
typein:12: SyntaxError: generator expression must be parenthesized:
typein:12:     if (1, 1 for (x in [])) {
typein:12: ...........^
Once again we would like input vs. output precedence in js_CodeSpec. Someone with more energy than I have right now, please file a FIXME bug.

/be
Attachment #266509 - Attachment is obsolete: true
Attachment #266533 - Flags: review?(mrbkap)
Attachment #266533 - Flags: review?(mrbkap) → review+
I'm likely to commit these changes with a tachyonal r+ bc message.

/be
Attachment #266558 - Flags: review?
Fixed on trunk:

js/src/js.c 3.144
js/src/jsconfig.h 3.45
js/src/jsfun.h 3.35
js/src/jsopcode.c 3.248
js/src/jsopcode.tbl 3.95
js/src/jsparse.c 3.285

/be
Status: ASSIGNED → RESOLVED
Closed: 17 years ago
Resolution: --- → FIXED
Test changes committed:

js/tests/js1_7/block/regress-352609.js 1.3
js/tests/js1_7/decompilation/regress-350704.js 1.3
js/tests/js1_7/decompilation/regress-352026.js 1.3
js/tests/js1_7/decompilation/regress-352079.js 1.3
js/tests/js1_7/decompilation/regress-352272.js 1.3

This means to test the 1.8 branch you'll need an older version of the suite. I hope that's ok. If not, what to do?

/be
Comment on attachment 266558 [details] [diff] [review]
testsuite patch to track paren minimization

form looks fine.
Attachment #266558 - Flags: review? → review+
(In reply to comment #48)
> 
> This means to test the 1.8 branch you'll need an older version of the suite. I
> hope that's ok. If not, what to do?

This is not that much different from the other changes that have caused failures on older branches. I should be able to flag known failure patterns by branch shortly, so that we could safely ignore the 1.8 branch failures while still catching new failure modes. The other choice is to branch the tests for the 1.8 branch which I would prefer not to do.
Depends on: 382400
Flags: in-testsuite+
verified fixed 1.9.0 linux/mac*/windows.
Status: RESOLVED → VERIFIED
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: