Adding initial implementation of here-comments (block comments) Issue #368

This commit is contained in:
Jeremy Ashkenas 2010-05-12 20:56:44 -04:00
parent 7e3c71ed19
commit 8aceef20e1
8 changed files with 252 additions and 212 deletions

View File

@ -158,6 +158,8 @@
Comment: [
o("COMMENT", function() {
return new CommentNode($1);
}), o("HERECOMMENT", function() {
return new CommentNode($1, 'herecomment');
})
],
// [The existential operator](http://jashkenas.github.com/coffee-script/#existence).

View File

@ -187,12 +187,40 @@
return false;
}
quote = match[1].substr(0, 1);
doc = this.sanitize_heredoc(match[2] || match[4], quote);
doc = this.sanitize_heredoc(match[2] || match[4], {
quote: quote
});
this.interpolate_string(("" + quote + doc + quote));
this.line += count(match[1], "\n");
this.i += match[1].length;
return true;
};
// Matches and conumes comments. We pass through comments into JavaScript,
// so they're treated as real tokens, like any other part of the language.
Lexer.prototype.comment_token = function comment_token() {
var comment, i, lines, match;
if (!(match = this.chunk.match(COMMENT))) {
return false;
}
if (match[3]) {
comment = this.sanitize_heredoc(match[3], {
herecomment: true
});
this.token('HERECOMMENT', compact(comment.split(MULTILINER)));
} else {
lines = compact(match[1].replace(COMMENT_CLEANER, '').split(MULTILINER));
i = this.tokens.length - 1;
if (this.unfinished()) {
while (this.tokens[i] && !include(LINE_BREAK, this.tokens[i][0])) {
i -= 1;
}
}
this.tokens.splice(i + 1, 0, ['COMMENT', lines, this.line], ['TERMINATOR', '\n', this.line]);
}
this.line += count(match[1], "\n");
this.i += match[1].length;
return true;
};
// Matches JavaScript interpolated directly into the source via backticks.
Lexer.prototype.js_token = function js_token() {
var script;
@ -244,25 +272,6 @@
delimited = __slice.call(arguments, 0, _d - 0);
return balanced_string(this.chunk, delimited);
};
// Matches and conumes comments. We pass through comments into JavaScript,
// so they're treated as real tokens, like any other part of the language.
Lexer.prototype.comment_token = function comment_token() {
var comment, i, lines;
if (!(comment = this.match(COMMENT, 1))) {
return false;
}
this.line += (comment.match(MULTILINER) || []).length;
lines = compact(comment.replace(COMMENT_CLEANER, '').split(MULTILINER));
i = this.tokens.length - 1;
if (this.unfinished()) {
while (this.tokens[i] && !include(LINE_BREAK, this.tokens[i][0])) {
i -= 1;
}
}
this.tokens.splice(i + 1, 0, ['COMMENT', lines, this.line], ['TERMINATOR', '\n', this.line]);
this.i += comment.length;
return true;
};
// Matches newlines, indents, and outdents, and determines which is which.
// If we can detect that the current line is continued onto the the next line,
// then the newline is suppressed:
@ -276,7 +285,7 @@
if (!(indent = this.match(MULTI_DENT, 1))) {
return false;
}
this.line += indent.match(MULTILINER).length;
this.line += count(indent, "\n");
this.i += indent.length;
prev = this.prev(2);
size = indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length;
@ -405,12 +414,16 @@
}
}
};
// Sanitize a heredoc by escaping internal double quotes and erasing all
// external indentation on the left-hand side.
Lexer.prototype.sanitize_heredoc = function sanitize_heredoc(doc, quote) {
// Sanitize a heredoc or herecomment by escaping internal double quotes and
// erasing all external indentation on the left-hand side.
Lexer.prototype.sanitize_heredoc = function sanitize_heredoc(doc, options) {
var indent;
indent = (doc.match(HEREDOC_INDENT) || ['']).sort()[0];
return doc.replace(new RegExp("^" + indent, 'gm'), '').replace(MULTILINER, "\\n").replace(new RegExp(quote, 'g'), '\\"');
doc = doc.replace(new RegExp("^" + indent, 'gm'), '');
if (options.herecomment) {
return doc;
}
return doc.replace(MULTILINER, "\\n").replace(new RegExp(options.quote, 'g'), '\\"');
};
// Tag a half assignment.
Lexer.prototype.tag_half_assignment = function tag_half_assignment(tag) {
@ -636,7 +649,7 @@
INTERPOLATION = /^\$([a-zA-Z_@]\w*(\.\w+)*)/;
OPERATOR = /^([+\*&|\/\-%=<>:!?]+)([ \t]*)/;
WHITESPACE = /^([ \t]+)/;
COMMENT = /^(((\n?[ \t]*)?#[^\n]*)+)/;
COMMENT = /^((\n?[ \t]*)?#{3}(?!#)\n*([\s\S]*?)\n*([ \t]*)#{3}|((\n?[ \t]*)?#[^\n]*)+)/;
CODE = /^((-|=)>)/;
MULTI_DENT = /^((\n([ \t]*))+)(\.)?/;
LAST_DENTS = /\n([ \t]*)/g;

View File

@ -510,8 +510,9 @@
// CoffeeScript passes through comments as JavaScript comments at the
// same position.
exports.CommentNode = (function() {
CommentNode = function CommentNode(lines) {
CommentNode = function CommentNode(lines, type) {
this.lines = lines;
this.type = type;
this;
return this;
};
@ -520,7 +521,13 @@
return this;
};
CommentNode.prototype.compile_node = function compile_node(o) {
return ("" + this.tab + "//") + this.lines.join(("\n" + this.tab + "//"));
var sep;
if (this.type === 'herecomment') {
sep = '\n' + this.idt(1);
return "" + this.tab + "/*" + sep + (this.lines.join(sep)) + "\n" + this.tab + "*/";
} else {
return ("" + this.tab + "//") + this.lines.join(("\n" + this.tab + "//"));
}
};
return CommentNode;
})();

File diff suppressed because one or more lines are too long

View File

@ -165,6 +165,7 @@ grammar: {
# positions in which they can occur in the grammar.
Comment: [
o "COMMENT", -> new CommentNode $1
o "HERECOMMENT", -> new CommentNode $1, 'herecomment'
]
# [The existential operator](http://jashkenas.github.com/coffee-script/#existence).

View File

@ -126,12 +126,29 @@ exports.Lexer: class Lexer
heredoc_token: ->
return false unless match: @chunk.match(HEREDOC)
quote: match[1].substr 0, 1
doc: @sanitize_heredoc match[2] or match[4], quote
doc: @sanitize_heredoc match[2] or match[4], {quote}
@interpolate_string "$quote$doc$quote"
@line: + count match[1], "\n"
@i: + match[1].length
true
# Matches and conumes comments. We pass through comments into JavaScript,
# so they're treated as real tokens, like any other part of the language.
comment_token: ->
return false unless match: @chunk.match(COMMENT)
if match[3]
comment: @sanitize_heredoc match[3], {herecomment: true}
@token 'HERECOMMENT', compact comment.split MULTILINER
else
lines: compact match[1].replace(COMMENT_CLEANER, '').split MULTILINER
i: @tokens.length - 1
if @unfinished()
i: - 1 while @tokens[i] and not include LINE_BREAK, @tokens[i][0]
@tokens.splice(i + 1, 0, ['COMMENT', lines, @line], ['TERMINATOR', '\n', @line])
@line: + count match[1], "\n"
@i: + match[1].length
true
# Matches JavaScript interpolated directly into the source via backticks.
js_token: ->
return false unless starts @chunk, '`'
@ -165,19 +182,6 @@ exports.Lexer: class Lexer
balanced_token: (delimited...) ->
balanced_string @chunk, delimited
# Matches and conumes comments. We pass through comments into JavaScript,
# so they're treated as real tokens, like any other part of the language.
comment_token: ->
return false unless comment: @match COMMENT, 1
@line: + (comment.match(MULTILINER) or []).length
lines: compact comment.replace(COMMENT_CLEANER, '').split MULTILINER
i: @tokens.length - 1
if @unfinished()
i: - 1 while @tokens[i] and not include LINE_BREAK, @tokens[i][0]
@tokens.splice(i + 1, 0, ['COMMENT', lines, @line], ['TERMINATOR', '\n', @line])
@i: + comment.length
true
# Matches newlines, indents, and outdents, and determines which is which.
# If we can detect that the current line is continued onto the the next line,
# then the newline is suppressed:
@ -190,7 +194,7 @@ exports.Lexer: class Lexer
# can close multiple indents, so we need to know how far in we happen to be.
line_token: ->
return false unless indent: @match MULTI_DENT, 1
@line: + indent.match(MULTILINER).length
@line: + count indent, "\n"
@i : + indent.length
prev: @prev(2)
size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
@ -286,13 +290,14 @@ exports.Lexer: class Lexer
else
@tag 1, 'PROPERTY_ACCESS'
# Sanitize a heredoc by escaping internal double quotes and erasing all
# external indentation on the left-hand side.
sanitize_heredoc: (doc, quote) ->
# Sanitize a heredoc or herecomment by escaping internal double quotes and
# erasing all external indentation on the left-hand side.
sanitize_heredoc: (doc, options) ->
indent: (doc.match(HEREDOC_INDENT) or ['']).sort()[0]
doc.replace(new RegExp("^" +indent, 'gm'), '')
.replace(MULTILINER, "\\n")
.replace(new RegExp(quote, 'g'), '\\"')
doc: doc.replace(new RegExp("^" +indent, 'gm'), '')
return doc if options.herecomment
doc.replace(MULTILINER, "\\n")
.replace(new RegExp(options.quote, 'g'), '\\"')
# Tag a half assignment.
tag_half_assignment: (tag) ->
@ -477,7 +482,7 @@ HEREDOC : /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\
INTERPOLATION : /^\$([a-zA-Z_@]\w*(\.\w+)*)/
OPERATOR : /^([+\*&|\/\-%=<>:!?]+)([ \t]*)/
WHITESPACE : /^([ \t]+)/
COMMENT : /^(((\n?[ \t]*)?#[^\n]*)+)/
COMMENT : /^((\n?[ \t]*)?#{3}(?!#)\n*([\s\S]*?)\n*([ \t]*)#{3}|((\n?[ \t]*)?#[^\n]*)+)/
CODE : /^((-|=)>)/
MULTI_DENT : /^((\n([ \t]*))+)(\.)?/
LAST_DENTS : /\n([ \t]*)/g

View File

@ -358,15 +358,20 @@ children ValueNode, 'base', 'properties'
# same position.
exports.CommentNode: class CommentNode extends BaseNode
constructor: (lines) ->
constructor: (lines, type) ->
@lines: lines
@type: type
this
make_return: ->
this
compile_node: (o) ->
"$@tab//" + @lines.join("\n$@tab//")
if @type is 'herecomment'
sep: '\n' + @idt(1)
"$@tab/*$sep${ @lines.join(sep) }\n$@tab*/"
else
"$@tab//" + @lines.join("\n$@tab//")
statement CommentNode

View File

@ -54,3 +54,8 @@ test:
'test'
ok test is 'test test test'
###
This is a here-comment.
Kind of like a heredoc.
###