mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
Merge branch 'master' of http://github.com/jashkenas/coffee-script into refactorTests
This commit is contained in:
commit
f0a62e83c8
15 changed files with 142 additions and 85 deletions
|
@ -287,6 +287,14 @@ Expressions
|
|||
Value "x"</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--nodejs</code></td>
|
||||
<td>
|
||||
The <tt>node</tt> executable has some useful options you can set,
|
||||
such as <tt>--debug</tt> and <tt>--max-stack-size</tt>. Use this
|
||||
flag to forward options directly to Node.js.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
|
@ -537,6 +545,11 @@ coffee --bare --print --stdio</pre>
|
|||
by adding a meaningful return value, like <tt>true</tt>, or <tt>null</tt>,
|
||||
to the bottom of your function.
|
||||
</p>
|
||||
<p>
|
||||
To step through a range comprehension in fixed-size chunks,
|
||||
use <tt>by</tt>, for example:<br />
|
||||
<tt>evens = (x for x in [0..10] by 2)</tt>
|
||||
</p>
|
||||
<p>
|
||||
Comprehensions can also be used to iterate over the keys and values in
|
||||
an object. Use <tt>of</tt> to signal comprehension over the properties of
|
||||
|
|
15
index.html
15
index.html
|
@ -239,7 +239,7 @@ cubes = (function() {
|
|||
</p>
|
||||
|
||||
<p>
|
||||
To install, first make sure you have a working copy of the latest tagged version of
|
||||
To install, first make sure you have a working copy of the latest stable version of
|
||||
<a href="http://nodejs.org/">Node.js</a>, and <a href="http://npmjs.org">NPM</a>
|
||||
(the Node Package Manager). You can then install CoffeeScript with NPM:
|
||||
</p>
|
||||
|
@ -365,6 +365,14 @@ Expressions
|
|||
Value "x"</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--nodejs</code></td>
|
||||
<td>
|
||||
The <tt>node</tt> executable has some useful options you can set,
|
||||
such as <tt>--debug</tt> and <tt>--max-stack-size</tt>. Use this
|
||||
flag to forward options directly to Node.js.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
|
@ -913,6 +921,11 @@ countdown = (function() {
|
|||
by adding a meaningful return value, like <tt>true</tt>, or <tt>null</tt>,
|
||||
to the bottom of your function.
|
||||
</p>
|
||||
<p>
|
||||
To step through a range comprehension in fixed-size chunks,
|
||||
use <tt>by</tt>, for example:<br />
|
||||
<tt>evens = (x for x in [0..10] by 2)</tt>
|
||||
</p>
|
||||
<p>
|
||||
Comprehensions can also be used to iterate over the keys and values in
|
||||
an object. Use <tt>of</tt> to signal comprehension over the properties of
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
return compile(content);
|
||||
});
|
||||
}
|
||||
exports.VERSION = '0.9.6';
|
||||
exports.VERSION = '1.0.0-pre';
|
||||
exports.RESERVED = RESERVED;
|
||||
exports.helpers = require('./helpers');
|
||||
exports.compile = compile = function(code, options) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
(function() {
|
||||
var ALL_SWITCHES, BANNER, CoffeeScript, DEPRECATED_SWITCHES, EventEmitter, SWITCHES, compileOptions, compileScript, compileScripts, compileStdio, exec, fs, helpers, lint, optionParser, optparse, opts, parseOptions, path, printLine, printTokens, printWarn, sources, spawn, usage, util, version, watch, writeJs, _ref;
|
||||
var BANNER, CoffeeScript, EventEmitter, SWITCHES, compileOptions, compileScript, compileScripts, compileStdio, exec, forkNode, fs, helpers, lint, optionParser, optparse, opts, parseOptions, path, printLine, printTokens, printWarn, sources, spawn, usage, util, version, watch, writeJs, _ref;
|
||||
fs = require('fs');
|
||||
path = require('path');
|
||||
util = require('util');
|
||||
|
@ -16,15 +16,15 @@
|
|||
return process.binding('stdio').writeError(line + '\n');
|
||||
};
|
||||
BANNER = 'Usage: coffee [options] path/to/script.coffee';
|
||||
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'], ['-r', '--require [FILE*]', 'require a library before executing your script'], ['-b', '--bare', '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']];
|
||||
DEPRECATED_SWITCHES = [['--no-wrap', 'compile without the top-level function wrapper']];
|
||||
ALL_SWITCHES = SWITCHES.concat(DEPRECATED_SWITCHES);
|
||||
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'], ['-r', '--require [FILE*]', 'require a library before executing your script'], ['-b', '--bare', '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'], ['--nodejs [ARGS]', 'pass options through to the "node" binary'], ['-v', '--version', 'display CoffeeScript version'], ['-h', '--help', 'display this help message']];
|
||||
opts = {};
|
||||
sources = [];
|
||||
optionParser = null;
|
||||
exports.run = function() {
|
||||
var flags, separator;
|
||||
parseOptions();
|
||||
if (opts.nodejs) {
|
||||
return forkNode();
|
||||
}
|
||||
if (opts.help) {
|
||||
return usage();
|
||||
}
|
||||
|
@ -43,21 +43,16 @@
|
|||
if (!sources.length) {
|
||||
return require('./repl');
|
||||
}
|
||||
separator = sources.indexOf('--');
|
||||
flags = [];
|
||||
if (separator >= 0) {
|
||||
flags = sources.splice(separator + 1);
|
||||
sources.pop();
|
||||
}
|
||||
if (opts.run) {
|
||||
flags = sources.splice(1).concat(flags);
|
||||
opts.literals = sources.splice(1).concat(opts.literals);
|
||||
}
|
||||
process.ARGV = process.argv = process.argv.slice(0, 2).concat(flags);
|
||||
process.ARGV = process.argv = process.argv.slice(0, 2).concat(opts.literals);
|
||||
return compileScripts();
|
||||
};
|
||||
compileScripts = function() {
|
||||
var base, compile, source, _fn, _i, _len, _results;
|
||||
var source, _fn, _i, _len, _results;
|
||||
_fn = function(source) {
|
||||
var base, compile;
|
||||
base = path.join(source);
|
||||
compile = function(source, topLevel) {
|
||||
return path.exists(source, function(exists) {
|
||||
|
@ -226,22 +221,30 @@
|
|||
};
|
||||
parseOptions = function() {
|
||||
var o;
|
||||
optionParser = new optparse.OptionParser(ALL_SWITCHES, BANNER);
|
||||
optionParser = new optparse.OptionParser(SWITCHES, BANNER);
|
||||
o = opts = optionParser.parse(process.argv.slice(2));
|
||||
o.compile || (o.compile = !!o.output);
|
||||
o.run = !(o.compile || o.print || o.lint);
|
||||
o.print = !!(o.print || (o.eval || o.stdio && o.compile));
|
||||
sources = o.arguments;
|
||||
if (opts['no-wrap']) {
|
||||
return printWarn('--no-wrap is deprecated; please use --bare instead.');
|
||||
}
|
||||
return sources = o.arguments;
|
||||
};
|
||||
compileOptions = function(fileName) {
|
||||
return {
|
||||
fileName: fileName,
|
||||
bare: opts.bare || opts['no-wrap']
|
||||
bare: opts.bare
|
||||
};
|
||||
};
|
||||
forkNode = function() {
|
||||
var args, nodeArgs;
|
||||
nodeArgs = opts.nodejs.split(/\s+/);
|
||||
args = process.argv.slice(1);
|
||||
args.splice(args.indexOf('--nodejs'), 2);
|
||||
return spawn(process.execPath, nodeArgs.concat(args), {
|
||||
cwd: process.cwd(),
|
||||
env: process.env,
|
||||
customFds: [0, 1, 2]
|
||||
});
|
||||
};
|
||||
usage = function() {
|
||||
printLine((new optparse.OptionParser(SWITCHES, BANNER)).help());
|
||||
return process.exit(0);
|
||||
|
|
34
lib/lexer.js
34
lib/lexer.js
|
@ -128,7 +128,7 @@
|
|||
this.token('STRING', (string = match[0]).replace(MULTILINER, '\\\n'));
|
||||
break;
|
||||
case '"':
|
||||
if (!(string = this.balancedString(this.chunk, [['"', '"'], ['#{', '}']]))) {
|
||||
if (!(string = this.balancedString(this.chunk, '"'))) {
|
||||
return 0;
|
||||
}
|
||||
if (0 < string.indexOf('#{', 1)) {
|
||||
|
@ -447,34 +447,32 @@
|
|||
Lexer.prototype.assignmentError = function() {
|
||||
throw SyntaxError("Reserved word \"" + (this.value()) + "\" on line " + (this.line + 1) + " can't be assigned");
|
||||
};
|
||||
Lexer.prototype.balancedString = function(str, delimited, options) {
|
||||
var i, open, pair, stack, _i, _len, _ref;
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
stack = [delimited[0]];
|
||||
Lexer.prototype.balancedString = function(str, end) {
|
||||
var i, letter, prev, stack, _ref;
|
||||
stack = [end];
|
||||
for (i = 1, _ref = str.length; (1 <= _ref ? i < _ref : i > _ref); (1 <= _ref ? i += 1 : i -= 1)) {
|
||||
switch (str.charAt(i)) {
|
||||
switch (letter = str.charAt(i)) {
|
||||
case '\\':
|
||||
i++;
|
||||
continue;
|
||||
case stack[stack.length - 1][1]:
|
||||
case end:
|
||||
stack.pop();
|
||||
if (!stack.length) {
|
||||
return str.slice(0, i + 1);
|
||||
}
|
||||
end = stack[stack.length - 1];
|
||||
continue;
|
||||
}
|
||||
for (_i = 0, _len = delimited.length; _i < _len; _i++) {
|
||||
pair = delimited[_i];
|
||||
if ((open = pair[0]) === str.substr(i, open.length)) {
|
||||
stack.push(pair);
|
||||
i += open.length - 1;
|
||||
break;
|
||||
}
|
||||
if (end === '}' && (letter === '"' || letter === "'")) {
|
||||
stack.push(end = letter);
|
||||
} else if (end === '}' && letter === '{') {
|
||||
stack.push(end = '}');
|
||||
} else if (end === '"' && prev === '#' && letter === '{') {
|
||||
stack.push(end = '}');
|
||||
}
|
||||
prev = letter;
|
||||
}
|
||||
throw new Error("unterminated " + (stack.pop()[0]) + " on line " + (this.line + 1));
|
||||
throw new Error("missing " + (stack.pop()) + ", starting on line " + (this.line + 1));
|
||||
};
|
||||
Lexer.prototype.interpolateString = function(str, options) {
|
||||
var expr, heredoc, i, inner, interpolated, letter, nested, pi, regex, tag, tokens, value, _len, _ref, _ref2, _ref3;
|
||||
|
@ -490,7 +488,7 @@
|
|||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (!(letter === '#' && str.charAt(i + 1) === '{' && (expr = this.balancedString(str.slice(i + 1), [['{', '}']])))) {
|
||||
if (!(letter === '#' && str.charAt(i + 1) === '{' && (expr = this.balancedString(str.slice(i + 1), '}')))) {
|
||||
continue;
|
||||
}
|
||||
if (pi < i) {
|
||||
|
|
|
@ -1893,7 +1893,6 @@
|
|||
args.unshift(new Literal('this'));
|
||||
}
|
||||
body.expressions[idx] = new Call(base, args);
|
||||
o.sharedScope = true;
|
||||
defs += this.tab + new Assign(ref, fn).compile(o, LEVEL_TOP) + ';\n';
|
||||
}
|
||||
return defs;
|
||||
|
|
|
@ -8,11 +8,16 @@
|
|||
OptionParser.prototype.parse = function(args) {
|
||||
var arg, i, isOption, matchedRule, options, rule, value, _i, _len, _len2, _ref;
|
||||
options = {
|
||||
arguments: []
|
||||
arguments: [],
|
||||
literals: []
|
||||
};
|
||||
args = normalizeArguments(args);
|
||||
for (i = 0, _len = args.length; i < _len; i++) {
|
||||
arg = args[i];
|
||||
if (arg === '--') {
|
||||
options.literals = args.slice(i + 1);
|
||||
break;
|
||||
}
|
||||
isOption = !!(arg.match(LONG_FLAG) || arg.match(SHORT_FLAG));
|
||||
matchedRule = false;
|
||||
_ref = this.rules;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"description": "Unfancy JavaScript",
|
||||
"keywords": ["javascript", "language", "coffeescript", "compiler"],
|
||||
"author": "Jeremy Ashkenas",
|
||||
"version": "0.9.6",
|
||||
"version": "1.0.0-pre",
|
||||
"licenses": [{
|
||||
"type": "MIT",
|
||||
"url": "http://github.com/jashkenas/coffee-script/raw/master/LICENSE"
|
||||
|
|
|
@ -20,7 +20,7 @@ else if require.registerExtension
|
|||
require.registerExtension '.coffee', (content) -> compile content
|
||||
|
||||
# The current CoffeeScript version number.
|
||||
exports.VERSION = '0.9.6'
|
||||
exports.VERSION = '1.0.0-pre'
|
||||
|
||||
# Words that cannot be used as identifiers in CoffeeScript code
|
||||
exports.RESERVED = RESERVED
|
||||
|
|
|
@ -39,17 +39,11 @@ SWITCHES = [
|
|||
['-b', '--bare', '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']
|
||||
[ '--nodejs [ARGS]', 'pass options through to the "node" binary']
|
||||
['-v', '--version', 'display CoffeeScript version']
|
||||
['-h', '--help', 'display this help message']
|
||||
]
|
||||
|
||||
# Switches that are still supported, but will cause a warning message.
|
||||
DEPRECATED_SWITCHES = [
|
||||
['--no-wrap', 'compile without the top-level function wrapper']
|
||||
]
|
||||
|
||||
ALL_SWITCHES = SWITCHES.concat DEPRECATED_SWITCHES
|
||||
|
||||
# Top-level objects shared by all the functions.
|
||||
opts = {}
|
||||
sources = []
|
||||
|
@ -60,20 +54,16 @@ optionParser = null
|
|||
# `--` will be passed verbatim to your script as arguments in `process.argv`
|
||||
exports.run = ->
|
||||
parseOptions()
|
||||
return usage() if opts.help
|
||||
return version() if opts.version
|
||||
return require './repl' if opts.interactive
|
||||
return compileStdio() if opts.stdio
|
||||
return compileScript null, sources[0] if opts.eval
|
||||
return require './repl' unless sources.length
|
||||
separator = sources.indexOf '--'
|
||||
flags = []
|
||||
if separator >= 0
|
||||
flags = sources.splice separator + 1
|
||||
sources.pop()
|
||||
return forkNode() if opts.nodejs
|
||||
return usage() if opts.help
|
||||
return version() if opts.version
|
||||
return require './repl' if opts.interactive
|
||||
return compileStdio() if opts.stdio
|
||||
return compileScript null, sources[0] if opts.eval
|
||||
return require './repl' unless sources.length
|
||||
if opts.run
|
||||
flags = sources.splice(1).concat flags
|
||||
process.ARGV = process.argv = process.argv.slice(0, 2).concat flags
|
||||
opts.literals = sources.splice(1).concat opts.literals
|
||||
process.ARGV = process.argv = process.argv.slice(0, 2).concat opts.literals
|
||||
compileScripts()
|
||||
|
||||
# Asynchronously read in each CoffeeScript in a list of source files and
|
||||
|
@ -180,17 +170,26 @@ printTokens = (tokens) ->
|
|||
# Use the [OptionParser module](optparse.html) to extract all options from
|
||||
# `process.argv` that are specified in `SWITCHES`.
|
||||
parseOptions = ->
|
||||
optionParser = new optparse.OptionParser ALL_SWITCHES, BANNER
|
||||
optionParser = new optparse.OptionParser SWITCHES, BANNER
|
||||
o = opts = optionParser.parse process.argv.slice 2
|
||||
o.compile or= !!o.output
|
||||
o.run = not (o.compile or o.print or o.lint)
|
||||
o.print = !! (o.print or (o.eval or o.stdio and o.compile))
|
||||
sources = o.arguments
|
||||
if opts['no-wrap']
|
||||
printWarn '--no-wrap is deprecated; please use --bare instead.'
|
||||
|
||||
# The compile-time options to pass to the CoffeeScript compiler.
|
||||
compileOptions = (fileName) -> {fileName, bare: opts.bare or opts['no-wrap']}
|
||||
compileOptions = (fileName) -> {fileName, bare: opts.bare}
|
||||
|
||||
# Start up a new Node.js instance with the arguments in `--nodejs` passed to
|
||||
# the `node` binary, preserving the other options.
|
||||
forkNode = ->
|
||||
nodeArgs = opts.nodejs.split /\s+/
|
||||
args = process.argv[1..]
|
||||
args.splice args.indexOf('--nodejs'), 2
|
||||
spawn process.execPath, nodeArgs.concat(args),
|
||||
cwd: process.cwd()
|
||||
env: process.env
|
||||
customFds: [0, 1, 2]
|
||||
|
||||
# Print the `--help` usage message and exit. Deprecated switches are not
|
||||
# shown.
|
||||
|
|
|
@ -139,7 +139,7 @@ exports.Lexer = class Lexer
|
|||
return 0 unless match = SIMPLESTR.exec @chunk
|
||||
@token 'STRING', (string = match[0]).replace MULTILINER, '\\\n'
|
||||
when '"'
|
||||
return 0 unless string = @balancedString @chunk, [['"', '"'], ['#{', '}']]
|
||||
return 0 unless string = @balancedString @chunk, '"'
|
||||
if 0 < string.indexOf '#{', 1
|
||||
@interpolateString string.slice 1, -1
|
||||
else
|
||||
|
@ -387,22 +387,27 @@ exports.Lexer = class Lexer
|
|||
# a series of delimiters, all of which must be nested correctly within the
|
||||
# contents of the string. This method allows us to have strings within
|
||||
# interpolations within strings, ad infinitum.
|
||||
balancedString: (str, delimited, options = {}) ->
|
||||
stack = [delimited[0]]
|
||||
balancedString: (str, end) ->
|
||||
stack = [end]
|
||||
for i in [1...str.length]
|
||||
switch str.charAt i
|
||||
switch letter = str.charAt i
|
||||
when '\\'
|
||||
i++
|
||||
continue
|
||||
when stack[stack.length - 1][1]
|
||||
when end
|
||||
stack.pop()
|
||||
return str.slice 0, i + 1 unless stack.length
|
||||
unless stack.length
|
||||
return str.slice 0, i + 1
|
||||
end = stack[stack.length - 1]
|
||||
continue
|
||||
for pair in delimited when (open = pair[0]) is str.substr i, open.length
|
||||
stack.push pair
|
||||
i += open.length - 1
|
||||
break
|
||||
throw new Error "unterminated #{ stack.pop()[0] } on line #{ @line + 1 }"
|
||||
if end is '}' and letter in ['"', "'"]
|
||||
stack.push end = letter
|
||||
else if end is '}' and letter is '{'
|
||||
stack.push end = '}'
|
||||
else if end is '"' and prev is '#' and letter is '{'
|
||||
stack.push end = '}'
|
||||
prev = letter
|
||||
throw new Error "missing #{ stack.pop() }, starting on line #{ @line + 1 }"
|
||||
|
||||
|
||||
# Expand variables and expressions inside double-quoted strings using
|
||||
|
@ -423,7 +428,7 @@ exports.Lexer = class Lexer
|
|||
i += 1
|
||||
continue
|
||||
unless letter is '#' and str.charAt(i+1) is '{' and
|
||||
(expr = @balancedString str.slice(i+1), [['{', '}']])
|
||||
(expr = @balancedString str.slice(i + 1), '}')
|
||||
continue
|
||||
tokens.push ['NEOSTRING', str.slice(pi, i)] if pi < i
|
||||
inner = expr.slice(1, -1)
|
||||
|
|
|
@ -1512,7 +1512,6 @@ exports.For = class For extends Base
|
|||
[val.base, base] = [base, val]
|
||||
args.unshift new Literal 'this'
|
||||
body.expressions[idx] = new Call base, args
|
||||
o.sharedScope = yes
|
||||
defs += @tab + new Assign(ref, fn).compile(o, LEVEL_TOP) + ';\n'
|
||||
defs
|
||||
|
||||
|
|
|
@ -18,13 +18,18 @@ exports.OptionParser = class OptionParser
|
|||
|
||||
# Parse the list of arguments, populating an `options` object with all of the
|
||||
# specified options, and returning it. `options.arguments` will be an array
|
||||
# containing the remaining non-option arguments. This is a simpler API than
|
||||
# many option parsers that allow you to attach callback actions for every
|
||||
# flag. Instead, you're responsible for interpreting the options object.
|
||||
# containing the remaining non-option arguments. `options.literals` will be
|
||||
# an array of options that are meant to be passed through directly to the
|
||||
# executing script. This is a simpler API than many option parsers that allow
|
||||
# you to attach callback actions for every flag. Instead, you're responsible
|
||||
# for interpreting the options object.
|
||||
parse: (args) ->
|
||||
options = arguments: []
|
||||
options = arguments: [], literals: []
|
||||
args = normalizeArguments args
|
||||
for arg, i in args
|
||||
if arg is '--'
|
||||
options.literals = args[(i + 1)..]
|
||||
break
|
||||
isOption = !!(arg.match(LONG_FLAG) or arg.match(SHORT_FLAG))
|
||||
matchedRule = no
|
||||
for rule in @rules
|
||||
|
|
|
@ -249,3 +249,12 @@ for d in a.b?.c
|
|||
e = d
|
||||
|
||||
eq e, 3
|
||||
|
||||
|
||||
# Issue #948. Capturing loop variables.
|
||||
funcs = []
|
||||
for y in [1, 2, 3]
|
||||
z = y
|
||||
funcs.push -> "y is #{y} and z is #{z}"
|
||||
|
||||
eq funcs[1](), "y is 2 and z is 2"
|
||||
|
|
|
@ -107,3 +107,12 @@ eq 'multiline nested "interpolations" work', """multiline #{
|
|||
"\"interpolations\""
|
||||
}"
|
||||
} work"""
|
||||
|
||||
|
||||
# Issue #923: Tricky interpolation.
|
||||
eq "#{ "{" }", "{"
|
||||
|
||||
eq "#{ '#{}}' } }", '#{}} }'
|
||||
|
||||
eq "#{"'#{ ({a: "b#{1}"}['a']) }'"}", "'b1'"
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue