Using an experimental version of new optional-brace object literals.

This commit is contained in:
Jeremy Ashkenas
2010-07-24 23:42:37 -07:00
parent f9dff6ffc4
commit d1ffffab04
12 changed files with 232 additions and 147 deletions

View File

@@ -48,7 +48,7 @@ exports.run = ->
path.exists 'Cakefile', (exists) ->
throw new Error("Cakefile not found in ${process.cwd()}") unless exists
args = process.argv[2...process.argv.length]
CoffeeScript.run fs.readFileSync('Cakefile').toString(), {source: 'Cakefile'}
CoffeeScript.run fs.readFileSync('Cakefile').toString(), source: 'Cakefile'
oparse = new optparse.OptionParser switches
return printTasks() unless args.length
options = oparse.parse(args)

View File

@@ -287,6 +287,7 @@ grammar = {
o "", -> []
o "ClassAssign", -> [$1]
o "ClassBody TERMINATOR ClassAssign", -> $1.concat $3
o "{ ClassBody }", -> $2
]
# The three flavors of function call: normal, object instantiation with `new`,

View File

@@ -131,7 +131,7 @@ exports.Lexer = class Lexer
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}
@interpolateString "$quote$doc$quote", heredoc: yes
@line += count match[1], "\n"
@i += match[1].length
true
@@ -142,7 +142,7 @@ exports.Lexer = class Lexer
@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
@@ -169,7 +169,7 @@ exports.Lexer = class Lexer
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}
@interpolateString "\"$str\"", escapeQuotes: yes
@tokens.splice @tokens.length, 0, [',', ','], ['STRING', "\"$flags\""] if flags
@tokens.splice @tokens.length, 0, [')', ')'], [')', ')']
else
@@ -405,7 +405,7 @@ exports.Lexer = class Lexer
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}
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]

View File

@@ -224,7 +224,7 @@ exports.Expressions = class Expressions extends BaseNode
# statement, ask the statement to do so.
compileExpression: (node, o) ->
@tab = o.indent
compiledNode = node.compile merge o, {top: true}
compiledNode = node.compile merge o, top: true
if node.isStatement() then compiledNode else "${@idt()}$compiledNode;"
# Wrap up the given nodes as an **Expressions**, unless it already happens
@@ -542,9 +542,9 @@ exports.RangeNode = class RangeNode extends BaseNode
# Compiles the range's source variables -- where it starts and where it ends.
# But only if they need to be cached to avoid double evaluation.
compileVariables: (o) ->
o = merge(o, {top: true})
[@from, @fromVar] = @from.compileReference o, {precompile: yes}
[@to, @toVar] = @to.compileReference o, {precompile: yes}
o = merge(o, top: true)
[@from, @fromVar] = @from.compileReference o, precompile: yes
[@to, @toVar] = @to.compileReference o, precompile: yes
[@fromNum, @toNum] = [@fromVar.match(SIMPLENUM), @toVar.match(SIMPLENUM)]
parts = []
parts.push @from if @from isnt @fromVar
@@ -579,7 +579,7 @@ exports.RangeNode = class RangeNode extends BaseNode
# When used as a value, expand the range into the equivalent array.
compileArray: (o) ->
idt = @idt 1
vars = @compileVariables(merge(o, {indent: idt}))
vars = @compileVariables merge o, indent: idt
result = o.scope.freeVariable()
i = o.scope.freeVariable()
pre = "\n${idt}${result} = []; ${vars}"
@@ -832,7 +832,7 @@ exports.AssignNode = class AssignNode extends BaseNode
# Compile the assignment from an array splice literal, using JavaScript's
# `Array#splice` method.
compileSplice: (o) ->
name = @variable.compile merge o, {onlyFirst: true}
name = @variable.compile merge o, onlyFirst: true
l = @variable.properties.length
range = @variable.properties[l - 1].range
plus = if range.exclusive then '' else ' + 1'
@@ -1006,7 +1006,7 @@ exports.WhileNode = class WhileNode extends BaseNode
pre = "$set${@tab}while ($cond)"
@body = Expressions.wrap([new IfNode(@guard, @body)]) if @guard
if @returns
post = '\n' + new ReturnNode(literal(rvar)).compile(merge(o, {indent: @idt()}))
post = '\n' + new ReturnNode(literal(rvar)).compile(merge(o, indent: @idt()))
else
post = ''
"$pre {\n${ @body.compile(o) }\n$@tab}$post"
@@ -1107,7 +1107,7 @@ exports.InNode = class InNode extends BaseNode
@array instanceof ValueNode and @array.isArray()
compileNode: (o) ->
[@obj1, @obj2] = @object.compileReference o, {precompile: yes}
[@obj1, @obj2] = @object.compileReference o, precompile: yes
if @isArray() then @compileOrTest(o) else @compileLoopTest(o)
compileOrTest: (o) ->
@@ -1116,7 +1116,7 @@ exports.InNode = class InNode extends BaseNode
"(${tests.join(' || ')})"
compileLoopTest: (o) ->
[@arr1, @arr2] = @array.compileReference o, {precompile: yes}
[@arr1, @arr2] = @array.compileReference o, precompile: yes
[i, l] = [o.scope.freeVariable(), o.scope.freeVariable()]
prefix = if @obj1 isnt @obj2 then @obj1 + '; ' else ''
"!!(function(){ ${prefix}for (var $i=0, $l=${@arr1}.length; $i<$l; $i++) if (${@arr2}[$i] === $@obj2) return true; }).call(this)"
@@ -1289,7 +1289,7 @@ exports.ForNode = class ForNode extends BaseNode
body = Expressions.wrap([@body])
if range
sourcePart = source.compileVariables(o)
forPart = source.compile merge o, {index: ivar, step: @step}
forPart = source.compile merge o, index: ivar, step: @step
else
svar = scope.freeVariable()
sourcePart = "$svar = ${ @source.compile(o) };"

View File

@@ -23,7 +23,7 @@ exports.OptionParser = class OptionParser
# many option parsers that allow you to attach callback actions for every
# flag. Instead, you're responsible for interpreting the options object.
parse: (args) ->
options = {arguments: []}
options = arguments: []
args = normalizeArguments args
for arg, i in args
isOption = !!(arg.match(LONG_FLAG) or arg.match(SHORT_FLAG))

View File

@@ -13,16 +13,14 @@ readline = require 'readline'
stdio = process.openStdin()
# Quick alias for quitting the REPL.
helpers.extend global, {
quit: -> process.exit(0)
}
helpers.extend global, quit: -> process.exit(0)
# The main REPL function. **run** is called every time a line of code is entered.
# Attempt to evaluate the command. If there's an exception, print it out instead
# of exiting.
run = (buffer) ->
try
val = CoffeeScript.run buffer.toString(), {noWrap: true, globals: true, source: 'repl'}
val = CoffeeScript.run buffer.toString(), noWrap: true, globals: true, source: 'repl'
puts inspect val if val isnt undefined
catch err
puts err.stack or err.toString()

View File

@@ -31,6 +31,7 @@ exports.Rewriter = class Rewriter
@removeMidExpressionNewlines()
@closeOpenCallsAndIndexes()
@addImplicitIndentation()
@addImplicitBraces()
@addImplicitParentheses()
@ensureBalance BALANCED_PAIRS
@rewriteClosingParens()
@@ -110,6 +111,41 @@ exports.Rewriter = class Rewriter
brackets[brackets.length - 1] -= 1
return 1
# Object literals may be written with implicit braces, for simple cases.
# Insert the missing braces here, so that the parser doesn't have to.
addImplicitBraces: ->
stack = [0]
closeBrackets = (i) =>
len = stack.length - 1
for tmp in [0...stack[len]]
@tokens.splice(i, 0, ['}', '}', @tokens[i][2]])
size = stack[len] + 1
stack[len] = 0
size
@scanTokens (prev, token, post, i) =>
tag = token[0]
len = stack.length - 1
before = @tokens[i - 2]
after = @tokens[i + 2]
open = stack[len] > 0
if include EXPRESSION_START, tag
stack.push(if tag is '{' then 1 else 0)
return 2 if tag is '{' and post and post[0] is 'INDENT'
else if include EXPRESSION_END, tag
return 1 if tag is 'OUTDENT' and post and post[0] is '}'
stack[len - 1] += stack.pop()
stack[len - 1] -= 1 if tag is '}'
else if tag is ':' and not open
idx = if before and before[0] is '@' then i - 2 else i - 1
@tokens.splice idx, 0, ['{', '{', token[2]]
stack[stack.length - 1] += 1
return 2
else if tag is 'TERMINATOR' and
not ((after and after[0] is ':') or (post and post[0] is '@' and @tokens[i + 3] and @tokens[i + 3][0] is ':'))
size = closeBrackets(i)
return size
return 1
# Methods may be optionally called without parentheses, for simple cases.
# Insert the implicit parentheses here, so that the parser doesn't have to
# deal with them.

View File

@@ -61,7 +61,7 @@ exports.Scope = class Scope
# Ensure that an assignment is made at the top of this scope
# (or at the top-level scope, if requested).
assign: (name, value) ->
@variables[name] = {value: value, assigned: true}
@variables[name] = value: value, assigned: true
# Does this scope reference any variables that need to be declared in the
# given function body?