lexer now distinguishes between IN/OF and FORIN/FOROF to help grammar, fixing #737

This commit is contained in:
satyr
2010-10-06 04:46:17 +09:00
parent 380bee97dd
commit 2e6b50335f
8 changed files with 101 additions and 90 deletions

View File

@@ -476,13 +476,13 @@ grammar =
# clause. If it's an array comprehension, you can also choose to step through
# in fixed-size increments.
ForSource: [
o "IN Expression", -> source: $2
o "OF Expression", -> source: $2, object: true
o "IN Expression WHEN Expression", -> source: $2, guard: $4
o "OF Expression WHEN Expression", -> source: $2, guard: $4, object: true
o "IN Expression BY Expression", -> source: $2, step: $4
o "IN Expression WHEN Expression BY Expression", -> source: $2, guard: $4, step: $6
o "IN Expression BY Expression WHEN Expression", -> source: $2, step: $4, guard: $6
o "FORIN Expression", -> source: $2
o "FOROF Expression", -> source: $2, object: true
o "FORIN Expression WHEN Expression", -> source: $2, guard: $4
o "FOROF Expression WHEN Expression", -> source: $2, guard: $4, object: true
o "FORIN Expression BY Expression", -> source: $2, step: $4
o "FORIN Expression WHEN Expression BY Expression", -> source: $2, guard: $4, step: $6
o "FORIN Expression BY Expression WHEN Expression", -> source: $2, step: $4, guard: $6
]
Switch: [
@@ -552,14 +552,14 @@ grammar =
o "Value COMPOUND_ASSIGN Expression", -> new OpNode $2, $1, $3
o "Value COMPOUND_ASSIGN INDENT Expression OUTDENT", -> new OpNode $2, $1, $4
o "Expression IN Expression", -> new InNode $1, $3
o "Expression OF Expression", -> new OpNode $2, $1, $3
o "Expression INSTANCEOF Expression", -> new OpNode $2, $1, $3
o "Expression NOT_RELATED Expression", ->
if $2 is 'in'
new OpNode '!', new InNode $1, $3
o "Expression RELATION Expression", ->
if $2.charAt(0) is '!'
if $2 is '!in'
new OpNode '!', new InNode $1, $3
else
new OpNode '!', new ParentheticalNode new OpNode $2[1..], $1, $3
else
new OpNode '!', new ParentheticalNode new OpNode $2, $1, $3
if $2 is 'in' then new InNode $1, $3 else new OpNode $2, $1, $3
]
@@ -583,13 +583,13 @@ operators = [
["left", '+', '-']
["left", 'SHIFT']
["left", 'COMPARE']
["left", 'INSTANCEOF', 'NOT_RELATED']
["left", 'RELATION']
["left", '==', '!=']
["left", 'LOGIC']
["right", 'COMPOUND_ASSIGN']
["left", '.']
["nonassoc", 'INDENT', 'OUTDENT']
["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW']
["right", 'WHEN', 'LEADING_WHEN', 'FORIN', 'FOROF', 'BY', 'THROW']
["right", 'IF', 'UNLESS', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'EXTENDS']
["right", '=', ':', 'RETURN']
["right", '->', '=>', 'UNLESS', 'POST_IF', 'POST_UNLESS']

View File

@@ -41,6 +41,7 @@ exports.Lexer = class Lexer
@indent = 0 # The current indentation level.
@indebt = 0 # The over-indentation at the current level.
@outdebt = 0 # The under-outdentation at the current level.
@seenFor = no # The flag for distinguishing FORIN/FOROF from IN/OF.
@indents = [] # The stack of all current indentation levels.
@tokens = [] # Stream of parsed tokens in the form ['TYPE', value, line]
# At every position, run through this list of attempted matches,
@@ -84,11 +85,19 @@ exports.Lexer = class Lexer
tag = id.toUpperCase()
if tag is 'WHEN' and include LINE_BREAK, @tag()
tag = 'LEADING_WHEN'
else if tag is 'FOR'
@seenFor = yes
else if include UNARY, tag
tag = 'UNARY'
else if include(RELATION, tag) and @value() in ['not', '!']
@tokens.pop()
tag = 'NOT_RELATED'
else if include RELATION, tag
if tag isnt 'INSTANCEOF' and @seenFor
@seenFor = no
tag = 'FOR' + tag
else
tag = 'RELATION'
if @value() is '!'
@tokens.pop()
id = '!' + id
if include JS_FORBIDDEN, id
if forcedIdentifier
tag = 'STRING'
@@ -100,7 +109,7 @@ exports.Lexer = class Lexer
else if include(RESERVED, id)
@identifierError id
unless forcedIdentifier
tag = id = CONVERSIONS[id] if include COFFEE_ALIASES, id
tag = id = COFFEE_ALIASES[id] if COFFEE_ALIASES.hasOwnProperty id
if id is '!'
tag = 'UNARY'
else if include LOGIC, id
@@ -307,7 +316,7 @@ exports.Lexer = class Lexer
if pval in ['or', 'and']
prev = last @tokens
prev[0] = 'COMPOUND_ASSIGN'
prev[1] = CONVERSIONS[pval] + '='
prev[1] = COFFEE_ALIASES[pval] + '='
return true
if ';' is value then tag = 'TERMINATOR'
else if include LOGIC , value then tag = 'LOGIC'
@@ -511,14 +520,20 @@ JS_KEYWORDS = [
'this', 'null', 'debugger'
]
# CoffeeScript-only keywords, which we're more relaxed about allowing. They can't
# be used standalone, but you can reference them as an attached property.
COFFEE_ALIASES = ['and', 'or', 'is', 'isnt', 'not']
COFFEE_KEYWORDS = COFFEE_ALIASES.concat [
# CoffeeScript-only keywords.
COFFEE_KEYWORDS = [
'then', 'unless', 'until', 'loop'
'yes', 'no', 'on', 'off'
'of', 'by', 'when'
]
COFFEE_ALIASES =
and : '&&'
or : '||'
is : '=='
isnt : '!='
not : '!'
COFFEE_KEYWORDS.push op for all op of COFFEE_ALIASES
COFFEE_ALIASES['==='] = '=='
# The list of keywords that are reserved by JavaScript, but not used, or are
# used by CoffeeScript internally. We throw an error when these are encountered,
@@ -611,12 +626,3 @@ CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@', 'THIS', '?', ':
# occurs at the start of a line. We disambiguate these from trailing whens to
# avoid an ambiguity in the grammar.
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR']
# Conversions from CoffeeScript operators into JavaScript ones.
CONVERSIONS =
'and': '&&'
'or': '||'
'is': '=='
'isnt': '!='
'not': '!'
'===': '=='

View File

@@ -102,8 +102,8 @@ class exports.Rewriter
# calls that close on the same line, just before their outdent.
closeOpenCalls: ->
condition = (token, i) ->
(token[0] in [')', 'CALL_END']) or
token[0] is 'OUTDENT' and @tag(i - 1) is ')'
token[0] in [')', 'CALL_END'] or
token[0] is 'OUTDENT' and @tag(i - 1) is ')'
action = (token, i) ->
@tokens[if token[0] is 'OUTDENT' then i - 1 else i][0] = 'CALL_END'
@scanTokens (token, i) ->
@@ -127,8 +127,8 @@ class exports.Rewriter
return false if 'HERECOMMENT' in [@tag(i + 1), @tag(i - 1)]
[one, two, three] = @tokens.slice i + 1, i + 4
[tag] = token
(tag in ['TERMINATOR', 'OUTDENT']) and not (two?[0] is ':' or one?[0] is '@' and three?[0] is ':') or
tag is ',' and (one?[0] not in ['IDENTIFIER', 'STRING', '@', 'TERMINATOR', 'OUTDENT'])
tag in ['TERMINATOR', 'OUTDENT'] and not (two?[0] is ':' or one?[0] is '@' and three?[0] is ':') or
tag is ',' and one?[0] not in ['IDENTIFIER', 'STRING', '@', 'TERMINATOR', 'OUTDENT']
action = (token, i) -> @tokens.splice i, 0, ['}', '}', token[2]]
@scanTokens (token, i, tokens) ->
if include EXPRESSION_START, tag = token[0]
@@ -170,7 +170,7 @@ class exports.Rewriter
token.call = yes if prev and not prev.spaced and tag is '?'
if callObject or
prev and prev.spaced and (prev.call or include(IMPLICIT_FUNC, prev[0])) and include(IMPLICIT_CALL, tag) and
not (tag is 'UNARY' and (@tag(i + 1) in ['IN', 'OF', 'INSTANCEOF']))
not (tag is 'UNARY' and @tag(i + 1) in ['IN', 'OF', 'INSTANCEOF'])
tokens.splice i, 0, ['CALL_START', '(', token[2]]
condition = (token, i) ->
return yes if not seenSingle and token.fromThen
@@ -196,7 +196,7 @@ class exports.Rewriter
if tag is 'ELSE' and @tag(i - 1) isnt 'OUTDENT'
tokens.splice i, 0, @indentation(token)...
return 2
if tag is 'CATCH' and (@tag(i + 2) in ['TERMINATOR', 'FINALLY'])
if tag is 'CATCH' and @tag(i + 2) in ['TERMINATOR', 'FINALLY']
tokens.splice i + 2, 0, @indentation(token)...
return 4
if include(SINGLE_LINERS, tag) and @tag(i + 1) isnt 'INDENT' and