From d880b8b8f2f74dd69a0e52b9039f8adc1f179460 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 17 Mar 2010 20:47:27 -0400 Subject: [PATCH] adding interpolation to heredocs, using the same rules as for strings --- lib/command.js | 2 +- lib/lexer.js | 11 ++++++----- lib/nodes.js | 2 +- src/lexer.coffee | 9 +++++---- test/test_heredocs.coffee | 18 +++++++++++++++++- 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/lib/command.js b/lib/command.js index dce89920..4f8c0c53 100644 --- a/lib/command.js +++ b/lib/command.js @@ -11,7 +11,7 @@ optparse = require('./optparse'); CoffeeScript = require('./coffee-script'); // The help banner that is printed when `coffee` is called without arguments. - BANNER = "coffee compiles CoffeeScript source files into JavaScript.\n\nUsage:\n coffee path/to/script.coffee"; + BANNER = 'coffee compiles CoffeeScript source files into JavaScript.\n\nUsage:\n coffee path/to/script.coffee'; // The list of all the valid option flags that `coffee` knows how to handle. SWITCHES = [['-c', '--compile', 'compile to JavaScript and save as .js files'], ['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-o', '--output [DIR]', 'set the directory for compiled JavaScript'], ['-w', '--watch', 'watch scripts for changes, and recompile'], ['-p', '--print', 'print the compiled JavaScript to stdout'], ['-l', '--lint', 'pipe the compiled JavaScript through JSLint'], ['-s', '--stdio', 'listen for and compile scripts over stdio'], ['-e', '--eval', 'compile a string from the command line'], ['--no-wrap', 'compile without the top-level function wrapper'], ['-t', '--tokens', 'print the tokens that the lexer produces'], ['-n', '--nodes', 'print the parse tree that Jison produces'], ['-v', '--version', 'display CoffeeScript version'], ['-h', '--help', 'display this help message']]; // Top-level objects shared by all the functions. diff --git a/lib/lexer.js b/lib/lexer.js index 73ce9090..c4b7bf3e 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -169,12 +169,13 @@ // Matches heredocs, adjusting indentation to the correct level, as heredocs // preserve whitespace, but ignore indentation to the left. Lexer.prototype.heredoc_token = function heredoc_token() { - var doc, match; + var doc, match, quote; if (!((match = this.chunk.match(HEREDOC)))) { return false; } - doc = this.sanitize_heredoc(match[2] || match[4]); - this.token('STRING', "\"" + doc + "\""); + quote = match[1].substr(0, 1); + doc = this.sanitize_heredoc(match[2] || match[4], quote); + this.interpolate_string('' + quote + doc + quote); this.line += count(match[1], "\n"); this.i += match[1].length; return true; @@ -383,10 +384,10 @@ }; // 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) { + Lexer.prototype.sanitize_heredoc = function sanitize_heredoc(doc, quote) { var indent; indent = (doc.match(HEREDOC_INDENT) || ['']).sort()[0]; - return doc.replace(new RegExp("^" + indent, 'gm'), '').replace(MULTILINER, "\\n").replace(/"/g, '\\"'); + return doc.replace(new RegExp("^" + indent, 'gm'), '').replace(MULTILINER, "\\n").replace(new RegExp(quote, 'g'), '\\"'); }; // A source of ambiguity in our grammar used to be parameter lists in function // definitions versus argument lists in function calls. Walk backwards, tagging diff --git a/lib/nodes.js b/lib/nodes.js index 7ad5d5a2..ddda62a3 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -494,7 +494,7 @@ }; __extends(ExtendsNode, BaseNode); ExtendsNode.prototype.type = 'Extends'; - ExtendsNode.prototype.code = "function(child, parent) {\n var ctor = function(){ };\n ctor.prototype = parent.prototype;\n child.__superClass__ = parent.prototype;\n child.prototype = new ctor();\n child.prototype.constructor = child;\n }"; + ExtendsNode.prototype.code = 'function(child, parent) {\n var ctor = function(){ };\n ctor.prototype = parent.prototype;\n child.__superClass__ = parent.prototype;\n child.prototype = new ctor();\n child.prototype.constructor = child;\n }'; // Hooks one constructor into another's prototype chain. ExtendsNode.prototype.compile_node = function compile_node(o) { var call, ref; diff --git a/src/lexer.coffee b/src/lexer.coffee index 92fda0da..a9c35c82 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -126,8 +126,9 @@ exports.Lexer: class Lexer # preserve whitespace, but ignore indentation to the left. heredoc_token: -> return false unless match = @chunk.match(HEREDOC) - doc: @sanitize_heredoc match[2] or match[4] - @token 'STRING', "\"$doc\"" + quote: match[1].substr(0, 1) + doc: @sanitize_heredoc match[2] or match[4], quote + @interpolate_string "$quote$doc$quote" @line += count match[1], "\n" @i += match[1].length true @@ -285,11 +286,11 @@ exports.Lexer: class Lexer # Sanitize a heredoc by escaping internal double quotes and erasing all # external indentation on the left-hand side. - sanitize_heredoc: (doc) -> + sanitize_heredoc: (doc, quote) -> indent: (doc.match(HEREDOC_INDENT) or ['']).sort()[0] doc.replace(new RegExp("^" +indent, 'gm'), '') .replace(MULTILINER, "\\n") - .replace(/"/g, '\\"') + .replace(new RegExp(quote, 'g'), '\\"') # A source of ambiguity in our grammar used to be parameter lists in function # definitions versus argument lists in function calls. Walk backwards, tagging diff --git a/test/test_heredocs.coffee b/test/test_heredocs.coffee index b9cd2e2c..01421d6a 100644 --- a/test/test_heredocs.coffee +++ b/test/test_heredocs.coffee @@ -49,4 +49,20 @@ ok a is "a\n\n\nb c" a: '''more"than"one"quote''' -ok a is 'more"than"one"quote' \ No newline at end of file +ok a is 'more"than"one"quote' + + +val: 10 + +a: """ + basic heredoc $val + on two lines + """ + +b: ''' + basic heredoc $val + on two lines + ''' + +ok a is "basic heredoc 10\non two lines" +ok b is "basic heredoc \$val\non two lines"