/
inquiry.js
165 lines (165 loc) · 7.19 KB
/
inquiry.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
! function (definition) {
if (typeof window != "undefined") window.inquiry = definition();
else if (typeof define == "function") define(definition);
else module.exports = definition();
} (function () {
function parse (rest, fixup, nesting, stop) {
var expression = [], slash = '/', args, struct, source, i, $, mod;
rest = rest.trim()
while (rest && rest[0] != stop) {
if (rest[0] != '/') {
if (/^[>![{]/.test(rest[0])) {
rest = '/.' + rest;
} else {
rest = slash + rest;
}
}
// todo: can you fold this into the regex somehow?
var descend = false
if (rest[1] == '/') {
descend = true
rest = rest.substring(1)
}
slash = '';
$ = /^\/((?:!(?![[{])|[^>![{/`"\]]|"(?:[^\\"]|\\.)*")*)(([>!]?)([[{]))?(.*)/.exec(rest);
if (!$) throw new Error("bad pattern");
//struct = [ /^['"]/.test($[1].trim()) ? $[1].trim().replace(/^(['"])(.*)\1$/g, "$2").replace(/\\(.)/g, "$1") : decodeURIComponent($[1].trim()) ]
struct = [ /^"/.exec($[1].trim()) ? JSON.parse($[1].trim()) : decodeURIComponent($[1].trim()) ]
rest = $[5];
// Check for have a predicate or a sub-expression.
switch ($[4]) {
// We want to consume the contents of the curly braces that define a
// predicate to construct a function body.
case "{":
// Depth is the number of curly braces we've encountered. We match curly
// braces until it is time to pop.
// **TODO**: Test against regular expressions. We are going to have to
// document the one valid regular expression that we know of that we
// cannot match: `/[/]/`.
mod = $[3]
if (mod == '>') {
$[3] = ''
}
source = $[3] + '(';
$ = /^(((?:[^'"}]*|'(?:[^\\']|\\.)*'|"(?:[^\\"]|\\.)*")*)})/.exec(rest);
if (!$) throw new Error("bad pattern");
source += $[2];
rest = rest.substring($[1].length);
args = ['_'];
for (i = 0; i < nesting; i++) {
args.push(Array(i + 2).join('$'), Array(i + 2).join('$') + 'i');
}
args.push.apply(args, fixup)
$ = 0; // evil reuse of `$` to count airty. for the bytes, always for the bytes
source.replace(/\$(\d+)/g, function (_, number) { $ = Math.max($, +number) });
for (i = 0; i < $; i++) {
args.push('$' + (i + 1));
}
args.push('return ' + source + ')');
if (mod == '>') {
struct.push((function (predicate) {
return function (candidate, vargs) {
var result = predicate.apply(this, [ candidate._, candidate.o, candidate.i ].concat(vargs))
if (result === (void(0))) {
return []
}
if (!Array.isArray(result)) {
result = [ result ]
}
return result.map(function (result, index) {
return { _: candidate._, o: result, i: index }
})
}
})(Function.apply(Function, args)));
} else {
struct.push((function (predicate) {
return function (candidate, vargs) {
return predicate.apply(this, [ candidate._, candidate.o, candidate.i ].concat(vargs))
? [ candidate ] : []
}
})(Function.apply(Function, args)));
}
break;
// We want to consume the contents of brackets as a sub-expression, so we
// call ourselves recursively.
case "[":
struct.push((function (negate, subquery) {
return function (candidate, args) {
var length = subquery.call(this, candidate, [ candidate.o, candidate.i ].concat(args)).length;
return (negate ? ! length : length) ? [ candidate ] : [];
}
})($[3] == '!', ($ = parse(rest, fixup, nesting + 1, "]"))[0]));
rest = $[1].slice(1);
break;
default:
struct.push(null);
}
struct.push(descend);
expression.push(struct);
rest = rest.trim()
}
return [ function (candidate, vargs) {
var candidates = [], stack = [ candidate ],
star, i, j, path, object, _;
// todo: we might be able to get: div/p/3 (third paragraph)
// todo: we might be able to get: .{ $.tag == 'div' }/.{ $.tag == 'p' }/3 (third paragraph)
// todo: appears as though .o is just .p[0] so why both?
// todo: _ appears to track the path, appears in correct for arrays, and
// appears unused, not returned to the user.
for (i = 0; i < expression.length; i++) {
while (stack.length) {
candidate = stack.shift(), object = candidate.o, path = candidate.p, _ = candidate._;
if (expression[i][2] && !Array.isArray(object)) {
for (var key in object) {
stack.unshift({ o: object[key], _: [ key ].concat(_), p: [ object[key] ].concat(path) });
}
}
if (object[expression[i][0]] !== (void(0))) {
candidates.push({ o: object[expression[i][0]], _: [ expression[i][0] ].concat(_), p: [ object ].concat(path) });
} else if (expression[i][0] == '.') {
candidates.push(candidate);
} else if (expression[i][0] == '..') {
path = path.slice();
candidates.unshift({ o: path.shift(), _: _.slice(1), p: path, i: 0 });
} else if (Array.isArray(object)) {
for (j = object.length - 1; j > -1; --j) {
stack.unshift({ o: object[j], _: [ j, expression[i][0] ].concat(_), p: [ object ].concat(path) });
}
} else if (~(star = expression[i][0].indexOf('*'))) {
for (j in object) {
if (j.indexOf(expression[i][0].substring(0, star)) == 0
&& j.lastIndexOf(expression[i][0].substring(star + 1) == j.length - (expression[i][0].length - star))) {
candidates.push({ o: object[j], _: [ j ].concat(_), p: [ object ].concat(path) });
}
}
}
}
if (expression[i][1]) {
while (candidates.length) {
candidate = candidates.shift(), object = candidate.o;
if (Array.isArray(object)) {
for (j = object.length - 1; j > -1; --j) {
candidates.unshift({ o: object[j], _: [ j, expression[i][0] ].concat(_), p: [ object ].concat(path), i: j });
}
} else {
stack.push.apply(stack, expression[i][1].call(this, candidate, vargs))
}
}
} else {
stack.unshift.apply(stack, candidates.splice(0));
}
}
// todo: I wonder if simply reversing this stack gives us descents in
// document order. if so, we sould go through the array in order and
// reverse the array here, or document reverse document order.
// todo: benchmark against simple recursion.
// todo: how to return the path.
for (j = stack.length - 1; j > -1; --j) stack[j] = stack[j].o;
return stack;
}, rest ];
}
return function (query, args) {
var func = parse(query, args || [], 1)[0];
return function (object) { return func.call(this, { o: object, _: [], p: [] }, [].slice.call(arguments, 1)) };
}
});