UNPKG

@cheap-glitch/jsonast

Version:

A JSON to AST parser which allows error recovery.

262 lines (261 loc) 6.01 kB
"use strict"; 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'); } }