typesxml
Version:
Open source XML library written in TypeScript
295 lines • 10.1 kB
JavaScript
"use strict";
/*******************************************************************************
* Copyright (c) 2023-2026 Maxprograms.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse License 1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-v10.html
*
* Contributors:
* Maxprograms - initial API and implementation
*******************************************************************************/
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsonTokenizer = void 0;
const NeedMoreDataError_js_1 = require("../NeedMoreDataError.js");
class JsonTokenizer {
buffer = '';
offset = 0;
finished = false;
lookahead;
enqueue(chunk) {
if (chunk.length === 0) {
return;
}
this.buffer += chunk;
}
markFinished() {
this.finished = true;
}
nextToken() {
if (this.lookahead) {
const token = this.lookahead;
this.lookahead = undefined;
return token;
}
return this.readToken();
}
peekToken() {
if (!this.lookahead) {
this.lookahead = this.readToken();
}
return this.lookahead;
}
readToken() {
this.skipWhitespace();
if (this.offset >= this.buffer.length) {
if (this.finished) {
return { type: 'eof' };
}
throw new NeedMoreDataError_js_1.NeedMoreDataError();
}
const char = this.buffer[this.offset];
switch (char) {
case '{':
this.offset++;
this.compactBuffer();
return { type: 'braceOpen' };
case '}':
this.offset++;
this.compactBuffer();
return { type: 'braceClose' };
case '[':
this.offset++;
this.compactBuffer();
return { type: 'bracketOpen' };
case ']':
this.offset++;
this.compactBuffer();
return { type: 'bracketClose' };
case ':':
this.offset++;
this.compactBuffer();
return { type: 'colon' };
case ',':
this.offset++;
this.compactBuffer();
return { type: 'comma' };
case '"':
this.offset++;
return this.makeStringToken();
case 't':
this.readLiteral('true');
return { type: 'boolean', value: true };
case 'f':
this.readLiteral('false');
return { type: 'boolean', value: false };
case 'n':
this.readLiteral('null');
return { type: 'null' };
default:
if (char === '-' || this.isDigit(char)) {
const value = this.readNumber();
return { type: 'number', value };
}
throw new Error('Unexpected character in JSON stream: ' + char);
}
}
makeStringToken() {
const openingOffset = this.offset - 1;
let result = '';
let escaping = false;
while (true) {
if (this.offset >= this.buffer.length) {
if (this.finished) {
throw new Error('Unterminated string literal');
}
this.offset = openingOffset;
throw new NeedMoreDataError_js_1.NeedMoreDataError();
}
const char = this.buffer[this.offset];
this.offset++;
if (escaping) {
escaping = false;
switch (char) {
case '"':
result += '"';
break;
case '\\':
result += '\\';
break;
case '/':
result += '/';
break;
case 'b':
result += '\b';
break;
case 'f':
result += '\f';
break;
case 'n':
result += '\n';
break;
case 'r':
result += '\r';
break;
case 't':
result += '\t';
break;
case 'u':
result += this.readUnicodeEscape(openingOffset);
break;
default:
throw new Error('Invalid escape sequence in string literal');
}
continue;
}
if (char === '\\') {
escaping = true;
continue;
}
if (char === '"') {
this.compactBuffer();
return { type: 'string', value: result };
}
result += char;
}
}
readUnicodeEscape(resetOffset) {
if (this.offset + 4 > this.buffer.length) {
if (this.finished) {
throw new Error('Unterminated unicode escape sequence');
}
this.offset = resetOffset;
throw new NeedMoreDataError_js_1.NeedMoreDataError();
}
const hexDigits = this.buffer.substring(this.offset, this.offset + 4);
if (!/^[0-9A-Fa-f]{4}$/.test(hexDigits)) {
throw new Error('Invalid unicode escape sequence: \\u' + hexDigits);
}
this.offset += 4;
const codePoint = Number.parseInt(hexDigits, 16);
return String.fromCodePoint(codePoint);
}
readNumber() {
const startOffset = this.offset;
let index = this.offset;
const charAt = (position) => {
if (position >= this.buffer.length) {
if (this.finished) {
return undefined;
}
this.offset = startOffset;
throw new NeedMoreDataError_js_1.NeedMoreDataError();
}
return this.buffer[position];
};
const firstChar = charAt(index);
if (firstChar === undefined) {
throw new Error('Invalid number literal');
}
if (firstChar === '-') {
index++;
}
const leadingDigit = charAt(index);
if (leadingDigit === undefined || !this.isDigit(leadingDigit)) {
throw new Error('Invalid number literal');
}
if (leadingDigit === '0') {
index++;
}
else {
do {
index++;
const nextChar = charAt(index);
if (nextChar === undefined || !this.isDigit(nextChar)) {
break;
}
} while (true);
}
const fractionIndicator = charAt(index);
if (fractionIndicator === '.') {
index++;
const fractionDigit = charAt(index);
if (fractionDigit === undefined || !this.isDigit(fractionDigit)) {
throw new Error('Invalid fractional part in number literal');
}
do {
index++;
const nextChar = charAt(index);
if (nextChar === undefined || !this.isDigit(nextChar)) {
break;
}
} while (true);
}
const exponentIndicator = charAt(index);
if (exponentIndicator === 'e' || exponentIndicator === 'E') {
index++;
const signChar = charAt(index);
if (signChar === '+' || signChar === '-') {
index++;
}
const exponentDigit = charAt(index);
if (exponentDigit === undefined || !this.isDigit(exponentDigit)) {
throw new Error('Invalid exponent in number literal');
}
do {
index++;
const nextChar = charAt(index);
if (nextChar === undefined || !this.isDigit(nextChar)) {
break;
}
} while (true);
}
const fragment = this.buffer.substring(startOffset, index);
const value = Number(fragment);
if (!Number.isFinite(value)) {
throw new Error('Invalid number literal: ' + fragment);
}
this.offset = index;
this.compactBuffer();
return value;
}
readLiteral(expected) {
const startOffset = this.offset;
const endOffset = this.offset + expected.length;
if (endOffset > this.buffer.length) {
if (this.finished) {
throw new Error('Unexpected end of JSON stream while reading literal');
}
this.offset = startOffset;
throw new NeedMoreDataError_js_1.NeedMoreDataError();
}
const fragment = this.buffer.substring(this.offset, endOffset);
if (fragment !== expected) {
throw new Error('Unexpected literal: ' + fragment);
}
this.offset = endOffset;
this.compactBuffer();
}
skipWhitespace() {
while (this.offset < this.buffer.length) {
const char = this.buffer[this.offset];
if (char === ' ' || char === '\n' || char === '\r' || char === '\t') {
this.offset++;
continue;
}
break;
}
this.compactBuffer();
}
isDigit(char) {
return char >= '0' && char <= '9';
}
compactBuffer() {
if (this.offset === 0) {
return;
}
if (this.offset >= 4096) {
this.buffer = this.buffer.substring(this.offset);
this.offset = 0;
}
}
}
exports.JsonTokenizer = JsonTokenizer;
//# sourceMappingURL=JsonTokenizer.js.map