UNPKG

5.29 kBJavaScriptView Raw
1"use strict";
2
3/**
4 * @param {string} text - Text to parse.
5 * @param {number} begin - Where the parsing begins (default is 0).
6 * @param {number} end - Upper bound of text to be parsed (default is `text.length`). If `end == n`, the parsing will stop at char `n - 1`.
7 */
8var TextParser = function( text, begin, end ) {
9 if( typeof text === 'undefined' ) text = '';
10 if( typeof begin === 'undefined' ) begin = 0;
11 if( typeof end === 'undefined' ) end = text.length;
12
13 this._text = text;
14 this._cursor = begin;
15 this._end = end;
16};
17
18
19/**
20 * End of stream.
21 * @return `true` is there is at least one character left in the stream.
22 */
23TextParser.prototype.eos = function() {
24 return this._cursor >= this._end;
25};
26
27/**
28 * @return Next character without advancing in the stream, or `null` if at end of stream.
29 */
30TextParser.prototype.peek = function() {
31 if (this.eos()) return null;
32 return this._text.charAt( this._cursor );
33};
34
35/**
36 * @return Next character and advance in the stream, or `null` if at end of stream.
37 */
38TextParser.prototype.next = function() {
39 if (this.eos()) return null;
40 return this._text.charAt( this._cursor++ );
41};
42
43/**
44 * If the current text starts with one of the arguments, advance the stream and return that text.
45 *
46 * @return `null` at end of stream or if no text has been found.
47 */
48TextParser.prototype.eat = function() {
49 if (!this.eos()) {
50 var i, arg;
51 for (i = 0 ; i < arguments.length ; i++) {
52 arg = arguments[i];
53 if (this._text.substr( this._cursor, arg.length ) == arg) {
54 this._cursor += arg.length;
55 return arg;
56 }
57 }
58 }
59 return null;
60};
61
62/**
63 * Return the result if `regexp` matches the text.
64 *
65 * @param {Regex} regex - Regular expression to match.
66 * @param {number} group - Group index to return (default is 0).
67 * @return void
68 */
69TextParser.prototype.eatRegex = function(regex, group) {
70 if (!regex.global) throw Error("[text-parser:eatRegex] Regular expression MUST be global! "
71 + "Please add option `g` at the end of /" + regex.source + "/.");
72 if( typeof group !== 'number' ) group = 0;
73 if (this.eos()) return null;
74 regex.lastIndex = this._cursor;
75 var matcher = regex.exec( this._text );
76 if (!matcher) return null;
77 // Match must be done at current position.
78 if (matcher.index != this._cursor) return null;
79 this._cursor += matcher[0].length;
80 return matcher[group];
81};
82
83/**
84 * Advance the stream until we reach a character in `chars`.
85 *
86 * @return `null` at end of stream, or the found string.
87 */
88TextParser.prototype.eatUntilChar = function( chars ) {
89 if (this.eos()) return null;
90 var begin = this._cursor;
91 while (this._cursor < this._end) {
92 if (chars.indexOf( this._text.charAt( this._cursor ) ) != -1 ) {
93 break;
94 }
95 this._cursor++;
96 }
97 return this._text.substr( begin, this._cursor - begin );
98};
99
100/**
101 * Advance the stream until we reach `text`.
102 *
103 * @return `null` at end of stream, or the found string.
104 */
105TextParser.prototype.eatUntilText = function( text ) {
106 if (this.eos()) return null;
107 var begin = this._cursor;
108 var len = text.length;
109 while (this._cursor < this._end) {
110 if (this._text.substr( this._cursor, len ) == text) break;
111 this._cursor++;
112 }
113 return this._text.substr( begin, this._cursor - begin );
114};
115
116/**
117 * Advance the stream until the `regex` matches.
118 *
119 * @return `null` at end of stream, or the found string.
120 */
121TextParser.prototype.eatUntilRegex = function( regex ) {
122 if (this.eos()) return null;
123 if (!regex.global) throw Error("[text-parser:eatRegex] Regular expression MUST be global! "
124 + "Please add option `g` at the end of /" + regex.source + "/.");
125 var begin = this._cursor;
126 var matcher;
127 while (this._cursor < this._end) {
128 regex.lastIndex = this._cursor;
129 matcher = regex.exec( this._text );
130 if (matcher) break;
131 this._cursor++;
132 }
133 return this._text.substr( begin, this._cursor - begin );
134};
135
136
137
138module.exports = function( args ) {
139 if( typeof args !== 'object' || args === null ) {
140 throw Error("[text-parser] Bad argument! Expecting {text, grammar, state, begin, end}.");
141 }
142
143 var grammar = args.grammar;
144 if( typeof grammar === 'undefined' ) {
145 throw Error("[text-parser] Missing mandatory argument `grammar`!");
146 }
147 var step = '';
148 // Take the first key as starting step.
149 for( step in grammar ) break;
150
151 if( typeof args.state === 'undefined' ) args.state = {};
152 var stream = new TextParser( args.text, args.begin, args.end );
153 var nextStep;
154 var stepFunc = args.grammar[step];
155 while (!stream.eos()) {
156 nextStep = stepFunc( stream, args.state );
157 if (nextStep === null) break;
158 if (typeof nextStep === 'string') {
159 stepFunc = args.grammar[nextStep];
160 if (typeof stepFunc !== 'function') {
161 throw Error("[text-parser] Invalid next step in step `"
162 + step + "`: `" + nextStep + "`!");
163 }
164 step = stepFunc;
165 }
166 }
167
168 return args.state;
169};