stringstream.js 4.8 KB

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