summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.markdown13
-rw-r--r--Rakefile9
-rwxr-xr-xbin/handlebars12
-rw-r--r--lib/handlebars/base.js32
-rw-r--r--lib/handlebars/compiler/base.js4
-rw-r--r--lib/handlebars/compiler/compiler.js4
-rw-r--r--lib/handlebars/runtime.js4
-rw-r--r--lib/handlebars/utils.js12
-rw-r--r--package.json4
-rw-r--r--spec/qunit_spec.js75
-rw-r--r--spec/tokenizer_spec.rb33
-rw-r--r--src/handlebars.l13
13 files changed, 170 insertions, 46 deletions
diff --git a/.gitignore b/.gitignore
index a98a3e7..4657058 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ lib/handlebars/compiler/parser.js
node_modules
*.sublime-project
*.sublime-workspace
+npm-debug.log
diff --git a/README.markdown b/README.markdown
index ed0b022..28fa9a5 100644
--- a/README.markdown
+++ b/README.markdown
@@ -19,7 +19,7 @@ In general, the syntax of Handlebars.js templates is a superset of Mustache temp
Once you have a template, use the Handlebars.compile method to compile the template into a function. The generated function takes a context argument, which will be used to render the template.
```js
-var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
+var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
"{{kids.length}} kids:</p>" +
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
var template = Handlebars.compile(source);
@@ -112,7 +112,7 @@ instance:
```js
Handlebars.registerHelper('link_to', function(title, context) {
- return "<a href='/posts" + context.id + "'>" + title + "</a>"
+ return "<a href='/posts" + context.url + "'>" + title + "!</a>"
});
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
@@ -124,7 +124,7 @@ template(context);
// Would render:
//
// <ul>
-// <li><a href='/hello-world'>Post!</a></li>
+// <li><a href='/posts/hello-world'>Post!</a></li>
// </ul>
```
@@ -137,9 +137,9 @@ gets passed to the helper function.
Handlebars.js also adds the ability to define block helpers. Block helpers are functions that can be called from anywhere in the template. Here's an example:
```js
-var source = "<ul>{{#people}}<li>{{{#link}}}{{name}}{{/link}}</li>{{/people}}</ul>";
-Handlebars.registerHelper('link', function(context, fn) {
- return '<a href="/people/' + this.__get__("id") + '">' + fn(this) + '</a>';
+var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
+Handlebars.registerHelper('link', function(context, options) {
+ return '<a href="/people/' + this.id + '">' + context.fn(this) + '</a>';
});
var template = Handlebars.compile(source);
@@ -220,6 +220,7 @@ Precompile handlebar templates.
Usage: handlebars template...
Options:
+ -a, --amd Create an AMD format function (allows loading with RequireJS) [boolean]
-f, --output Output File [string]
-k, --known Known helpers [string]
-o, --knownOnly Known helpers only [boolean]
diff --git a/Rakefile b/Rakefile
index b6043c2..55c78d2 100644
--- a/Rakefile
+++ b/Rakefile
@@ -2,7 +2,7 @@ require "rubygems"
require "bundler/setup"
def compile_parser
- system "jison src/handlebars.yy src/handlebars.l"
+ system "./node_modules/jison/lib/jison/cli-wrapper.js src/handlebars.yy src/handlebars.l"
if $?.success?
File.open("lib/handlebars/compiler/parser.js", "w") do |file|
file.puts File.read("handlebars.js") + ";"
@@ -15,11 +15,11 @@ def compile_parser
end
file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
- if ENV['PATH'].split(':').any? {|folder| File.exists?(folder+'/jison')}
+ if File.exists?('./node_modules/jison/lib/jison/cli-wrapper.js')
compile_parser
else
puts "Jison is not installed. Trying `npm install jison`."
- sh "npm install jison -g"
+ sh "npm install jison"
compile_parser
end
end
@@ -28,7 +28,8 @@ task :compile => "lib/handlebars/compiler/parser.js"
desc "run the spec suite"
task :spec => [:release] do
- system "rspec -cfs spec"
+ rc = system "rspec -cfs spec"
+ fail "rspec spec failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
end
task :default => [:compile, :spec]
diff --git a/bin/handlebars b/bin/handlebars
index 7a2fc1c..dd6e071 100755
--- a/bin/handlebars
+++ b/bin/handlebars
@@ -12,11 +12,17 @@ var optimist = require('optimist')
'description': 'Exports amd style (require.js)',
'alias': 'amd'
},
+ 'c': {
+ 'type': 'string',
+ 'description': 'Exports CommonJS style, path to Handlebars module',
+ 'alias': 'commonjs',
+ 'default': null
+ },
'h': {
'type': 'string',
'description': 'Path to handlebar.js (only valid for amd-style)',
'alias': 'handlebarPath',
- 'default': ''
+ 'default': ''
},
'k': {
'type': 'string',
@@ -91,6 +97,8 @@ var output = [];
if (!argv.simple) {
if (argv.amd) {
output.push('define([\'' + argv.handlebarPath + 'handlebars\'], function(Handlebars) {\n');
+ } else if (argv.commonjs) {
+ output.push('var Handlebars = require("' + argv.commonjs + '");');
} else {
output.push('(function() {\n');
}
@@ -139,7 +147,7 @@ argv._.forEach(function(template) {
if (!argv.simple) {
if (argv.amd) {
output.push('});');
- } else {
+ } else if (!argv.commonjs) {
output.push('})();');
}
}
diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 8640cb8..f5f5996 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -43,13 +43,10 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) {
return inverse(this);
} else if(type === "[object Array]") {
if(context.length > 0) {
- for(var i=0, j=context.length; i<j; i++) {
- ret = ret + fn(context[i]);
- }
+ return Handlebars.helpers.each(context, options);
} else {
- ret = inverse(this);
+ return inverse(this);
}
- return ret;
} else {
return fn(context);
}
@@ -66,20 +63,33 @@ Handlebars.createFrame = Object.create || function(object) {
Handlebars.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
- var ret = "", data;
+ var i = 0, ret = "", data;
if (options.data) {
data = Handlebars.createFrame(options.data);
}
- if(context && context.length > 0) {
- for(var i=0, j=context.length; i<j; i++) {
- if (data) { data.index = i; }
- ret = ret + fn(context[i], { data: data });
+ if(context && typeof context === 'object') {
+ if(context instanceof Array){
+ for(var j = context.length; i<j; i++) {
+ if (data) { data.index = i; }
+ ret = ret + fn(context[i], { data: data });
+ }
+ } else {
+ for(var key in context) {
+ if(context.hasOwnProperty(key)) {
+ if(data) { data.key = key; }
+ ret = ret + fn(context[key], {data: data});
+ i++;
+ }
+ }
}
- } else {
+ }
+
+ if(i === 0){
ret = inverse(this);
}
+
return ret;
});
diff --git a/lib/handlebars/compiler/base.js b/lib/handlebars/compiler/base.js
index 5319cf9..6f8fdc5 100644
--- a/lib/handlebars/compiler/base.js
+++ b/lib/handlebars/compiler/base.js
@@ -24,7 +24,7 @@ Handlebars.logger = {
Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
-return Handlebars;
-
// END(BROWSER)
+
+return Handlebars;
};
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 97a87f0..6dc43d6 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -646,7 +646,7 @@ Handlebars.JavaScriptCompiler = function() {};
this.context.aliases.functionType = '"function"';
this.replaceStack(function(current) {
- return "typeof " + current + " === functionType ? " + current + "() : " + current;
+ return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
});
},
@@ -791,7 +791,7 @@ Handlebars.JavaScriptCompiler = function() {};
var nextStack = this.nextStack();
this.source.push('if (foundHelper) { ' + nextStack + ' = foundHelper.call(' + helper.callParams + '); }');
- this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '() : ' + nextStack + '; }');
+ this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }');
},
// [invokePartial]
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index 6fd38fd..4537687 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -57,7 +57,7 @@ Handlebars.VM = {
} else if (!Handlebars.compile) {
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
} else {
- partials[name] = Handlebars.compile(partial);
+ partials[name] = Handlebars.compile(partial, {data: data !== undefined});
return partials[name](context, options);
}
}
@@ -69,4 +69,4 @@ Handlebars.template = Handlebars.VM.template;
return Handlebars;
-}; \ No newline at end of file
+};
diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js
index d467205..cd02e95 100644
--- a/lib/handlebars/utils.js
+++ b/lib/handlebars/utils.js
@@ -2,14 +2,15 @@ exports.attach = function(Handlebars) {
// BEGIN(BROWSER)
+var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
+
Handlebars.Exception = function(message) {
var tmp = Error.prototype.constructor.apply(this, arguments);
- for (var p in tmp) {
- if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; }
+ // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
+ for (var idx = 0; idx < errorProps.length; idx++) {
+ this[errorProps[idx]] = tmp[errorProps[idx]];
}
-
- this.message = tmp.message;
};
Handlebars.Exception.prototype = new Error();
@@ -23,6 +24,7 @@ Handlebars.SafeString.prototype.toString = function() {
(function() {
var escape = {
+ "&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
@@ -30,7 +32,7 @@ Handlebars.SafeString.prototype.toString = function() {
"`": "&#x60;"
};
- var badChars = /&(?!\w+;)|[<>"'`]/g;
+ var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
var escapeChar = function(chr) {
diff --git a/package.json b/package.json
index d838894..b037cda 100644
--- a/package.json
+++ b/package.json
@@ -1,14 +1,14 @@
{
"name": "handlebars",
"description": "Extension of the Mustache logicless template language",
- "version": "1.0.6beta",
+ "version": "1.0.7",
"homepage": "http://www.handlebarsjs.com/",
"keywords": [
"handlebars mustache template html"
],
"repository": {
"type": "git",
- "url": "git://github.com/kpdecker/handlebars.js.git"
+ "url": "git://github.com/wycats/handlebars.js.git"
},
"engines": {
"node": ">=0.4.7"
diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js
index cbbc138..5c94253 100644
--- a/spec/qunit_spec.js
+++ b/spec/qunit_spec.js
@@ -33,7 +33,13 @@ Handlebars.registerHelper('helperMissing', function(helper, context) {
function shouldCompileTo(string, hashOrArray, expected, message) {
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
}
+
function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
+ var result = compileWithPartials(string, hashOrArray, partials);
+ equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
+}
+
+function compileWithPartials(string, hashOrArray, partials) {
var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
var helpers = hashOrArray[1];
@@ -51,18 +57,31 @@ function shouldCompileToWithPartials(string, hashOrArray, partials, expected, me
ary = [hashOrArray];
}
- var result = template.apply(this, ary);
- equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
+ return template.apply(this, ary);
}
function shouldThrow(fn, exception, message) {
- var caught = false;
+ var caught = false,
+ exType, exMessage;
+
+ if (exception instanceof Array) {
+ exType = exception[0];
+ exMessage = exception[1];
+ } else if (typeof exception === 'string') {
+ exType = Error;
+ exMessage = exception;
+ } else {
+ exType = exception;
+ }
+
try {
fn();
}
catch (e) {
- if (e instanceof exception) {
- caught = true;
+ if (e instanceof exType) {
+ if (!exMessage || e.message === exMessage) {
+ caught = true;
+ }
}
}
@@ -123,6 +142,8 @@ test("escaping expressions", function() {
shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
"by default expressions should be escaped");
+ shouldCompileTo("{{awesome}}", {awesome: "Escaped, <b> looks like: &lt;b&gt;"}, 'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;',
+ "escaping should properly handle amperstands");
});
test("functions returning safestrings shouldn't be escaped", function() {
@@ -134,10 +155,14 @@ test("functions returning safestrings shouldn't be escaped", function() {
test("functions", function() {
shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome",
"functions are called and render their output");
+ shouldCompileTo("{{awesome}}", {awesome: function() { return this.more; }, more: "More awesome"}, "More awesome",
+ "functions are bound to the context");
});
test("paths with hyphens", function() {
shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
+ shouldCompileTo("{{foo.foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
+ shouldCompileTo("{{foo/foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
});
test("nested paths", function() {
@@ -234,6 +259,16 @@ test("array", function() {
});
+test("array with @index", function() {
+ var string = "{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!";
+ var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
+
+ var template = CompilerContext.compile(string);
+ var result = template(hash);
+
+ equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
+});
+
test("empty block", function() {
var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
@@ -459,14 +494,14 @@ test("rendering undefined partial throws an exception", function() {
shouldThrow(function() {
var template = CompilerContext.compile("{{> whatever}}");
template();
- }, Handlebars.Exception, "Should throw exception");
+ }, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
});
test("rendering template partial in vm mode throws an exception", function() {
shouldThrow(function() {
var template = CompilerContext.compile("{{> whatever}}");
template();
- }, Handlebars.Exception, "Should throw exception");
+ }, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
});
test("rendering function partial in vm mode", function() {
@@ -602,6 +637,11 @@ test("Invert blocks work in knownHelpers only mode", function() {
var result = template({foo: false});
equal(result, "bar", "'bar' should === '" + result);
});
+test("Functions are bound to the context in knownHelpers only mode", function() {
+ var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
+ var result = template({foo: function() { return this.bar; }, bar: 'bar'});
+ equal(result, "bar", "'bar' should === '" + result);
+});
suite("blockHelperMissing");
@@ -610,6 +650,11 @@ test("lambdas are resolved by blockHelperMissing, not handlebars proper", functi
var data = { truthy: function() { return true; } };
shouldCompileTo(string, data, "yep");
});
+test("lambdas resolved by blockHelperMissing are bound to the context", function() {
+ var string = "{{#truthy}}yep{{/truthy}}";
+ var boundData = { truthy: function() { return this.truthiness(); }, truthiness: function() { return false; } };
+ shouldCompileTo(string, boundData, "");
+});
var teardown;
suite("built-in helpers", {
@@ -659,6 +704,22 @@ test("each", function() {
"each with array argument ignores the contents when empty");
});
+test("each with an object and @key", function() {
+ var string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!";
+ var hash = {goodbyes: {"<b>#1</b>": {text: "goodbye"}, 2: {text: "GOODBYE"}}, world: "world"};
+
+ // Object property iteration order is undefined according to ECMA spec,
+ // so we need to check both possible orders
+ // @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
+ var actual = compileWithPartials(string, hash);
+ var expected1 = "&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!";
+ var expected2 = "2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. goodbye! cruel world!";
+
+ ok(actual === expected1 || actual === expected2, "each with object argument iterates over the contents when not empty");
+ shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
+ "each with object argument ignores the contents when empty");
+});
+
test("each with @index", function() {
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
diff --git a/spec/tokenizer_spec.rb b/spec/tokenizer_spec.rb
index 2517ade..7a771ba 100644
--- a/spec/tokenizer_spec.rb
+++ b/spec/tokenizer_spec.rb
@@ -51,6 +51,15 @@ describe "Tokenizer" do
result[4].should be_token("CONTENT", "{{bar}} ")
end
+ it "supports escaping multiple delimiters" do
+ result = tokenize("{{foo}} \\{{bar}} \\{{baz}}")
+ result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT CONTENT))
+
+ result[3].should be_token("CONTENT", " ")
+ result[4].should be_token("CONTENT", "{{bar}} ")
+ result[5].should be_token("CONTENT", "{{baz}}")
+ end
+
it "supports escaping a triple stash" do
result = tokenize("{{foo}} \\{{{bar}}} {{baz}}")
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
@@ -149,6 +158,18 @@ describe "Tokenizer" do
result[1].should be_token("COMMENT", " this is a comment ")
end
+ it "tokenizes a block comment as 'COMMENT'" do
+ result = tokenize("foo {{!-- this is a {{comment}} --}} bar {{ baz }}")
+ result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
+ result[1].should be_token("COMMENT", " this is a {{comment}} ")
+ end
+
+ it "tokenizes a block comment with whitespace as 'COMMENT'" do
+ result = tokenize("foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}")
+ result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
+ result[1].should be_token("COMMENT", " this is a\n{{comment}}\n")
+ end
+
it "tokenizes open and closing blocks as 'OPEN_BLOCK ID CLOSE ... OPEN_ENDBLOCK ID CLOSE'" do
result = tokenize("{{#foo}}content{{/foo}}")
result.should match_tokens(%w(OPEN_BLOCK ID CLOSE CONTENT OPEN_ENDBLOCK ID CLOSE))
@@ -186,6 +207,12 @@ describe "Tokenizer" do
result[3].should be_token("STRING", "baz")
end
+ it "tokenizes mustaches with String params using single quotes as 'OPEN ID ID STRING CLOSE'" do
+ result = tokenize("{{ foo bar \'baz\' }}")
+ result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
+ result[3].should be_token("STRING", "baz")
+ end
+
it "tokenizes String params with spaces inside as 'STRING'" do
result = tokenize("{{ foo bar \"baz bat\" }}")
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
@@ -198,6 +225,12 @@ describe "Tokenizer" do
result[2].should be_token("STRING", %{bar"baz})
end
+ it "tokenizes String params using single quotes with escapes quotes as 'STRING'" do
+ result = tokenize(%|{{ foo 'bar\\'baz' }}|)
+ result.should match_tokens(%w(OPEN ID STRING CLOSE))
+ result[2].should be_token("STRING", %{bar'baz})
+ end
+
it "tokenizes numbers" do
result = tokenize(%|{{ foo 1 }}|)
result.should match_tokens(%w(OPEN ID INTEGER CLOSE))
diff --git a/src/handlebars.l b/src/handlebars.l
index 2e0c4f7..87dce26 100644
--- a/src/handlebars.l
+++ b/src/handlebars.l
@@ -1,5 +1,5 @@
-%x mu emu
+%x mu emu com
%%
@@ -11,7 +11,13 @@
[^\x00]+ { return 'CONTENT'; }
-<emu>[^\x00]{2,}?/("{{") { this.popState(); return 'CONTENT'; }
+<emu>[^\x00]{2,}?/("{{"|<<EOF>>) {
+ if(yytext.slice(-1) !== "\\") this.popState();
+ if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1);
+ return 'CONTENT';
+ }
+
+<com>[\s\S]*?"--}}" { yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; }
<mu>"{{>" { return 'OPEN_PARTIAL'; }
<mu>"{{#" { return 'OPEN_BLOCK'; }
@@ -20,6 +26,7 @@
<mu>"{{"\s*"else" { return 'OPEN_INVERSE'; }
<mu>"{{{" { return 'OPEN_UNESCAPED'; }
<mu>"{{&" { return 'OPEN_UNESCAPED'; }
+<mu>"{{!--" { this.popState(); this.begin('com'); }
<mu>"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; }
<mu>"{{" { return 'OPEN'; }
@@ -31,7 +38,7 @@
<mu>"}}}" { this.popState(); return 'CLOSE'; }
<mu>"}}" { this.popState(); return 'CLOSE'; }
<mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
-<mu>"'"("\\"[']|[^'])*"'" { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
+<mu>"'"("\\"[']|[^'])*"'" { yytext = yytext.substr(1,yyleng-2).replace(/\\'/g,"'"); return 'STRING'; }
<mu>"@"[a-zA-Z]+ { yytext = yytext.substr(1); return 'DATA'; }
<mu>"true"/[}\s] { return 'BOOLEAN'; }
<mu>"false"/[}\s] { return 'BOOLEAN'; }