Lutz Roeder 5 лет назад
Родитель
Сommit
9681ce8537
2 измененных файлов с 318 добавлено и 326 удалено
  1. 131 136
      src/protobuf.js
  2. 187 190
      tools/protoc.js

+ 131 - 136
src/protobuf.js

@@ -207,7 +207,7 @@ protobuf.Reader = class {
 
 
     skip(length) {
-        if (typeof length === "number") {
+        if (typeof length === 'number') {
             if (this._position + length > this._length) {
                 throw this._indexOutOfRangeError(length);
             }
@@ -244,14 +244,14 @@ protobuf.Reader = class {
                 this.skip(4);
                 break;
             default:
-                throw new protobuf.Error("invalid wire type " + wireType + " at offset " + this._position);
+                throw new protobuf.Error('invalid wire type ' + wireType + ' at offset ' + this._position);
         }
     }
 
     pair(obj, key, value) {
         this.skip();
         this._position++;
-        const k = typeof key === "object" ? protobuf.LongBits.hash(key()) : key();
+        const k = typeof key === 'object' ? protobuf.LongBits.hash(key()) : key();
         this._position++;
         const v = value();
         obj[k] = v;
@@ -317,25 +317,25 @@ protobuf.Reader = class {
                     return bits;
             }
         }
-        throw new protobuf.Error("Invalid varint encoding.");
+        throw new protobuf.Error('Invalid varint encoding.');
     }
 
     _indexOutOfRangeError(length) {
-        return RangeError("index out of range: " + this.pos + " + " + (length || 1) + " > " + this.len);
+        return RangeError('index out of range: ' + this.pos + ' + ' + (length || 1) + ' > ' + this.len);
     }
 };
 
 protobuf.TextReader = class {
 
     constructor(text) {
-        this.text = text;
-        this.position = 0;
-        this.lineEnd = -1;
-        this.lineStart = 0;
-        this.line = -1;
-        this.depth = 0;
-        this.array_depth = 0;
-        this.token = "";
+        this._text = text;
+        this._position = 0;
+        this._lineEnd = -1;
+        this._lineStart = 0;
+        this._line = -1;
+        this._depth = 0;
+        this._arrayDepth = 0;
+        this._token = '';
     }
 
     static create(text) {
@@ -343,28 +343,28 @@ protobuf.TextReader = class {
     }
 
     start() {
-        if (this.depth > 0) {
-            this.expect("{");
+        if (this._depth > 0) {
+            this.expect('{');
         }
-        this.depth++;
+        this._depth++;
     }
 
     end() {
         const token = this.peek();
-        if (this.depth > 0 && token === "}") {
-            this.expect("}");
-            this.match(";");
-            this.depth--;
+        if (this._depth > 0 && token === '}') {
+            this.expect('}');
+            this.match(';');
+            this._depth--;
             return true;
         }
-        return token === "";
+        return token === '';
     }
 
     tag() {
         const name = this.read();
         const separator = this.peek();
-        if (separator !== "[" && separator !== "{") {
-            this.expect(":");
+        if (separator !== '[' && separator !== '{') {
+            this.expect(':');
         }
         return name;
     }
@@ -412,16 +412,16 @@ protobuf.TextReader = class {
 
     double() {
         let token = this.read();
-        if (token.startsWith("nan")) {
+        if (token.startsWith('nan')) {
             return NaN;
         }
-        if (token.startsWith("inf")) {
+        if (token.startsWith('inf')) {
             return Infinity;
         }
-        if (token.startsWith("-inf")) {
+        if (token.startsWith('-inf')) {
             return -Infinity;
         }
-        if (token.endsWith("f")) {
+        if (token.endsWith('f')) {
             token = token.substring(0, token.length - 1);
         }
         const value = Number.parseFloat(token);
@@ -435,14 +435,14 @@ protobuf.TextReader = class {
     string() {
         const token = this.read();
         if (token.length < 2) {
-            throw new protobuf.Error("String is too short" + this.location());
+            throw new protobuf.Error('String is too short' + this.location());
         }
         const quote = token[0];
-        if (quote !== "'" && quote !== "\"") {
-            throw new protobuf.Error("String is not in quotes" + this.location());
+        if (quote !== "'" && quote !== '"') {
+            throw new protobuf.Error('String is not in quotes' + this.location());
         }
         if (quote !== token[token.length - 1]) {
-            throw new protobuf.Error("String quotes do not match" + this.location());
+            throw new protobuf.Error('String quotes do not match' + this.location());
         }
         const value = token.substring(1, token.length - 1);
         this.semicolon();
@@ -479,7 +479,7 @@ protobuf.TextReader = class {
             }
             else {
                 if (i >= length) {
-                    throw new protobuf.Error("Unexpected end of bytes string" + this.location());
+                    throw new protobuf.Error('Unexpected end of bytes string' + this.location());
                 }
                 c = token.charCodeAt(i++);
                 switch (c) {
@@ -494,7 +494,7 @@ protobuf.TextReader = class {
                     case 0x78: // X
                         for (let xi = 0; xi < 2; xi++) {
                             if (i >= length) {
-                                throw new protobuf.Error("Unexpected end of bytes string" + this.location());
+                                throw new protobuf.Error('Unexpected end of bytes string' + this.location());
                             }
                             let xd = token.charCodeAt(i++);
                             xd = xd >= 65 && xd <= 70 ? xd - 55 : xd >= 97 && xd <= 102 ? xd - 87 : xd >= 48 && xd <= 57 ? xd - 48 : -1;
@@ -512,7 +512,7 @@ protobuf.TextReader = class {
                         i--;
                         for (let oi = 0; oi < 3; oi++) {
                             if (i >= length) {
-                                throw new protobuf.Error("Unexpected end of bytes string" + this.location());
+                                throw new protobuf.Error('Unexpected end of bytes string' + this.location());
                             }
                             const od = token.charCodeAt(i++);
                             if (od < 48 || od > 57) {
@@ -543,18 +543,18 @@ protobuf.TextReader = class {
     }
 
     any(message) {
-        if (this.match("[")) {
+        if (this.match('[')) {
             this.read();
-            const begin = this.position;
-            const end = this.text.indexOf("]", begin);
+            const begin = this._position;
+            const end = this._text.indexOf(']', begin);
             if (end === -1 || end >= this.next) {
-                throw new protobuf.Error("End of Any type_url not found" + this.location());
+                throw new protobuf.Error('End of Any type_url not found' + this.location());
             }
-            message.type_url = this.text.substring(begin, end);
-            this.position = end + 1;
+            message.type_url = this._text.substring(begin, end);
+            this._position = end + 1;
             message.value = this.skip().substring(1);
-            this.expect("}");
-            this.match(";");
+            this.expect('}');
+            this.match(';');
             return true;
         }
         return false;
@@ -566,10 +566,10 @@ protobuf.TextReader = class {
         let v;
         while (!this.end()) {
             switch (this.tag()) {
-                case "key":
+                case 'key':
                     k = key();
                     break;
-                case "value":
+                case 'value':
                     v = value();
                     break;
             }
@@ -590,16 +590,16 @@ protobuf.TextReader = class {
     }
 
     first() {
-        if (this.match("[")) {
-            this.array_depth++;
+        if (this.match('[')) {
+            this._arrayDepth++;
             return true;
         }
         return false;
     }
 
     last() {
-        if (this.match("]")) {
-            this.array_depth--;
+        if (this.match(']')) {
+            this._arrayDepth--;
             return true;
         }
         return false;
@@ -607,11 +607,11 @@ protobuf.TextReader = class {
 
     next() {
         const token = this.peek();
-        if (token === ",") {
+        if (token === ',') {
             this.read();
             return;
         }
-        if (token === "]") {
+        if (token === ']') {
             return;
         }
         this.handle(token);
@@ -619,37 +619,37 @@ protobuf.TextReader = class {
 
     skip() {
         let token = this.peek();
-        if (token === "{") {
-            const message = this.position;
-            const depth = this.depth;
+        if (token === '{') {
+            const message = this._position;
+            const depth = this._depth;
             this.start();
-            while (!this.end() || depth < this.depth) {
+            while (!this.end() || depth < this._depth) {
                 token = this.peek();
-                if (token === "{") {
+                if (token === '{') {
                     this.start();
                 }
-                else if (token !== "}") {
+                else if (token !== '}') {
                     this.read();
-                    this.match(";");
+                    this.match(';');
                 }
             }
-            return this.text.substring(message, this.position);
+            return this._text.substring(message, this._position);
         }
-        else if (token === "[") {
-            const list = this.position;
+        else if (token === '[') {
+            const list = this._position;
             this.read();
             while (!this.last()) {
                 token = this.read();
-                if (token === "") {
+                if (token === '') {
                     this.handle(token);
                 }
             }
-            return this.text.substring(list, this.position);
+            return this._text.substring(list, this._position);
         }
-        const position = this.position;
+        const position = this._position;
         this.read();
         this.semicolon();
-        return this.text.substring(position, this.position);
+        return this._text.substring(position, this._position);
     }
 
     handle(token) {
@@ -662,27 +662,27 @@ protobuf.TextReader = class {
 
     whitespace() {
         for (;;) {
-            while (this.position >= this.lineEnd) {
-                this.lineStart = this.lineEnd + 1;
-                this.position = this.lineStart;
-                if (this.position >= this.text.length) {
+            while (this._position >= this._lineEnd) {
+                this._lineStart = this._lineEnd + 1;
+                this._position = this._lineStart;
+                if (this._position >= this._text.length) {
                     return false;
                 }
-                this.lineEnd = this.text.indexOf("\n", this.position);
-                if (this.lineEnd === -1) {
-                    this.lineEnd = this.text.length;
+                this._lineEnd = this._text.indexOf('\n', this._position);
+                if (this._lineEnd === -1) {
+                    this._lineEnd = this._text.length;
                 }
-                this.line++;
+                this._line++;
             }
-            const c = this.text[this.position];
+            const c = this._text[this._position];
             switch (c) {
-                case " ":
-                case "\r":
-                case "\t":
-                    this.position++;
+                case ' ':
+                case '\r':
+                case '\t':
+                    this._position++;
                     break;
-                case "#":
-                    this.position = this.lineEnd;
+                case '#':
+                    this._position = this._lineEnd;
                     break;
                 default:
                     return true;
@@ -692,62 +692,62 @@ protobuf.TextReader = class {
 
     tokenize() {
         if (!this.whitespace()) {
-            this.token = "";
-            return this.token;
-        }
-        let c = this.text[this.position];
-        if (c === "[" && this.position + 2 < this.lineEnd) {
-            let i = this.position + 1;
-            let x = this.text[i];
-            if (x >= "a" && x <= "z" || x >= "A" && x <= "Z") {
+            this._token = '';
+            return this._token;
+        }
+        let c = this._text[this._position];
+        if (c === '[' && this._position + 2 < this._lineEnd) {
+            let i = this._position + 1;
+            let x = this._text[i];
+            if (x >= 'a' && x <= 'z' || x >= 'A' && x <= 'Z') {
                 i++;
-                while (i < this.lineEnd) {
-                    x = this.text[i];
+                while (i < this._lineEnd) {
+                    x = this._text[i];
                     i++;
-                    if (x >= "a" && x <= "z" || x >= "A" && x <= "Z" || x >= "0" && x <= "9" || x === "." || x === "/") {
+                    if (x >= 'a' && x <= 'z' || x >= 'A' && x <= 'Z' || x >= '0' && x <= '9' || x === '.' || x === '/') {
                         continue;
                     }
-                    if (x === "]") {
-                        this.token = this.text.substring(this.position, i);
-                        return this.token;
+                    if (x === ']') {
+                        this._token = this._text.substring(this._position, i);
+                        return this._token;
                     }
                 }
             }
         }
-        if (c === "{" || c === "}" || c === ":" || c === "[" || c === "," || c === "]" || c === ";") {
-            this.token = c;
-            return this.token;
+        if (c === '{' || c === '}' || c === ':' || c === '[' || c === ',' || c === ']' || c === ';') {
+            this._token = c;
+            return this._token;
         }
-        let position = this.position + 1;
-        if (c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_" || c === "$") {
-            while (position < this.lineEnd) {
-                c = this.text[position];
-                if (c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" || c === "_" || c === "+" || c === "-") {
+        let position = this._position + 1;
+        if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c === '_' || c === '$') {
+            while (position < this._lineEnd) {
+                c = this._text[position];
+                if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c === '_' || c === '+' || c === '-') {
                     position++;
                     continue;
                 }
                 break;
             }
-            this.token = this.text.substring(this.position, position);
-            return this.token;
+            this._token = this._text.substring(this._position, position);
+            return this._token;
         }
-        if (c >= "0" && c <= "9" || c === "-" || c === "+" || c === ".") {
-            while (position < this.lineEnd) {
-                c = this.text[position];
-                if (c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" || c === "_" || c === "+" || c === "-" || c === ".") {
+        if (c >= '0' && c <= '9' || c === '-' || c === '+' || c === '.') {
+            while (position < this._lineEnd) {
+                c = this._text[position];
+                if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c === '_' || c === '+' || c === '-' || c === '.') {
                     position++;
                     continue;
                 }
                 break;
             }
-            this.token = this.text.substring(this.position, position);
-            return this.token;
+            this._token = this._text.substring(this._position, position);
+            return this._token;
         }
-        if (c === "\"" || c === "'") {
+        if (c === '"' || c === "'") {
             const quote = c;
-            while (position < this.lineEnd) {
-                c = this.text[position];
-                if (c === "\\" && position < this.lineEnd) {
+            while (position < this._lineEnd) {
+                c = this._text[position];
+                if (c === '\\' && position < this._lineEnd) {
                     position += 2;
                     continue;
                 }
@@ -756,27 +756,27 @@ protobuf.TextReader = class {
                     break;
                 }
             }
-            this.token = this.text.substring(this.position, position);
-            return this.token;
+            this._token = this._text.substring(this._position, position);
+            return this._token;
         }
         throw new protobuf.Error("Unexpected token '" + c + "'" + this.location());
     }
 
     peek() {
-        if (!this.cache) {
-            this.token = this.tokenize();
-            this.cache = true;
+        if (!this._cache) {
+            this._token = this.tokenize();
+            this._cache = true;
         }
-        return this.token;
+        return this._token;
     }
 
     read() {
-        if (!this.cache) {
-            this.token = this.tokenize();
+        if (!this._cache) {
+            this._token = this.tokenize();
         }
-        this.position += this.token.length;
-        this.cache = false;
-        return this.token;
+        this._position += this._token.length;
+        this._cache = false;
+        return this._token;
     }
 
     expect(value) {
@@ -795,13 +795,13 @@ protobuf.TextReader = class {
     }
 
     semicolon() {
-        if (this.array_depth === 0) {
-            this.match(";");
+        if (this._arrayDepth === 0) {
+            this.match(';');
         }
     }
 
     location() {
-        return " at " + (this.line + 1).toString() + ":" + (this.position - this.lineStart + 1).toString();
+        return ' at ' + (this._line + 1).toString() + ':' + (this._position - this._lineStart + 1).toString();
     }
 };
 
@@ -853,10 +853,10 @@ protobuf.LongBits = class {
     }
 
     from(value) {
-        if (typeof value === "number") {
+        if (typeof value === 'number') {
             return protobuf.LongBits.fromNumber(value);
         }
-        if (typeof value === "string" || value instanceof String) {
+        if (typeof value === 'string' || value instanceof String) {
             if (!protobuf.Long) {
                 return protobuf.LongBits.fromNumber(parseInt(value, 10));
             }
@@ -876,19 +876,14 @@ protobuf.LongBits.zero.zzDecode = function() { return this; };
 
 protobuf.Error = class extends Error {
 
-    constructor(message, properties) {
+    constructor(message) {
         super(message);
         this.name = 'Protocol Buffer Error';
         this.message = message;
-        if (properties) {
-            for (const key of Object.keys(properties)) {
-                this[key] = properties[key];
-            }
-        }
     }
 };
 
-if (typeof module !== "undefined" && typeof module.exports === "object") {
+if (typeof module !== 'undefined' && typeof module.exports === 'object') {
     module.exports.Reader = protobuf.Reader;
     module.exports.TextReader = protobuf.TextReader;
     module.exports.Error = protobuf.Error;

+ 187 - 190
tools/protoc.js

@@ -434,182 +434,11 @@ protoc.MapField = class extends protoc.Field {
     }
 };
 
-protoc.Tokenizer = class {
-
-    constructor(source, file) {
-        this._source = source.toString();
-        this._file = file;
-        this._offset = 0;
-        this._length = source.length;
-        this._line = 1;
-        this._stack = [];
-        this._delimiter = null;
-    }
-
-    get file() {
-        return this._file;
-    }
-
-    get line() {
-        return this._line;
-    }
-
-    next() {
-        if (this._stack.length > 0) {
-            return this._stack.shift();
-        }
-        if (this._delimiter) {
-            return this._readString();
-        }
-
-        let repeat;
-        let prev;
-        let curr;
-        do {
-            if (this._offset === this._length) {
-                return null;
-            }
-            repeat = false;
-            while (/\s/.test(curr = this._get(this._offset))) {
-                if (curr === '\n') {
-                    this._line++;
-                }
-                if (++this._offset === this._length) {
-                    return null;
-                }
-            }
-
-            if (this._get(this._offset) === '/') {
-                if (++this._offset === this._length) {
-                    throw this._readError('Invalid comment');
-                }
-                if (this._get(this._offset) === '/') {
-                    while (this._get(++this._offset) !== '\n') {
-                        if (this._offset === this._length) {
-                            return null;
-                        }
-                    }
-                    this._offset++;
-                    this._line++;
-                    repeat = true;
-                }
-                else if ((curr = this._get(this._offset)) === '*') {
-                    do {
-                        if (curr === '\n') {
-                            this._line++;
-                        }
-                        if (++this._offset === this._length) {
-                            throw this._readError('Invalid comment');
-                        }
-                        prev = curr;
-                        curr = this._get(this._offset);
-                    } while (prev !== '*' || curr !== '/');
-                    this._offset++;
-                    repeat = true;
-                }
-                else {
-                    return '/';
-                }
-            }
-        }
-        while (repeat);
-
-        let end = this._offset;
-        const delimRe = /[\s{}=;:[\],'"()<>]/g;
-        delimRe.lastIndex = 0;
-        const delim = delimRe.test(this._get(end++));
-        if (!delim) {
-            while (end < this._length && !delimRe.test(this._get(end))) {
-                end++;
-            }
-        }
-        const token = this._source.substring(this._offset, this._offset = end);
-        if (token === '"' || token === "'") {
-            this._delimiter = token;
-        }
-        return token;
-    }
-
-    peek() {
-        if (!this._stack.length) {
-            const token = this.next();
-            if (token === null) {
-                return null;
-            }
-            this.push(token);
-        }
-        return this._stack[0];
-    }
-
-    push(token) {
-        this._stack.push(token);
-    }
-
-    expect(value) {
-        const token = this.peek();
-        if (token !== value) {
-            throw this._readError("Unexpected '" + token + "' instead of '" + value + "'");
-        }
-        this.next();
-    }
-
-    eat(value) {
-        const token = this.peek();
-        if (token === value) {
-            this.next();
-            return true;
-        }
-        return false;
-    }
-
-    _get(pos) {
-        return this._source.charAt(pos);
-    }
-
-    static _unescape(str) {
-        return str.replace(/\\(.?)/g, function($0, $1) {
-            switch ($1) {
-                case '\\':
-                case '':
-                    return $1;
-                case '0':
-                    return '\0';
-                case 'r':
-                    return '\r';
-                case 'n':
-                    return '\n';
-                case 't':
-                    return '\t';
-                default:
-                    return '';
-            }
-        });
-    }
-
-    _readString() {
-        const re = this._delimiter === "'" ? /(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g : /(?:"([^"\\]*(?:\\.[^"\\]*)*)")/g;
-        re.lastIndex = this._offset - 1;
-        const match = re.exec(this._source);
-        if (!match) {
-            throw this._readError('Invalid string');
-        }
-        this._offset = re.lastIndex;
-        this.push(this._delimiter);
-        this._delimiter = null;
-        return protoc.Tokenizer._unescape(match[1]);
-    }
-
-    _readError(message) {
-        const location = ' at ' + this._file + ':' + this._line.toString();
-        return new protoc.Error(message + location + '.');
-    }
-};
-
 protoc.Parser = class {
 
-    constructor(source, file, root) {
+    constructor(text, file, root) {
         this._context = root;
-        this._tokenizer = new protoc.Tokenizer(source, file);
+        this._tokenizer = new protoc.Parser.Tokenizer(text, file);
         this._head = true;
         this._imports = [];
         this._weakImports = [];
@@ -1116,6 +945,180 @@ protoc.Parser = class {
     }
 };
 
+protoc.Parser.Tokenizer = class {
+
+    constructor(text, file) {
+        this._text = text;
+        this._file = file;
+        this._position = 0;
+        this._length = text.length;
+        this._line = 1;
+        this._stack = [];
+        this._delimiter = null;
+    }
+
+    get file() {
+        return this._file;
+    }
+
+    get line() {
+        return this._line;
+    }
+
+    next() {
+        if (this._stack.length > 0) {
+            return this._stack.shift();
+        }
+        if (this._delimiter) {
+            return this._readString();
+        }
+
+        let repeat;
+        let prev;
+        let curr;
+        do {
+            if (this._position === this._length) {
+                return null;
+            }
+            repeat = false;
+            while (/\s/.test(curr = this._get(this._position))) {
+                if (curr === '\n') {
+                    this._line++;
+                }
+                this._position++;
+                if (this._position === this._length) {
+                    return null;
+                }
+            }
+
+            if (this._get(this._position) === '/') {
+                this._position++;
+                if (this._position === this._length) {
+                    throw this._readError('Invalid comment');
+                }
+                if (this._get(this._position) === '/') {
+                    while (this._get(++this._position) !== '\n') {
+                        if (this._position === this._length) {
+                            return null;
+                        }
+                    }
+                    this._position++;
+                    this._line++;
+                    repeat = true;
+                }
+                else if ((curr = this._get(this._position)) === '*') {
+                    do {
+                        if (curr === '\n') {
+                            this._line++;
+                        }
+                        this._position++;
+                        if (this._position === this._length) {
+                            throw this._readError('Invalid comment');
+                        }
+                        prev = curr;
+                        curr = this._get(this._position);
+                    } while (prev !== '*' || curr !== '/');
+                    this._position++;
+                    repeat = true;
+                }
+                else {
+                    return '/';
+                }
+            }
+        }
+        while (repeat);
+
+        let end = this._position;
+        const delimRe = /[\s{}=;:[\],'"()<>]/g;
+        delimRe.lastIndex = 0;
+        const delim = delimRe.test(this._get(end++));
+        if (!delim) {
+            while (end < this._length && !delimRe.test(this._get(end))) {
+                end++;
+            }
+        }
+        const token = this._text.substring(this._position, this._position = end);
+        if (token === '"' || token === "'") {
+            this._delimiter = token;
+        }
+        return token;
+    }
+
+    peek() {
+        if (!this._stack.length) {
+            const token = this.next();
+            if (token === null) {
+                return null;
+            }
+            this.push(token);
+        }
+        return this._stack[0];
+    }
+
+    push(token) {
+        this._stack.push(token);
+    }
+
+    expect(value) {
+        const token = this.peek();
+        if (token !== value) {
+            throw this._readError("Unexpected '" + token + "' instead of '" + value + "'");
+        }
+        this.next();
+    }
+
+    eat(value) {
+        const token = this.peek();
+        if (token === value) {
+            this.next();
+            return true;
+        }
+        return false;
+    }
+
+    _get(pos) {
+        return this._text.charAt(pos);
+    }
+
+    static _unescape(str) {
+        return str.replace(/\\(.?)/g, function($0, $1) {
+            switch ($1) {
+                case '\\':
+                case '':
+                    return $1;
+                case '0':
+                    return '\0';
+                case 'r':
+                    return '\r';
+                case 'n':
+                    return '\n';
+                case 't':
+                    return '\t';
+                default:
+                    return '';
+            }
+        });
+    }
+
+    _readString() {
+        const re = this._delimiter === "'" ? /(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g : /(?:"([^"\\]*(?:\\.[^"\\]*)*)")/g;
+        re.lastIndex = this._position - 1;
+        const match = re.exec(this._text);
+        if (!match) {
+            throw this._readError('Invalid string');
+        }
+        this._position = re.lastIndex;
+        this.push(this._delimiter);
+        this._delimiter = null;
+        return protoc.Parser.Tokenizer._unescape(match[1]);
+    }
+
+    _readError(message) {
+        const location = ' at ' + this._file + ':' + this._line.toString();
+        return new protoc.Error(message + location + '.');
+    }
+};
+
 protoc.Generator = class {
 
     constructor(root, text) {
@@ -1492,48 +1495,42 @@ protoc.Error = class extends Error {
 
 const main = (args) => {
 
-    let verbose = false;
-    let root = 'default';
-    let out = '';
-    let text = false;
-    const paths = [];
-    const files = [];
-
+    const options = { verbose: false, root: 'default', out: '', text: false, paths: [], files: [] };
     while (args.length > 0) {
         const arg = args.shift();
         switch (arg) {
             case '--verbose':
-                verbose = true;
+                options.verbose = true;
                 break;
             case '--out':
-                out = args.shift();
+                options.out = args.shift();
                 break;
             case '--root':
-                root = args.shift();
+                options.root = args.shift();
                 break;
             case '--text':
-                text = true;
+                options.text = true;
                 break;
             case '--path':
-                paths.push(args.shift());
+                options.paths.push(args.shift());
                 break;
             default:
                 if (arg.startsWith('-')) {
                     throw new protoc.Error("Invalid command line argument '" + arg + "'.");
                 }
-                files.push(arg);
+                options.files.push(arg);
                 break;
         }
     }
 
     try {
-        const content = new protoc.Generator(new protoc.Root(root, paths, files), text).content;
-        if (out) {
-            fs.writeFileSync(out, content, 'utf-8');
+        const content = new protoc.Generator(new protoc.Root(options.root, options.paths, options.files), options.text).content;
+        if (options.out) {
+            fs.writeFileSync(options.out, content, 'utf-8');
         }
     }
     catch (err) {
-        if (err instanceof protoc.Error && !verbose) {
+        if (err instanceof protoc.Error && !options.verbose) {
             process.stderr.write(err.message + '\n');
         }
         else {