PageSourceSearch

http://mahabadcity.ir/JavaScripts/Modules/TopNews/jsinq-query.js

js mahabadcity.ir collected 2026-10-02 23:45:47 UTC 51,809 bytes, 1,776 lines download raw bytes

1/* 
2 * JSINQ, JavaScript integrated query
3 * Copyright (c) 2009 Kai Jäger. Some rights reserved.
4 *
5 * Use of this source code is governed by an MIT-style license that can be
6 * found in the license.txt file. 
7 */
8 
9 if (typeof jsinq == 'undefined') {
10	jsinq = {};
11 }
12 
13(function() { 
14	/**
15	 * An exception that is thrown when the translation of a query expression
16	 * has failed because it contained syntactic errors. A description of
17	 * the error is given in the message field of the exception object. The
18	 * position of the error can be obtained via the line, offset and 
19	 * context fields of the exception.
20	 * @constructor
21	 * @param message The error message
22	 * @param line The line at which the error occurred
23	 * @param offset The character offset from the beginning og the line where
24	 * 	the error occurred
25	 * @param context A string that contains the part of the query expression
26	 * 	where the error occurred
27	 */
28	function QueryTranslationException(message, line, offset, context) {
29		this.message = message;
30		this.line = line;
31		this.offset = offset;
32		this.context = context;
33	}
34	QueryTranslationException.prototype = new Error();
35	QueryTranslationException.prototype.name = 'QueryTranslationException';
36	 
37	// A list of keywords that are reserved in either ECMAScript or LINQ. This
38	// list is used to determine whether a string of characters may be used
39	// as an identifier (see function isIdentifier).
40	let reservedWords = ['break', 'else', 'new', 'let', 'case', 'finally', 
41		'return', 'void', 'catch', 'for', 'switch', 'while', 'continue', 
42		'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 
43		'in', 'try', 'do', 'instanceof', 'typeof', 'abstract', 'enum', 
44		'int', 'short', 'boolean', 'export', 'interface', 'static', 'byte', 
45		'extends', 'long', 'super', 'char', 'final', 'native', 'synchronized', 
46		'class', 'float', 'package', 'throws', 'const', 'goto', 'private', 
47		'transient', 'debugger', 'implements', 'protected', 'volatile', 
48		'double', 'import', 'public', 'from', 'where', 'select', 'group',
49		'into', 'orderby', 'join', 'let', 'on', 'equals', 'by', 'ascending',
50		'descending'];
51	
52	// Characters which according to the ECMAScript language specification are
53	// considered whitespace or line terminators.
54	let whitespaceCharacters = ['\u000A', '\u000D', '\u2028', '\u2029',
55		'\u0009', '\u000B', '\u000C', '\u0020', '\u00A0', '\u1680', '\u180E',
56		'\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005', '\u2006', 
57		'\u2007', '\u2008', '\u2009', '\u200A', '\u202F', '\u205F', '\u3000'];
58	
59	// A list of binary operators as specified by the ECMAScript language
60	// specification.
61	let binaryOperators = ['<', '>', '<=', '>=', '==', '!=', '===', '!==',
62		'+', '-', '*', '%', '<<', '>>', '>>>', '&', '|', '^', '&&', '||', 
63		'=', '+=', '-=', '*=', '%=', '<<=', '>>=', '>>>=', '&=', '|=', '^=', 
64		'/', '/='];
65	
66	// Length in characters of the longest binary operator 
67	let MAX_BINARY_OPERATOR_CHARACTERS = 4;
68	
69	// Helper function that, given a linear array turns it into a look-up table
70	// and returns a function that, given a value, returns true if that value
71	// exists in the look-up table.
72	function createLookUpFunction(array) {
73		return (function() {
74			let lookUpTable = {};
75			for (let i = 0; i < array.length; i++) {
76				lookUpTable[array[i]] = true;
77			}
78			
79			return function(value) {
80				return typeof lookUpTable[value] != 'undefined';
81			}
82		})();
83	}
84	
85	// Returns true if the given character is one which may appear at the
86	// beginning of an identifier. This specifically excludes numbers.
87	// Todo: The ECMAScript language specification allows for identifiers to
88	// start with any unicode letter or with a unicode escape sequence. This
89	// function does not take this into account.
90	function isIdentifierStart(value) {
91		value = value.charAt(0);
92		return value == '$' || value == '_' || 
93			(value >= 'a' && value <= 'z') || (value >= 'A' && value <= 'Z');
94	}
95	
96	// Returns true if the given character is one which may appear anywhere
97	// in an identifier but not necessarily at the beginning. This specifically
98	// includes numbers.
99	// Todo: According to the ECMAScript language specification, identifier
100	// parts can also be unicode combining marks, unicode digits, unicode
101	// connector punctuation or unicode escape sequences. This function does
102	// not return true for any of those.
103	function isIdentifierPart(value) {
104		return isIdentifierStart(value) || isDecimalDigit(value);
105	}
106	
107	// Returns true if the given character is decimal digit.
108	function isDecimalDigit(value) {
109		return value >= '0' && value <= '9';
110	}
111	
112	// Returns true if the given character is a hexadecimal digit.
113	function isHexDigit(value) {
114		return isDecimalDigit(value) || value >= 'a' && value <= 'f' ||
115			value >= 'A' && value <= 'F';
116	}
117	
118	// Returns true if the specified string is a reserved keyword
119	let isReservedWord = createLookUpFunction(reservedWords);
120	
121	// Returns true if the specified character is whitespace or a line 
122	// terminator.
123	let isWhitespaceCharacter = createLookUpFunction(whitespaceCharacters);
124	
125	// Returns ture if the specified string is a binary operator.
126	let isBinaryOperator = createLookUpFunction(binaryOperators);
127	
128	// Constructor for a parse error object. An instance of ParseError is
129	// returned by the parsers when they fail to consume input.
130	// A parser error consists of an error message and a field 
131	// "unparsedCharacters" which is used both to determine how much input
132	// the parser consumed before failing and to find the line number and 
133	// offset of where the error happened.
134	function ParseError(message) {
135		this.message = message;
136		this.unparsedCharacters = -1;
137	}
138	
139	// This function returns true if the parse error does not denote a 
140	// complete failure, but a partial success (i.e. the parser consumed some
141	// input before failing). The parameter "rest" is the input originally fed
142	// to the parser.
143	ParseError.prototype.isPartialSuccess = function(rest) {
144		rest = rest.replace(/^\s+/g, '');
145		return this.unparsedCharacters > -1 &&
146			this.unparsedCharacters < rest.length;
147	};
148	
149	// Function that receives a string containing a query expression and 
150	// that returns either the parse tree of that expression or an error 
151	// message.
152	let parseQuery = (function() {
153		let parsers = this;
154		
155		// A parser combinator for sequential composition which, given two
156		// parsers, succeeds only if both parsers succeed. It invokes the 
157		// second parser with the input left over by the first parser. 
158		// This function is called in an unusual manner to achieve something
159		// similar to operator overloading. It allows parsers to be combined
160		// using dot notation, i.e. terminal('a').terminal('b') will invoke
161		// terminal('a') and then terminal('b') with whatever is left of the
162		// input string.
163		// The label field is used to produce more meaningful error messages.
164		function sequence(right, label) {	
165			let _this = this;
166			let func = null;
167			if (typeof _this == 'function') {
168				func = function(input) {
169					let result1 = _this(input);
170					if (result1 instanceof ParseError) {
171						return result1;
172					}
173
174					let result2 = right(result1.rest);
175					if (result2 instanceof ParseError) {
176						result2.unparsedCharacters = result1.rest.length;
177						return result2;
178					}
179					
180					return {
181						parsed: result1.parsed.concat(result2.parsed),
182						rest: result2.rest
183					};
184				};
185			} else {
186				func = right; 
187			}
188			for (let method in parsers) {
189				if (typeof parsers[method] == 'function') {
190					func[method] = parsers[method];
191				}
192			}
193			func.label = label;
194			return func;
195		}			
196		
197		// A parser that consumes a specific character sequence (a terminal).
198		this.terminal = function(terminal) {
199			return sequence.call(this, function(input) {
200				let terminalLength = terminal.length;
201				if (input.substring(0, terminalLength) == terminal) {
202					return {
203						parsed: [terminal],
204						rest: input.substring(terminalLength)
205					};
206				} else {
207					return new ParseError("'" + terminal + "' expected");
208				}
209			}, "'" + terminal + "'");
210		};	
211		
212		// Receives any number of parsers and uses the first one that does not
213		// fail. Only fails if all the parsers passed to it fail. This
214		// corresponds to the following in EBNF notation:
215		// p = (p1 | p2 | p3 | ...)
216		this.oneOf = function() {
217			let parsers = [];
218			for (let i = 0; i < arguments.length; i++) {
219				parsers.push(arguments[i]);
220			}
221			return sequence.call(this, function(input) {
222				let result;
223				let minUnparsedChars = input.length;
224				let bestParserError = null;
225				for (let i = 0; i < parsers.length; i++) {
226					result = parsers[i](input);
227					if (!(result instanceof ParseError)) {
228						return result;
229					} else if (result.isPartialSuccess(input)) {
230						minUnparsedChars = result.unparsedCharacters;
231						bestParserError = result;						
232					}
233				}
234				
235				if (bestParserError) {
236					return bestParserError;
237				}
238				
239				let labels = [];
240				for (let i = 0; i < parsers.length; i++) {
241					labels.push(parsers[i].label);
242				}
243				let last = labels.pop();
244				if (labels.length > 0) {
245					last = 'or ' + last;
246				}
247				return new ParseError('Expecting ' + 
248					labels.join(', ') + ' ' + last);
249			});	
250		};
251		
252		// Applies the specified parser zero or more times. This corresponds
253		// to the following in EBNF notation:
254		// p = {p1}
255		this.zeroOrMore = function(parser) {
256			return sequence.call(this, function(input) {
257				let last = input;
258				let accumulated = [];
259				let result;
260				do {
261					result = parser(last);
262					if (!(result instanceof ParseError)) {
263						last = result.rest;
264						accumulated = accumulated.concat(result.parsed);
265					}
266				} while (!(result instanceof ParseError));
267				
268				if (result.isPartialSuccess(last)) {
269					return result;
270				}				
271				
272				return {
273					parsed: accumulated,
274					rest: last
275				};
276			});	
277		};
278		
279		// Applies the specified parser at least once and for as long as it
280		// continues to consume input. Corresponds to the following in EBNF:
281		// p = p1 {p1}
282		this.oneOrMore = function(parser) {
283			return sequence.call(this, function(input) {
284				let last = input;
285				let accumulated = [];
286				let result;
287				do {
288					result = parser(last);
289					if (!(result instanceof ParseError)) {
290						last = result.rest;
291						accumulated = accumulated.concat(result.parsed);
292					}
293				} while (!(result instanceof ParseError));
294				
295				if (result.isPartialSuccess(last)) {
296					return result;
297				}
298				
299				if (accumulated.length > 0) {
300					return {
301						parsed: accumulated,
302						rest: last
303					};
304				} else {					
305					return new ParseError('Expecting one or more ' +
306						input.label);
307				}
308			});
309		};
310		
311		// Optionally applies the specified parser. Corresponds to the 
312		// following in EBNF:
313		// p = [p1]
314		this.optional = function(parser) {
315			return sequence.call(this, function(input) {
316				let result = parser(input);
317				if (!(result instanceof ParseError)) {
318					return result;
319				} else {
320					if (result.isPartialSuccess(input)) {
321						return result;
322					}
323					return {
324						parsed: [],
325						rest: input
326					};
327				}
328			});
329		};
330		
331		// Applies the specified parser but does not record its output. Fails
332		// if the specified parser fails.
333		this.skip = function(parser) {
334			return sequence.call(this, function(input) {
335				let result = parser(input);
336				if (!(result instanceof ParseError)) {
337					result.parsed = [];					
338				}
339				return result;
340			});			
341		};		
342		
343		// Wraps a parser into lambda. This is so that parsers defined using
344		// sequential composition can themselves be used in other sequentially
345		// composed parsers. Also used to give a label to a parser, which
346		// allows for better error messages.
347		this.asParser = function(label) {
348			let func = this;
349			return function() {
350				return sequence.call(this, function(input) {
351					return func(input);
352				}, label);
353			};
354		};
355		
356		// Lazily invokes the specified parser. This is used to define
357		// recursive which would otherwise enter an infinite recursive loop
358		// upon their definition. Also used to refer to parsers which are
359		// not yet defined.
360		this.lazy = function(parser) {
361			return sequence.call(this, function(input) {
362				return parser()(input);
363			});				
364		};
365		
366		// Invokes the specified parser and groups the result.
367		this.group = function(parser) {
368			return sequence.call(this, function(input) {
369				let result = parser(input);
370				if (!(result instanceof ParseError)) {
371					return {
372						parsed: [result.parsed],
373						rest: result.rest
374					};
375				} else {
376					return result;
377				}
378			});		
379		}
380		
381		// Takes the result of a parser and flattens it into a single value.
382		this.flatten = function(parser) {
383			return sequence.call(this, function(input) {
384				let result = parser(input);
385				if (!(result instanceof ParseError)) {
386					return {
387						parsed: [result.parsed.join(' ')],
388						rest: result.rest
389					};
390				} else {
391					return result;
392				}
393			});		
394		}		
395		
396		// Consumes a single whitespace character.
397		this.whitespace = function() {
398			return sequence.call(this, function(input) {	
399				if (input.length > 0 && 
400					isWhitespaceCharacter(input.charAt(0))) {
401					return {
402						parsed: [input.substring(0, 1)],
403						rest: input.substring(1)
404					};
405				} else {
406					return new ParseError('Expecting whitespace');
407				}
408			}, 'whitespace');
409		};		
410		
411		// Consumes any number of whitespace characters but fails if no 
412		// whitespace is consumed. The whitespace is not added to the parse
413		// tree.
414		this.mandatoryWhitespace = 
415			this.skip(
416				this.oneOrMore(
417					this.whitespace()
418				)
419			).asParser('whitespace');
420		
421		// Consumes any number of whitespace characters or none at all.
422		// Does not fail if no whitespace is consumed. THe whitespace is not
423		// added to the parse tree.
424		this.optionalWhitespace =
425			this.skip(
426				this.zeroOrMore(
427					this.whitespace()
428				)
429			).asParser('whitespace');
430		
431		// The following parsers are used to accept ECMAScript expressions.
432		// Note that JSINQ does not actually do anything with the expressions
433		// other than to put them into anonymous functions. Expressions are
434		// parsed to reliably separate them from the query keywords. 
435		// The grammar used below is adapted from the ECMAScript language
436		// specification. However, it has been heavily simplified and is often
437		// more permissive than the original grammar.
438		// The expression parser may also be helpful for implementing nested
439		// queries in the future.
440		
441		// Parses an identifier. Note that this does not comply with the
442		// ECMAScript language specification in that it neither supports
443		// unicode identifiers nor unicode escape sequences.
444		this.identifier = function() {
445			return sequence.call(this, function(input) {	
446				if (input.length < 1) {
447					return new ParseError('Unexpected end of file');
448				}
449				if (!isIdentifierStart(input.charAt(0))) {
450					return new ParseError('Invalid character found, ' +
451						'expecting identifier.');
452				}				
453				let index = 1;
454				while (index < input.length && 
455					isIdentifierPart(input.charAt(index))) {
456					++index;
457				}
458				let identifier = input.substring(0, index);
459				if (isReservedWord(identifier)) {
460					return new ParseError('Reserved word \'' + identifier + 
461						'\' cannot be used as identifier');
462				}
463				
464				// Translate placeholders in the query expression into 
465				// subscripting into the query parameters array.
466				// Todo: This is probably not the best place for this.
467				let regExp = new RegExp('^\\$[0-9]+$', 'g');
468				if (regExp.test(identifier)) {
469					identifier = '_$qp[' + identifier.substring(1) + ']';
470				}
471				
472				return {
473					parsed: [identifier],
474					rest: input.substring(index)
475				};
476			}, 'identifier');
477		};			
478		
479		// Parses a binary operator (see operator table at the beginning of 
480		// this file). Only binary operators composed of special characters
481		// are consumed (this excludes 'in' and 'instanceof').
482		this.binaryOperator = function() {
483			return sequence.call(this, function(input) {
484				for (let i = MAX_BINARY_OPERATOR_CHARACTERS; i >= 1; i--) {
485					let operator = input.substring(0, i);
486					if (isBinaryOperator(operator)) {
487						return {
488							parsed: operator,
489							rest: input.substring(i)							
490						};
491					}
492				}
493				return new ParseError('Expecting binary operator');
494			}, 'binary operator');
495		};
496		
497		// Parses a string literal, delimited by either single- or double-
498		// quotation marks.
499		this.stringLiteral = function() {
500			return sequence.call(this, function(input) {
501				if (input.length < 2 || input.charAt(0) != '"' && 
502					input.charAt(0) != "'") {
503					return new ParseError('Malformed string literal');
504				}
505				let delimiter = input.charAt(0);
506				let last = delimiter;
507				let current;	
508				for (let i = 1; i < input.length; i++) {
509					current = input.charAt(i);
510					if (current == delimiter && last != '\\') {
511						++i;
512						return {
513							parsed: [input.substring(0, i)],
514							rest: input.substring(i)
515						};
516					}
517					last = current;
518				}
519				return new ParseError('Unterminated string literal');
520			}, 'string literal');
521		};
522		
523		// Parses a number literal, i.e. a decimal number, a hex number or
524		// a floating point number in either decimal or exponential notation.
525		this.numericLiteral = function() {
526			return sequence.call(this, function(input) {
527				if (input.length < 1) {
528					return new ParseError('Unexpected end of file');
529				}
530				
531				let digitFunction = isDecimalDigit;
532				let wantDecimalSeparator = true;
533				let wantExponent = false;	
534				let wantSign = false;
535				let startIndex = 0;
536				
537				if (input.substring(0, 2).toLowerCase() == '0x') {
538					digitFunction = isHexDigit;
539					wantDecimalSeparator = false;
540					startIndex = 2;
541				} else if (input.charAt(0) == '.') {
542					wantDecimalSeparator = false;
543					wantExponent = true;
544					startIndex = 1;
545				}
546				
547				let index = startIndex;
548				for (let i = startIndex; i < input.length; i++) {
549					let current = input.charAt(i);
550					if (wantDecimalSeparator && current == '.') {
551						wantDecimalSeparator = false;
552						wantExponent = true;
553					} else if (wantExponent && (current == 'e' ||
554						current == 'E')) {
555						wantExponent = false;
556						wantSign = true;
557					} else if (wantSign && (current == '+' || 
558						current == '-')) {
559						wantSign = false;
560					} else if (digitFunction(current)) {
561						wantSign = false;
562					} else {
563						break;
564					}
565					++index;
566				}
567				if (index == startIndex) {
568					return new ParseError('Invalid number');
569				}
570				return {
571					parsed: [input.substring(0, index)],
572					rest: input.substring(index)
573				};
574			}, 'number literal');
575		};
576		
577		// Parses the body of a function expression (i.e. an inline function
578		// definition). The actual contents of the function body are ignored.
579		// (What this parser really does is to count curly braces).
580		this.functionExpressionBody = function() {
581			return sequence.call(this, function(input) {
582				if (input.charAt(0) != '{') {
583					return new ParseError("'{' expected");
584				}
585				let last = '';
586				let inString = false;
587				let stringTerminator = '';
588				let curlyOpenCount = 1;
589				for (let i = 1; i < input.length; i++) {
590					let current = input.charAt(i);
591					if (!inString) {
592						if (current == '{') {
593							++curlyOpenCount;
594						} else if (current == '}') {
595							--curlyOpenCount;
596						} else if (current == '"' || current == "'") {
597							inString = true;
598							stringTerminator = current;
599						}
600					} else {
601						if (current == stringTerminator && last != '\\') {
602							inString = false;
603						}
604					}
605					last = current;
606					if (curlyOpenCount == 0) {
607						++i;
608						return {
609							parsed: [input.substring(0, i)],
610							rest: input.substring(i)
611						};
612					}
613				}
614				return new ParseError('Unexpected end of file');
615			}, 'function expression');
616		};
617		
618		// The following productions are adapted from the ECMAScript grammar.
619		
620		this.arrayLiteral =
621			this.terminal('[').
622			optionalWhitespace().
623			optional(			
624				this.optional(
625					this.lazy(function() {
626						return parsers.secondaryExpression();
627					})
628				).				
629				zeroOrMore(
630					this.optionalWhitespace().
631					terminal(',').
632					optionalWhitespace().
633					optional(
634						this.lazy(function() {
635							return parsers.secondaryExpression();
636						})
637					)
638				).
639				optionalWhitespace()
640			).
641			optionalWhitespace().
642			terminal(']').
643			asParser('array literal');
644		
645		this.propertyName = 
646			this.oneOf(
647				this.identifier(),
648				this.stringLiteral(),
649				this.numericLiteral()
650			).asParser('property name');
651		
652		this.propertyNameAndValue = 
653			this.propertyName().
654			optionalWhitespace().
655			terminal(':').
656			optionalWhitespace().
657			lazy(function() {
658				return parsers.secondaryExpression();
659			}).
660			asParser('property name and value');	
661		
662		this.objectLiteral =
663			this.terminal('{').
664			optionalWhitespace().
665			optional(
666				this.propertyNameAndValue().
667				zeroOrMore(
668					this.optionalWhitespace().
669					terminal(',').
670					optionalWhitespace().
671					propertyNameAndValue()
672				)
673			).
674			optionalWhitespace().
675			terminal('}').
676			asParser('object literal');
677		
678		this.literal =
679			this.oneOf(
680				this.terminal('null'),
681				this.terminal('true'),
682				this.terminal('false'),
683				this.terminal('Infinity'),
684				this.numericLiteral(),
685				this.stringLiteral(),
686				this.arrayLiteral(),
687				this.objectLiteral()
688			).asParser('literal');
689		
690		this.primaryExpression = 
691			this.oneOf(
692				this.terminal('this'),
693				this.identifier(),
694				this.literal(),
695				this.terminal('(').
696				optionalWhitespace().
697				lazy(function() {
698					return parsers.expression()
699				}).
700				optionalWhitespace().
701				terminal(')')
702			).asParser('primary expression');
703		
704		this.parameters = 
705			this.terminal('(').
706			optionalWhitespace().
707			optional(
708				this.lazy(function() {
709					return parsers.secondaryExpression();
710				}).
711				zeroOrMore(				
712					this.optionalWhitespace().
713					terminal(',').
714					optionalWhitespace().
715					lazy(function() {
716						return parsers.secondaryExpression();
717					})
718				)
719			).
720			optionalWhitespace().
721			terminal(')').
722			asParser('arguments');
723				
724		this.functionExpression =
725			this.terminal('function').
726			optional(
727				this.mandatoryWhitespace().				
728				identifier()
729			).
730			optionalWhitespace().
731			terminal('(').
732			optionalWhitespace().
733			optional(
734				this.identifier().
735				zeroOrMore(
736					this.optionalWhitespace().
737					terminal(',').
738					optionalWhitespace().
739					identifier()
740				)
741			).
742			optionalWhitespace().
743			terminal(')').			
744			optionalWhitespace().
745			functionExpressionBody().
746			asParser('function expression');		
747		
748		this.leftHandSideExpression =
749			this.zeroOrMore(
750				this.terminal('new').
751				mandatoryWhitespace()
752			).
753			oneOf(
754				this.primaryExpression(),
755				this.functionExpression()
756			).
757			zeroOrMore(
758				this.oneOf(
759					this.optionalWhitespace().				
760					parameters(),
761					this.optionalWhitespace().				
762					terminal('[').				
763					optionalWhitespace().
764					lazy(function() {
765						return parsers.secondaryExpression();
766					}).
767					optionalWhitespace().
768					terminal(']'),
769					this.optionalWhitespace().
770					terminal('.').
771					optionalWhitespace().
772					identifier()
773				)
774			).asParser('left-hand-side expression');
775		
776		this.unaryExpression =
777			this.zeroOrMore(
778				this.oneOf(
779					this.terminal('delete').
780					mandatoryWhitespace(),
781					this.terminal('void').
782					mandatoryWhitespace(),
783					this.terminal('typeof').
784					mandatoryWhitespace(),
785					this.terminal('++'),
786					this.terminal('--'),
787					this.terminal('+'),
788					this.terminal('-'),
789					this.terminal('~'),
790					this.terminal('!')
791				).
792				optionalWhitespace()
793			).
794			leftHandSideExpression().
795			zeroOrMore(
796				this.optionalWhitespace().
797				oneOf(
798					this.terminal('++'),
799					this.terminal('--')
800				)
801			).
802			asParser('unary expression');
803		
804		this.secondaryExpression =
805			this.flatten(
806				this.unaryExpression().
807				zeroOrMore(
808					this.optionalWhitespace().
809					oneOf(
810						this.binaryOperator(),
811						this.terminal('in').
812						mandatoryWhitespace(),
813						this.terminal('instanceof').
814						mandatoryWhitespace()
815					).
816					optionalWhitespace().
817					unaryExpression()
818				)
819			).asParser('expression');
820		
821		this.expression = 
822			this.flatten(
823				this.secondaryExpression().
824				zeroOrMore(
825					this.optionalWhitespace().
826					terminal(',').
827					optionalWhitespace().
828					secondaryExpression()
829				)
830			).asParser('expression');
831		
832		// The following is an almost direct translation of the LINQ grammar 
833		// given in the official C# Language Specification into combinatory 
834		// parsers.
835		
836		this.fromClause = 
837			this.terminal('from').
838			group(
839				this.mandatoryWhitespace().
840				identifier().
841				mandatoryWhitespace().
842				skip(
843					this.terminal('in')
844				).
845				mandatoryWhitespace().
846				expression()
847			).asParser('from clause');
848		
849		this.letClause = 			
850			this.terminal('let').
851			group(
852				this.mandatoryWhitespace().
853				identifier().
854				optionalWhitespace().
855				skip(
856					this.terminal('=')
857				).
858				optionalWhitespace().
859				expression()
860			).asParser('let clause');
861		
862		this.whereClause =
863			this.terminal('where').
864			group(
865				this.mandatoryWhitespace().
866				expression()				
867			).asParser('where clause');
868		
869		this.joinClause = 
870			this.terminal('join').
871			group(
872				this.mandatoryWhitespace().
873				identifier().
874				mandatoryWhitespace().
875				skip(
876					this.terminal('in')
877				).
878				mandatoryWhitespace().
879				expression().
880				mandatoryWhitespace().
881				skip(
882					this.terminal('on')
883				).
884				mandatoryWhitespace().
885				expression().
886				mandatoryWhitespace().
887				skip(
888					this.terminal('equals')
889				).
890				mandatoryWhitespace().
891				expression()
892			).asParser('join clause');	
893		
894		this.joinIntoClause = 
895			this.terminal('join').
896			group(
897				this.mandatoryWhitespace().
898				identifier().
899				mandatoryWhitespace().
900				skip(
901					this.terminal('in')
902				).
903				mandatoryWhitespace().
904				expression().
905				mandatoryWhitespace().
906				skip(
907					this.terminal('on')
908				).
909				mandatoryWhitespace().
910				expression().
911				mandatoryWhitespace().
912				skip(
913					this.terminal('equals')
914				).
915				mandatoryWhitespace().
916				expression().
917				mandatoryWhitespace().
918				skip(
919					this.terminal('into')
920				).
921				mandatoryWhitespace().
922				identifier()
923			).asParser('join into clause');			
924		
925		this.orderingDirection = 
926			this.oneOf(
927				this.skip(				
928					this.terminal('ascending')
929				), 
930				this.terminal('descending')
931			).asParser('ordering direction');
932		
933		this.ordering = 
934			this.group(
935				this.secondaryExpression().				
936				optional(
937					this.mandatoryWhitespace().
938					orderingDirection()
939				)
940			).asParser('ordering');
941		
942		this.orderings =
943			this.ordering().
944			zeroOrMore(
945				this.optionalWhitespace().
946				skip(
947					this.terminal(',')
948				).
949				optionalWhitespace().
950				ordering()
951			).asParser('orderings');
952		
953		this.orderByClause =
954			this.terminal('orderby').
955			group(
956				this.mandatoryWhitespace().
957				orderings()
958			).asParser('orderby clause');
959
960		this.selectClause =
961			this.terminal('select').
962			group(
963				this.mandatoryWhitespace().
964				expression()
965			).asParser('select clause');
966		
967		this.groupClause = 
968			this.terminal('group').
969			group(
970				this.mandatoryWhitespace().
971				expression().
972				mandatoryWhitespace().
973				skip(
974					this.terminal('by')
975				).
976				mandatoryWhitespace().
977				expression()
978			).asParser('group by clause');
979		
980		this.selectOrGroupClause =
981			this.group(
982				this.oneOf(
983					this.selectClause(),
984					this.groupClause()
985				)
986			).asParser('select or group by clause');
987		
988		this.queryBodyClause =
989			this.group(
990				this.oneOf(
991					this.fromClause(),
992					this.letClause(),
993					this.whereClause(),
994					this.joinIntoClause(),
995					this.joinClause(),
996					this.orderByClause()
997				)
998			).asParser('query body');
999		
1000		this.queryBodyClauses = 
1001			this.queryBodyClause().
1002			zeroOrMore(
1003				this.mandatoryWhitespace().
1004				queryBodyClause()
1005			).asParser('query body');
1006		
1007		this.queryBody = 
1008			this.optional(
1009				this.queryBodyClauses().
1010				mandatoryWhitespace()
1011			).			
1012			selectOrGroupClause().
1013			optional(
1014				this.group(
1015					this.mandatoryWhitespace().
1016					lazy(function() { 
1017						return parsers.queryContinuation(); 
1018					})
1019				)
1020			).asParser('query body');
1021		
1022		this.queryContinuation =
1023			this.terminal('into').
1024			group(
1025				this.mandatoryWhitespace().
1026				identifier().
1027				mandatoryWhitespace().
1028				lazy(function() { 
1029					return parsers.queryBody(); 
1030				})
1031			).asParser('into clause');	
1032		
1033		this.queryExpression =
1034			this.optionalWhitespace().			
1035			group(
1036				this.fromClause()
1037			).
1038			mandatoryWhitespace().
1039			queryBody();
1040		
1041		return this.queryExpression;
1042	}).call({});
1043	
1044	// Function that receives a query expression tree (as produced by the
1045	// parseQuery function) and translates it into JavaScript code.
1046	let compileQuery = (function() {
1047		// Values returned by a visitor function that tell the tree-
1048		// traverser if and how to continue. 			
1049		// Parent = continue with parent node, Continue = continue with next
1050		// node, Abort = abort the traversal
1051		let TRAVERSE_PARENT = 1;
1052		let TRAVERSE_CONTINUE = 2;
1053		let TRAVERSE_ABORT = 3;
1054		
1055		// Prefix for transparent identifiers injected during query 
1056		// transformation
1057		let TRANSPARENT_IDENTIFIER_PREFIX = '_$ti';
1058		
1059		// Sequencing-mechanism for transparent identifiers
1060		let lastTransparentIdentifier = 0;
1061		
1062		// Keeps track of transparent identifiers and which other transparent
1063		// identifiers they capture.
1064		let transparentIdentifiers = {};
1065		
1066		// Returns true if the specified identifier is "transparent" (i.e.
1067		// generated by the compiler)
1068		function isTransparentIdentifier(identifier) {
1069			return identifier.substring(0, 
1070				TRANSPARENT_IDENTIFIER_PREFIX.length) == 
1071				TRANSPARENT_IDENTIFIER_PREFIX;
1072		}
1073		
1074		// Retrieves a transparent identifier for an object that captures
1075		// multiple range letiables. The names of the captured range variables
1076		// are passed to this function.
1077		function getTransparentIdentifierFor() {
1078			lastTransparentIdentifier++;
1079			let identifier = TRANSPARENT_IDENTIFIER_PREFIX + 
1080				lastTransparentIdentifier;				
1081			let transparentMembers = [];
1082			for (let i = 0; i < arguments.length; i++) {
1083				if (isTransparentIdentifier(arguments[i])) {
1084					transparentMembers.push(arguments[i]);
1085				}
1086			}			
1087			transparentIdentifiers[identifier] = transparentMembers;			
1088			return identifier;
1089		}
1090		
1091		// Emits the JavaScript source code for a lambda expression with the
1092		// specified parameters and body.
1093		function emitLambda(parameters, body) {
1094			let code = ['function(', parameters.join(', '), ') {'];
1095			
1096			let withOpen = [];
1097			let withClose = [];
1098			let resolveStack = [];
1099			
1100			// If any of the parameters of the lambda expression is a 
1101			// transparent identifier, put a with statement around the
1102			// body of the lambda so that its members are in scope. Do this
1103			// "recursively" if necessary.
1104			for (let i = 0; i < parameters.length; i++) {
1105				if (isTransparentIdentifier(parameters[i])) {
1106					resolveStack.push(parameters[i]);
1107					do {
1108						let topOfStack = resolveStack.pop();
1109						withOpen.push('with (', topOfStack, ') {');
1110						withClose.push('}');
1111						let child = transparentIdentifiers[topOfStack];
1112						if (transparentIdentifiers[topOfStack].length > 0) {
1113							resolveStack.push(child[0]);
1114						} else {
1115							break;
1116						}
1117					} while (true);			
1118				}
1119			}
1120			
1121			code = code.concat(withOpen);
1122			code.push(body);
1123			code = code.concat(withClose);			
1124			code.push('}');
1125			return code.join('');
1126		}
1127		
1128		// The query compiler works by repeatedly applying transformations
1129		// to the expression tree. Each transformation is associated with a
1130		// specific pattern of query clauses and it is applied for as long as
1131		// that specific pattern occurs in the expression tree. Additionally,
1132		// transformations are grouped into sections and each section is again
1133		// applied repeatedly until all patterns it recognizes have either been
1134		// compiled to JavaScript code or transformed into something else.
1135		// The transformations are kept in the following array. They are 
1136		// applied in the order in which they are specified. 
1137		// Note that this corresponds directly to the point "Query expression
1138		// translation" in the C# Language Specification.
1139		
1140		let transformations = [
1141			// Select and groupby clauses with continuations
1142			[
1143				{
1144					pattern: ['*', 'into', null],
1145					transformer: function(root, match) {
1146						let intoClause = root[match[1]];
1147						
1148						root[match[1]] = intoClause[1][1];
1149						for (let j = 2; j < intoClause[1].length; j++) {
1150							root.splice(match[1] + (j - 1), 0, 
1151								intoClause[1][j]);
1152						}	
1153						root.unshift([
1154							'from',
1155							[
1156								intoClause[1][0],
1157								root.splice(0, match[0])
1158							]
1159						]);
1160						return true;
1161					}
1162				}
1163			],
1164			// Degenerate query expressions
1165			[
1166				{
1167					pattern: ['from', 'select', null],
1168					transformer: function(root, match) {
1169						let fromClause = root[match[0]][1];
1170						let selectClause = root[match[1]][1];
1171						if (match[1] - match[0] > 1) {
1172							return false;
1173						}
1174						if (fromClause[0] == selectClause[0]) {
1175							root.splice(match[0], 2, [
1176								'compiled',
1177								[
1178									'(', fromClause[1], ').select(',
1179									emitLambda([fromClause[0]], 'return ' + 
1180										fromClause[0] + ';'), ')'
1181								]
1182							]);
1183							return true;
1184						}
1185						return false;
1186					}
1187				}
1188			],
1189			// From, let, where, join and orderby clauses
1190			[
1191				{
1192					pattern: ['from', 'from', 'select', null],
1193					transformer: function(root, match) {
1194						let firstFromClause = root[match[0]][1];
1195						let secondFromClause = root[match[1]][1];
1196						let selectClause = root[match[2]][1];
1197
1198						if (match[1] - match[0] > 1) {
1199							return false;
1200						}
1201						
1202						root.splice(match[0], 2, [
1203							'compiled',
1204							[
1205								'(', firstFromClause[1], ')'
1206							]
1207						]);
1208					
1209						root.splice(match[1], 1, [
1210							'compiled',
1211							[
1212								'.selectMany(',
1213								emitLambda([firstFromClause[0]], 'return ' +
1214									secondFromClause[1] + ';'), ', ',
1215								emitLambda([firstFromClause[0], 
1216									secondFromClause[0]], 'return ' + 
1217									selectClause[0] + ';'), ')'
1218							]
1219						]);
1220						return true;
1221					}
1222				},
1223				{
1224					pattern: ['from', 'from'],
1225					transformer: function(root, match) {
1226						let firstFromClause = root[match[0]][1];
1227						let secondFromClause = root[match[1]][1];
1228						root.splice(match[0], 1, [
1229							'from',
1230							[
1231								getTransparentIdentifierFor(firstFromClause[0], 
1232									secondFromClause[0]),
1233								firstFromClause[1]
1234							]
1235						]);
1236						root.splice(match[1], 1, [
1237							'compiled',
1238							[
1239								'.selectMany(', emitLambda(
1240									[firstFromClause[0]], 'return ' + 
1241									secondFromClause[1] + ';'), 
1242								', ', emitLambda([firstFromClause[0], 
1243									secondFromClause[0]], 'return { \'' + 
1244									firstFromClause[0] + '\':' +
1245									firstFromClause[0] + ', \'' + 
1246									secondFromClause[0] + '\':' + 
1247									secondFromClause[0] + '};'), ')'
1248							]
1249						]);
1250						return true;
1251					}
1252				},
1253				{
1254					pattern: ['from', 'let'],
1255					transformer: function(root, match) {
1256						let fromClause = root[match[0]][1];
1257						let letClause = root[match[1]][1];
1258						root.splice(match[0], 1, [
1259							'from',
1260							[
1261								getTransparentIdentifierFor(fromClause[0],
1262									letClause[0]),
1263								fromClause[1]
1264							]
1265						]);
1266						root.splice(match[1], 1, [
1267							'compiled',
1268							[
1269								'.select(', emitLambda([fromClause[0]], 
1270									'return {\'' + fromClause[0] + '\': ' + 
1271									fromClause[0] + ', \'' + letClause[0] +
1272									'\': ' + letClause[1] + '};'), ')'
1273							]
1274						]);
1275						return true;
1276					}
1277				},
1278				{
1279					pattern: ['from', 'where'],
1280					transformer: function(root, match) {
1281						let fromClause = root[match[0]][1];
1282						let whereClause = root[match[1]][1];
1283						root.splice(match[1], 1, [
1284							'compiled',
1285							[
1286								'.where(', emitLambda([fromClause[0]], 
1287									'return ' + whereClause[0] + ';'), ')'
1288							]
1289						]);
1290						return true;
1291					}				
1292				},
1293				{
1294					pattern: ['from', 'join', 'select', null],
1295					transformer: function(root, match) {
1296						let fromClause = root[match[0]][1];
1297						let joinClause = root[match[1]][1];
1298						let selectClause = root[match[2]][1];												
1299						
1300						if (match[2] - match[1] > 1) {
1301							return false;
1302						}						
1303						
1304						// Exclude join-intos
1305						if (joinClause.length > 4) {
1306							return false;
1307						}
1308						root.splice(match[0], 1, [
1309							'compiled',
1310							[
1311								'(', fromClause[1], ')'
1312							]
1313						]);					
1314						root.splice(match[1], 2, [
1315							'compiled',
1316							[
1317								'.join(', joinClause[1], ', ', 
1318								emitLambda([fromClause[0]], 'return ' + 
1319									joinClause[2] + ';'), ', ', 
1320								emitLambda([joinClause[0]], 'return ' + 
1321									joinClause[3] + ';'), ', ', 
1322								emitLambda([fromClause[0], joinClause[0]], 
1323									'return ' + selectClause[0] + ';'), ')'
1324							]
1325						]);
1326						return true;
1327					}
1328				},
1329				{
1330					pattern: ['from', 'join'],
1331					transformer: function(root, match) {
1332						let fromClause = root[match[0]][1];
1333						let joinClause = root[match[1]][1];
1334						// Exclude join-intos
1335						if (joinClause.length > 4) {
1336							return false;
1337						}
1338						root.splice(match[0], 1, [
1339							'from',
1340							[
1341								getTransparentIdentifierFor(fromClause[0],
1342									joinClause[0]),
1343								fromClause[1]
1344							]
1345						]);
1346						root.splice(match[1], 1, [
1347							'compiled',
1348							[
1349								'.join(', joinClause[1], 
1350								', ', emitLambda([fromClause[0]], 'return ' +
1351									joinClause[2] + ';'), ', ', 
1352								emitLambda([joinClause[0]], 'return ' + 
1353									joinClause[3] + ';'), ', ',
1354								emitLambda([fromClause[0], joinClause[0]], 
1355									'return {\'' + fromClause[0] + '\': ' +
1356									fromClause[0] + ', \'' + joinClause[0] + 
1357									'\': ' + joinClause[0] + '};'), ')'
1358							]
1359						]);
1360						return true;
1361					}
1362				},
1363				{
1364					pattern: ['from', 'join', 'select', null],
1365					transformer: function(root, match) {
1366						let fromClause = root[match[0]][1];
1367						let joinClause = root[match[1]][1];
1368						let selectClause = root[match[2]][1];
1369						
1370						if (match[2] - match[1] > 1) {
1371							return false;
1372						}
1373						if (joinClause.length < 5) {
1374							return false;
1375						}						
1376						
1377						root.splice(match[0], 1, [
1378							'compiled',
1379							[
1380								'(', fromClause[1], ')'
1381							]
1382						]);
1383						root.splice(match[1], 2, [
1384							'compiled',
1385							[
1386								'.groupJoin(', joinClause[1], ', ', 
1387								emitLambda([fromClause[0]], 'return ' + 
1388									joinClause[2] + ';'), ', ', 
1389								emitLambda([joinClause[0]], 'return ' + 
1390									joinClause[3] + ';'), ', ',
1391								emitLambda([fromClause[0], joinClause[4]], 
1392									'return ' + selectClause[0] + ';'), ')'
1393							]
1394						]);
1395						return true;
1396					}
1397				},
1398				{
1399					pattern: ['from', 'join'],
1400					transformer: function(root, match) {
1401						let fromClause = root[match[0]][1];
1402						let joinClause = root[match[1]][1];
1403						
1404						if (joinClause.length < 5) {
1405							return false;
1406						}												
1407						
1408						root.splice(match[0], 1, [
1409							'from',
1410							[
1411								getTransparentIdentifierFor(fromClause[0],
1412									joinClause[4]),
1413								fromClause[1]
1414							]
1415						]);
1416						root.splice(match[1], 1, [
1417							'compiled',
1418							[
1419								'.groupJoin(', joinClause[1], ', ', 
1420								emitLambda([fromClause[0]], 'return ' +
1421									joinClause[2] + ';'), ', ',
1422								emitLambda([joinClause[0]], 'return ' + 
1423									joinClause[3] + ';'), ', ',
1424								emitLambda([fromClause[0], joinClause[4]], 
1425									'return {\'' + fromClause[0] + '\': ' +
1426									fromClause[0] + ', \'' + joinClause[4] + 
1427									'\': ' + joinClause[4] + '};'), ')'
1428							]
1429						]);
1430						return true;
1431					}
1432				},
1433				{
1434					pattern: ['from', 'orderby'],
1435					transformer: function(root, match) {
1436						let fromClause = root[match[0]][1];
1437						let orderbyClause = root[match[1]][1];
1438						let orderingPostfix = '';
1439						if (orderbyClause[0].length > 1) {
1440							orderingPostfix = 'Descending';
1441						}
1442						
1443						let code = ['.orderBy', orderingPostfix, '(', 
1444							emitLambda([fromClause[0]], 'return ' + 
1445								orderbyClause[0][0] + ';'), ')'];
1446						
1447						for (let i = 1; i < orderbyClause.length; i++) {
1448							orderingPostfix = '';
1449							if (orderbyClause[i].length > 1) {
1450								orderingPostfix = 'Descending';
1451							}
1452							code.push('.thenBy', orderingPostfix, '(',
1453								emitLambda([fromClause[0]], 'return ' +
1454									orderbyClause[i][0] + ';'), ')');
1455						}
1456						
1457						root.splice(match[1], 1, [
1458							'compiled',
1459							code
1460						]);
1461						return true;
1462					}				
1463				}
1464			],
1465			// Select clauses
1466			[
1467				{
1468					pattern: ['from', 'select', null],
1469					transformer: function(root, match) {
1470						let fromClause = root[match[0]][1];
1471						let selectClause = root[match[1]][1];					
1472						root.splice(match[0], 1, [
1473							'compiled',
1474							[
1475								'(', fromClause[1], ')'
1476							]
1477						]);
1478						root.splice(match[1], 1, [
1479							'compiled',
1480							[
1481								'.select(', emitLambda([fromClause[0]], 
1482									'return ' + selectClause[0] + ';'), ')'
1483							]
1484						]);
1485						return true;
1486					}				
1487				}
1488			],
1489			// Groupby clauses
1490			[
1491				{
1492					pattern: ['from', 'group', null],
1493					transformer: function(root, match) {
1494						let fromClause = root[match[0]][1];
1495						let groupClause = root[match[1]][1];					
1496						root.splice(match[0], 1, [
1497							'compiled',
1498							[
1499								'(', fromClause[1], ')'
1500							]
1501						]);
1502						let code = ['.groupBy(', emitLambda([fromClause[0]], 
1503							'return ' + groupClause[1] + ';')];
1504						
1505						if (fromClause[0] != groupClause[0]) {
1506							code.push(', ', emitLambda([fromClause[0]], 
1507								'return ' + groupClause[0] + ';'));
1508						}
1509						code.push(')');
1510								
1511						root.splice(match[1], 1, [
1512							'compiled',
1513							code
1514						]);					
1515						return true;
1516					}				
1517				}	
1518			]
1519		];
1520
1521		// Traverses the expression tree in post-order and invokes the 
1522		// specified visitor function for each node in the tree.
1523		function postOrderTraverse(root, visitor) {
1524			let i;
1525			for (i = 0; i < root.length; i++) {
1526				if (root[i][0] == 'from' && root[i][1][1] instanceof Array) {
1527					postOrderTraverse(root[i][1][1], visitor);
1528				} else if (root[i][0] == 'compiled') {					
1529					for (let j = 0; j < root[i][1].length; j++) {
1530						if (root[i][1][j] instanceof Array) {
1531							postOrderTraverse(root[i][1][j], visitor);
1532						}
1533					}
1534				}
1535				let result = visitor(root, i, root[i]);
1536				if (result == TRAVERSE_PARENT) {
1537					break;
1538				} else if (result == TRAVERSE_ABORT) {
1539					return;
1540				}
1541			}
1542			visitor(root, i, null);
1543		}
1544		
1545		// Applies the transformations specified above.
1546		function applyTransformations(expressionTree) {			
1547			let transformationBlockCount = transformations.length;
1548			let transformationCount;
1549			let transformation;
1550			let pattern;
1551			let patternIndex;
1552			let match;
1553			let transformationApplied;
1554			let blockApplied;
1555			
1556			// This function performs the pattern matching used to determine
1557			// which transformation to apply.
1558			function visitor(root, index, node) {
1559				let nodeType = null;
1560				if (node != null) {
1561					nodeType = node[0];					
1562				}
1563					
1564				// Ignore node that are already compiled
1565				if (nodeType == 'compiled') {
1566					return TRAVERSE_CONTINUE
1567				}				
1568				
1569				let expectedNodeType = pattern[patternIndex];
1570				let nextExpectedNodeType = 
1571					pattern[patternIndex + 1];				
1572			
1573				if (nodeType == expectedNodeType) {					
1574					match.push(index);					
1575					++patternIndex;
1576				} else if (expectedNodeType == '*') {
1577					if (nodeType == nextExpectedNodeType &&
1578						typeof match[patternIndex] != 'undefined') {
1579						patternIndex += 2;
1580						match.push(index);
1581					} else {
1582						if (typeof match[patternIndex] == 'undefined') {
1583							match[patternIndex] = 0;
1584						}	
1585						match[patternIndex]++;
1586					}
1587				} else {
1588					if (node == null) {
1589						patternIndex = 0;
1590						match = [];
1591					}
1592					return TRAVERSE_PARENT;
1593				}
1594				
1595				if (node == null) {					
1596					let returnCode = TRAVERSE_PARENT;
1597					if (patternIndex >= pattern.length) {
1598						let applied = transformation.transformer(root, match);
1599						if (applied) {
1600							returnCode = TRAVERSE_ABORT;
1601							transformationApplied = true;
1602							blockApplied = true;
1603						}
1604					}
1605					patternIndex = 0;
1606					match = [];
1607					return returnCode;
1608				}
1609				
1610				return TRAVERSE_CONTINUE;
1611			}
1612			
1613			for (let i = 0; i < transformationBlockCount; i++) {
1614				transformationBlock = transformations[i];
1615				transformationCount = transformationBlock.length;
1616				do {
1617					blockApplied = false;
1618					for (let j = 0; j < transformationCount; j++) {
1619						transformation = transformationBlock[j];
1620						do {				
1621							pattern = transformation.pattern;
1622							patternIndex = 0;
1623							match = [];
1624							transformationApplied = false;
1625							postOrderTraverse(expressionTree, visitor);
1626						} while (transformationApplied);
1627					}
1628				} while (blockApplied);
1629			}
1630			
1631			return expressionTree;
1632		}
1633		
1634		// After all transformations have been applied, the expression tree
1635		// consists only of nodes that contain JavaScript code. This function
1636		// traverses the expression tree one last time, gathers the JavaScript
1637		// code and finally returns it as a string.
1638		function gatherCode(expressionTree) {
1639			let code = [];
1640			
1641			function traverser(root) {
1642				let i;
1643				for (i = 0; i < root.length; i++) {					
1644					if (root[i][0] == 'compiled') {
1645						for (let j = 0; j < root[i][1].length; j++) {
1646							if (root[i][1][j] instanceof Array) {
1647								traverser(root[i][1][j]);
1648							} else {
1649								code = code.concat(root[i][1][j]);		
1650							}
1651						}
1652					}
1653				}
1654			}
1655			traverser(expressionTree);						
1656			return code.join('');
1657		}
1658		
1659		return function(expressionTree) {
1660			// Reset the transparent identifier list and -counter
1661			lastTransparentIdentifier = 0;
1662			transparentIdentifiers = {};
1663			
1664			if (expressionTree instanceof ParseError) {
1665				throw expressionTree;
1666			} else if (expressionTree.rest.replace(/^\s+/, '').length > 0) {
1667				throw new ParseError('End of file expected', 
1668					expressionTree.rest.length);
1669			}
1670			
1671			expressionTree = applyTransformations(expressionTree.parsed);			
1672			return 'return ' + gatherCode(expressionTree) + ';';
1673		};
1674	})();
1675	
1676	// Given a ParseError object, this function retrieves the line number,
1677	// offset and context of the error from the original query string.
1678	function getErrorContext(error, query) {
1679		let totalOffset = query.length - error.unparsedCharacters;
1680		let context = {
1681			line: 1,
1682			offset: 1,
1683			context: 0
1684		};
1685
1686		let lastBreak = 0;
1687		let lastCharacter = '';
1688		for (let i = 0; i < totalOffset; i++) {
1689			let character = query.charAt(i);
1690			if (character == "\r" || character == "\n" && 
1691				lastCharacter != "\r") {
1692				++context.line;
1693				lastBreak = i;
1694			}		
1695		}
1696		context.offset = totalOffset - lastBreak;
1697		context.context = query.substring(totalOffset);
1698		return context;
1699	}
1700 
1701	/**
1702	 * Constructor for a query object. A query object works a lot like a
1703	 * prepared statement in that invoking the constructor will compile but
1704	 * not execute the query. Such a query object can be applied to the same
1705	 * or even to different datasets any number of times without having to 
1706	 * re-compile the query. Inside the query expression, you may use 
1707	 * placeholders for letiables that you wish to pass into the query from 
1708	 * the outside. Such placeholders begin with a dollar-sign ($) and are
1709	 * followed by an index. You can assign concrete values to these 
1710	 * placeholders by invoking the setValue method on the query object.
1711	 * An examplary query that uses placeholders is the following:
1712	 * 		from c in $0 select c.lastname
1713	 * Upon invoking this constructor, the query is compiled into JavaScript
1714	 * code. Syntactical errors in the query expression will cause the
1715	 * constructor to throw a QueryTranslationException. Semantic errors
1716	 * in the query expression (such as the use of undeclared letiables) do
1717	 * not usually surface at this stage. Instead, their appearence is 
1718	 * generally deferred until the execute method is invoked.
1719	 * @constructor
1720	 * @param query The string containing the query
1721	 */
1722	function Query(query) {		
1723		let queryParameters = [];
1724		let queryFunction = null;
1725		try {
1726			queryFunction = new Function('_$qp', 
1727				compileQuery(parseQuery(query)));
1728		} catch (e) {			
1729			if (e instanceof ParseError) {
1730				let errorContext = getErrorContext(e, query);
1731				throw new QueryTranslationException(e.message,
1732					errorContext.line, errorContext.offset, 
1733					errorContext.context);
1734			} else {
1735				throw e;
1736			}
1737		}
1738		
1739		/**
1740		 * Binds a specified value to a placeholder
1741		 * @param index Index of the placeholder
1742		 * @param value The value to bind to the placeholder
1743		 */		
1744		this.setValue = function(index, value) {
1745			if (queryFunction == null) {
1746				throw new jsinq.InvalidOperationException();
1747			}
1748			queryParameters[index] = value;	
1749		};		
1750
1751		/**
1752		 * Executes the query and returns the result
1753		 * @return The result of the query
1754		 */		
1755		this.execute = function() {
1756			if (queryFunction == null) {
1757				throw new jsinq.InvalidOperationException();
1758			}
1759			return queryFunction(queryParameters);
1760		};
1761		
1762		/**
1763		 * Returns the anonymous function that executes the query
1764		 * @return The query function
1765		 */
1766		this.getQueryFunction = function() {
1767			if (queryFunction == null) {
1768				throw new jsinq.InvalidOperationException();
1769			}			
1770			return queryFunction;
1771		};
1772	}
1773	
1774	this.QueryTranslationException = QueryTranslationException;
1775	this.Query = Query;
1776 }).call(jsinq);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.