mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-01-13 16:57:54 -05:00
adding passed-through block comments back to the grammar/lexer/rewriter/nodes ... thanks, Trevor Burnham.
This commit is contained in:
@@ -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);
|
||||
|
||||
15
lib/lexer.js
15
lib/lexer.js
@@ -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;
|
||||
|
||||
70
lib/nodes.js
70
lib/nodes.js
@@ -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 {
|
||||
|
||||
380
lib/parser.js
380
lib/parser.js
File diff suppressed because one or more lines are too long
@@ -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 = [];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user