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

@@ -236,6 +236,8 @@
return [$1];
}), o("ClassBody TERMINATOR ClassAssign", function() {
return $1.concat($3);
}), o("{ ClassBody }", function() {
return $2;
})
],
Call: [

File diff suppressed because one or more lines are too long

View File

@@ -19,6 +19,7 @@
this.removeMidExpressionNewlines();
this.closeOpenCallsAndIndexes();
this.addImplicitIndentation();
this.addImplicitBraces();
this.addImplicitParentheses();
this.ensureBalance(BALANCED_PAIRS);
this.rewriteClosingParens();
@@ -115,6 +116,56 @@
}
})(this));
};
Rewriter.prototype.addImplicitBraces = function() {
var closeBrackets, stack;
stack = [0];
closeBrackets = (function(_this) {
return function(i) {
var _c, len, size, tmp;
len = stack.length - 1;
_c = stack[len];
for (tmp = 0; (0 <= _c ? tmp < _c : tmp > _c); (0 <= _c ? tmp += 1 : tmp -= 1)) {
_this.tokens.splice(i, 0, ['}', '}', _this.tokens[i][2]]);
}
size = stack[len] + 1;
stack[len] = 0;
return size;
}
})(this);
return this.scanTokens((function(_this) {
return function(prev, token, post, i) {
var after, before, idx, len, open, size, tag;
tag = token[0];
len = stack.length - 1;
before = _this.tokens[i - 2];
after = _this.tokens[i + 2];
open = stack[len] > 0;
if (include(EXPRESSION_START, tag)) {
stack.push(tag === '{' ? 1 : 0);
if (tag === '{' && post && post[0] === 'INDENT') {
return 2;
}
} else if (include(EXPRESSION_END, tag)) {
if (tag === 'OUTDENT' && post && post[0] === '}') {
return 1;
}
stack[len - 1] += stack.pop();
if (tag === '}') {
stack[len - 1] -= 1;
}
} else if (tag === ':' && !open) {
idx = before && before[0] === '@' ? i - 2 : i - 1;
_this.tokens.splice(idx, 0, ['{', '{', token[2]]);
stack[stack.length - 1] += 1;
return 2;
} else if (tag === 'TERMINATOR' && !((after && after[0] === ':') || (post && post[0] === '@' && _this.tokens[i + 3] && _this.tokens[i + 3][0] === ':'))) {
size = closeBrackets(i);
return size;
}
return 1;
}
})(this));
};
Rewriter.prototype.addImplicitParentheses = function() {
var closeCalls, stack;
stack = [0];

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?

View File

@@ -53,11 +53,6 @@ trailingComma = [
trailingComma = {k1: "v1", k2: 4, k3: (-> true),}
ok trailingComma.k3() and (trailingComma.k2 is 4) and (trailingComma.k1 is "v1")
multiline = {a: 15,
b: 26}
ok multiline.b is 26
money$ = 'dollars'