| /* String streams are the things fed to parsers (which can feed them |
| * to a tokenizer if they want). They provide peek and next methods |
| * for looking at the current character (next 'consumes' this |
| * character, peek does not), and a get method for retrieving all the |
| * text that was consumed since the last time get was called. |
| * |
| * An easy mistake to make is to let a StopIteration exception finish |
| * the token stream while there are still characters pending in the |
| * string stream (hitting the end of the buffer while parsing a |
| * token). To make it easier to detect such errors, the stringstreams |
| * throw an exception when this happens. |
| */ |
| |
| // Make a stringstream stream out of an iterator that returns strings. |
| // This is applied to the result of traverseDOM (see codemirror.js), |
| // and the resulting stream is fed to the parser. |
| var stringStream = function(source){ |
| // String that's currently being iterated over. |
| var current = ""; |
| // Position in that string. |
| var pos = 0; |
| // Accumulator for strings that have been iterated over but not |
| // get()-ed yet. |
| var accum = ""; |
| // Make sure there are more characters ready, or throw |
| // StopIteration. |
| function ensureChars() { |
| while (pos == current.length) { |
| accum += current; |
| current = ""; // In case source.next() throws |
| pos = 0; |
| try {current = source.next();} |
| catch (e) { |
| if (e != StopIteration) throw e; |
| else return false; |
| } |
| } |
| return true; |
| } |
| |
| return { |
| // Return the next character in the stream. |
| peek: function() { |
| if (!ensureChars()) return null; |
| return current.charAt(pos); |
| }, |
| // Get the next character, throw StopIteration if at end, check |
| // for unused content. |
| next: function() { |
| if (!ensureChars()) { |
| if (accum.length > 0) |
| throw "End of stringstream reached without emptying buffer ('" + accum + "')."; |
| else |
| throw StopIteration; |
| } |
| return current.charAt(pos++); |
| }, |
| // Return the characters iterated over since the last call to |
| // .get(). |
| get: function() { |
| var temp = accum; |
| accum = ""; |
| if (pos > 0){ |
| temp += current.slice(0, pos); |
| current = current.slice(pos); |
| pos = 0; |
| } |
| return temp; |
| }, |
| // Push a string back into the stream. |
| push: function(str) { |
| current = current.slice(0, pos) + str + current.slice(pos); |
| }, |
| lookAhead: function(str, consume, skipSpaces, caseInsensitive) { |
| function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} |
| str = cased(str); |
| var found = false; |
| |
| var _accum = accum, _pos = pos; |
| if (skipSpaces) this.nextWhileMatches(/[\s\u00a0]/); |
| |
| while (true) { |
| var end = pos + str.length, left = current.length - pos; |
| if (end <= current.length) { |
| found = str == cased(current.slice(pos, end)); |
| pos = end; |
| break; |
| } |
| else if (str.slice(0, left) == cased(current.slice(pos))) { |
| accum += current; current = ""; |
| try {current = source.next();} |
| catch (e) {break;} |
| pos = 0; |
| str = str.slice(left); |
| } |
| else { |
| break; |
| } |
| } |
| |
| if (!(found && consume)) { |
| current = accum.slice(_accum.length) + current; |
| pos = _pos; |
| accum = _accum; |
| } |
| |
| return found; |
| }, |
| |
| // Utils built on top of the above |
| more: function() { |
| return this.peek() !== null; |
| }, |
| applies: function(test) { |
| var next = this.peek(); |
| return (next !== null && test(next)); |
| }, |
| nextWhile: function(test) { |
| var next; |
| while ((next = this.peek()) !== null && test(next)) |
| this.next(); |
| }, |
| matches: function(re) { |
| var next = this.peek(); |
| return (next !== null && re.test(next)); |
| }, |
| nextWhileMatches: function(re) { |
| var next; |
| while ((next = this.peek()) !== null && re.test(next)) |
| this.next(); |
| }, |
| equals: function(ch) { |
| return ch === this.peek(); |
| }, |
| endOfLine: function() { |
| var next = this.peek(); |
| return next == null || next == "\n"; |
| } |
| }; |
| }; |