mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-10-30 14:49:14 +00:00
266 lines
5.0 KiB
TypeScript
266 lines
5.0 KiB
TypeScript
import type {JsonToken} from './constants';
|
|
import {
|
|
ARR,
|
|
ARR_VAL,
|
|
ARR_VAL_COMPLETED,
|
|
ARR_VAL_STR,
|
|
OBJ,
|
|
OBJ_KEY,
|
|
OBJ_KEY_STR,
|
|
OBJ_VAL,
|
|
OBJ_VAL_COMPLETED,
|
|
OBJ_VAL_STR,
|
|
} from './constants';
|
|
|
|
/**
|
|
* Evaluate an (incomplete) JSON string.
|
|
*/
|
|
export function evaluateJson(json: string): JsonToken[] {
|
|
const stack: JsonToken[] = [];
|
|
|
|
for (let pos = 0; pos < json.length; pos++) {
|
|
_evaluateJsonPos(stack, json, pos);
|
|
}
|
|
|
|
return stack;
|
|
}
|
|
|
|
function _evaluateJsonPos(stack: JsonToken[], json: string, pos: number): void {
|
|
const curStep = stack[stack.length - 1];
|
|
|
|
const char = json[pos];
|
|
|
|
const whitespaceRegex = /\s/;
|
|
|
|
if (whitespaceRegex.test(char)) {
|
|
return;
|
|
}
|
|
|
|
if (char === '"' && !_isEscaped(json, pos)) {
|
|
_handleQuote(stack, curStep);
|
|
return;
|
|
}
|
|
|
|
// eslint-disable-next-line default-case
|
|
switch (char) {
|
|
case '{':
|
|
_handleObj(stack, curStep);
|
|
break;
|
|
case '[':
|
|
_handleArr(stack, curStep);
|
|
break;
|
|
case ':':
|
|
_handleColon(stack, curStep);
|
|
break;
|
|
case ',':
|
|
_handleComma(stack, curStep);
|
|
break;
|
|
case '}':
|
|
_handleObjClose(stack, curStep);
|
|
break;
|
|
case ']':
|
|
_handleArrClose(stack, curStep);
|
|
break;
|
|
}
|
|
}
|
|
|
|
function _handleQuote(stack: JsonToken[], curStep: JsonToken): void {
|
|
// End of obj value
|
|
if (curStep === OBJ_VAL_STR) {
|
|
stack.pop();
|
|
stack.push(OBJ_VAL_COMPLETED);
|
|
return;
|
|
}
|
|
|
|
// End of arr value
|
|
if (curStep === ARR_VAL_STR) {
|
|
stack.pop();
|
|
stack.push(ARR_VAL_COMPLETED);
|
|
return;
|
|
}
|
|
|
|
// Start of obj value
|
|
if (curStep === OBJ_VAL) {
|
|
stack.push(OBJ_VAL_STR);
|
|
return;
|
|
}
|
|
|
|
// Start of arr value
|
|
if (curStep === ARR_VAL) {
|
|
stack.push(ARR_VAL_STR);
|
|
return;
|
|
}
|
|
|
|
// Start of obj key
|
|
if (curStep === OBJ) {
|
|
stack.push(OBJ_KEY_STR);
|
|
return;
|
|
}
|
|
|
|
// End of obj key
|
|
if (curStep === OBJ_KEY_STR) {
|
|
stack.pop();
|
|
stack.push(OBJ_KEY);
|
|
return;
|
|
}
|
|
}
|
|
|
|
function _handleObj(stack: JsonToken[], curStep: JsonToken): void {
|
|
// Initial object
|
|
if (!curStep) {
|
|
stack.push(OBJ);
|
|
return;
|
|
}
|
|
|
|
// New object as obj value
|
|
if (curStep === OBJ_VAL) {
|
|
stack.push(OBJ);
|
|
return;
|
|
}
|
|
|
|
// New object as array element
|
|
if (curStep === ARR_VAL) {
|
|
stack.push(OBJ);
|
|
}
|
|
|
|
// New object as first array element
|
|
if (curStep === ARR) {
|
|
stack.push(OBJ);
|
|
return;
|
|
}
|
|
}
|
|
|
|
function _handleArr(stack: JsonToken[], curStep: JsonToken): void {
|
|
// Initial array
|
|
if (!curStep) {
|
|
stack.push(ARR);
|
|
stack.push(ARR_VAL);
|
|
return;
|
|
}
|
|
|
|
// New array as obj value
|
|
if (curStep === OBJ_VAL) {
|
|
stack.push(ARR);
|
|
stack.push(ARR_VAL);
|
|
return;
|
|
}
|
|
|
|
// New array as array element
|
|
if (curStep === ARR_VAL) {
|
|
stack.push(ARR);
|
|
stack.push(ARR_VAL);
|
|
}
|
|
|
|
// New array as first array element
|
|
if (curStep === ARR) {
|
|
stack.push(ARR);
|
|
stack.push(ARR_VAL);
|
|
return;
|
|
}
|
|
}
|
|
|
|
function _handleColon(stack: JsonToken[], curStep: JsonToken): void {
|
|
if (curStep === OBJ_KEY) {
|
|
stack.pop();
|
|
stack.push(OBJ_VAL);
|
|
}
|
|
}
|
|
|
|
function _handleComma(stack: JsonToken[], curStep: JsonToken): void {
|
|
// Comma after obj value
|
|
if (curStep === OBJ_VAL) {
|
|
stack.pop();
|
|
return;
|
|
}
|
|
if (curStep === OBJ_VAL_COMPLETED) {
|
|
// Pop OBJ_VAL_COMPLETED & OBJ_VAL
|
|
stack.pop();
|
|
stack.pop();
|
|
return;
|
|
}
|
|
|
|
// Comma after arr value
|
|
if (curStep === ARR_VAL) {
|
|
// do nothing - basically we'd pop ARR_VAL but add it right back
|
|
return;
|
|
}
|
|
|
|
if (curStep === ARR_VAL_COMPLETED) {
|
|
// Pop ARR_VAL_COMPLETED
|
|
stack.pop();
|
|
|
|
// basically we'd pop ARR_VAL but add it right back
|
|
return;
|
|
}
|
|
}
|
|
|
|
function _handleObjClose(stack: JsonToken[], curStep: JsonToken): void {
|
|
// Empty object {}
|
|
if (curStep === OBJ) {
|
|
stack.pop();
|
|
}
|
|
|
|
// Object with element
|
|
if (curStep === OBJ_VAL) {
|
|
// Pop OBJ_VAL, OBJ
|
|
stack.pop();
|
|
stack.pop();
|
|
}
|
|
|
|
// Obj with element
|
|
if (curStep === OBJ_VAL_COMPLETED) {
|
|
// Pop OBJ_VAL_COMPLETED, OBJ_VAL, OBJ
|
|
stack.pop();
|
|
stack.pop();
|
|
stack.pop();
|
|
}
|
|
|
|
// if was obj value, complete it
|
|
if (stack[stack.length - 1] === OBJ_VAL) {
|
|
stack.push(OBJ_VAL_COMPLETED);
|
|
}
|
|
|
|
// if was arr value, complete it
|
|
if (stack[stack.length - 1] === ARR_VAL) {
|
|
stack.push(ARR_VAL_COMPLETED);
|
|
}
|
|
}
|
|
|
|
function _handleArrClose(stack: JsonToken[], curStep: JsonToken): void {
|
|
// Empty array []
|
|
if (curStep === ARR) {
|
|
stack.pop();
|
|
}
|
|
|
|
// Array with element
|
|
if (curStep === ARR_VAL) {
|
|
// Pop ARR_VAL, ARR
|
|
stack.pop();
|
|
stack.pop();
|
|
}
|
|
|
|
// Array with element
|
|
if (curStep === ARR_VAL_COMPLETED) {
|
|
// Pop ARR_VAL_COMPLETED, ARR_VAL, ARR
|
|
stack.pop();
|
|
stack.pop();
|
|
stack.pop();
|
|
}
|
|
|
|
// if was obj value, complete it
|
|
if (stack[stack.length - 1] === OBJ_VAL) {
|
|
stack.push(OBJ_VAL_COMPLETED);
|
|
}
|
|
|
|
// if was arr value, complete it
|
|
if (stack[stack.length - 1] === ARR_VAL) {
|
|
stack.push(ARR_VAL_COMPLETED);
|
|
}
|
|
}
|
|
|
|
function _isEscaped(str: string, pos: number): boolean {
|
|
const previousChar = str[pos - 1];
|
|
|
|
return previousChar === '\\' && !_isEscaped(str, pos - 1);
|
|
}
|