Cleanup parser code to expose smaller API

This commit is contained in:
Misko Hevery 2011-04-05 11:00:26 -07:00
parent bb67ee8d28
commit 0e17ade959
7 changed files with 31 additions and 54 deletions

View file

@ -35,19 +35,14 @@ function toJson(obj, pretty) {
function fromJson(json, useNative) { function fromJson(json, useNative) {
if (!isString(json)) return json; if (!isString(json)) return json;
var obj, p, expression; var obj;
try { try {
if (useNative && window.JSON && window.JSON.parse) { if (useNative && window.JSON && window.JSON.parse) {
obj = JSON.parse(json); obj = JSON.parse(json);
return transformDates(obj); return transformDates(obj);
} }
return parser(json, true).primary()();
p = parser(json, true);
expression = p.primary();
p.assertAllConsumed();
return expression();
} catch (e) { } catch (e) {
error("fromJson error: ", json, e); error("fromJson error: ", json, e);
throw e; throw e;

View file

@ -92,7 +92,6 @@ function expressionCompile(exp){
if (!fn) { if (!fn) {
var p = parser(exp); var p = parser(exp);
var fnSelf = p.statements(); var fnSelf = p.statements();
p.assertAllConsumed();
fn = compileCache[exp] = extend( fn = compileCache[exp] = extend(
function(){ return fnSelf(this);}, function(){ return fnSelf(this);},
{fnSelf: fnSelf}); {fnSelf: fnSelf});

View file

@ -240,22 +240,29 @@ function parser(text, json){
function (){ throwError("is not valid json", {text:text, index:0}); }; function (){ throwError("is not valid json", {text:text, index:0}); };
} }
return { return {
assertAllConsumed: assertAllConsumed, assignable: assertConsumed(assignable),
assignable: assignable, primary: assertConsumed(primary),
primary: primary, statements: assertConsumed(statements),
statements: statements, validator: assertConsumed(validator),
validator: validator, formatter: assertConsumed(formatter),
formatter: formatter, filter: assertConsumed(filter)
filter: filter,
//TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular)
watch: watch
}; };
function assertConsumed(fn) {
return function(){
var value = fn();
if (tokens.length !== 0) {
throwError("is an unexpected token", tokens[0]);
}
return value;
};
}
/////////////////////////////////// ///////////////////////////////////
function throwError(msg, token) { function throwError(msg, token) {
throw Error("Parse Error: Token '" + token.text + throw Error("Syntax Error: Token '" + token.text +
"' " + msg + " at column " + "' " + msg + " at column " +
(token.index + 1) + " of expression [" + (token.index + 1) + " of the expression [" +
text + "] starting at [" + text.substring(token.index) + "]."); text + "] starting at [" + text.substring(token.index) + "].");
} }
@ -313,12 +320,6 @@ function parser(text, json){
return tokens.length > 0; return tokens.length > 0;
} }
function assertAllConsumed(){
if (tokens.length !== 0) {
throwError("is extra token not part of expression", tokens[0]);
}
}
function statements(){ function statements(){
var statements = []; var statements = [];
while(true) { while(true) {
@ -639,24 +640,6 @@ function parser(text, json){
}; };
} }
//TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular)
function watch () {
var decl = [];
while(hasTokens()) {
decl.push(watchDecl());
if (!expect(';')) {
assertAllConsumed();
}
}
assertAllConsumed();
return function (self){
for ( var i = 0; i < decl.length; i++) {
var d = decl[i](self);
self.addListener(d.name, d.fn);
}
};
}
function watchDecl () { function watchDecl () {
var anchorName = expect().text; var anchorName = expect().text;
consume(":"); consume(":");

View file

@ -581,7 +581,7 @@ describe('Binder', function(){
browserTrigger(second, 'click'); browserTrigger(second, 'click');
assertTrue(second.hasClass("ng-exception")); assertTrue(second.hasClass("ng-exception"));
expect(errorLogs.shift()[0]).toMatchError(/Parse Error: Token ':' not a primary expression/); expect(errorLogs.shift()[0]).toMatchError(/Syntax Error: Token ':' not a primary expression/);
}); });
it('ItShouldSelectTheCorrectRadioBox', function(){ it('ItShouldSelectTheCorrectRadioBox', function(){

View file

@ -163,37 +163,37 @@ describe('json', function(){
describe('security', function(){ describe('security', function(){
it('should not allow naked expressions', function(){ it('should not allow naked expressions', function(){
expect(function(){fromJson('1+2');}). expect(function(){fromJson('1+2');}).
toThrow(new Error("Parse Error: Token '+' is extra token not part of expression at column 2 of expression [1+2] starting at [+2].")); toThrow(new Error("Syntax Error: Token '+' is an unexpected token at column 2 of the expression [1+2] starting at [+2]."));
}); });
it('should not allow naked expressions group', function(){ it('should not allow naked expressions group', function(){
expect(function(){fromJson('(1+2)');}). expect(function(){fromJson('(1+2)');}).
toThrow(new Error("Parse Error: Token '(' is not valid json at column 1 of expression [(1+2)] starting at [(1+2)].")); toThrow(new Error("Syntax Error: Token '(' is not valid json at column 1 of the expression [(1+2)] starting at [(1+2)]."));
}); });
it('should not allow expressions in objects', function(){ it('should not allow expressions in objects', function(){
expect(function(){fromJson('{a:abc()}');}). expect(function(){fromJson('{a:abc()}');}).
toThrow(new Error("Parse Error: Token 'abc' is not valid json at column 4 of expression [{a:abc()}] starting at [abc()}].")); toThrow(new Error("Syntax Error: Token 'abc' is not valid json at column 4 of the expression [{a:abc()}] starting at [abc()}]."));
}); });
it('should not allow expressions in arrays', function(){ it('should not allow expressions in arrays', function(){
expect(function(){fromJson('[1+2]');}). expect(function(){fromJson('[1+2]');}).
toThrow(new Error("Parse Error: Token '+' is not valid json at column 3 of expression [[1+2]] starting at [+2]].")); toThrow(new Error("Syntax Error: Token '+' is not valid json at column 3 of the expression [[1+2]] starting at [+2]]."));
}); });
it('should not allow vars', function(){ it('should not allow vars', function(){
expect(function(){fromJson('[1, x]');}). expect(function(){fromJson('[1, x]');}).
toThrow(new Error("Parse Error: Token 'x' is not valid json at column 5 of expression [[1, x]] starting at [x]].")); toThrow(new Error("Syntax Error: Token 'x' is not valid json at column 5 of the expression [[1, x]] starting at [x]]."));
}); });
it('should not allow dereference', function(){ it('should not allow dereference', function(){
expect(function(){fromJson('["".constructor]');}). expect(function(){fromJson('["".constructor]');}).
toThrow(new Error("Parse Error: Token '.' is not valid json at column 4 of expression [[\"\".constructor]] starting at [.constructor]].")); toThrow(new Error("Syntax Error: Token '.' is not valid json at column 4 of the expression [[\"\".constructor]] starting at [.constructor]]."));
}); });
it('should not allow expressions ofter valid json', function(){ it('should not allow expressions ofter valid json', function(){
expect(function(){fromJson('[].constructor');}). expect(function(){fromJson('[].constructor');}).
toThrow(new Error("Parse Error: Token '.' is not valid json at column 3 of expression [[].constructor] starting at [.constructor].")); toThrow(new Error("Syntax Error: Token '.' is not valid json at column 3 of the expression [[].constructor] starting at [.constructor]."));
}); });
it('should not allow object dereference', function(){ it('should not allow object dereference', function(){

View file

@ -200,7 +200,7 @@ describe('parser', function() {
expect(function() { expect(function() {
scope.$eval("1|nonExistant"); scope.$eval("1|nonExistant");
}).toThrow(new Error("Parse Error: Token 'nonExistant' should be a function at column 3 of expression [1|nonExistant] starting at [nonExistant].")); }).toThrow(new Error("Syntax Error: Token 'nonExistant' should be a function at column 3 of the expression [1|nonExistant] starting at [nonExistant]."));
scope.$set('offset', 3); scope.$set('offset', 3);
expect(scope.$eval("'abcd'|upper._case")).toEqual("ABCD"); expect(scope.$eval("'abcd'|upper._case")).toEqual("ABCD");

View file

@ -537,7 +537,7 @@ describe("widget", function(){
compile('<input type="text" name="throw \'\'" value="x"/>'); compile('<input type="text" name="throw \'\'" value="x"/>');
expect(element.hasClass('ng-exception')).toBeTruthy(); expect(element.hasClass('ng-exception')).toBeTruthy();
expect(scope.$service('$log').error.logs.shift()[0]). expect(scope.$service('$log').error.logs.shift()[0]).
toMatchError(/Parse Error: Token '''' is extra token not part of expression/); toMatchError(/Syntax Error: Token '''' is an unexpected token/);
}); });
it('should report error on ng:change exception', function(){ it('should report error on ng:change exception', function(){
@ -545,7 +545,7 @@ describe("widget", function(){
browserTrigger(element); browserTrigger(element);
expect(element.hasClass('ng-exception')).toBeTruthy(); expect(element.hasClass('ng-exception')).toBeTruthy();
expect(scope.$service('$log').error.logs.shift()[0]). expect(scope.$service('$log').error.logs.shift()[0]).
toMatchError(/Parse Error: Token '=' implies assignment but \[a-2\] can not be assigned to/); toMatchError(/Syntax Error: Token '=' implies assignment but \[a-2\] can not be assigned to/);
}); });
}); });