Skip to content
This repository was archived by the owner on Jul 15, 2021. It is now read-only.

Commit b5f41fc

Browse files
committed
Merge branch 'tracer'
# Conflicts: # Gruntfile.js # demo/js/sqlite-parser-demo.js # dist/sqlite-parser-min.js # dist/sqlite-parser.js # index.js # lib/parser.js # package.json # test/index-spec.js
2 parents dd25059 + bc5536a commit b5f41fc

7 files changed

Lines changed: 243 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
All notable changes to this project will be documented in this file.
33

44
## [Unreleased][unreleased]
5+
### Changed
6+
- Created a `tracer` branch to continue developing the `Tracer` class into something viable.
57

68
## [v0.12.0-beta.1] - 2015-09-29
79
### Changed

Gruntfile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ module.exports = function(grunt) {
8383
options: {
8484
failOnError: true
8585
},
86-
command: './node_modules/.bin/pegjs --optimize speed src/grammar.pegjs lib/parser.js'
86+
command: './node_modules/.bin/pegjs --trace src/grammar.pegjs lib/parser.js'
8787
},
8888
test: {
8989
options: {

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
[![devDependencies Status Image](https://img.shields.io/david/dev/codeschool/sqlite-parser.svg)](https://github.com/codeschool/sqlite-parser/)
66
[![License Type Image](https://img.shields.io/github/license/codeschool/sqlite-parser.svg)](https://github.com/codeschool/sqlite-parser/blob/master/LICENSE)
77

8+
## This branch is a work-in-progress
9+
_Note: There is a currently a significant performance penalty (14x) to using this branch for the smart-error functionality._
10+
811
This library parses SQLite queries, using JavaScript, and generates
912
_abstract syntax tree_ (AST) representations of the input strings. A
1013
syntax error is produced if an AST cannot be generated.
@@ -47,6 +50,13 @@ sqliteParser(sampleSQL, function (err, res) {
4750
});
4851
```
4952

53+
## Syntax Errors
54+
55+
This parser uses the `--trace` flag exposed in `pegjs` to create "smart" error
56+
messages. The parser includes a `Trace` class that keeps track of which grammar
57+
rules were being traversed just prior to the error and uses that information
58+
to improve the error message and location information.
59+
5060
## AST
5161

5262
**NOTE: The SQLite AST is a work-in-progress and subject to change.**

index.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
/**
22
* sqlite-parser
3+
* @copyright Code School 2015 {@link http://codeschool.com}
4+
* @author Nick Wronski <nick@javascript.com>
35
*/
4-
var parser = require('./lib/parser');
6+
var parser = require('./lib/parser'),
7+
Tracer = require('./lib/tracer');
8+
59
function sqliteParser(source, callback) {
10+
var t = Tracer(), res;
611
try {
7-
callback(null, parser.parse(source));
12+
res = parser.parse(source, {
13+
'tracer': t
14+
});
15+
callback(null, res);
816
} catch (e) {
9-
callback(e);
17+
callback(e instanceof parser.SyntaxError ? t.smartError(e) : e);
1018
}
1119
}
1220

lib/tracer.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*!
2+
* sqlite-parser
3+
* @copyright Code School 2015 {@link http://codeschool.com}
4+
* @author Nick Wronski <nick@javascript.com>
5+
*/
6+
var parserUtils = require('./parser-util');
7+
8+
module.exports = (function (util) {
9+
Tracer = function Tracer() {
10+
if (!(this instanceof Tracer)) {
11+
return new Tracer();
12+
}
13+
this.events = [];
14+
this.indentation = 0;
15+
this.whitespaceRule = /(^whitespace)|(char$)|(^[oe]$)/i;
16+
this.statementRule = /Statement$/i;
17+
this.firstNodeRule = /(Statement|Clause)$/i;
18+
};
19+
20+
Tracer.prototype.trace = function trace(event) {
21+
var that = this, lastIndex, lastWsIndex;
22+
event.indentation = this.indentation;
23+
switch (event.type) {
24+
case 'rule.enter':
25+
// add entered leaf
26+
this.events.push(event);
27+
this.indentation += 1;
28+
break;
29+
case 'rule.match':
30+
this.indentation -= 1;
31+
break;
32+
case 'rule.fail':
33+
// remove failed leaf
34+
lastIndex = util.findLastIndex(this.events, {rule: event.rule});
35+
lastWsIndex = util.findLastIndex(this.events, function (e) {
36+
return !that.whitespaceRule.test(e.rule);
37+
});
38+
if (that.whitespaceRule.test(event.rule) || lastIndex === lastWsIndex) {
39+
this.events.splice(lastIndex, 1);
40+
}
41+
this.indentation -= 1;
42+
break;
43+
}
44+
};
45+
46+
/**
47+
* @note
48+
* There is way too much magic/nonsense in this method now. Need to
49+
* come up with an alternative approach to getting the right
50+
* information for syntax errors.
51+
*/
52+
Tracer.prototype.smartError = function smartError(err) {
53+
var that = this, message, location, chain, chainDetail, firstNode,
54+
bestNode = {indentation: -1}, deep = false, stmts = 0,
55+
namedEvents = this.events
56+
.filter(function (e) {
57+
return e.description !== null &&
58+
!that.whitespaceRule.test(e.rule);
59+
})
60+
.reverse();
61+
62+
chain = util.takeWhile(namedEvents, function (elem) {
63+
if (/^(sym\_semi)$/i.test(elem.rule)) {
64+
stmts += 1;
65+
}
66+
if (stmts > 1) {
67+
return false;
68+
}
69+
if (!deep) {
70+
if (elem.indentation > bestNode.indentation) {
71+
bestNode = elem;
72+
} else {
73+
deep = true;
74+
}
75+
} else if (/^(stmt)$/i.test(elem.rule)) {
76+
deep = true;
77+
return true;
78+
}
79+
return true;
80+
});
81+
82+
if (chain.length) {
83+
location = bestNode.location;
84+
firstNode = util.findWhere(chain, function (elem) {
85+
return that.firstNodeRule.test(elem.description) &&
86+
elem.description !== bestNode.description &&
87+
elem.indentation !== bestNode.indentation;
88+
});
89+
if (firstNode != null) {
90+
if (this.statementRule.test(bestNode.description) &&
91+
this.statementRule.test(firstNode.description)) {
92+
chainDetail = firstNode.description;
93+
} else {
94+
chainDetail = bestNode.description + ' (' + firstNode.description + ')';
95+
}
96+
} else {
97+
chainDetail = bestNode.description;
98+
}
99+
message = 'Syntax error found near ' + chainDetail;
100+
util.extend(err, {
101+
'message': message,
102+
'location': location
103+
});
104+
}
105+
return err;
106+
};
107+
108+
return Tracer;
109+
})(parserUtils);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"load-grunt-tasks": "^3.3.0",
4747
"lodash": "^3.10.0",
4848
"mocha": "^2.2.5",
49-
"pegjs": "git+https://github.com/dmajda/pegjs.git#master",
49+
"pegjs": "git+https://github.com/nwronski/pegjs.git#master",
5050
"prettyjson": "^1.1.2",
5151
"promise": "^7.0.3"
5252
},

src/tracer.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*!
2+
* sqlite-parser
3+
* @copyright Code School 2015 {@link http://codeschool.com}
4+
* @author Nick Wronski <nick@javascript.com>
5+
*/
6+
var parserUtils = require('./parser-util');
7+
8+
module.exports = (function (util) {
9+
Tracer = function Tracer() {
10+
if (!(this instanceof Tracer)) {
11+
return new Tracer();
12+
}
13+
this.events = [];
14+
this.indentation = 0;
15+
this.whitespaceRule = /(^whitespace)|(char$)|(^[oe]$)/i;
16+
this.statementRule = /Statement$/i;
17+
this.firstNodeRule = /(Statement|Clause)$/i;
18+
};
19+
20+
Tracer.prototype.trace = function trace(event) {
21+
var that = this, lastIndex, lastWsIndex;
22+
event.indentation = this.indentation;
23+
switch (event.type) {
24+
case 'rule.enter':
25+
// add entered leaf
26+
this.events.push(event);
27+
this.indentation += 1;
28+
break;
29+
case 'rule.match':
30+
this.indentation -= 1;
31+
break;
32+
case 'rule.fail':
33+
// remove failed leaf
34+
lastIndex = util.findLastIndex(this.events, {rule: event.rule});
35+
lastWsIndex = util.findLastIndex(this.events, function (e) {
36+
return !that.whitespaceRule.test(e.rule);
37+
});
38+
if (that.whitespaceRule.test(event.rule) || lastIndex === lastWsIndex) {
39+
this.events.splice(lastIndex, 1);
40+
}
41+
this.indentation -= 1;
42+
break;
43+
}
44+
};
45+
46+
/**
47+
* @note
48+
* There is way too much magic/nonsense in this method now. Need to
49+
* come up with an alternative approach to getting the right
50+
* information for syntax errors.
51+
*/
52+
Tracer.prototype.smartError = function smartError(err) {
53+
var that = this, message, location, chain, chainDetail, firstNode,
54+
bestNode = {indentation: -1}, deep = false, stmts = 0,
55+
namedEvents = this.events
56+
.filter(function (e) {
57+
return e.description !== null &&
58+
!that.whitespaceRule.test(e.rule);
59+
})
60+
.reverse();
61+
62+
chain = util.takeWhile(namedEvents, function (elem) {
63+
if (/^(sym\_semi)$/i.test(elem.rule)) {
64+
stmts += 1;
65+
}
66+
if (stmts > 1) {
67+
return false;
68+
}
69+
if (!deep) {
70+
if (elem.indentation > bestNode.indentation) {
71+
bestNode = elem;
72+
} else {
73+
deep = true;
74+
}
75+
} else if (/^(stmt)$/i.test(elem.rule)) {
76+
deep = true;
77+
return true;
78+
}
79+
return true;
80+
});
81+
82+
if (chain.length) {
83+
location = bestNode.location;
84+
firstNode = util.findWhere(chain, function (elem) {
85+
return that.firstNodeRule.test(elem.description) &&
86+
elem.description !== bestNode.description &&
87+
elem.indentation !== bestNode.indentation;
88+
});
89+
if (firstNode != null) {
90+
if (this.statementRule.test(bestNode.description) &&
91+
this.statementRule.test(firstNode.description)) {
92+
chainDetail = firstNode.description;
93+
} else {
94+
chainDetail = bestNode.description + ' (' + firstNode.description + ')';
95+
}
96+
} else {
97+
chainDetail = bestNode.description;
98+
}
99+
message = 'Syntax error found near ' + chainDetail;
100+
util.extend(err, {
101+
'message': message,
102+
'location': location
103+
});
104+
}
105+
return err;
106+
};
107+
108+
return Tracer;
109+
})(parserUtils);

0 commit comments

Comments
 (0)