@cheap-glitch/jsonast
Version:
A JSON to AST parser which allows error recovery.
262 lines (261 loc) • 6.01 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseJSON = void 0;
const character_stream_1 = require("./character-stream");
__exportStar(require("./types"), exports);
function parseJSON(text) {
let result;
const cs = new character_stream_1.CharacterStream(text);
ws(cs);
if (cs.ch === '{') {
result = object(cs);
}
else if (cs.ch === '[') {
result = array(cs);
}
ws(cs);
if (!cs.eoi) {
throw new Error(`Unexpected character '${cs.ch}' at ${cs.line}:${cs.column}. Expected end of input.`);
}
return result;
}
exports.parseJSON = parseJSON;
function object(cs) {
function getMembers(cs1) {
function pair(cs2) {
ws(cs2);
const key = string(cs2);
ws(cs2);
cs2.accept(':');
return {
key,
value: getValue(cs2),
};
}
const members = [];
let next = true;
while (next) {
members.push(pair(cs1));
ws(cs1);
if (cs1.ch === ',') {
cs1.next();
}
else if (cs1.ch === '"') {
}
else {
next = false;
}
}
return members;
}
const ast = {
type: 'object',
pos: {
start: cs.pos,
end: cs.pos,
},
};
ws(cs);
cs.accept('{');
ws(cs);
if (cs.ch === '"') {
ast.members = getMembers(cs);
}
ws(cs);
cs.accept('}');
ast.pos.end = cs.pos;
return ast;
}
function array(cs) {
const ast = {
type: 'array',
pos: {
start: cs.pos,
end: cs.pos,
},
};
ws(cs);
cs.accept('[');
if (cs.ch !== ']') {
ast.elements = [];
let next = true;
while (next) {
ast.elements.push(getValue(cs));
ws(cs);
if (cs.ch === ',') {
cs.next();
}
else if (cs.ch === ']') {
next = false;
}
else {
}
}
}
ws(cs);
cs.accept(']');
ast.pos.end = cs.pos;
return ast;
}
function getValue(cs) {
ws(cs);
switch (cs.ch) {
case '"':
return string(cs);
case '{':
return object(cs);
case '[':
return array(cs);
case 't':
return trueLiteral(cs);
case 'f':
return falseLiteral(cs);
case 'n':
return nullLiteral(cs);
default:
return getNumber(cs);
}
}
function string(cs) {
const start = cs.pos;
let value = '';
cs.accept('"');
while (cs.ch !== '"') {
if (cs.ch === '\\') {
cs.next();
cs.expect('"');
}
value += cs.ch;
cs.next();
}
cs.accept('"');
return {
type: 'string',
value,
pos: {
start,
end: cs.pos,
},
};
}
function trueLiteral(cs) {
ws(cs);
const start = cs.pos;
cs.accept('t');
cs.accept('r');
cs.accept('u');
cs.accept('e');
return {
type: 'true',
pos: {
start,
end: cs.pos,
},
};
}
function falseLiteral(cs) {
ws(cs);
const start = cs.pos;
cs.accept('f');
cs.accept('a');
cs.accept('l');
cs.accept('s');
cs.accept('e');
return {
type: 'false',
pos: {
start,
end: cs.pos,
},
};
}
function nullLiteral(cs) {
ws(cs);
const start = cs.pos;
cs.accept('n');
cs.accept('u');
cs.accept('l');
cs.accept('l');
return {
type: 'null',
pos: {
start,
end: cs.pos,
},
};
}
function getNumber(cs) {
function digit() {
let number = '';
const ch = cs.ch;
if (ch === '0' || ch === '1' || ch === '2' || ch === '3' || ch === '4'
|| ch === '5' || ch === '6' || ch === '7' || ch === '8' || ch === '9') {
number = ch;
cs.next();
}
return number;
}
function digits() {
let number = digit();
let temporary = digit();
while (temporary !== '') {
number += temporary;
temporary = digit();
}
return number;
}
const start = cs.pos;
const negative = cs.skip('-') ? '-' : '';
let int;
if (cs.ch === '0') {
int = cs.ch;
cs.next();
}
else {
int = digits();
}
let frac = '';
if (cs.ch === '.') {
cs.next();
frac = '.' + digits();
}
let exp = '';
if (cs.ch === 'e' || cs.ch === 'E') {
cs.next();
exp = 'e';
exp += cs.skip('+') ? '+' : '';
exp += cs.skip('-') ? '-' : '';
exp += digits();
}
return {
type: 'number',
value: Number.parseFloat(`${negative}${int}${frac}${exp}`),
pos: {
start,
end: cs.pos,
},
};
}
function ws(cs) {
let next = true;
while (next) {
next = cs.skip(' ')
|| cs.skip('\t')
|| cs.skip('\n')
|| cs.skip('\r');
}
}