Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

using 'yield' automatically turns functions into generators #3240

Merged
merged 24 commits into from
Sep 19, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f51cbd7
removed 'yield' from the reserved keywords
alubbe Nov 15, 2013
dafc7bd
added 'yield' to the unary keywords
alubbe Nov 15, 2013
9941050
using 'yield' automatically turns functions into generators
alubbe Nov 15, 2013
f11ca98
added a test for generators
alubbe Nov 30, 2013
7906a2b
removed yield from the reserved words
alubbe Nov 30, 2013
d712a6c
npm run-script test-harmony executes generator tests
alubbe Nov 30, 2013
74a92db
improved readability of generator test
alubbe Nov 30, 2013
9d29a83
entire generator test file is now ignored if generators are not avail…
alubbe Nov 30, 2013
85c7fff
improved readability of cakefile generator check
alubbe Dec 1, 2013
f4b850d
further improved readability of cakefile generator check
alubbe Dec 3, 2013
c02a403
fixed misspelling in Cakefile
alubbe Dec 5, 2013
e100020
Merge github.com:jashkenas/coffee-script
alubbe Dec 5, 2013
56b04a5
first attempt at using '->*" and '=>*' for generators
alubbe Dec 19, 2013
dab4ae9
'->*' and '=>*' now produce generators
alubbe Dec 20, 2013
25b1eee
first attempt at including 'yield*'
alubbe Dec 24, 2013
64e78a2
updated lexer to allow 'yield*'
alubbe Dec 26, 2013
1e377ed
'yield*' now works as expected
alubbe Dec 28, 2013
f375394
Merge https://github.com/jashkenas/coffee-script
alubbe Jan 26, 2014
7590066
Merge remote-tracking branch 'A/master'
alubbe Sep 6, 2014
565d78f
removed support for '->*" and '=>*'
alubbe Sep 6, 2014
c725566
added 'yield from'
alubbe Sep 6, 2014
437b9ed
added 'yield return'
alubbe Sep 6, 2014
781ea22
always wrap 'yield' in () to allow composability with all other opera…
alubbe Sep 6, 2014
efca286
added tests for yield, yield from, yield return and yield in if state…
alubbe Sep 6, 2014
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cakefile
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@ runTests = (CoffeeScript) ->

# Run every test in the `test` folder, recording failures.
files = fs.readdirSync 'test'

# Ignore generators test file if generators are not available
generatorsAreAvailable = '--harmony' in process.execArgv or
'--harmony-generators' in process.execArgv
files.splice files.indexOf('generators.coffee'), 1 if not generatorsAreAvailable

for file in files when helpers.isCoffee file
literate = helpers.isLiterate file
currentFile = filename = path.join 'test', file
Expand Down
8 changes: 7 additions & 1 deletion lib/coffee-script/grammar.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions lib/coffee-script/lexer.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 36 additions & 1 deletion lib/coffee-script/nodes.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 28 additions & 21 deletions lib/coffee-script/parser.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/coffee-script/rewriter.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
},
"preferGlobal": true,
"scripts": {
"test": "node ./bin/cake test"
"test": "node ./bin/cake test",
"test-harmony": "node --harmony ./bin/cake test"
},
"homepage": "http://coffeescript.org",
"bugs": "https://github.com/jashkenas/coffeescript/issues",
Expand Down
4 changes: 4 additions & 0 deletions src/grammar.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,9 @@ grammar =
o 'UNARY_MATH Expression', -> new Op $1 , $2
o '- Expression', (-> new Op '-', $2), prec: 'UNARY_MATH'
o '+ Expression', (-> new Op '+', $2), prec: 'UNARY_MATH'
o 'YIELD Statement', -> new Op $1 , $2
o 'YIELD Expression', -> new Op $1 , $2
o 'YIELD FROM Expression', -> new Op $1.concat($2) , $3

o '-- SimpleAssignable', -> new Op '--', $2
o '++ SimpleAssignable', -> new Op '++', $2
Expand Down Expand Up @@ -586,6 +589,7 @@ operators = [
['nonassoc', '++', '--']
['left', '?']
['right', 'UNARY']
['right', 'YIELD']
['right', '**']
['right', 'UNARY_MATH']
['left', 'MATH']
Expand Down
11 changes: 7 additions & 4 deletions src/lexer.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ exports.Lexer = class Lexer
if id is 'own' and @tag() is 'FOR'
@token 'OWN', id
return id.length
if id is 'from' and @tag() is 'YIELD'
@token 'FROM', id
return id.length
forcedIdentifier = colon or
(prev = last @tokens) and (prev[0] in ['.', '?.', '::', '?::'] or
not prev.spaced and prev[0] is '@')
Expand Down Expand Up @@ -688,7 +691,7 @@ exports.Lexer = class Lexer
# Are we in the midst of an unfinished expression?
unfinished: ->
LINE_CONTINUER.test(@chunk) or
@tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', 'UNARY_MATH', '+', '-',
@tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', 'UNARY_MATH', '+', '-', 'YIELD',
'**', 'SHIFT', 'RELATION', 'COMPARE', 'LOGIC', 'THROW', 'EXTENDS']

# Remove newlines from beginning and (non escaped) from end of string literals.
Expand Down Expand Up @@ -729,7 +732,7 @@ exports.Lexer = class Lexer
JS_KEYWORDS = [
'true', 'false', 'null', 'this'
'new', 'delete', 'typeof', 'in', 'instanceof'
'return', 'throw', 'break', 'continue', 'debugger'
'return', 'throw', 'break', 'continue', 'debugger', 'yield'
'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally'
'class', 'extends', 'super'
]
Expand Down Expand Up @@ -758,10 +761,10 @@ RESERVED = [
'case', 'default', 'function', 'var', 'void', 'with', 'const', 'let', 'enum'
'export', 'import', 'native', '__hasProp', '__extends', '__slice', '__bind'
'__indexOf', 'implements', 'interface', 'package', 'private', 'protected'
'public', 'static', 'yield'
'public', 'static'
]

STRICT_PROSCRIBED = ['arguments', 'eval']
STRICT_PROSCRIBED = ['arguments', 'eval', 'yield*']

# The superset of both JavaScript keywords and reserved words, none of which may
# be used as identifiers or properties.
Expand Down
40 changes: 30 additions & 10 deletions src/nodes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,6 @@ exports.Return = class Return extends Base
answer.push @makeCode ";"
return answer


#### Value

# A value, variable or literal or parenthesized, indexed or dotted into,
Expand Down Expand Up @@ -1315,9 +1314,11 @@ exports.Assign = class Assign extends Base
# has no *children* -- they're within the inner scope.
exports.Code = class Code extends Base
constructor: (params, body, tag) ->
@params = params or []
@body = body or new Block
@bound = tag is 'boundfunc'
@params = params or []
@body = body or new Block
@bound = tag is 'boundfunc'
@isGenerator = @body.contains (node) ->
node instanceof Op and node.operator in ['yield', 'yield*']
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nowhere near sufficient to detect yield in the body. This assumes the function has a completely flat structure. f (yield 0), (yield 1) will fail the test.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f (yield 0), (yield 1) fails because yield is used outside of a function definition. The parser works recursively, so nested structures will indeed pass the test. For example, x = -> f (yield 0), (yield 1) works correctly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, @body.contains walks the tree. I had thought it was shallow.


children: ['params', 'body']

Expand Down Expand Up @@ -1384,9 +1385,10 @@ exports.Code = class Code extends Base
node.error "multiple parameters named '#{name}'" if name in uniqs
uniqs.push name
@body.makeReturn() unless wasEmpty or @noReturn
code = 'function'
code += ' ' + @name if @ctor
code += '('
code = 'function'
code += '*' if @isGenerator
code += ' ' + @name if @ctor
code += '('
answer = [@makeCode(code)]
for p, i in params
if i then answer.push @makeCode ", "
Expand Down Expand Up @@ -1612,9 +1614,10 @@ exports.Op = class Op extends Base

# The map of conversions from CoffeeScript to JavaScript symbols.
CONVERSIONS =
'==': '==='
'!=': '!=='
'of': 'in'
'==': '==='
'!=': '!=='
'of': 'in'
'yieldfrom': 'yield*'

# The map of invertible operators.
INVERSIONS =
Expand All @@ -1625,6 +1628,9 @@ exports.Op = class Op extends Base

isSimpleNumber: NO

isYield: ->
@operator in ['yield', 'yield*']

isUnary: ->
not @second

Expand Down Expand Up @@ -1691,6 +1697,7 @@ exports.Op = class Op extends Base
@error 'delete operand may not be argument or var'
if @operator in ['--', '++'] and @first.unwrapAll().value in STRICT_PROSCRIBED
@error "cannot increment/decrement \"#{@first.unwrapAll().value}\""
return @compileYield o if @isYield()
return @compileUnary o if @isUnary()
return @compileChain o if isChain
switch @operator
Expand Down Expand Up @@ -1745,6 +1752,19 @@ exports.Op = class Op extends Base
parts.reverse() if @flip
@joinFragmentArrays parts, ''

compileYield: (o) ->
parts = []
op = @operator
if not o.scope.parent?
@error 'yield statements must occur within a function generator.'
if 'expression' in Object.keys @first
parts.push @first.expression.compileToFragments o, LEVEL_OP if @first.expression?
else
parts.push [@makeCode "(#{op} "]
parts.push @first.compileToFragments o, LEVEL_OP
parts.push [@makeCode ")"]
@joinFragmentArrays parts, ''

compilePower: (o) ->
# Make a Math.pow call
pow = new Value new Literal('Math'), [new Access new Literal 'pow']
Expand Down
2 changes: 1 addition & 1 deletion src/rewriter.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@
# If preceded by an `IMPLICIT_FUNC`, indicates a function invocation.
IMPLICIT_CALL = [
'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS'
'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY',
'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'YIELD'
'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'
]

Expand Down
Loading