converted nodes.coffee. a big step

This commit is contained in:
Jeremy Ashkenas 2010-07-24 18:21:25 -07:00
parent bfc7704ca1
commit 5c85bf22bb
3 changed files with 589 additions and 589 deletions

View File

@ -317,7 +317,7 @@
exports.ValueNode = (function() {
ValueNode = function(base, properties) {
this.base = base;
this.properties = (properties || []);
this.properties = properties || [];
return this;
};
__extends(ValueNode, BaseNode);

View File

@ -93,13 +93,13 @@ exports.Lexer = class Lexer
tag = 'STRING'
id = "'$id'"
if forcedIdentifier is 'accessor'
close_index: true
close_index = true
@tokens.pop() if @tag() isnt '@'
@token 'INDEX_START', '['
else if include(RESERVED, id)
@identifierError id
unless forcedIdentifier
tag: id: CONVERSIONS[id] if include COFFEE_ALIASES, id
tag = id = CONVERSIONS[id] if include COFFEE_ALIASES, id
return @tagHalfAssignment tag if @prev() and @prev()[0] is 'ASSIGN' and include HALF_ASSIGNMENTS, tag
@token tag, id
@token ']', ']' if close_index
@ -108,9 +108,9 @@ exports.Lexer = class Lexer
# Matches numbers, including decimals, hex, and exponential notation.
# Be careful not to interfere with ranges-in-progress.
numberToken: ->
return false unless number: @match NUMBER, 1
return false unless number = @match NUMBER, 1
return false if @tag() is '.' and starts number, '.'
@i: + number.length
@i += number.length
@token 'NUMBER', number
true
@ -118,32 +118,32 @@ exports.Lexer = class Lexer
# are balanced within the string's contents, and within nested interpolations.
stringToken: ->
return false unless starts(@chunk, '"') or starts(@chunk, "'")
return false unless string:
return false unless string =
@balancedToken(['"', '"'], ['${', '}']) or
@balancedToken ["'", "'"]
@interpolateString string.replace STRING_NEWLINES, " \\\n"
@line: + count string, "\n"
@i: + string.length
@line += count string, "\n"
@i += string.length
true
# Matches heredocs, adjusting indentation to the correct level, as heredocs
# preserve whitespace, but ignore indentation to the left.
heredocToken: ->
return false unless match: @chunk.match(HEREDOC)
quote: match[1].substr 0, 1
doc: @sanitizeHeredoc match[2] or match[4], {quote}
return false unless match = @chunk.match(HEREDOC)
quote = match[1].substr 0, 1
doc = @sanitizeHeredoc match[2] or match[4], {quote}
@interpolateString "$quote$doc$quote", {heredoc: yes}
@line: + count match[1], "\n"
@i: + match[1].length
@line += count match[1], "\n"
@i += match[1].length
true
# Matches and conumes comments.
commentToken: ->
return false unless match: @chunk.match(COMMENT)
@line: + count match[1], "\n"
@i: + match[1].length
return false unless match = @chunk.match(COMMENT)
@line += count match[1], "\n"
@i += match[1].length
if match[2]
comment: @sanitizeHeredoc match[2], {herecomment: true}
comment = @sanitizeHeredoc match[2], {herecomment: true}
@token 'HERECOMMENT', comment.split MULTILINER
@token 'TERMINATOR', '\n'
true
@ -151,9 +151,9 @@ exports.Lexer = class Lexer
# Matches JavaScript interpolated directly into the source via backticks.
jsToken: ->
return false unless starts @chunk, '`'
return false unless script: @balancedToken ['`', '`']
return false unless script = @balancedToken ['`', '`']
@token 'JS', script.replace JS_CLEANER, ''
@i: + script.length
@i += script.length
true
# Matches regular expression literals. Lexing regular expressions is difficult
@ -163,19 +163,19 @@ exports.Lexer = class Lexer
regexToken: ->
return false unless @chunk.match REGEX_START
return false if include NOT_REGEX, @tag()
return false unless regex: @balancedToken ['/', '/']
return false unless end: @chunk.substr(regex.length).match REGEX_END
regex: + flags: end[2] if end[2]
return false unless regex = @balancedToken ['/', '/']
return false unless end = @chunk.substr(regex.length).match REGEX_END
regex += flags = end[2] if end[2]
if regex.match REGEX_INTERPOLATION
str: regex.substring(1).split('/')[0]
str: str.replace REGEX_ESCAPE, (escaped) -> '\\' + escaped
@tokens: @tokens.concat [['(', '('], ['NEW', 'new'], ['IDENTIFIER', 'RegExp'], ['CALL_START', '(']]
str = regex.substring(1).split('/')[0]
str = str.replace REGEX_ESCAPE, (escaped) -> '\\' + escaped
@tokens = @tokens.concat [['(', '('], ['NEW', 'new'], ['IDENTIFIER', 'RegExp'], ['CALL_START', '(']]
@interpolateString "\"$str\"", {escapeQuotes: yes}
@tokens.splice @tokens.length, 0, [',', ','], ['STRING', "\"$flags\""] if flags
@tokens.splice @tokens.length, 0, [')', ')'], [')', ')']
else
@token 'REGEX', regex
@i: + regex.length
@i += regex.length
true
# Matches a token in which which the passed delimiter pairs must be correctly
@ -194,24 +194,24 @@ exports.Lexer = class Lexer
# Keeps track of the level of indentation, because a single outdent token
# can close multiple indents, so we need to know how far in we happen to be.
lineToken: ->
return false unless indent: @match MULTI_DENT, 1
@line: + count indent, "\n"
@i : + indent.length
prev: @prev(2)
size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
nextCharacter: @match NEXT_CHARACTER, 1
noNewlines: nextCharacter is '.' or nextCharacter is ',' or @unfinished()
return false unless indent = @match MULTI_DENT, 1
@line += count indent, "\n"
@i += indent.length
prev = @prev(2)
size = indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
nextCharacter = @match NEXT_CHARACTER, 1
noNewlines = nextCharacter is '.' or nextCharacter is ',' or @unfinished()
if size is @indent
return @suppressNewlines() if noNewlines
return @newlineToken indent
else if size > @indent
return @suppressNewlines() if noNewlines
diff: size - @indent
diff = size - @indent
@token 'INDENT', diff
@indents.push diff
else
@outdentToken @indent - size, noNewlines
@indent: size
@indent = size
true
# Record an outdent token or multiple tokens, if we happen to be moving back
@ -219,22 +219,22 @@ exports.Lexer = class Lexer
outdentToken: (moveOut, noNewlines) ->
if moveOut > -@outdebt
while moveOut > 0 and @indents.length
lastIndent: @indents.pop()
lastIndent = @indents.pop()
@token 'OUTDENT', lastIndent
moveOut: - lastIndent
moveOut -= lastIndent
else
@outdebt: + moveOut
@outdebt: moveOut unless noNewlines
@outdebt += moveOut
@outdebt = moveOut unless noNewlines
@token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR' or noNewlines
true
# Matches and consumes non-meaningful whitespace. Tag the previous token
# as being "spaced", because there are some cases where it makes a difference.
whitespaceToken: ->
return false unless space: @match WHITESPACE, 1
prev: @prev()
prev.spaced: true if prev
@i: + space.length
return false unless space = @match WHITESPACE, 1
prev = @prev()
prev.spaced = true if prev
@i += space.length
true
# Generate a newline token. Consecutive newlines get merged together.
@ -254,28 +254,28 @@ exports.Lexer = class Lexer
# here. `;` and newlines are both treated as a `TERMINATOR`, we distinguish
# parentheses that indicate a method call from regular parentheses, and so on.
literalToken: ->
match: @chunk.match OPERATOR
value: match and match[1]
space: match and match[2]
match = @chunk.match OPERATOR
value = match and match[1]
space = match and match[2]
@tagParameters() if value and value.match CODE
value: or @chunk.substr 0, 1
prevSpaced: @prev() and @prev().spaced
tag: value
value = or @chunk.substr 0, 1
prevSpaced = @prev() and @prev().spaced
tag = value
if value.match ASSIGNMENT
tag: 'ASSIGN'
tag = 'ASSIGN'
@assignmentError() if include JS_FORBIDDEN, @value
else if value is ';'
tag: 'TERMINATOR'
tag = 'TERMINATOR'
else if value is '?' and prevSpaced
tag: 'OP?'
tag = 'OP?'
else if include(CALLABLE, @tag()) and not prevSpaced
if value is '('
tag: 'CALL_START'
tag = 'CALL_START'
else if value is '['
tag: 'INDEX_START'
tag = 'INDEX_START'
@tag 1, 'INDEX_SOAK' if @tag() is '?'
@tag 1, 'INDEX_PROTO' if @tag() is '::'
@i: + value.length
@i += value.length
return @tagHalfAssignment tag if space and prevSpaced and @prev()[0] is 'ASSIGN' and include HALF_ASSIGNMENTS, tag
@token tag, value
true
@ -287,8 +287,8 @@ exports.Lexer = class Lexer
# if it's a special kind of accessor. Return `true` if any type of accessor
# is the previous token.
tagAccessor: ->
return false if (not prev: @prev()) or (prev and prev.spaced)
accessor: if prev[1] is '::'
return false if (not prev = @prev()) or (prev and prev.spaced)
accessor = if prev[1] is '::'
@tag 1, 'PROTOTYPE_ACCESS'
else if prev[1] is '.' and not (@value(2) is '.')
if @tag(2) is '?'
@ -303,18 +303,18 @@ exports.Lexer = class Lexer
# Sanitize a heredoc or herecomment by escaping internal double quotes and
# erasing all external indentation on the left-hand side.
sanitizeHeredoc: (doc, options) ->
while match: HEREDOC_INDENT.exec doc
attempt: if match[2]? then match[2] else match[3]
indent: attempt if not indent or attempt.length < indent.length
doc: doc.replace(new RegExp("^" +indent, 'gm'), '')
while match = HEREDOC_INDENT.exec doc
attempt = if match[2]? then match[2] else match[3]
indent = attempt if not indent or attempt.length < indent.length
doc = doc.replace(new RegExp("^" +indent, 'gm'), '')
return doc if options.herecomment
doc.replace(MULTILINER, "\\n")
.replace(new RegExp(options.quote, 'g'), "\\$options.quote")
# Tag a half assignment.
tagHalfAssignment: (tag) ->
tag: '?' if tag is 'OP?'
last: @tokens.pop()
tag = '?' if tag is 'OP?'
last = @tokens.pop()
@tokens.push ["$tag=", "$tag=", last[2]]
true
@ -323,15 +323,15 @@ exports.Lexer = class Lexer
# parameters specially in order to make things easier for the parser.
tagParameters: ->
return if @tag() isnt ')'
i: 0
i = 0
loop
i: + 1
tok: @prev i
i = + 1
tok = @prev i
return if not tok
switch tok[0]
when 'IDENTIFIER' then tok[0]: 'PARAM'
when ')' then tok[0]: 'PARAM_END'
when '(', 'CALL_START' then return tok[0]: 'PARAM_START'
when 'IDENTIFIER' then tok[0] = 'PARAM'
when ')' then tok[0] = 'PARAM_END'
when '(', 'CALL_START' then return tok[0] = 'PARAM_START'
true
# Close up all remaining open blocks at the end of the file.
@ -353,27 +353,27 @@ exports.Lexer = class Lexer
# contents of the string. This method allows us to have strings within
# interpolations within strings, ad infinitum.
balancedString: (str, delimited, options) ->
options: or {}
slash: delimited[0][0] is '/'
levels: []
i: 0
options = or {}
slash = delimited[0][0] is '/'
levels = []
i = 0
while i < str.length
if levels.length and starts str, '\\', i
i: + 1
i = + 1
else
for pair in delimited
[open, close]: pair
[open, close] = pair
if levels.length and starts(str, close, i) and levels[levels.length - 1] is pair
levels.pop()
i: + close.length - 1
i: + 1 unless levels.length
i = + close.length - 1
i = + 1 unless levels.length
break
else if starts str, open, i
levels.push(pair)
i: + open.length - 1
i = + open.length - 1
break
break if not levels.length or slash and starts str, '\n', i
i: + 1
i = + 1
if levels.length
return false if slash
throw new Error "SyntaxError: Unterminated ${levels.pop()[0]} starting on line ${@line + 1}"
@ -390,48 +390,48 @@ exports.Lexer = class Lexer
# new Lexer, tokenize the interpolated contents, and merge them into the
# token stream.
interpolateString: (str, options) ->
options: or {}
options = or {}
if str.length < 3 or not starts str, '"'
@token 'STRING', str
else
lexer: new Lexer
tokens: []
quote: str.substring 0, 1
[i, pi]: [1, 1]
lexer = new Lexer
tokens = []
quote = str.substring 0, 1
[i, pi] = [1, 1]
while i < str.length - 1
if starts str, '\\', i
i: + 1
else if match: str.substring(i).match INTERPOLATION
[group, interp]: match
interp: "this.${ interp.substring(1) }" if starts interp, '@'
i = + 1
else if match = str.substring(i).match INTERPOLATION
[group, interp] = match
interp = "this.${ interp.substring(1) }" if starts interp, '@'
tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i
tokens.push ['IDENTIFIER', interp]
i: + group.length - 1
pi: i + 1
else if (expr: @balancedString str.substring(i), [['${', '}']])
i = + group.length - 1
pi = i + 1
else if (expr = @balancedString str.substring(i), [['${', '}']])
tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i
inner: expr.substring(2, expr.length - 1)
inner = expr.substring(2, expr.length - 1)
if inner.length
inner: inner.replace new RegExp('\\\\' + quote, 'g'), quote if options.heredoc
nested: lexer.tokenize "($inner)", {line: @line}
(tok[0]: ')') for tok, idx in nested when tok[0] is 'CALL_END'
inner = inner.replace new RegExp('\\\\' + quote, 'g'), quote if options.heredoc
nested = lexer.tokenize "($inner)", {line: @line}
(tok[0] = ')') for tok, idx in nested when tok[0] is 'CALL_END'
nested.pop()
tokens.push ['TOKENS', nested]
else
tokens.push ['STRING', "$quote$quote"]
i: + expr.length - 1
pi: i + 1
i: + 1
i = + expr.length - 1
pi = i + 1
i = + 1
tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i and pi < str.length - 1
tokens.unshift ['STRING', '""'] unless tokens[0][0] is 'STRING'
interpolated: tokens.length > 1
interpolated = tokens.length > 1
@token '(', '(' if interpolated
for token, i in tokens
[tag, value]: token
[tag, value] = token
if tag is 'TOKENS'
@tokens: @tokens.concat value
@tokens = @tokens.concat value
else if tag is 'STRING' and options.escapeQuotes
escaped: value.substring(1, value.length - 1).replace(/"/g, '\\"')
escaped = value.substring(1, value.length - 1).replace(/"/g, '\\"')
@token tag, "\"$escaped\""
else
@token tag, value
@ -448,14 +448,14 @@ exports.Lexer = class Lexer
# Peek at a tag in the current token stream.
tag: (index, newTag) ->
return unless tok: @prev index
return tok[0]: newTag if newTag?
return unless tok = @prev index
return tok[0] = newTag if newTag?
tok[0]
# Peek at a value in the current token stream.
value: (index, val) ->
return unless tok: @prev index
return tok[1]: val if val?
return unless tok = @prev index
return tok[1] = val if val?
tok[1]
# Peek at a previous token, entire.
@ -465,12 +465,12 @@ exports.Lexer = class Lexer
# Attempt to match a string against the current chunk, returning the indexed
# match if successful, and `false` otherwise.
match: (regex, index) ->
return false unless m: @chunk.match regex
return false unless m = @chunk.match regex
if m then m[index] else false
# Are we in the midst of an unfinished expression?
unfinished: ->
prev: @prev(2)
prev = @prev(2)
@value() and @value().match and @value().match(NO_NEWLINE) and
prev and (prev[0] isnt '.') and not @value().match(CODE)
@ -478,7 +478,7 @@ exports.Lexer = class Lexer
# ---------
# Keywords that CoffeeScript shares in common with JavaScript.
JS_KEYWORDS: [
JS_KEYWORDS = [
"if", "else",
"true", "false",
"new", "return",
@ -492,8 +492,8 @@ JS_KEYWORDS: [
# 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 [
COFFEE_ALIASES = ["and", "or", "is", "isnt", "not"]
COFFEE_KEYWORDS = COFFEE_ALIASES.concat [
"then", "unless", "until", "loop",
"yes", "no", "on", "off",
"of", "by", "where", "when"
@ -502,7 +502,7 @@ COFFEE_KEYWORDS: COFFEE_ALIASES.concat [
# 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,
# to avoid having a JavaScript error at runtime.
RESERVED: [
RESERVED = [
"case", "default", "do", "function", "var", "void", "with",
"const", "let", "enum", "export", "import", "native",
"__hasProp", "__extends", "__slice"
@ -510,36 +510,36 @@ RESERVED: [
# The superset of both JavaScript keywords and reserved words, none of which may
# be used as identifiers or properties.
JS_FORBIDDEN: JS_KEYWORDS.concat RESERVED
JS_FORBIDDEN = JS_KEYWORDS.concat RESERVED
# Token matching regexes.
IDENTIFIER : /^([a-zA-Z\$_](\w|\$)*)/
NUMBER : /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b/i
HEREDOC : /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\n?([ \t]*)'{3})/
INTERPOLATION : /^\$([a-zA-Z_@]\w*(\.\w+)*)/
OPERATOR : /^(-[\-=>]?|\+[+=]?|[*&|\/%=<>:!?]+)([ \t]*)/
WHITESPACE : /^([ \t]+)/
COMMENT : /^(\s*#{3}(?!#)[ \t]*\n+([\s\S]*?)[ \t]*\n+[ \t]*#{3}|(\s*#(?!##[^#])[^\n]*)+)/
CODE : /^((-|=)>)/
MULTI_DENT : /^((\n([ \t]*))+)(\.)?/
LAST_DENTS : /\n([ \t]*)/g
LAST_DENT : /\n([ \t]*)/
ASSIGNMENT : /^[:=]$/
IDENTIFIER = /^([a-zA-Z\$_](\w|\$)*)/
NUMBER = /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b/i
HEREDOC = /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\n?([ \t]*)'{3})/
INTERPOLATION = /^\$([a-zA-Z_@]\w*(\.\w+)*)/
OPERATOR = /^(-[\-=>]?|\+[+=]?|[*&|\/%=<>:!?]+)([ \t]*)/
WHITESPACE = /^([ \t]+)/
COMMENT = /^(\s*#{3}(?!#)[ \t]*\n+([\s\S]*?)[ \t]*\n+[ \t]*#{3}|(\s*#(?!##[^#])[^\n]*)+)/
CODE = /^((-|=)>)/
MULTI_DENT = /^((\n([ \t]*))+)(\.)?/
LAST_DENTS = /\n([ \t]*)/g
LAST_DENT = /\n([ \t]*)/
ASSIGNMENT = /^[:=]$/
# Regex-matching-regexes.
REGEX_START : /^\/[^\/ ]/
REGEX_INTERPOLATION: /([^\\]\$[a-zA-Z_@]|[^\\]\$\{.*[^\\]\})/
REGEX_END : /^(([imgy]{1,4})\b|\W|$)/
REGEX_ESCAPE : /\\[^\$]/g
REGEX_START = /^\/[^\/ ]/
REGEX_INTERPOLATION= /([^\\]\$[a-zA-Z_@]|[^\\]\$\{.*[^\\]\})/
REGEX_END = /^(([imgy]{1,4})\b|\W|$)/
REGEX_ESCAPE = /\\[^\$]/g
# Token cleaning regexes.
JS_CLEANER : /(^`|`$)/g
MULTILINER : /\n/g
STRING_NEWLINES : /\n[ \t]*/g
NO_NEWLINE : /^([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/
HEREDOC_INDENT : /(\n+([ \t]*)|^([ \t]+))/g
ASSIGNED : /^([a-zA-Z\$_]\w*[ \t]*?[:=][^=])/
NEXT_CHARACTER : /^\s*(\S)/
JS_CLEANER = /(^`|`$)/g
MULTILINER = /\n/g
STRING_NEWLINES = /\n[ \t]*/g
NO_NEWLINE = /^([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/
HEREDOC_INDENT = /(\n+([ \t]*)|^([ \t]+))/g
ASSIGNED = /^([a-zA-Z\$_]\w*[ \t]*?[:=][^=])/
NEXT_CHARACTER = /^\s*(\S)/
# Tokens which a regular expression will never immediately follow, but which
# a division operator might.
@ -547,25 +547,25 @@ NEXT_CHARACTER : /^\s*(\S)/
# See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
#
# Our list is shorter, due to sans-parentheses method calls.
NOT_REGEX: [
NOT_REGEX = [
'NUMBER', 'REGEX', '++', '--', 'FALSE', 'NULL', 'TRUE', ']'
]
# Tokens which could legitimately be invoked or indexed. A opening
# parentheses or bracket following these tokens will be recorded as the start
# of a function invocation or indexing operation.
CALLABLE: ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@', 'THIS', '?', '::']
CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@', 'THIS', '?', '::']
# Tokens that, when immediately preceding a `WHEN`, indicate that the `WHEN`
# 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']
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR']
# Half-assignments...
HALF_ASSIGNMENTS: ['-', '+', '/', '*', '%', '||', '&&', '?', 'OP?']
HALF_ASSIGNMENTS = ['-', '+', '/', '*', '%', '||', '&&', '?', 'OP?']
# Conversions from CoffeeScript operators into JavaScript ones.
CONVERSIONS: {
CONVERSIONS = {
'and': '&&'
'or': '||'
'is': '=='

File diff suppressed because it is too large Load Diff