0
|
1 /* String streams are the things fed to parsers (which can feed them
|
|
2 * to a tokenizer if they want). They provide peek and next methods
|
|
3 * for looking at the current character (next 'consumes' this
|
|
4 * character, peek does not), and a get method for retrieving all the
|
|
5 * text that was consumed since the last time get was called.
|
|
6 *
|
|
7 * An easy mistake to make is to let a StopIteration exception finish
|
|
8 * the token stream while there are still characters pending in the
|
|
9 * string stream (hitting the end of the buffer while parsing a
|
|
10 * token). To make it easier to detect such errors, the stringstreams
|
|
11 * throw an exception when this happens.
|
|
12 */
|
|
13
|
|
14 // Make a stringstream stream out of an iterator that returns strings.
|
|
15 // This is applied to the result of traverseDOM (see codemirror.js),
|
|
16 // and the resulting stream is fed to the parser.
|
|
17 var stringStream = function(source){
|
|
18 // String that's currently being iterated over.
|
|
19 var current = "";
|
|
20 // Position in that string.
|
|
21 var pos = 0;
|
|
22 // Accumulator for strings that have been iterated over but not
|
|
23 // get()-ed yet.
|
|
24 var accum = "";
|
|
25 // Make sure there are more characters ready, or throw
|
|
26 // StopIteration.
|
|
27 function ensureChars() {
|
|
28 while (pos == current.length) {
|
|
29 accum += current;
|
|
30 current = ""; // In case source.next() throws
|
|
31 pos = 0;
|
|
32 try {current = source.next();}
|
|
33 catch (e) {
|
|
34 if (e != StopIteration) throw e;
|
|
35 else return false;
|
|
36 }
|
|
37 }
|
|
38 return true;
|
|
39 }
|
|
40
|
|
41 return {
|
|
42 // peek: -> character
|
|
43 // Return the next character in the stream.
|
|
44 peek: function() {
|
|
45 if (!ensureChars()) return null;
|
|
46 return current.charAt(pos);
|
|
47 },
|
|
48 // next: -> character
|
|
49 // Get the next character, throw StopIteration if at end, check
|
|
50 // for unused content.
|
|
51 next: function() {
|
|
52 if (!ensureChars()) {
|
|
53 if (accum.length > 0)
|
|
54 throw "End of stringstream reached without emptying buffer ('" + accum + "').";
|
|
55 else
|
|
56 throw StopIteration;
|
|
57 }
|
|
58 return current.charAt(pos++);
|
|
59 },
|
|
60 // get(): -> string
|
|
61 // Return the characters iterated over since the last call to
|
|
62 // .get().
|
|
63 get: function() {
|
|
64 var temp = accum;
|
|
65 accum = "";
|
|
66 if (pos > 0){
|
|
67 temp += current.slice(0, pos);
|
|
68 current = current.slice(pos);
|
|
69 pos = 0;
|
|
70 }
|
|
71 return temp;
|
|
72 },
|
|
73 // Push a string back into the stream.
|
|
74 push: function(str) {
|
|
75 current = current.slice(0, pos) + str + current.slice(pos);
|
|
76 },
|
|
77 lookAhead: function(str, consume, skipSpaces, caseInsensitive) {
|
|
78 function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
|
|
79 str = cased(str);
|
|
80 var found = false;
|
|
81
|
|
82 var _accum = accum, _pos = pos;
|
|
83 if (skipSpaces) this.nextWhileMatches(/[\s\u00a0]/);
|
|
84
|
|
85 while (true) {
|
|
86 var end = pos + str.length, left = current.length - pos;
|
|
87 if (end <= current.length) {
|
|
88 found = str == cased(current.slice(pos, end));
|
|
89 pos = end;
|
|
90 break;
|
|
91 }
|
|
92 else if (str.slice(0, left) == cased(current.slice(pos))) {
|
|
93 accum += current; current = "";
|
|
94 try {current = source.next();}
|
|
95 catch (e) {if (e != StopIteration) throw e; break;}
|
|
96 pos = 0;
|
|
97 str = str.slice(left);
|
|
98 }
|
|
99 else {
|
|
100 break;
|
|
101 }
|
|
102 }
|
|
103
|
|
104 if (!(found && consume)) {
|
|
105 current = accum.slice(_accum.length) + current;
|
|
106 pos = _pos;
|
|
107 accum = _accum;
|
|
108 }
|
|
109
|
|
110 return found;
|
|
111 },
|
|
112 // Wont't match past end of line.
|
|
113 lookAheadRegex: function(regex, consume) {
|
|
114 if (regex.source.charAt(0) != "^")
|
|
115 throw new Error("Regexps passed to lookAheadRegex must start with ^");
|
|
116
|
|
117 // Fetch the rest of the line
|
|
118 while (current.indexOf("\n", pos) == -1) {
|
|
119 try {current += source.next();}
|
|
120 catch (e) {if (e != StopIteration) throw e; break;}
|
|
121 }
|
|
122 var matched = current.slice(pos).match(regex);
|
|
123 if (matched && consume) pos += matched[0].length;
|
|
124 return matched;
|
|
125 },
|
|
126
|
|
127 // Utils built on top of the above
|
|
128 // more: -> boolean
|
|
129 // Produce true if the stream isn't empty.
|
|
130 more: function() {
|
|
131 return this.peek() !== null;
|
|
132 },
|
|
133 applies: function(test) {
|
|
134 var next = this.peek();
|
|
135 return (next !== null && test(next));
|
|
136 },
|
|
137 nextWhile: function(test) {
|
|
138 var next;
|
|
139 while ((next = this.peek()) !== null && test(next))
|
|
140 this.next();
|
|
141 },
|
|
142 matches: function(re) {
|
|
143 var next = this.peek();
|
|
144 return (next !== null && re.test(next));
|
|
145 },
|
|
146 nextWhileMatches: function(re) {
|
|
147 var next;
|
|
148 while ((next = this.peek()) !== null && re.test(next))
|
|
149 this.next();
|
|
150 },
|
|
151 equals: function(ch) {
|
|
152 return ch === this.peek();
|
|
153 },
|
|
154 endOfLine: function() {
|
|
155 var next = this.peek();
|
|
156 return next == null || next == "\n";
|
|
157 }
|
|
158 };
|
|
159 };
|