Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

fix the null reference problem (failed with {$foo.bar} where $foo is …

…null).

- version is now 0.2.1
- add strictly-equals operator (===) support.
  • Loading branch information...
commit 97b245993a9db3e2cc9840ab8a8d5dbf0860f690 1 parent b127501
@kotas authored
Showing with 104 additions and 18 deletions.
  1. +48 −17 src/jarty.js
  2. +56 −1 tests/jarty/compiler_test.js
View
65 src/jarty.js
@@ -28,7 +28,7 @@
(function () {
var Jarty = window.Jarty = {
- version: '0.2.0',
+ version: '0.2.1',
debug: false,
compiler: null,
__globals: null,
@@ -699,6 +699,31 @@ Jarty.Namespace = function (dict) {
this[key] = dict[key];
};
+Jarty.Getter = function (ns /* , keys... */) {
+ var obj = arguments[0], thisObj, lastObj, i = 1, l = arguments.length, key;
+ while (obj && i < l) {
+ key = arguments[i++];
+ if (key instanceof Array) {
+ if (obj instanceof Function) {
+ thisObj = lastObj;
+ lastObj = obj;
+ obj = obj.apply(thisObj, key);
+ } else {
+ obj = null;
+ }
+ } else {
+ if (key in obj) {
+ lastObj = obj;
+ obj = obj[key];
+ } else {
+ obj = null;
+ }
+ }
+ }
+ return obj;
+};
+
+
var eDoubleQuoteString = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"',
eSingleQuoteString = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'',
eString = '(?:' + eDoubleQuoteString + '|' + eSingleQuoteString + ')',
@@ -725,7 +750,7 @@ Jarty.Rules = {
enter: function (out) {
out.write("_=_||{};",
"if(Jarty.__globals){_=new Jarty.Namespace(_)}",
- "var r=new Jarty.Runtime(_),p=Jarty.Pipe,f=Jarty.Function;");
+ "var g=Jarty.Getter,r=new Jarty.Runtime(_),p=Jarty.Pipe,f=Jarty.Function;");
},
leave: function (out) {
out.write("return r.finish();");
@@ -849,10 +874,16 @@ Jarty.Rules = {
this.transitTo("inEnvVar", matched[2], closePipe);
closePipe = undefined;
} else {
- out.write("_[", Jarty.Utils.quote(matched[1]), "]");
+ out.write("g(_,", Jarty.Utils.quote(matched[1]));
if (matched[2]) { // suffix
- this.transitTo("inVariableSuffix", matched[2], closePipe);
+ var oldClosePipe = closePipe;
+ this.transitTo("inVariableSuffix", matched[2], function (out) {
+ out.write(")");
+ if (oldClosePipe) oldClosePipe.call(this, out);
+ });
closePipe = undefined;
+ } else {
+ out.write(")");
}
}
} else if (matched[3]) { // string
@@ -897,9 +928,11 @@ Jarty.Rules = {
if (matched[1]) {
out.write(Jarty.Utils.quote(matched[1]));
} else if (matched[2]) {
- out.write("_[", Jarty.Utils.quote(matched[2]), "]");
+ out.write("g(_,", Jarty.Utils.quote(matched[2]));
if (matched[3]) {
- this.transitTo("inIndexer", matched[3]);
+ this.transitTo("inIndexer", matched[3], function (out) { out.write(")") });
+ } else {
+ out.write(")");
}
}
},
@@ -911,13 +944,13 @@ Jarty.Rules = {
search: new RegExp('^(?:(?:\\.|->)(\\w+)|(?:\\.|->)\\$(\\w+)(' + eIndexer + '*)|(' + eIndexer + ')|(' + eFuncCall + '))'),
found: function (out, matched) {
if (matched[1]) {
- out.write("[", Jarty.Utils.quote(matched[1]), "]");
+ out.write(",", Jarty.Utils.quote(matched[1]));
} else if (matched[2]) {
- out.write("[_[", Jarty.Utils.quote(matched[2]), "]");
+ out.write(",g(_,", Jarty.Utils.quote(matched[2]));
if (matched[3]) {
- this.transitTo("inIndexer", matched[3], function (out) { out.write("]") });
+ this.transitTo("inIndexer", matched[3], function (out) { out.write(")") });
} else {
- out.write("]");
+ out.write(")");
}
} else if (matched[4]) {
this.transitTo("inIndexer", matched[4]);
@@ -932,10 +965,8 @@ Jarty.Rules = {
inIndexer: {
search: /^\[\s*((?:[^\[\]]|\[[^\]]+\])+)\s*\]/,
found: function (out, matched) {
- out.write("[");
- this.transitTo("inValue", matched[1], function (out) {
- out.write("]");
- });
+ out.write(",");
+ this.transitTo("inValue", matched[1]);
},
notfound: function (out, extra) {
this.raiseParseError("invalid indexer");
@@ -944,9 +975,9 @@ Jarty.Rules = {
inFuncCall: {
search: /^\(\s*([^\)]*)\s*\)/,
found: function (out, matched) {
- out.write("(");
+ out.write(",[");
this.transitTo("inFuncCallArgs", matched[1], function (out) {
- out.write(")");
+ out.write("]");
});
},
notfound: function (out, extra) {
@@ -1051,7 +1082,7 @@ Jarty.Rules = {
}
},
inIfCondition: {
- search: new RegExp("^\\s*(?:(\\(|\\)|&&|\\|\\||==|>=|<=|!=|[!><%+/*-])|(and)|(or)|(not)|(" + eValue + "))"),
+ search: new RegExp("^\\s*(?:(\\(|\\)|&&|\\|\\||===?|>=|<=|!=|[!><%+/*-])|(and)|(or)|(not)|(" + eValue + "))"),
found: function (out, matched) {
if (matched[1]) {
out.write(matched[1]);
View
57 tests/jarty/compiler_test.js
@@ -7,7 +7,16 @@ JsUnitTest.Unit.Testcase.prototype.assertCompiled = function (expected, source,
this.error(e);
return;
}
-// this.info(source + " => " + compiled.toString());
+ if (/[?&]debug=1/.test(location.search)) {
+ var js = compiled.toString().replace(/\r?\n/g, "");
+ js = js.replace(/^function.*\(_\)\s*{\s*_\s*=\s*[^;]+;/i, "");
+ js = js.replace(/^\s*if\s*\(\s*Jarty.__globals\s*\)\s*{.+?}/i, "");
+ js = js.replace(/^\s*var\s*.+?;/i, "");
+ js = js.replace(/\s*return\s*r\.finish\(\);\s*}$/i, "");
+ js = js.replace(/;/g, ";\n");
+ js = js.replace(/^\s+|\s+$/mg, "");
+ this.info(source + "\n" + js.replace(/^/mg, "> "));
+ }
try {
actual = compiled(namespace);
} catch (e) {
@@ -86,11 +95,30 @@ new Test.Unit.Runner({
this.assertCompiled("abc", "{$foo.bar.baz}", { foo: { bar: { baz: "abc" } } });
this.assertCompiled("abc", "{$foo.$bar.baz}", { foo: { hoge: { baz: "abc" } }, bar: "hoge" });
},
+ testEmbedDottedNullReferenceReturnsEmptyString: function () {
+ this.assertCompiled("", "{$foo.bar}", { foo: null });
+ this.assertCompiled("", "{$foo.bar.baz}", { foo: { bar: null } });
+ this.assertCompiled("", "{$foo.$bar.baz}", { foo: null });
+ this.assertCompiled("", "{$foo.$bar.baz}", { foo: { hoge: null }, bar: "hoge" });
+ this.assertCompiled("", "{$foo.$bar.baz}", { foo: { hoge: { baz: "abc" } }, bar: null });
+ },
+ testEmbedDottedUndefinedReferenceReturnsEmptyString: function () {
+ this.assertCompiled("", "{$foo.bar}", { });
+ this.assertCompiled("", "{$foo.bar.baz}", { foo: { } });
+ },
testEmbedVariableWithCursors: function () {
this.assertCompiled("abc", "{$foo->bar}", { foo: { bar: "abc" } });
this.assertCompiled("abc", "{$foo->bar->baz}", { foo: { bar: { baz: "abc" } } });
this.assertCompiled("abc", "{$foo->$bar->baz}", { foo: { hoge: { baz: "abc" } }, bar: "hoge" });
},
+ testEmbedCursorNullReferenceReturnsEmptyString: function () {
+ this.assertCompiled("", "{$foo->bar}", { foo: null });
+ this.assertCompiled("", "{$foo->bar->baz}", { foo: { bar: null } });
+ },
+ testEmbedCursorUndefinedReferenceReturnsEmptyString: function () {
+ this.assertCompiled("", "{$foo->bar}", { });
+ this.assertCompiled("", "{$foo->bar->baz}", { foo: { } });
+ },
testEmbedVariableWithIndexer: function () {
this.assertCompiled("abc", "{$foo[bar]}", { foo: { bar: "abc" } });
this.assertCompiled("abc", "{$foo[123]}", { foo: { 123: "abc"} });
@@ -120,6 +148,12 @@ new Test.Unit.Runner({
this.assertCompiled("abc", "{$foo($bar)}", { foo: function (a) { return a }, bar: "abc" });
this.assertCompiled("abcdef", "{$foo($bar, \"def\")}", { foo: function (a, b) { return a + b }, bar: "abc" });
},
+ testEmbedVariableWithMethodCall: function () {
+ var klass = function (s) { this.s = s; };
+ klass.prototype.bar = function () { return this.s };
+ this.assertCompiled("abc", "{$foo.bar()}", { foo: new klass("abc") });
+ this.assertCompiled("abc", "{$foo.bar().bar()}", { foo: new klass(new klass("abc")) });
+ },
testEmbedVariableWithNestedFunctionCallFails: function () {
this.assertRaise("SyntaxError", function () {
var compiled = Jarty.compile("{$foo($bar())}");
@@ -261,6 +295,12 @@ new Test.Unit.Runner({
this.assertCompiled("def", "{if $foo && $bar}abc{else}def{/if}", { foo: true, bar: false });
this.assertCompiled("def", "{if $foo && $bar}abc{else}def{/if}", { foo: false, bar: true });
this.assertCompiled("def", "{if $foo && $bar}abc{else}def{/if}", { foo: false, bar: false });
+ this.assertCompiled("abc", "{if $foo && $bar && $baz}abc{else}def{/if}", { foo: true, bar: true, baz: true });
+ this.assertCompiled("def", "{if $foo && $bar && $baz}abc{else}def{/if}", { foo: true, bar: true, baz: false });
+ this.assertCompiled("def", "{if $foo && $bar && $baz}abc{else}def{/if}", { foo: true, bar: false, baz: true });
+ this.assertCompiled("def", "{if $foo && $bar && $baz}abc{else}def{/if}", { foo: true, bar: false, baz: false });
+ this.assertCompiled("def", "{if $foo && $bar && $baz}abc{else}def{/if}", { foo: false, bar: false, baz: false });
+ this.assertCompiled("def", "{if $foo && $bar && $baz}abc{else}def{/if}", { foo: false, bar: true, baz: true });
this.assertCompiled("abc", "{if $foo and $bar}abc{else}def{/if}", { foo: true, bar: true });
},
testIfOr: function () {
@@ -268,6 +308,11 @@ new Test.Unit.Runner({
this.assertCompiled("abc", "{if $foo || $bar}abc{else}def{/if}", { foo: true, bar: false });
this.assertCompiled("abc", "{if $foo || $bar}abc{else}def{/if}", { foo: false, bar: true });
this.assertCompiled("def", "{if $foo || $bar}abc{else}def{/if}", { foo: false, bar: false });
+ this.assertCompiled("abc", "{if $foo || $bar || $baz}abc{else}def{/if}", { foo: true, bar: true, baz: true });
+ this.assertCompiled("abc", "{if $foo || $bar || $baz}abc{else}def{/if}", { foo: true, bar: true, baz: false });
+ this.assertCompiled("abc", "{if $foo || $bar || $baz}abc{else}def{/if}", { foo: false, bar: false, baz: true });
+ this.assertCompiled("abc", "{if $foo || $bar || $baz}abc{else}def{/if}", { foo: true, bar: false, baz: false });
+ this.assertCompiled("def", "{if $foo || $bar || $baz}abc{else}def{/if}", { foo: false, bar: false, baz: false });
this.assertCompiled("abc", "{if $foo or $bar}abc{else}def{/if}", { foo: true, bar: true });
},
testIfNot: function () {
@@ -284,6 +329,16 @@ new Test.Unit.Runner({
this.assertCompiled("", "{if $foo == 456}abc{/if}", { foo: 123 });
this.assertCompiled("abc", "{if $foo == 123 && $bar == 456}abc{/if}", { foo: 123, bar: 456 });
},
+ testIfStrictEqual: function () {
+ this.assertCompiled("abc", "{if $foo === 123}abc{/if}", { foo: 123 });
+ this.assertCompiled("", "{if $foo === 123}abc{/if}", { foo: "123" });
+ this.assertCompiled("", "{if $foo === '123'}abc{/if}", { foo: 123 });
+ this.assertCompiled("abc", "{if $foo === 'hoge'}abc{/if}", { foo: "hoge" });
+ this.assertCompiled("abc", "{if $foo === $bar}abc{/if}", { foo: 123, bar: 123 });
+ this.assertCompiled("", "{if $foo === $bar}abc{/if}", { foo: 123, bar: "123" });
+ this.assertCompiled("", "{if $foo === 456}abc{/if}", { foo: 123 });
+ this.assertCompiled("abc", "{if $foo === 123 && $bar == 456}abc{/if}", { foo: 123, bar: 456 });
+ },
testIfNotEqual: function () {
this.assertCompiled("", "{if $foo != 123}abc{/if}", { foo: 123 });
this.assertCompiled("", "{if $foo != 'hoge'}abc{/if}", { foo: "hoge" });
Please sign in to comment.
Something went wrong with that request. Please try again.