From d624310be12ab5b0d2be445b139982465d2b5ff7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 26 Jul 2010 23:08:09 -0400 Subject: [PATCH] waypoint commit with both # and $ performing interpolation. Issue #544 --- lib/lexer.js | 6 +- src/cake.coffee | 8 +- src/coffee-script.coffee | 2 +- src/command.coffee | 10 +- src/grammar.coffee | 6 +- src/lexer.coffee | 40 ++--- src/nodes.coffee | 214 +++++++++++++------------- src/optparse.coffee | 8 +- src/repl.coffee | 2 +- src/rewriter.coffee | 4 +- src/scope.coffee | 2 +- test/test_string_interpolation.coffee | 98 ++++++------ 12 files changed, 200 insertions(+), 200 deletions(-) diff --git a/lib/lexer.js b/lib/lexer.js index b4a6927d..c40f4ed5 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -129,7 +129,7 @@ if (!(starts(this.chunk, '"') || starts(this.chunk, "'"))) { return false; } - if (!(string = this.balancedToken(['"', '"'], ['${', '}']) || this.balancedToken(["'", "'"]))) { + if (!(string = this.balancedToken(['"', '"'], ['${', '}'], ['#{', '}']) || this.balancedToken(["'", "'"]))) { return false; } this.interpolateString(string.replace(STRING_NEWLINES, " \\\n")); @@ -471,7 +471,7 @@ tokens.push(['IDENTIFIER', interp]); i += group.length - 1; pi = i + 1; - } else if ((expr = this.balancedString(str.substring(i), [['${', '}']]))) { + } else if ((expr = this.balancedString(str.substring(i), [['${', '}'], ['#{', '}']]))) { if (pi < i) { tokens.push(['STRING', ("" + quote + (str.substring(pi, i)) + quote)]); } @@ -580,7 +580,7 @@ IDENTIFIER = /^([a-zA-Z\$_](\w|\$)*)/; NUMBER = /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b/i; HEREDOC = /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\n?([ \t]*)'{3})/; - INTERPOLATION = /^\$([a-zA-Z_@]\w*(\.\w+)*)/; + INTERPOLATION = /^[$#]([a-zA-Z_@]\w*(\.\w+)*)/; OPERATOR = /^(-[\-=>]?|\+[+=]?|[*&|\/%=<>:!?]+)([ \t]*)/; WHITESPACE = /^([ \t]+)/; COMMENT = /^(\s*#{3}(?!#)[ \t]*\n+([\s\S]*?)[ \t]*\n+[ \t]*#{3}|(\s*#(?!##[^#])[^\n]*)+)/; diff --git a/src/cake.coffee b/src/cake.coffee index aeef23f5..018aa1c2 100644 --- a/src/cake.coffee +++ b/src/cake.coffee @@ -45,7 +45,7 @@ helpers.extend global, # If no tasks are passed, print the help screen. exports.run = -> path.exists 'Cakefile', (exists) -> - throw new Error("Cakefile not found in ${process.cwd()}") unless 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' oparse = new optparse.OptionParser switches @@ -59,11 +59,11 @@ printTasks = -> for all name, task of tasks spaces = 20 - name.length spaces = if spaces > 0 then (' ' for i in [0..spaces]).join('') else '' - desc = if task.description then "# $task.description" else '' - puts "cake $name$spaces $desc" + desc = if task.description then "# #task.description" else '' + puts "cake #name#spaces #desc" puts oparse.help() if switches.length # Print an error and exit when attempting to all an undefined task. missingTask = (task) -> - puts "No such task: \"$task\"" + puts "No such task: \"#task\"" process.exit 1 diff --git a/src/coffee-script.coffee b/src/coffee-script.coffee index 926a0fe2..2d32c01e 100644 --- a/src/coffee-script.coffee +++ b/src/coffee-script.coffee @@ -34,7 +34,7 @@ exports.compile = compile = (code, options) -> try (parser.parse lexer.tokenize code).compile options catch err - err.message = "In $options.source, $err.message" if options.source + err.message = "In #options.source, #err.message" if options.source throw err # Tokenize a string of CoffeeScript code, and return the array of tokens. diff --git a/src/command.coffee b/src/command.coffee index b89e588c..c4e7217a 100644 --- a/src/command.coffee +++ b/src/command.coffee @@ -71,7 +71,7 @@ compileScripts = -> base = source compile = (source, topLevel) -> path.exists source, (exists) -> - throw new Error "File not found: $source" unless exists + throw new Error "File not found: #source" unless exists fs.stat source, (err, stats) -> if stats.isDirectory() fs.readdir source, (err, files) -> @@ -130,9 +130,9 @@ writeJs = (source, js, base) -> jsPath = path.join dir, filename compile = -> fs.writeFile jsPath, js, (err) -> - puts "Compiled $source" if options.compile and options.watch + puts "Compiled #source" if options.compile and options.watch path.exists dir, (exists) -> - if exists then compile() else exec "mkdir -p $dir", compile + if exists then compile() else exec "mkdir -p #dir", compile # Pipe compiled JS through JSLint (requires a working `jsl` command), printing # any errors or warnings that arise. @@ -148,7 +148,7 @@ lint = (js) -> printTokens = (tokens) -> strings = for token in tokens [tag, value] = [token[0], token[1].toString().replace(/\n/, '\\n')] - "[$tag $value]" + "[#tag #value]" puts strings.join(' ') # Use the [OptionParser module](optparse.html) to extract all options from @@ -174,5 +174,5 @@ usage = -> # Print the `--version` message and exit. version = -> - puts "CoffeeScript version $CoffeeScript.VERSION" + puts "CoffeeScript version #CoffeeScript.VERSION" process.exit 0 diff --git a/src/grammar.coffee b/src/grammar.coffee index ed41396b..97668efe 100644 --- a/src/grammar.coffee +++ b/src/grammar.coffee @@ -32,8 +32,8 @@ unwrap = /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/ # previous nonterminal. o = (patternString, action, options) -> return [patternString, '$$ = $1;', options] unless action - action = if match = (action + '').match(unwrap) then match[1] else "($action())" - [patternString, "$$ = $action;", options] + action = if match = (action + '').match(unwrap) then match[1] else "(#action())" + [patternString, "$$ = #action;", options] # Grammatical Rules # ----------------- @@ -613,7 +613,7 @@ for name, alternatives of grammar grammar[name] = for alt in alternatives for token in alt[0].split ' ' tokens.push token unless grammar[token] - alt[1] = "return ${alt[1]}" if name is 'Root' + alt[1] = "return #{alt[1]}" if name is 'Root' alt # Initialize the **Parser** with our list of terminal **tokens**, our **grammar** diff --git a/src/lexer.coffee b/src/lexer.coffee index 6595640d..5de496d5 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -91,7 +91,7 @@ exports.Lexer = class Lexer if include(JS_FORBIDDEN, id) if forcedIdentifier tag = 'STRING' - id = "'$id'" + id = "'#id'" if forcedIdentifier is 'accessor' close_index = true @tokens.pop() if @tag() isnt '@' @@ -118,7 +118,7 @@ exports.Lexer = class Lexer stringToken: -> return false unless starts(@chunk, '"') or starts(@chunk, "'") return false unless string = - @balancedToken(['"', '"'], ['${', '}']) or + @balancedToken(['"', '"'], ['${', '}'], ['#{', '}']) or @balancedToken ["'", "'"] @interpolateString string.replace STRING_NEWLINES, " \\\n" @line += count string, "\n" @@ -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 @@ -169,8 +169,8 @@ 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 - @tokens.splice @tokens.length, 0, [',', ','], ['STRING', "\"$flags\""] if flags + @interpolateString "\"#str\"", escapeQuotes: yes + @tokens.splice @tokens.length, 0, [',', ','], ['STRING', "\"#flags\""] if flags @tokens.splice @tokens.length, 0, [')', ')'], [')', ')'] else @token 'REGEX', regex @@ -308,7 +308,7 @@ exports.Lexer = class Lexer doc = doc.replace(new RegExp("^" +indent, 'gm'), '') return doc if options.herecomment doc.replace(MULTILINER, "\\n") - .replace(new RegExp(options.quote, 'g'), "\\$options.quote") + .replace(new RegExp(options.quote, 'g'), "\\#options.quote") # A source of ambiguity in our grammar used to be parameter lists in function # definitions versus argument lists in function calls. Walk backwards, tagging @@ -333,12 +333,12 @@ exports.Lexer = class Lexer # The error for when you try to use a forbidden word in JavaScript as # an identifier. identifierError: (word) -> - throw new Error "SyntaxError: Reserved word \"$word\" on line ${@line + 1}" + throw new Error "SyntaxError: Reserved word \"#word\" on line #{@line + 1}" # The error for when you try to assign to a reserved word in JavaScript, # like "function" or "default". assignmentError: -> - throw new Error "SyntaxError: Reserved word \"${@value()}\" on line ${@line + 1} can't be assigned" + throw new Error "SyntaxError: Reserved word \"#{@value()}\" on line #{@line + 1} can't be assigned" # Matches a balanced group such as a single or double-quoted string. Pass in # a series of delimiters, all of which must be nested correctly within the @@ -368,15 +368,15 @@ exports.Lexer = class Lexer i += 1 if levels.length return false if slash - throw new Error "SyntaxError: Unterminated ${levels.pop()[0]} starting on line ${@line + 1}" + throw new Error "SyntaxError: Unterminated #{levels.pop()[0]} starting on line #{@line + 1}" if not i then false else str.substring(0, i) # Expand variables and expressions inside double-quoted strings using # [ECMA Harmony's interpolation syntax](http://wiki.ecmascript.org/doku.php?id=strawman:string_interpolation) # for substitution of bare variables as well as arbitrary expressions. # - # "Hello $name." - # "Hello ${name.capitalize()}." + # "Hello #name." + # "Hello #{name.capitalize()}." # # If it encounters an interpolation, this method will recursively create a # new Lexer, tokenize the interpolated contents, and merge them into the @@ -395,26 +395,26 @@ exports.Lexer = class Lexer i += 1 else if match = str.substring(i).match INTERPOLATION [group, interp] = match - interp = "this.${ interp.substring(1) }" if starts interp, '@' - tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i + interp = "this.#{ interp.substring(1) }" if starts interp, '@' + tokens.push ['STRING', "#quote#{ str.substring(pi, i) }#quote"] if pi < i tokens.push ['IDENTIFIER', interp] i += group.length - 1 pi = i + 1 - else if (expr = @balancedString str.substring(i), [['${', '}']]) - tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i + else if (expr = @balancedString str.substring(i), [['${', '}'], ['#{', '}']]) + tokens.push ['STRING', "#quote#{ str.substring(pi, i) }#quote"] if pi < i 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] else - tokens.push ['STRING', "$quote$quote"] + tokens.push ['STRING', "#quote#quote"] i += expr.length - 1 pi = i + 1 i += 1 - tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i and pi < str.length - 1 + tokens.push ['STRING', "#quote#{ str.substring(pi, i) }#quote"] if pi < i and pi < str.length - 1 tokens.unshift ['STRING', '""'] unless tokens[0][0] is 'STRING' interpolated = tokens.length > 1 @token '(', '(' if interpolated @@ -424,7 +424,7 @@ exports.Lexer = class Lexer @tokens = @tokens.concat value else if tag is 'STRING' and options.escapeQuotes escaped = value.substring(1, value.length - 1).replace(/"/g, '\\"') - @token tag, "\"$escaped\"" + @token tag, "\"#escaped\"" else @token tag, value @token '+', '+' if i < tokens.length - 1 @@ -509,7 +509,7 @@ JS_FORBIDDEN = JS_KEYWORDS.concat RESERVED IDENTIFIER = /^([a-zA-Z\$_](\w|\$)*)/ NUMBER = /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b/i HEREDOC = /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\n?([ \t]*)'{3})/ -INTERPOLATION = /^\$([a-zA-Z_@]\w*(\.\w+)*)/ +INTERPOLATION = /^[$#]([a-zA-Z_@]\w*(\.\w+)*)/ OPERATOR = /^(-[\-=>]?|\+[+=]?|[*&|\/%=<>:!?]+)([ \t]*)/ WHITESPACE = /^([ \t]+)/ COMMENT = /^(\s*#{3}(?!#)[ \t]*\n+([\s\S]*?)[ \t]*\n+[ \t]*#{3}|(\s*#(?!##[^#])[^\n]*)+)/ diff --git a/src/nodes.coffee b/src/nodes.coffee index 05821495..435c440d 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -203,14 +203,14 @@ exports.Expressions = class Expressions extends BaseNode code = @compileWithDeclarations(o) code = code.replace(TRAILING_WHITESPACE, '') code = code.replace(DOUBLE_PARENS, '($1)') - if o.noWrap then code else "(function() {\n$code\n})();\n" + if o.noWrap then code else "(function() {\n#code\n})();\n" # Compile the expressions body for the contents of a function, with # declarations of all inner variables pushed up to the top. compileWithDeclarations: (o) -> code = @compileNode(o) - code = "${@tab}var ${o.scope.compiledAssignments()};\n$code" if o.scope.hasAssignments(this) - code = "${@tab}var ${o.scope.compiledDeclarations()};\n$code" if not o.globals and o.scope.hasDeclarations(this) + code = "#{@tab}var #{o.scope.compiledAssignments()};\n#code" if o.scope.hasAssignments(this) + code = "#{@tab}var #{o.scope.compiledDeclarations()};\n#code" if not o.globals and o.scope.hasDeclarations(this) code # Compiles a single expression within the expressions body. If we need to @@ -219,7 +219,7 @@ exports.Expressions = class Expressions extends BaseNode compileExpression: (node, o) -> @tab = o.indent compiledNode = node.compile merge o, top: true - if node.isStatement() then compiledNode else "${@idt()}$compiledNode;" + if node.isStatement() then compiledNode else "#{@idt()}#compiledNode;" # Wrap up the given nodes as an **Expressions**, unless it already happens # to be one. @@ -248,10 +248,10 @@ exports.LiteralNode = class LiteralNode extends BaseNode compileNode: (o) -> idt = if @isStatement() then @idt() else '' end = if @isStatement() then ';' else '' - "$idt$@value$end" + "#idt#@value#end" toString: (idt) -> - " \"$@value\"" + " \"#@value\"" #### ReturnNode @@ -277,7 +277,7 @@ exports.ReturnNode = class ReturnNode extends BaseNode compileNode: (o) -> o.asStatement = true if @expression.isStatement() - "${@tab}return ${@expression.compile(o)};" + "#{@tab}return #{@expression.compile(o)};" #### ValueNode @@ -350,7 +350,7 @@ exports.ValueNode = class ValueNode extends BaseNode props = if only then @properties[0...@properties.length - 1] else @properties o.chainRoot or= this baseline = @base.compile o - baseline = "($baseline)" if @hasProperties() and (@base instanceof ObjectNode or @isNumber()) + baseline = "(#baseline)" if @hasProperties() and (@base instanceof ObjectNode or @isNumber()) complete = @last = baseline for prop, i in props @@ -358,8 +358,8 @@ exports.ValueNode = class ValueNode extends BaseNode if prop.soakNode if @base instanceof CallNode or @base.contains((n) -> n instanceof CallNode) and i is 0 temp = o.scope.freeVariable() - complete = "(${ baseline = temp } = ($complete))" - complete = "typeof $complete === \"undefined\" || $baseline" if i is 0 and @isStart(o) + complete = "(#{ baseline = temp } = (#complete))" + complete = "typeof #complete === \"undefined\" || #baseline" if i is 0 and @isStart(o) complete += @SOAK + (baseline += prop.compile(o)) else part = prop.compile(o) @@ -367,7 +367,7 @@ exports.ValueNode = class ValueNode extends BaseNode complete += part @last = part - if op and @wrapped then "($complete)" else complete + if op and @wrapped then "(#complete)" else complete #### CommentNode @@ -385,8 +385,8 @@ exports.CommentNode = class CommentNode extends BaseNode this compileNode: (o) -> - sep = "\n$@tab" - "$@tab/*$sep${ @lines.join(sep) }\n$@tab*/" + sep = "\n#@tab" + "#@tab/*#sep#{ @lines.join(sep) }\n#@tab*/" #### CallNode @@ -417,9 +417,9 @@ exports.CallNode = class CallNode extends BaseNode superReference: (o) -> methname = o.scope.method.name meth = if o.scope.method.proto - "${o.scope.method.proto}.__superClass__.$methname" + "#{o.scope.method.proto}.__superClass__.#methname" else if methname - "${methname}.__superClass__.constructor" + "#{methname}.__superClass__.constructor" else throw new Error "cannot call super on an anonymous function." # Compile a vanilla function call. @@ -430,13 +430,13 @@ exports.CallNode = class CallNode extends BaseNode unless compilation args = (arg.compile(o) for arg in @args).join(', ') compilation = if @isSuper then @compileSuper(args, o) - else "${@prefix()}${@variable.compile(o)}($args)" - if o.operation and @wrapped then "($compilation)" else compilation + else "#{@prefix()}#{@variable.compile(o)}(#args)" + if o.operation and @wrapped then "(#compilation)" else compilation # `super()` is converted into a call against the superclass's implementation # of the current function. compileSuper: (args, o) -> - "${@superReference(o)}.call(this${ if args.length then ', ' else '' }$args)" + "#{@superReference(o)}.call(this#{ if args.length then ', ' else '' }#args)" # If you call a function with a splat, it's converted into a JavaScript # `.apply()` call to allow an array of arguments to be passed. @@ -448,18 +448,18 @@ exports.CallNode = class CallNode extends BaseNode if obj.match(/\(/) temp = o.scope.freeVariable() obj = temp - meth = "($temp = ${ @variable.source })${ @variable.last }" + meth = "(#temp = #{ @variable.source })#{ @variable.last }" if @isNew utility 'extends' """ (function() { - ${@idt(1)}var ctor = function(){}; - ${@idt(1)}__extends(ctor, $meth); - ${@idt(1)}return ${meth}.apply(new ctor, ${ @compileSplatArguments(o) }); - $@tab}).call(this) + #{@idt(1)}var ctor = function(){}; + #{@idt(1)}__extends(ctor, #meth); + #{@idt(1)}return #{meth}.apply(new ctor, #{ @compileSplatArguments(o) }); + #@tab}).call(this) """ else - "${@prefix()}${meth}.apply($obj, ${ @compileSplatArguments(o) })" + "#{@prefix()}#{meth}.apply(#obj, #{ @compileSplatArguments(o) })" #### ExtendsNode @@ -497,7 +497,7 @@ exports.AccessorNode = class AccessorNode extends BaseNode compileNode: (o) -> name = @name.compile o o.chainRoot.wrapped or= @soakNode - namePart = if name.match(IS_STRING) then "[$name]" else ".$name" + namePart = if name.match(IS_STRING) then "[#name]" else ".#name" @prototype + namePart #### IndexNode @@ -515,7 +515,7 @@ exports.IndexNode = class IndexNode extends BaseNode o.chainRoot.wrapped or= @soakNode idx = @index.compile o prefix = if @proto then '.prototype' else '' - "$prefix[$idx]" + "#prefix[#idx]" #### RangeNode @@ -543,7 +543,7 @@ exports.RangeNode = class RangeNode extends BaseNode parts = [] parts.push @from if @from isnt @fromVar parts.push @to if @to isnt @toVar - if parts.length then "${parts.join('; ')}; " else '' + if parts.length then "#{parts.join('; ')}; " else '' # When compiled normally, the range returns the contents of the *for loop* # needed to iterate over the values in the range. Used by comprehensions. @@ -552,23 +552,23 @@ exports.RangeNode = class RangeNode extends BaseNode return @compileSimple(o) if @fromNum and @toNum idx = del o, 'index' step = del o, 'step' - vars = "$idx = $@fromVar" - intro = "($@fromVar <= $@toVar ? $idx" - compare = "$intro <$@equals $@toVar : $idx >$@equals $@toVar)" + vars = "#idx = #@fromVar" + intro = "(#@fromVar <= #@toVar ? #idx" + compare = "#intro <#@equals #@toVar : #idx >#@equals #@toVar)" stepPart = if step then step.compile(o) else '1' - incr = if step then "$idx += $stepPart" else "$intro += $stepPart : $idx -= $stepPart)" - "$vars; $compare; $incr" + incr = if step then "#idx += #stepPart" else "#intro += #stepPart : #idx -= #stepPart)" + "#vars; #compare; #incr" # Compile a simple range comprehension, with integers. compileSimple: (o) -> [from, to] = [parseInt(@fromNum, 10), parseInt(@toNum, 10)] idx = del o, 'index' step = del o, 'step' - step and= "$idx += ${step.compile(o)}" + step and= "#idx += #{step.compile(o)}" if from <= to - "$idx = $from; $idx <$@equals $to; ${step or "$idx++"}" + "#idx = #from; #idx <#@equals #to; #{step or "$idx++"}" else - "$idx = $from; $idx >$@equals $to; ${step or "$idx--"}" + "#idx = #from; #idx >#@equals #to; #{step or "#idx--"}" # When used as a value, expand the range into the equivalent array. compileArray: (o) -> @@ -576,15 +576,15 @@ exports.RangeNode = class RangeNode extends BaseNode vars = @compileVariables merge o, indent: idt result = o.scope.freeVariable() i = o.scope.freeVariable() - pre = "\n${idt}${result} = []; ${vars}" + pre = "\n#{idt}#{result} = []; #{vars}" if @fromNum and @toNum o.index = i body = @compileSimple o else - clause = "$@fromVar <= $@toVar ?" - body = "var $i = $@fromVar; $clause $i <$@equals $@toVar : $i >$@equals $@toVar; $clause $i += 1 : $i -= 1" - post = "{ ${result}.push($i) };\n${idt}return $result;\n$o.indent" - "(function() {${pre}\n${idt}for ($body)$post}).call(this)" + clause = "#@fromVar <= #@toVar ?" + body = "var #i = #@fromVar; #clause #i <#@equals #@toVar : #i >#@equals #@toVar; #clause #i += 1 : #i -= 1" + post = "{ #{result}.push(#i) };\n#{idt}return #result;\n#o.indent" + "(function() {#{pre}\n#{idt}for (#body)#post}).call(this)" #### SliceNode @@ -603,7 +603,7 @@ exports.SliceNode = class SliceNode extends BaseNode from = @range.from.compile(o) to = @range.to.compile(o) plusPart = if @range.exclusive then '' else ' + 1' - ".slice($from, $to$plusPart)" + ".slice(#from, #to#plusPart)" #### ObjectNode @@ -629,7 +629,7 @@ exports.ObjectNode = class ObjectNode extends BaseNode indent + prop.compile(o) + join props = props.join('') inner = if props then '\n' + props + '\n' + @idt() else '' - "{$inner}" + "{#inner}" #### ArrayNode @@ -652,16 +652,16 @@ exports.ArrayNode = class ArrayNode extends BaseNode if obj instanceof SplatNode return @compileSplatLiteral o else if obj instanceof CommentNode - objects.push "\n$code\n$o.indent" + objects.push "\n#code\n#o.indent" else if i is @objects.length - 1 objects.push code else - objects.push "$code, " + objects.push "#code, " objects = objects.join('') if indexOf(objects, '\n') >= 0 - "[\n${@idt(1)}$objects\n$@tab]" + "[\n#{@idt(1)}#objects\n#@tab]" else - "[$objects]" + "[#objects]" #### ClassNode @@ -719,19 +719,19 @@ exports.ClassNode = class ClassNode extends BaseNode me or= constScope.freeVariable() pname = pvar.compile(o) constructor.body.push new ReturnNode literal 'this' if constructor.body.empty() - constructor.body.unshift literal "this.${pname} = function(){ return ${className}.prototype.${pname}.apply($me, arguments); }" + constructor.body.unshift literal "this.#{pname} = function(){ return #{className}.prototype.#{pname}.apply(#me, arguments); }" if pvar access = if prop.context is 'this' then pvar.base.properties[0] else new AccessorNode(pvar, 'prototype') val = new ValueNode(@variable, [access]) prop = new AssignNode(val, func) props.push prop - constructor.body.unshift literal "$me = this" if me + constructor.body.unshift literal "#me = this" if me construct = @idt() + (new AssignNode(@variable, constructor)).compile(merge o, {sharedScope: constScope}) + ';' props = if !props.empty() then '\n' + props.compile(o) else '' extension = if extension then '\n' + @idt() + extension.compile(o) + ';' else '' returns = if @returns then '\n' + new ReturnNode(@variable).compile(o) else '' - "$construct$extension$props$returns" + "#construct#extension#props#returns" #### AssignNode @@ -783,11 +783,11 @@ exports.AssignNode = class AssignNode extends BaseNode @value.name = last if last.match(IDENTIFIER) @value.proto = proto if proto val = @value.compile o - return "$name: $val" if @context is 'object' + return "#name: #val" if @context is 'object' o.scope.find name unless @isValue() and (@variable.hasProperties() or @variable.namespaced) - val = "$name = $val" - return "$@tab$val;" if stmt - if top then val else "($val)" + val = "#name = #val" + return "#@tab#val;" if stmt + if top then val else "(#val)" # Brief implementation of recursive pattern matching, when assigning array or # object literals to a value. Peeks at their properties to assign inner names. @@ -796,7 +796,7 @@ exports.AssignNode = class AssignNode extends BaseNode compilePatternMatch: (o) -> valVar = o.scope.freeVariable() value = if @value.isStatement() then ClosureNode.wrap(@value) else @value - assigns = ["$@tab$valVar = ${ value.compile(o) };"] + assigns = ["#@tab#valVar = #{ value.compile(o) };"] o.top = true o.asStatement = true splat = false @@ -820,7 +820,7 @@ exports.AssignNode = class AssignNode extends BaseNode (olength = @variable.base.objects.length) - oindex - 1)) splat = true else - idx = literal(if splat then "${valVar}.length - ${olength - idx}" else idx) if typeof idx isnt 'object' + idx = literal(if splat then "#{valVar}.length - #{olength - idx}" else idx) if typeof idx isnt 'object' val = new ValueNode(literal(valVar), [new accessClass(idx)]) assigns.push(new AssignNode(obj, val).compile(o)) code = assigns.join("\n") @@ -836,7 +836,7 @@ exports.AssignNode = class AssignNode extends BaseNode from = range.from.compile(o) to = range.to.compile(o) + ' - ' + from + plus val = @value.compile(o) - "${name}.splice.apply($name, [$from, $to].concat($val))" + "#{name}.splice.apply(#name, [#from, #to].concat(#val))" #### CodeNode @@ -884,10 +884,10 @@ exports.CodeNode = class CodeNode extends BaseNode params = (param.compile(o) for param in params) @body.makeReturn() (o.scope.parameter(param)) for param in params - code = if @body.expressions.length then "\n${ @body.compileWithDeclarations(o) }\n" else '' - func = "function(${ params.join(', ') }) {$code${ code and @tab }}" - return "${utility('bind')}($func, this)" if @bound - if top then "($func)" else func + code = if @body.expressions.length then "\n#{ @body.compileWithDeclarations(o) }\n" else '' + func = "function(#{ params.join(', ') }) {#code#{ code and @tab }}" + return "#{utility('bind')}(#func, this)" if @bound + if top then "(#func)" else func topSensitive: -> true @@ -899,7 +899,7 @@ exports.CodeNode = class CodeNode extends BaseNode toString: (idt) -> idt or= '' children = (child.toString(idt + TAB) for child in @collectChildren()).join('') - "\n$idt$children" + "\n#idt#children" #### SplatNode @@ -927,18 +927,18 @@ exports.SplatNode = class SplatNode extends BaseNode len = o.scope.freeVariable() o.scope.assign len, "arguments.length" variadic = o.scope.freeVariable() - o.scope.assign variadic, "$len >= $@arglength" - end = if @trailings.length then ", $len - ${@trailings.length}" + o.scope.assign variadic, "#len >= #@arglength" + end = if @trailings.length then ", #len - #{@trailings.length}" for trailing, idx in @trailings pos = @trailings.length - idx - o.scope.assign(trailing.compile(o), "arguments[$variadic ? $len - $pos : ${@index + idx}]") - "$name = ${utility('slice')}.call(arguments, $@index$end)" + o.scope.assign(trailing.compile(o), "arguments[#variadic ? #len - #pos : #{@index + idx}]") + "#name = #{utility('slice')}.call(arguments, #@index#end)" # A compiling a splat as a destructuring assignment means slicing arguments # from the right-hand-side's corresponding array. compileValue: (o, name, index, trailings) -> - trail = if trailings then ", ${name}.length - $trailings" else '' - "${utility 'slice'}.call($name, $index$trail)" + trail = if trailings then ", #{name}.length - #trailings" else '' + "#{utility 'slice'}.call(#name, #index#trail)" # Utility function that converts arbitrary number of elements, mixed with # splats, to a proper array @@ -949,14 +949,14 @@ exports.SplatNode = class SplatNode extends BaseNode prev = args[last = args.length - 1] if not (arg instanceof SplatNode) if prev and starts(prev, '[') and ends(prev, ']') - args[last] = "${prev.substr(0, prev.length - 1)}, $code]" + args[last] = "#{prev.substr(0, prev.length - 1)}, #code]" continue else if prev and starts(prev, '.concat([') and ends(prev, '])') - args[last] = "${prev.substr(0, prev.length - 2)}, $code])" + args[last] = "#{prev.substr(0, prev.length - 2)}, #code])" continue else - code = "[$code]" - args.push(if i is 0 then code else ".concat($code)") + code = "[#code]" + args.push(if i is 0 then code else ".concat(#code)") args.join('') #### WhileNode @@ -999,15 +999,15 @@ exports.WhileNode = class WhileNode extends BaseNode set = '' unless top rvar = o.scope.freeVariable() - set = "$@tab$rvar = [];\n" + set = "#@tab#rvar = [];\n" @body = PushNode.wrap(rvar, @body) if @body - pre = "$set${@tab}while ($cond)" + 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())) else post = '' - "$pre {\n${ @body.compile(o) }\n$@tab}$post" + "#pre {\n#{ @body.compile(o) }\n#@tab}#post" #### OpNode @@ -1068,7 +1068,7 @@ exports.OpNode = class OpNode extends BaseNode shared = @first.unwrap().second [@first.second, shared] = shared.compileReference(o) if shared.containsType CallNode [first, second, shared] = [@first.compile(o), @second.compile(o), shared.compile(o)] - "($first) && ($shared $@operator $second)" + "(#first) && (#shared #@operator #second)" # When compiling a conditional assignment, take care to ensure that the # operands are only evaluated once, even though we have to reference them @@ -1076,15 +1076,15 @@ exports.OpNode = class OpNode extends BaseNode compileAssignment: (o) -> [first, second] = [@first.compile(o), @second.compile(o)] o.scope.find(first) if first.match(IDENTIFIER) - return "$first = ${ ExistenceNode.compileTest(o, @first) } ? $first : $second" if @operator is '?=' - "$first = $first ${ @operator.substr(0, 2) } $second" + return "#first = #{ ExistenceNode.compileTest(o, @first) } ? #first : #second" if @operator is '?=' + "#first = #first #{ @operator.substr(0, 2) } #second" # If this is an existence operator, we delegate to `ExistenceNode.compileTest` # to give us the safe references for the variables. compileExistence: (o) -> [first, second] = [@first.compile(o), @second.compile(o)] test = ExistenceNode.compileTest(o, @first) - "$test ? $first : $second" + "#test ? #first : #second" # Compile a unary **OpNode**. compileUnary: (o) -> @@ -1112,14 +1112,14 @@ exports.InNode = class InNode extends BaseNode compileOrTest: (o) -> tests = for item, i in @array.base.objects - "${item.compile(o)} === ${if i then @obj2 else @obj1}" - "(${tests.join(' || ')})" + "#{item.compile(o)} === #{if i then @obj2 else @obj1}" + "(#{tests.join(' || ')})" compileLoopTest: (o) -> [@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)" + "!!(function(){ #{prefix}for (var #i=0, #l=#{@arr1}.length; #i<#l; #i++) if (#{@arr2}[#i] === #@obj2) return true; }).call(this)" #### TryNode @@ -1147,10 +1147,10 @@ exports.TryNode = class TryNode extends BaseNode o.indent = @idt 1 o.top = true attemptPart = @attempt.compile(o) - errorPart = if @error then " (${ @error.compile(o) }) " else ' ' - catchPart = if @recovery then " catch$errorPart{\n${ @recovery.compile(o) }\n$@tab}" else '' - finallyPart = (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o)) + "\n$@tab}" - "${@tab}try {\n$attemptPart\n$@tab}$catchPart$finallyPart" + errorPart = if @error then " (#{ @error.compile(o) }) " else ' ' + catchPart = if @recovery then " catch#errorPart{\n#{ @recovery.compile(o) }\n#@tab}" else '' + finallyPart = (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o)) + "\n#@tab}" + "#{@tab}try {\n#attemptPart\n#@tab}#catchPart#finallyPart" #### ThrowNode @@ -1169,7 +1169,7 @@ exports.ThrowNode = class ThrowNode extends BaseNode return this compileNode: (o) -> - "${@tab}throw ${@expression.compile(o)};" + "#{@tab}throw #{@expression.compile(o)};" #### ExistenceNode @@ -1192,7 +1192,7 @@ exports.ExistenceNode = class ExistenceNode extends BaseNode # Be careful not to double-evaluate anything. @compileTest: (o, variable) -> [first, second] = variable.compileReference o - "(typeof ${first.compile(o)} !== \"undefined\" && ${second.compile(o)} !== null)" + "(typeof #{first.compile(o)} !== \"undefined\" && #{second.compile(o)} !== null)" #### ParentheticalNode @@ -1222,10 +1222,10 @@ exports.ParentheticalNode = class ParentheticalNode extends BaseNode top = del o, 'top' code = @expression.compile(o) if @isStatement() - return (if top then "$@tab$code;" else code) + return (if top then "#@tab#code;" else code) l = code.length code = code.substr(o, l-1) if code.substr(l-1, 1) is ';' - if @expression instanceof AssignNode then code else "($code)" + if @expression instanceof AssignNode then code else "(#code)" #### ForNode @@ -1292,34 +1292,34 @@ exports.ForNode = class ForNode extends BaseNode forPart = source.compile merge o, index: ivar, step: @step else svar = scope.freeVariable() - sourcePart = "$svar = ${ @source.compile(o) };" + sourcePart = "#svar = #{ @source.compile(o) };" if @pattern - namePart = new AssignNode(@name, literal("$svar[$ivar]")).compile(merge o, {indent: @idt(1), top: true}) + '\n' + namePart = new AssignNode(@name, literal("#svar[#ivar]")).compile(merge o, {indent: @idt(1), top: true}) + '\n' else - namePart = "$name = $svar[$ivar]" if name + namePart = "#name = #svar[#ivar]" if name unless @object lvar = scope.freeVariable() - stepPart = if @step then "$ivar += ${ @step.compile(o) }" else "$ivar++" - forPart = "$ivar = 0, $lvar = ${svar}.length; $ivar < $lvar; $stepPart" - sourcePart = (if rvar then "$rvar = []; " else '') + sourcePart - sourcePart = if sourcePart then "$@tab$sourcePart\n$@tab" else @tab + stepPart = if @step then "#ivar += #{ @step.compile(o) }" else "#ivar++" + forPart = "#ivar = 0, #lvar = #{svar}.length; #ivar < #lvar; #stepPart" + sourcePart = (if rvar then "#rvar = []; " else '') + sourcePart + sourcePart = if sourcePart then "#@tab#sourcePart\n#@tab" else @tab returnResult = @compileReturnValue(rvar, o) body = PushNode.wrap(rvar, body) unless topLevel if @guard body = Expressions.wrap([new IfNode(@guard, body)]) if codeInBody - body.unshift literal "var $namePart" if namePart - body.unshift literal "var $index = $ivar" if index + body.unshift literal "var #namePart" if namePart + body.unshift literal "var #index = #ivar" if index body = ClosureNode.wrap(body, true) else - varPart = (namePart or '') and (if @pattern then namePart else "${@idt(1)}$namePart;\n") + varPart = (namePart or '') and (if @pattern then namePart else "#{@idt(1)}#namePart;\n") if @object - forPart = "$ivar in $svar" - guardPart = "\n${@idt(1)}if (!${utility('hasProp')}.call($svar, $ivar)) continue;" unless @raw + forPart = "#ivar in #svar" + guardPart = "\n#{@idt(1)}if (!#{utility('hasProp')}.call(#svar, #ivar)) continue;" unless @raw body = body.compile(merge(o, {indent: @idt(1), top: true})) - vars = if range then name else "$name, $ivar" - "${sourcePart}for ($forPart) {$guardPart\n$varPart$body\n$@tab}$returnResult" + vars = if range then name else "#name, #ivar" + "#{sourcePart}for (#forPart) {#guardPart\n#varPart#body\n#@tab}#returnResult" #### IfNode @@ -1412,20 +1412,20 @@ exports.IfNode = class IfNode extends BaseNode ifDent = if child then '' else @idt() comDent = if child then @idt() else '' body = @body.compile(o) - ifPart = "${ifDent}if (${ @compileCondition(condO) }) {\n$body\n$@tab}" + ifPart = "#{ifDent}if (#{ @compileCondition(condO) }) {\n#body\n#@tab}" return ifPart unless @elseBody elsePart = if @isChain ' else ' + @elseBodyNode().compile(merge(o, {indent: @idt(), chainChild: true})) else - " else {\n${ @elseBody.compile(o) }\n$@tab}" - "$ifPart$elsePart" + " else {\n#{ @elseBody.compile(o) }\n#@tab}" + "#ifPart#elsePart" # Compile the IfNode as a ternary operator. compileTernary: (o) -> o.operation = true ifPart = @condition.compile(o) + ' ? ' + @bodyNode().compile(o) elsePart = if @elseBody then @elseBodyNode().compile(o) else 'null' - "$ifPart : $elsePart" + "#ifPart : #elsePart" # Faux-Nodes # ---------- diff --git a/src/optparse.coffee b/src/optparse.coffee index e9d0df56..afe67af4 100644 --- a/src/optparse.coffee +++ b/src/optparse.coffee @@ -33,7 +33,7 @@ exports.OptionParser = class OptionParser options[rule.name] = if rule.hasArgument then args[i += 1] else true matchedRule = yes break - throw new Error "unrecognized option: $arg" if isOption and not matchedRule + throw new Error "unrecognized option: #arg" if isOption and not matchedRule if not isOption options.arguments = args[i...args.length] break @@ -43,13 +43,13 @@ exports.OptionParser = class OptionParser # of the valid options, for `--help` and such. help: -> lines = ['Available options:'] - lines.unshift "$@banner\n" if @banner + lines.unshift "#@banner\n" if @banner for rule in @rules spaces = 15 - rule.longFlag.length spaces = if spaces > 0 then (' ' for i in [0..spaces]).join('') else '' letPart = if rule.shortFlag then rule.shortFlag + ', ' else ' ' - lines.push " $letPart$rule.longFlag$spaces$rule.description" - "\n${ lines.join('\n') }\n" + lines.push " #letPart#rule.longFlag#spaces#rule.description" + "\n#{ lines.join('\n') }\n" # Helpers # ------- diff --git a/src/repl.coffee b/src/repl.coffee index b7f28af4..2added2b 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -2,7 +2,7 @@ # and evaluates it. Good for simple tests, or poking around the **Node.js** API. # Using it looks like this: # -# coffee> puts "$num bottles of beer" for num in [99..1] +# coffee> puts "#num bottles of beer" for num in [99..1] # Require the **coffee-script** module to get access to the compiler. CoffeeScript = require './coffee-script' diff --git a/src/rewriter.coffee b/src/rewriter.coffee index 7b5d2e82..bc639f18 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -242,13 +242,13 @@ exports.Rewriter = class Rewriter openLine[open] = token[2] if levels[open] == 0 levels[open] += 1 levels[open] -= 1 if token[0] is close - throw new Error("too many ${token[1]} on line ${token[2] + 1}") if levels[open] < 0 + throw new Error("too many #{token[1]} on line #{token[2] + 1}") if levels[open] < 0 return 1 unclosed = key for key, value of levels when value > 0 if unclosed.length open = unclosed[0] line = openLine[open] + 1 - throw new Error "unclosed $open on line $line" + throw new Error "unclosed #open on line #line" # We'd like to support syntax like this: # diff --git a/src/scope.coffee b/src/scope.coffee index 3d43953e..c414eade 100644 --- a/src/scope.coffee +++ b/src/scope.coffee @@ -80,7 +80,7 @@ exports.Scope = class Scope # Return the list of assignments that are supposed to be made at the top # of this scope. assignedVariables: -> - "$key = $val.value" for key, val of @variables when val.assigned + "#key = #val.value" for key, val of @variables when val.assigned # Compile the JavaScript for all of the variable declarations in this scope. compiledDeclarations: -> diff --git a/test/test_string_interpolation.coffee b/test/test_string_interpolation.coffee index b77616be..8f85ae7e 100644 --- a/test/test_string_interpolation.coffee +++ b/test/test_string_interpolation.coffee @@ -1,80 +1,80 @@ hello = 'Hello' world = 'World' -ok '$hello $world!' is '$hello $world!' -ok '${hello} ${world}!' is '${hello} ${world}!' -ok "$hello $world!" is 'Hello World!' -ok "${hello} ${world}!" is 'Hello World!' -ok "[$hello$world]" is '[HelloWorld]' -ok "[${hello}${world}]" is '[HelloWorld]' -ok "$hello$$world" is 'Hello$World' -ok "${hello}$${world}" is 'Hello$World' -ok "Hello ${ 1 + 2 } World" is 'Hello 3 World' -ok "$hello ${ 1 + 2 } $world" is "Hello 3 World" +ok '#hello #world!' is '#hello #world!' +ok '#{hello} #{world}!' is '#{hello} #{world}!' +ok "#hello #world!" is 'Hello World!' +ok "#{hello} #{world}!" is 'Hello World!' +ok "[#hello#world]" is '[HelloWorld]' +ok "[#{hello}#{world}]" is '[HelloWorld]' +ok "#hello##world" is 'Hello#World' +ok "#{hello}##{world}" is 'Hello#World' +ok "Hello #{ 1 + 2 } World" is 'Hello 3 World' +ok "#hello #{ 1 + 2 } #world" is "Hello 3 World" [s, t, r, i, n, g] = ['s', 't', 'r', 'i', 'n', 'g'] -ok "$s$t$r$i$n$g" is 'string' -ok "${s}${t}${r}${i}${n}${g}" is 'string' -ok "\$s\$t\$r\$i\$n\$g" is '$s$t$r$i$n$g' -ok "\\$s\\$t\\$r\\$i\\$n\\$g" is '\\s\\t\\r\\i\\n\\g' -ok "\${s}\${t}\${r}\${i}\${n}\${g}" is '${s}${t}${r}${i}${n}${g}' -ok "\$string" is '$string' -ok "\${string}" is '${string}' +ok "#s#t#r#i#n#g" is 'string' +ok "#{s}#{t}#{r}#{i}#{n}#{g}" is 'string' +ok "\#s\#t\#r\#i\#n\#g" is '#s#t#r#i#n#g' +ok "\\#s\\#t\\#r\\#i\\#n\\#g" is '\\s\\t\\r\\i\\n\\g' +ok "\#{s}\#{t}\#{r}\#{i}\#{n}\#{g}" is '#{s}#{t}#{r}#{i}#{n}#{g}' +ok "\#string" is '#string' +ok "\#{string}" is '#{string}' -ok "\$Escaping first" is '$Escaping first' -ok "\${Escaping} first" is '${Escaping} first' -ok "Escaping \$in middle" is 'Escaping $in middle' -ok "Escaping \${in} middle" is 'Escaping ${in} middle' -ok "Escaping \$last" is 'Escaping $last' -ok "Escaping \${last}" is 'Escaping ${last}' +ok "\#Escaping first" is '#Escaping first' +ok "\#{Escaping} first" is '#{Escaping} first' +ok "Escaping \#in middle" is 'Escaping #in middle' +ok "Escaping \#{in} middle" is 'Escaping #{in} middle' +ok "Escaping \#last" is 'Escaping #last' +ok "Escaping \#{last}" is 'Escaping #{last}' -ok "$$" is '$$' -ok "${}" is '' -ok "${}A${} ${} ${}B${}" is 'A B' -ok "\\\\\$$" is '\\\\\$$' -ok "\\\${}" is '\\${}' +ok "##" is '##' +ok "#{}" is '' +ok "#{}A#{} #{} #{}B#{}" is 'A B' +ok "\\\\\##" is '\\\\\##' +ok "\\\#{}" is '\\#{}' -ok "I won $20 last night." is 'I won $20 last night.' -ok "I won $${20} last night." is 'I won $20 last night.' -ok "I won $#20 last night." is 'I won $#20 last night.' -ok "I won $${'#20'} last night." is 'I won $#20 last night.' +ok "I won #20 last night." is 'I won #20 last night.' +ok "I won ##{20} last night." is 'I won #20 last night.' +ok "I won ##20 last night." is 'I won ##20 last night.' +ok "I won ##{'#20'} last night." is 'I won ##20 last night.' -ok "${hello + world}" is 'HelloWorld' -ok "${hello + ' ' + world + '!'}" is 'Hello World!' +ok "#{hello + world}" is 'HelloWorld' +ok "#{hello + ' ' + world + '!'}" is 'Hello World!' list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -ok "values: ${list.join(', ')}, length: ${list.length}." is 'values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, length: 10.' -ok "values: ${list.join ' '}" is 'values: 0 1 2 3 4 5 6 7 8 9' +ok "values: #{list.join(', ')}, length: #{list.length}." is 'values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, length: 10.' +ok "values: #{list.join ' '}" is 'values: 0 1 2 3 4 5 6 7 8 9' obj = { name: 'Joe' - hi: -> "Hello $@name." - cya: -> "Hello $@name.".replace('Hello','Goodbye') + hi: -> "Hello #@name." + cya: -> "Hello #@name.".replace('Hello','Goodbye') } ok obj.hi() is "Hello Joe." ok obj.cya() is "Goodbye Joe." -ok "With ${"quotes"}" is 'With quotes' -ok 'With ${"quotes"}' is 'With ${"quotes"}' +ok "With #{"quotes"}" is 'With quotes' +ok 'With #{"quotes"}' is 'With #{"quotes"}' -ok "Where is ${obj["name"] + '?'}" is 'Where is Joe?' -ok "Where is $obj.name?" is 'Where is Joe?' +ok "Where is #{obj["name"] + '?'}" is 'Where is Joe?' +ok "Where is #obj.name?" is 'Where is Joe?' -ok "Where is ${"the nested ${obj["name"]}"}?" is 'Where is the nested Joe?' -ok "Hello ${world ? "$hello"}" is 'Hello World' +ok "Where is #{"the nested #{obj["name"]}"}?" is 'Where is the nested Joe?' +ok "Hello #{world ? "#hello"}" is 'Hello World' -ok "Hello ${"${"${obj["name"]}" + '!'}"}" is 'Hello Joe!' +ok "Hello #{"#{"#{obj["name"]}" + '!'}"}" is 'Hello Joe!' a = """ - Hello ${ "Joe" } + Hello #{ "Joe" } """ ok a is "Hello Joe" @@ -82,14 +82,14 @@ ok a is "Hello Joe" a = 1 b = 2 c = 3 -ok "$a$b$c" is '123' +ok "#a#b#c" is '123' result = null stash = (str) -> result = str -stash "a ${ ('aa').replace /a/g, 'b' } c" +stash "a #{ ('aa').replace /a/g, 'b' } c" ok result is 'a bb c' foo = "hello" -ok "${foo.replace("\"", "")}" is 'hello' \ No newline at end of file +ok "#{foo.replace("\"", "")}" is 'hello' \ No newline at end of file