adding passed-through block comments back to the grammar/lexer/rewriter/nodes ... thanks, Trevor Burnham.

This commit is contained in:
Jeremy Ashkenas
2010-07-01 21:26:33 -04:00
parent 364ec2a694
commit 77a75ed365
10 changed files with 384 additions and 213 deletions

View File

@@ -34,12 +34,14 @@
return new LiteralNode($1);
})
],
Expression: [o("Value"), o("Call"), o("Code"), o("Operation"), o("Assign"), o("If"), o("Try"), o("While"), o("For"), o("Switch"), o("Extends"), o("Class"), o("Splat"), o("Existence")],
Expression: [o("Value"), o("Call"), o("Code"), o("Operation"), o("Assign"), o("If"), o("Try"), o("While"), o("For"), o("Switch"), o("Extends"), o("Class"), o("Splat"), o("Existence"), o("Comment")],
Block: [
o("INDENT Body OUTDENT", function() {
return $2;
}), o("INDENT OUTDENT", function() {
return new Expressions();
}), o("TERMINATOR Comment", function() {
return Expressions.wrap([$2]);
})
],
Identifier: [
@@ -85,7 +87,7 @@
return new AssignNode(new ValueNode($1), $3, 'object');
}), o("AlphaNumeric ASSIGN Expression", function() {
return new AssignNode(new ValueNode($1), $3, 'object');
})
}), o("Comment")
],
Return: [
o("RETURN Expression", function() {
@@ -94,6 +96,11 @@
return new ReturnNode(new ValueNode(new LiteralNode('null')));
})
],
Comment: [
o("HERECOMMENT", function() {
return new CommentNode($1);
})
],
Existence: [
o("Expression ?", function() {
return new ExistenceNode($1);

View File

@@ -152,12 +152,19 @@
return true;
};
Lexer.prototype.commentToken = function() {
var match;
var comment, match;
if (!(match = this.chunk.match(COMMENT))) {
return false;
}
this.line += count(match[1], "\n");
this.i += match[1].length;
if (match[2]) {
comment = this.sanitizeHeredoc(match[2], {
herecomment: true
});
this.token('HERECOMMENT', comment.split(MULTILINER));
this.token('TERMINATOR', '\n');
}
return true;
};
Lexer.prototype.jsToken = function() {
@@ -352,7 +359,11 @@
indent = attempt;
}
}
return doc.replace(new RegExp("^" + indent, 'gm'), '').replace(MULTILINER, "\\n").replace(new RegExp(options.quote, 'g'), ("\\" + options.quote));
doc = doc.replace(new RegExp("^" + indent, 'gm'), '');
if (options.herecomment) {
return doc;
}
return doc.replace(MULTILINER, "\\n").replace(new RegExp(options.quote, 'g'), ("\\" + options.quote));
};
Lexer.prototype.tagHalfAssignment = function(tag) {
var last;

View File

@@ -1,5 +1,5 @@
(function(){
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, DOUBLE_PARENS, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NUMBER, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, _a, compact, del, flatten, helpers, include, indexOf, literal, merge, starts, utility;
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, DOUBLE_PARENS, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NUMBER, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, _a, compact, del, flatten, helpers, include, indexOf, literal, merge, starts, utility;
var __extends = function(child, parent) {
var ctor = function(){ };
ctor.prototype = parent.prototype;
@@ -36,7 +36,7 @@
}
}
top = this.topSensitive() ? this.options.top : del(this.options, 'top');
closure = this.isStatement() && !this.isPureStatement() && !top && !this.options.asStatement && !this.containsPureStatement();
closure = this.isStatement() && !this.isPureStatement() && !top && !this.options.asStatement && !(this instanceof CommentNode) && !this.containsPureStatement();
if (closure) {
return this.compileClosure(this.options);
} else {
@@ -197,6 +197,9 @@
var idx, last;
idx = this.expressions.length - 1;
last = this.expressions[idx];
if (last instanceof CommentNode) {
last = this.expressions[idx -= 1];
}
if (!last || last instanceof ReturnNode) {
return this;
}
@@ -418,6 +421,26 @@
};
return ValueNode;
})();
exports.CommentNode = (function() {
CommentNode = function(lines) {
this.lines = lines;
return this;
};
__extends(CommentNode, BaseNode);
CommentNode.prototype['class'] = 'CommentNode';
CommentNode.prototype.isStatement = function() {
return true;
};
CommentNode.prototype.makeReturn = function() {
return this;
};
CommentNode.prototype.compileNode = function(o) {
var sep;
sep = ("\n" + this.tab);
return "" + this.tab + "/*" + sep + (this.lines.join(sep)) + "\n" + this.tab + "*/";
};
return CommentNode;
})();
exports.CallNode = (function() {
CallNode = function(variable, args) {
this.isNew = false;
@@ -641,23 +664,38 @@
ObjectNode.prototype['class'] = 'ObjectNode';
ObjectNode.prototype.children = ['properties'];
ObjectNode.prototype.compileNode = function(o) {
var _b, _c, _d, i, inner, join, last, prop, props;
var _b, _c, _d, _e, _f, _g, _h, i, indent, inner, join, lastNoncom, nonComments, prop, props;
o.indent = this.idt(1);
last = this.properties.length - 1;
props = (function() {
_b = []; _c = this.properties;
for (i = 0, _d = _c.length; i < _d; i++) {
prop = _c[i];
_b.push((function() {
join = i === last ? '' : ',\n';
if (!(prop instanceof AssignNode)) {
prop = new AssignNode(prop, prop, 'object');
}
return this.idt(1) + prop.compile(o) + join;
}).call(this));
nonComments = (function() {
_b = []; _d = this.properties;
for (_c = 0, _e = _d.length; _c < _e; _c++) {
prop = _d[_c];
!(prop instanceof CommentNode) ? _b.push(prop) : null;
}
return _b;
}).call(this);
lastNoncom = nonComments[nonComments.length - 1];
props = (function() {
_f = []; _g = this.properties;
for (i = 0, _h = _g.length; i < _h; i++) {
prop = _g[i];
_f.push((function() {
join = ",\n";
if ((prop === lastNoncom) || (prop instanceof CommentNode)) {
join = "\n";
}
if (i === this.properties.length - 1) {
join = '';
}
indent = prop instanceof CommentNode ? '' : this.idt(1);
if (!(prop instanceof AssignNode || prop instanceof CommentNode)) {
prop = new AssignNode(prop, prop, 'object');
}
return indent + prop.compile(o) + join;
}).call(this));
}
return _f;
}).call(this);
props = props.join('');
inner = props ? '\n' + props + '\n' + this.idt() : '';
return "{" + inner + "}";
@@ -685,6 +723,8 @@
code = obj.compile(o);
if (obj instanceof SplatNode) {
return this.compileSplatLiteral(this.objects, o);
} else if (obj instanceof CommentNode) {
objects.push("\n" + code + "\n" + o.indent);
} else if (i === this.objects.length - 1) {
objects.push(code);
} else {

File diff suppressed because one or more lines are too long

View File

@@ -14,6 +14,7 @@
Rewriter = function() { };
Rewriter.prototype.rewrite = function(tokens) {
this.tokens = tokens;
this.adjustComments();
this.removeLeadingNewlines();
this.removeMidExpressionNewlines();
this.closeOpenCallsAndIndexes();
@@ -35,6 +36,32 @@
}
return true;
};
Rewriter.prototype.adjustComments = function() {
return this.scanTokens((function(__this) {
var __func = function(prev, token, post, i) {
var _c, _d, after, before;
if (!(token[0] === 'HERECOMMENT')) {
return 1;
}
_c = [this.tokens[i - 2], this.tokens[i + 2]];
before = _c[0];
after = _c[1];
if (after && after[0] === 'INDENT') {
this.tokens.splice(i + 2, 1);
before && before[0] === 'OUTDENT' && post && (prev[0] === post[0]) && (post[0] === 'TERMINATOR') ? this.tokens.splice(i - 2, 1) : this.tokens.splice(i, 0, after);
} else if (prev && !('TERMINATOR' === (_d = prev[0]) || 'INDENT' === _d || 'OUTDENT' === _d)) {
post && post[0] === 'TERMINATOR' && after && after[0] === 'OUTDENT' ? this.tokens.splice.apply(this.tokens, [i, 0].concat(this.tokens.splice(i + 2, 2))) : this.tokens.splice(i, 0, ['TERMINATOR', "\n", prev[2]]);
return 2;
} else if (before && before[0] === 'OUTDENT' && prev && prev[0] === 'TERMINATOR' && post && post[0] === 'TERMINATOR' && after && after[0] === 'ELSE') {
this.tokens.splice(i + 1, 0, this.tokens.splice(i - 2, 1)[0]);
}
return 1;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
Rewriter.prototype.removeLeadingNewlines = function() {
var _c;
_c = [];

View File

@@ -99,6 +99,7 @@ grammar: {
o "Class"
o "Splat"
o "Existence"
o "Comment"
]
# A an indented block of expressions. Note that the [Rewriter](rewriter.html)
@@ -107,6 +108,7 @@ grammar: {
Block: [
o "INDENT Body OUTDENT", -> $2
o "INDENT OUTDENT", -> new Expressions
o "TERMINATOR Comment", -> Expressions.wrap [$2]
]
# A literal identifier, a variable name or property.
@@ -147,6 +149,7 @@ grammar: {
o "AlphaNumeric"
o "Identifier ASSIGN Expression", -> new AssignNode new ValueNode($1), $3, 'object'
o "AlphaNumeric ASSIGN Expression", -> new AssignNode new ValueNode($1), $3, 'object'
o "Comment"
]
# A return statement from a function body.
@@ -155,6 +158,11 @@ grammar: {
o "RETURN", -> new ReturnNode new ValueNode new LiteralNode 'null'
]
# A block comment.
Comment: [
o "HERECOMMENT", -> new CommentNode $1
]
# [The existential operator](http://jashkenas.github.com/coffee-script/#existence).
Existence: [
o "Expression ?", -> new ExistenceNode $1

View File

@@ -141,6 +141,10 @@ exports.Lexer: class Lexer
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}
@token 'HERECOMMENT', comment.split MULTILINER
@token 'TERMINATOR', '\n'
true
# Matches JavaScript interpolated directly into the source via backticks.
@@ -292,14 +296,15 @@ exports.Lexer: class Lexer
prev[0] is '@'
if accessor then 'accessor' else false
# Sanitize a heredoc by escaping internal double quotes and
# 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.replace(new RegExp("^" +indent, 'gm'), '')
.replace(MULTILINER, "\\n")
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.

View File

@@ -47,7 +47,7 @@ exports.BaseNode: class BaseNode
del @options, 'chainRoot' unless this instanceof AccessorNode or this instanceof IndexNode
top: if @topSensitive() then @options.top else del @options, 'top'
closure: @isStatement() and not @isPureStatement() and not top and
not @options.asStatement and
not @options.asStatement and not (this instanceof CommentNode) and
not @containsPureStatement()
if closure then @compileClosure(@options) else @compileNode(@options)
@@ -179,6 +179,7 @@ exports.Expressions: class Expressions extends BaseNode
makeReturn: ->
idx: @expressions.length - 1
last: @expressions[idx]
last: @expressions[idx: - 1] if last instanceof CommentNode
return this if not last or last instanceof ReturnNode
@expressions[idx]: last.makeReturn()
this
@@ -364,6 +365,25 @@ exports.ValueNode: class ValueNode extends BaseNode
if op and @wrapped then "($complete)" else complete
#### CommentNode
# CoffeeScript passes through block comments as JavaScript block comments
# at the same position.
exports.CommentNode: class CommentNode extends BaseNode
class: 'CommentNode'
isStatement: -> yes
constructor: (lines) ->
@lines: lines
makeReturn: ->
this
compileNode: (o) ->
sep: "\n$@tab"
"$@tab/*$sep${ @lines.join(sep) }\n$@tab*/"
#### CallNode
# Node for a function invocation. Takes care of converting `super()` calls into
@@ -565,11 +585,15 @@ exports.ObjectNode: class ObjectNode extends BaseNode
compileNode: (o) ->
o.indent: @idt 1
last: @properties.length - 1
nonComments: prop for prop in @properties when not (prop instanceof CommentNode)
lastNoncom: nonComments[nonComments.length - 1]
props: for prop, i in @properties
join: if i is last then '' else ',\n'
prop: new AssignNode prop, prop, 'object' unless prop instanceof AssignNode
@idt(1) + prop.compile(o) + join
join: ",\n"
join: "\n" if (prop is lastNoncom) or (prop instanceof CommentNode)
join: '' if i is @properties.length - 1
indent: if prop instanceof CommentNode then '' else @idt 1
prop: new AssignNode prop, prop, 'object' unless prop instanceof AssignNode or prop instanceof CommentNode
indent + prop.compile(o) + join
props: props.join('')
inner: if props then '\n' + props + '\n' + @idt() else ''
"{$inner}"
@@ -594,6 +618,8 @@ exports.ArrayNode: class ArrayNode extends BaseNode
code: obj.compile(o)
if obj instanceof SplatNode
return @compileSplatLiteral @objects, o
else if obj instanceof CommentNode
objects.push "\n$code\n$o.indent"
else if i is @objects.length - 1
objects.push code
else

View File

@@ -26,6 +26,7 @@ exports.Rewriter: class Rewriter
# corrected before implicit parentheses can be wrapped around blocks of code.
rewrite: (tokens) ->
@tokens: tokens
@adjustComments()
@removeLeadingNewlines()
@removeMidExpressionNewlines()
@closeOpenCallsAndIndexes()
@@ -47,6 +48,29 @@ exports.Rewriter: class Rewriter
move: block @tokens[i - 1], @tokens[i], @tokens[i + 1], i
i: + move
true
# Massage newlines and indentations so that comments don't have to be
# correctly indented, or appear on a line of their own.
adjustComments: ->
@scanTokens (prev, token, post, i) =>
return 1 unless token[0] is 'HERECOMMENT'
[before, after]: [@tokens[i - 2], @tokens[i + 2]]
if after and after[0] is 'INDENT'
@tokens.splice i + 2, 1
if before and before[0] is 'OUTDENT' and post and prev[0] is post[0] is 'TERMINATOR'
@tokens.splice i - 2, 1
else
@tokens.splice i, 0, after
else if prev and prev[0] not in ['TERMINATOR', 'INDENT', 'OUTDENT']
if post and post[0] is 'TERMINATOR' and after and after[0] is 'OUTDENT'
@tokens.splice(i, 0, @tokens.splice(i + 2, 2)...)
else
@tokens.splice i, 0, ['TERMINATOR', "\n", prev[2]]
return 2
else if before and before[0] is 'OUTDENT' and prev and prev[0] is 'TERMINATOR' and
post and post[0] is 'TERMINATOR' and after and after[0] is 'ELSE'
@tokens.splice i + 1, 0, @tokens.splice(i - 2, 1)[0]
return 1
# Leading newlines would introduce an ambiguity in the grammar, so we
# dispatch them here.

View File

@@ -66,6 +66,21 @@ func: ->
###
code
obj: {
a: b
###
comment
###
c: d
}
arr: [
1, 2, 3,
###
four
###
5, 6, 7
]
# Spaced comments in if / elses.
result: if false