@init-kz/jsonparse-ts
Version:
Library for parsing partial jsons inspired by original jsonparse written on js
1 lines • 70.8 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/constants/constants.ts","../src/utils/utils.ts","../src/handlers/error-handler.ts","../src/handlers/event-emitter.ts","../src/handlers/state-handler.ts","../src/handlers/string-handler.ts","../src/handlers/token-handler.ts","../src/handlers/utf8-handler.ts","../src/parsers/base-parser.ts","../src/parsers/parser.ts"],"sourcesContent":["export { Parser } from \"@/parsers/parser\";\nexport * as CONSTANTS from \"./constants/constants\"\n","// Tokens\nexport const TOKENS = {\n LEFT_BRACE: 0x1,\n RIGHT_BRACE: 0x2,\n LEFT_BRACKET: 0x3,\n RIGHT_BRACKET: 0x4,\n COLON: 0x5,\n COMMA: 0x6,\n TRUE: 0x7,\n FALSE: 0x8,\n NULL: 0x9,\n STRING: 0xa,\n NUMBER: 0xb,\n} as const\n\n\n// Tokenizer States\nexport const TOKENIZER_STATES = {\n START: 0x11,\n STOP: 0x12,\n TRUE1: 0x21,\n TRUE2: 0x22,\n TRUE3: 0x23,\n FALSE1: 0x31,\n FALSE2: 0x32,\n FALSE3: 0x33,\n FALSE4: 0x34,\n NULL1: 0x41,\n NULL2: 0x42,\n NULL3: 0x43,\n NUMBER1: 0x51,\n NUMBER3: 0x53,\n STRING1: 0x61, // After open quote\n STRING2: 0x62, // After backslash\n // unicode hex codes\n STRING3: 0x63,\n STRING4: 0x64,\n STRING5: 0x65,\n STRING6: 0x66,\n} as const;\n\n// Parser States\nexport const PARSER_STATES = {\n VALUE: 0x71,\n KEY: 0x72,\n COLON: 0x3a, // :\n COMMA: 0x2c, // ,\n} as const\n\n// Parser Modes\nexport const PARSER_MODES = {\n OBJECT: 0x81,\n ARRAY: 0x82,\n} as const\n\n// Character constants\nexport const CHARS = {\n BACKSLASH: 0x5c, // '\\'\n FORWARD_SLASH: 0x2f, // '/'\n BACKSPACE: 0x8, // \\b\n FORM_FEED: 0xc, // \\f\n NEWLINE: 0xa, // \\n\n CARRIAGE_RETURN: 0xd, // \\r\n TAB: 0x9, // \\t\n SPACE: 0x20, // ' '\n\n\n LEFT_BRACE: 0x7b, // {\n RIGHT_BRACE: 0x7d, // }\n LEFT_BRACKET: 0x5b, // [\n RIGHT_BRACKET: 0x5d, // ]\n COLON: 0x3a, // :\n COMMA: 0x2c, // ,\n\n QUOTE: 0x22, // \"\n MINUS: 0x2d, // -\n PLUS: 0x2b, // +\n\n DOT: 0x2e, // .\n E: 0x65, // e\n BIG_E: 0x45, // E\n\n T: 0x74, // t\n F: 0x66, // f\n N: 0x6e, // n\n L: 0x6c, // l\n S: 0x73, // s\n\n B: 0x62, // b (Backspace)\n R: 0x72, // r (Carriage Return)\n U: 0x75, // u (Unicode sequence start)\n\n A: 0x61, // a\n\n BIG_A: 0x41, // A\n BIG_F: 0x46, // F\n\n ZERO: 0x30, // 0\n NINE: 0x39, // 9\n\n WHITESPACES: [\n 0x20, // ' '\n 0x9, // \\t\n 0xa, // \\n\n 0xd, // \\r\n ] as number[]\n} as const;\n\nexport const UTF8_BOUNDS = {\n MIN_MULTI_BYTE: 128,\n INVALID_LOWER: 193,\n BYTE_2_MIN: 194,\n BYTE_2_MAX: 223,\n BYTE_3_MIN: 224,\n BYTE_3_MAX: 239,\n BYTE_4_MIN: 240,\n BYTE_4_MAX: 244,\n\n HIGH_SURROGATE_START: 0xD800,\n HIGH_SURROGATE_END: 0xDBFF,\n LOW_SURROGATE_START: 0xDC00,\n LOW_SURROGATE_END: 0xDFFF,\n} as const;\n\nexport const STRING_BUFFER_SIZE = 64 * 1024;\n","import { PARSER_MODES, PARSER_STATES, TOKENIZER_STATES, TOKENS } from \"@/constants/constants\";\n\nexport function alloc(size: number): Uint8Array {\n if (typeof Buffer !== \"undefined\" && Buffer.alloc) {\n return Buffer.alloc(size); // Node.js\n }\n\n return new Uint8Array(size); // Browser-safe alternative\n}\n\n/**\n * Checks if the token is a primitive value (`string`, `number`, `true`, `false`, `null`).\n */\nexport function isPrimitive(token: number): boolean {\n return (\n token === TOKENS.STRING ||\n token === TOKENS.NUMBER ||\n token === TOKENS.TRUE ||\n token === TOKENS.FALSE ||\n token === TOKENS.NULL\n );\n}\n\n/**\n* Converts a token code to its string representation.\n* @param code The token code (numeric).\n* @returns The string representation of the token or hex value.\n*/\nexport function tokenName(code: number): string {\n const _constants = {\n ...TOKENS,\n ...TOKENIZER_STATES,\n ...PARSER_STATES,\n ...PARSER_MODES,\n };\n\n for (const key of Object.keys(_constants)) {\n if ((_constants as any)[key] === code) {\n return key;\n }\n }\n\n return code ? `0x${code.toString(16)}` : \"\";\n}\n\nexport function omitEmptyArrayOrObject<T>(value: T): T {\n if (Array.isArray(value)) {\n return value\n .map(omitEmptyArrayOrObject)\n .filter(item => !(Array.isArray(item) && item.length === 0) &&\n !(typeof item === \"object\" && item !== null && Object.keys(item).length === 0)) as T;\n } else if (typeof value === \"object\" && value !== null) {\n return Object.fromEntries(\n Object.entries(value)\n .map(([key, val]) => [key, omitEmptyArrayOrObject(val)])\n .filter(([, val]) => !(Array.isArray(val) && val.length === 0) &&\n !(typeof val === \"object\" && val !== null && Object.keys(val).length === 0))\n ) as T;\n }\n\n return value;\n}\n","import { TOKENIZER_STATES } from \"@/constants/constants\";\nimport { EventEmitter } from \"@/handlers/event-emitter\";\nimport { StateHandler } from \"@/handlers/state-handler\";\nimport { tokenName } from \"@/utils/utils\";\n\nexport class ErrorHandler {\n constructor(\n protected eventEmitter: EventEmitter,\n protected stateHandler: StateHandler\n ) {\n }\n\n /**\n * Handles unexpected token errors.\n */\n public parseError(token: number, value?: any): Error {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.STOP);\n\n const error = new Error(\n `Unexpected ${tokenName(token)}${value ? ` (${JSON.stringify(value)})` : \"\"\n } in state ${tokenName(this.stateHandler.getParsingState())}`\n );\n\n this.eventEmitter.handleError(error);\n\n return error\n }\n\n /**\n * Handles character-related errors.\n */\n public charError(buffer: Uint8Array, i: number): Error {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.STOP)\n const error = new Error(\n `Unexpected ${JSON.stringify(\n String.fromCharCode(buffer[i])\n )} at position ${i} in state ${tokenName(this.stateHandler.getTokenizerState())}`\n );\n\n this.eventEmitter.handleError(\n error\n );\n\n return error;\n }\n}\n","export class EventEmitter {\n private valueHandlers: ((value: any) => void)[] = [];\n private tokenHandlers: ((token: number, value: any) => void)[] = [];\n private errorHandlers: ((err: Error) => void)[] = [];\n\n /**\n * Subscribes a value event callback.\n */\n public onValue(callback: (value: any) => void): void {\n this.valueHandlers.push(callback);\n }\n\n /**\n * Emits a parsed value event.\n */\n public emitValue(value: any): boolean {\n this.valueHandlers.forEach((callback) => callback(value));\n\n return this.valueHandlers.length > 0;\n }\n\n /**\n * Unsubscribes a value event callback.\n */\n public offValue(callback: (value: any) => void): void {\n this.valueHandlers = this.valueHandlers.filter(cb => cb !== callback);\n }\n\n /**\n * Returns true if length of value subscribers more than zero\n */\n public hasValueHandler() {\n return this.valueHandlers.length > 0;\n }\n\n /**\n * Subscribes a callback to token events.\n * @param callback - Function to handle tokens.\n */\n public onToken(callback: (token: number, value: any) => void): void {\n this.tokenHandlers.push(callback);\n }\n\n /**\n * Subscribes a callback to token events.\n * @param callback - Function to handle tokens.\n */\n public offToken(callback: (token: number, value: any) => void): void {\n this.tokenHandlers = this.tokenHandlers.filter(cb => cb !== callback);\n }\n\n /**\n * Emits a token event to all subscribers.\n * @param token - The token type.\n * @param value - The associated value.\n */\n public emitToken(token: number, value: any): boolean {\n if (this.tokenHandlers.length > 0) {\n this.tokenHandlers.forEach((callback) => callback(token, value));\n }\n\n return this.tokenHandlers.length > 0;\n }\n\n /**\n * Subscribes an error handler callback.\n * @param callback - A function to handle errors.\n */\n public onError(callback: (err: Error) => void): void {\n this.errorHandlers.push(callback);\n }\n\n /**\n * Subscribes an error handler callback.\n * @param callback - A function to handle errors.\n */\n public offError(callback: (err: Error) => void): void {\n this.errorHandlers = this.errorHandlers.filter(cb => cb !== callback);\n }\n\n /**\n * Handles errors by either notifying subscribers or throwing the error.\n * @param err - The error to handle.\n */\n public handleError(err: Error): void {\n if (this.errorHandlers.length > 0) {\n this.errorHandlers.forEach((callback) => callback(err));\n } else {\n throw err; // Default behavior if no handlers are set\n }\n }\n}\n","import { PARSER_STATES, TOKENIZER_STATES } from \"@/constants/constants\";\nimport { EventEmitter } from \"@/handlers/event-emitter\";\nimport { omitEmptyArrayOrObject, tokenName } from \"@/utils/utils\";\nimport { StringHandler } from \"./string-handler\";\n\nexport class StateHandler {\n protected eventEmitter: EventEmitter;\n protected stringHandler: StringHandler;\n\n /** Tokenizer State - Tracks the current state of the tokenizer */\n protected tState: number = TOKENIZER_STATES.START;\n\n /** Current Parsed Value */\n protected value: any = undefined;\n protected lastValue: any = undefined;\n\n /** Parser Mode (OBJECT, ARRAY) */\n protected mode: number | undefined = undefined;\n\n /** Stack to maintain parsing context */\n protected stack: Array<{ value: any; key?: string | number; mode?: number }>;\n\n /** Current Parsing State (VALUE, KEY) */\n protected state: number = PARSER_STATES.VALUE;\n\n /** Current byte offset in the stream */\n protected offset: number = -1;\n\n /** Current key being processed (for objects) */\n protected key: string | undefined | number = undefined;\n\n constructor(eventEmitter: EventEmitter, stringHandler: StringHandler) {\n this.eventEmitter = eventEmitter;\n this.stringHandler = stringHandler;\n this.stack = [];\n }\n\n public getTokenizerState() {\n return this.tState\n }\n\n public incTokenizerState() {\n this.tState++;\n return this;\n }\n\n public setOffset(n: number) {\n this.offset = n\n return this;\n }\n\n public getOffset() {\n return this.offset;\n }\n\n public addOffset(n: number) {\n this.offset += n;\n return this;\n }\n\n public setTokenizerState(n: number) {\n this.tState = n;\n return this\n }\n\n public getParsingState() {\n return this.state;\n }\n\n public getStack() {\n return this.stack\n }\n\n /**\n * Pushes the current parsing context onto the stack.\n */\n public push(): void {\n this.stack.push({ value: this.value, key: this.key, mode: this.mode });\n }\n\n /**\n * Pops the last parsing context from the stack and emits a value.\n */\n public pop(): void {\n const value = this.value;\n const parent = this.stack.pop();\n\n this.value = parent?.value;\n this.key = parent?.key;\n this.mode = parent?.mode;\n\n if (typeof parent?.value !== undefined) {\n this.lastValue = value;\n }\n\n if (this.mode) {\n this.state = PARSER_STATES.COMMA;\n }\n\n this.eventEmitter.emitValue(value);\n\n if (!this.mode) {\n this.state = PARSER_STATES.VALUE;\n }\n }\n\n public incOffset() {\n this.offset++\n return this;\n }\n\n public getValue() {\n return this.value;\n }\n\n public getLastIncompleteValue(omitEmpty = false) {\n if (typeof this.lastValue === \"undefined\" || this.stack.length > 0) {\n let currentValue: any = undefined;\n let settedCurrentValue = false;\n\n if (this.mode) {\n switch (this.tState) {\n case TOKENIZER_STATES.NULL1:\n case TOKENIZER_STATES.NULL2:\n case TOKENIZER_STATES.NULL3:\n currentValue = null;\n break;\n case TOKENIZER_STATES.TRUE1:\n case TOKENIZER_STATES.TRUE2:\n case TOKENIZER_STATES.TRUE3:\n currentValue = true;\n break;\n case TOKENIZER_STATES.FALSE1:\n case TOKENIZER_STATES.FALSE2:\n case TOKENIZER_STATES.FALSE3:\n case TOKENIZER_STATES.FALSE4:\n currentValue = false;\n break;\n case TOKENIZER_STATES.STRING1:\n case TOKENIZER_STATES.STRING2:\n case TOKENIZER_STATES.STRING3:\n case TOKENIZER_STATES.STRING4:\n case TOKENIZER_STATES.STRING5:\n case TOKENIZER_STATES.STRING6:\n currentValue = this.stringHandler.flushBuffer().getString();\n break;\n }\n if (typeof currentValue !== \"undefined\" && typeof this.key !== \"undefined\" && typeof this.value[this.key as string] === \"undefined\") {\n this.value[this.key as string] = currentValue\n settedCurrentValue = true;\n }\n }\n\n const newStack = [...this.stack, { value: this.value, key: this.key, mode: this.mode }]\n let parent: any = null;\n\n for (let i = newStack.length - 1; i >= 0; i--) {\n const currentStack = newStack[i];\n\n if (typeof currentStack?.value !== \"undefined\") {\n parent = currentStack?.value\n }\n }\n\n if (parent) {\n parent = JSON.parse(JSON.stringify(parent))\n }\n\n if (typeof currentValue !== \"undefined\" && typeof this.key !== \"undefined\" && settedCurrentValue) delete this.value[this.key as string];\n\n if (parent) return omitEmpty ? omitEmptyArrayOrObject(parent) : parent;\n }\n\n return this.lastValue ?? null;\n }\n\n public getLastValue() {\n if (typeof this.lastValue === \"undefined\" || this.stack.length > 0) {\n const newStack = [...this.stack, { value: this.value, key: this.key, mode: this.mode }]\n\n let parent: any = null;\n\n for (let i = newStack.length - 1; i >= 0; i--) {\n const currentStack = newStack[i];\n\n if (typeof currentStack?.value !== \"undefined\") {\n parent = currentStack?.value\n }\n }\n\n if (parent) {\n return JSON.parse(JSON.stringify(parent))\n }\n }\n\n return this.lastValue ?? null;\n }\n\n public getKey() {\n return this.key;\n }\n\n public getTokenizerStateName() {\n return tokenName(this.getTokenizerState())\n }\n\n public getMode() {\n return this.mode;\n }\n\n\n public setValue(value: any) {\n this.value = value;\n return this;\n }\n\n public setKey(value: any) {\n this.key = value;\n return this;\n }\n\n public setKeyValue(value: any) {\n if (this.value && (this.key || this.key === 0)) {\n this.value[this.key] = value;\n }\n }\n\n public setState(n: number) {\n this.state = n;\n return this;\n }\n\n public setMode(n: number) {\n this.mode = n;\n return this;\n }\n}\n","import { STRING_BUFFER_SIZE } from \"@/constants/constants\";\nimport { alloc } from \"@/utils/utils\";\n\nexport class StringHandler {\n /** Current string being parsed */\n protected string: string | undefined = undefined;\n\n /** Buffer to store string data */\n protected stringBuffer: Uint8Array;\n\n /** Offset within the string buffer */\n protected stringBufferOffset: number = 0;\n\n /** Unicode processing variables */\n protected unicode: string | undefined = undefined;\n protected highSurrogate: number | undefined = undefined;\n\n constructor() {\n this.stringBuffer = alloc(STRING_BUFFER_SIZE);\n }\n\n /**\n * Appends a character to the string buffer.\n */\n public appendStringChar(char: number): void {\n if (this.stringBufferOffset >= STRING_BUFFER_SIZE) {\n if (this.string === undefined) this.string = \"\";\n this.string += new TextDecoder().decode(\n this.stringBuffer.subarray(0, this.stringBufferOffset),\n );\n this.stringBufferOffset = 0;\n }\n\n this.stringBuffer[this.stringBufferOffset++] = char;\n }\n\n /**\n * Appends a portion of a buffer to the internal string buffer.\n * @param buf - The source Uint8Array buffer.\n * @param start - The starting index (optional, defaults to 0).\n * @param end - The ending index (optional, defaults to `buf.length`).\n */\n public appendStringBuf(\n buf: Uint8Array,\n start: number = 0,\n end?: number\n ): void {\n let size = buf.length;\n\n if (typeof start === \"number\") {\n if (typeof end === \"number\") {\n if (end < 0) {\n // Handling negative `end` value\n size = buf.length - start + end;\n } else {\n size = end - start;\n }\n } else {\n size = buf.length - start;\n }\n }\n\n // Prevent negative size values\n if (size < 0) size = 0;\n\n // If adding this buffer would overflow the stringBuffer, flush it\n if (this.stringBufferOffset + size > STRING_BUFFER_SIZE) {\n if (this.string === undefined) this.string = \"\";\n this.string += new TextDecoder().decode(\n this.stringBuffer.subarray(0, this.stringBufferOffset)\n );\n this.stringBufferOffset = 0;\n }\n\n // Copy buffer content to `stringBuffer`\n this.stringBuffer.set(\n buf.subarray(start, start + size),\n this.stringBufferOffset\n );\n\n this.stringBufferOffset += size;\n }\n\n /**\n * Flushes the current buffer into the string.\n */\n public flushBuffer() {\n if (this.stringBufferOffset > 0) {\n if (this.string === undefined) {\n this.string = \"\";\n }\n\n this.string += new TextDecoder().decode(\n this.stringBuffer.subarray(0, this.stringBufferOffset)\n );\n\n this.stringBufferOffset = 0;\n }\n\n return this;\n }\n\n /**\n * Gets the final string value.\n * Ensures that if no string has been accumulated, it returns an empty string.\n * @returns {string} The current accumulated string.\n */\n public getString(): string {\n return this.string || \"\";\n }\n\n /**\n * Resets the string buffer and optionally sets a new initial string value.\n * This is useful when starting a new string parsing session.\n * @param {string} [stringValue] - Optional initial string value after reset.\n */\n public reset(stringValue?: string) {\n this.stringBufferOffset = 0;\n this.string = stringValue;\n }\n\n /**\n * Resets only the stored string without affecting the buffer offset.\n * This can be useful when clearing the current string but keeping buffer state.\n * @param {string} [stringValue] - Optional new string value.\n */\n public resetString(stringValue?: string) {\n this.string = stringValue;\n }\n\n /**\n * Sets the string value from a single character's char code.\n * Useful when initializing a string with a character (e.g., starting a number or string).\n * @param {number} n - The character code to set as the string.\n */\n public setFromCharcode(n: number) {\n this.string = String.fromCharCode(n);\n }\n\n /**\n * Appends a single character (from its char code) to the existing string.\n * Useful for dynamically building strings one character at a time.\n * @param {number} n - The character code to append to the string.\n */\n public addFromCharCode(n: number) {\n if (this.string === undefined) {\n this.string = \"\";\n }\n this.string += String.fromCharCode(n);\n return this;\n }\n\n public setUnicode(unicode?: string) {\n this.unicode = unicode\n return this;\n }\n\n public getUnicode() {\n return this.unicode || \"\";\n }\n\n public setHighSurrogate(n?: number) {\n this.highSurrogate = n\n return this;\n }\n\n public getHighSurrogate() {\n return this.highSurrogate\n }\n\n public addUnicodeFromCharCode(n: number) {\n if (this.unicode === undefined) {\n this.unicode = \"\";\n }\n\n this.unicode += String.fromCharCode(n);\n return this;\n }\n}\n","import { CHARS, PARSER_MODES, PARSER_STATES, TOKENS } from \"@/constants/constants\";\nimport { ErrorHandler } from \"@/handlers/error-handler\";\nimport { EventEmitter } from \"@/handlers/event-emitter\";\nimport { StateHandler } from \"@/handlers/state-handler\";\nimport { isPrimitive } from \"@/utils/utils\";\n\nexport class TokenHandler {\n constructor(\n protected eventEmitter: EventEmitter,\n protected stateHandler: StateHandler,\n protected errorHandler: ErrorHandler,\n ) {\n }\n\n /**\n * Handles incoming tokens and builds the JSON structure.\n */\n public handleToken(token: number, value: any): void {\n switch (this.stateHandler.getParsingState()) {\n case PARSER_STATES.VALUE:\n this.handleValueToken(token, value);\n break;\n case PARSER_STATES.KEY:\n this.handleKeyToken(token, value);\n break;\n case PARSER_STATES.COLON:\n this.handleColonToken(token);\n break;\n case PARSER_STATES.COMMA:\n this.handleCommaToken(token);\n break;\n default:\n this.errorHandler.parseError(token, value);\n }\n\n this.eventEmitter.emitToken(token, value);\n }\n\n /**\n * Handles tokens when in VALUE state.\n */\n private handleValueToken(token: number, value: any): void {\n if (isPrimitive(token)) {\n this.stateHandler.setKeyValue(value);\n\n if (this.stateHandler.getMode()) {\n this.stateHandler.setState(PARSER_STATES.COMMA)\n }\n\n this.eventEmitter.emitValue(value);\n } else if (token === TOKENS.LEFT_BRACE) {\n this.startObject();\n } else if (token === TOKENS.LEFT_BRACKET) {\n this.startArray();\n } else if (\n (token === TOKENS.RIGHT_BRACE && this.stateHandler.getMode() === PARSER_MODES.OBJECT) ||\n (token === TOKENS.RIGHT_BRACKET && this.stateHandler.getMode() === PARSER_MODES.ARRAY)\n ) {\n this.stateHandler.pop();\n } else {\n this.errorHandler.parseError(token, value);\n }\n }\n\n /**\n * Handles tokens when in KEY state.\n */\n private handleKeyToken(token: number, value: any): void {\n if (token === TOKENS.STRING) {\n this.stateHandler.setKey(value);\n this.stateHandler.setState(PARSER_STATES.COLON)\n } else if (token === TOKENS.RIGHT_BRACE) {\n this.stateHandler.pop();\n } else {\n this.errorHandler.parseError(token, value);\n }\n }\n\n /**\n * Handles tokens when in COLON state.\n */\n private handleColonToken(token: number): void {\n if (token === TOKENS.COLON) {\n this.stateHandler.setState(PARSER_STATES.VALUE);\n } else {\n this.errorHandler.parseError(token);\n }\n }\n\n /**\n * Handles tokens when in COMMA state.\n */\n private handleCommaToken(token: number): void {\n if (token === TOKENS.COMMA) {\n const mode = this.stateHandler.getMode();\n\n if (mode === PARSER_MODES.ARRAY) {\n const key = this.stateHandler.getKey() as number || 0;\n this.stateHandler.setKey(key + 1);\n this.stateHandler.setState(PARSER_STATES.VALUE)\n } else if(mode === PARSER_MODES.OBJECT) {\n this.stateHandler.setState(PARSER_STATES.KEY)\n }\n } else if (\n (token === TOKENS.RIGHT_BRACKET && this.stateHandler.getMode() === PARSER_MODES.ARRAY) ||\n (token === TOKENS.RIGHT_BRACE && this.stateHandler.getMode() === PARSER_MODES.OBJECT)\n ) {\n this.stateHandler.pop();\n } else {\n this.errorHandler.parseError(token);\n }\n }\n\n /**\n * Starts a new object `{}`.\n */\n private startObject(): void {\n this.stateHandler.push();\n\n let value = this.stateHandler.getValue();\n if(value) {\n value = value[this.stateHandler.getKey() as string] = {}\n } else {\n value = {};\n }\n\n this.stateHandler\n .setValue(value)\n .setKey(undefined)\n .setState(PARSER_STATES.KEY)\n .setMode(PARSER_MODES.OBJECT)\n }\n\n /**\n * Starts a new array `[]`.\n */\n private startArray(): void {\n this.stateHandler.push();\n\n let value = this.stateHandler.getValue();\n if(value) {\n value = value[this.stateHandler.getKey() as string] = []\n } else {\n value = [];\n }\n\n this.stateHandler\n .setValue(value)\n .setKey(0)\n .setMode(PARSER_MODES.ARRAY)\n .setState(PARSER_STATES.VALUE)\n }\n}\n","import { alloc } from \"@/utils/utils\";\n\nexport class UTF8Handler {\n /** Remaining bytes for a multi-byte UTF-8 character, number of bytes remaining in multi byte utf8 char to read after split boundary */\n protected bytes_remaining: number = 0;\n\n /** Total bytes in the current UTF-8 character sequence, bytes in multi byte utf8 char to read */\n protected bytes_in_sequence: number = 0;\n\n /** Temporary buffers for rebuilding chars split before boundary is reached */\n protected temp_buffs: Record<number, Uint8Array>;\n\n constructor() {\n // Initialize temp buffers for multi-byte characters\n this.temp_buffs = {\n 2: alloc(2),\n 3: alloc(3),\n 4: alloc(4),\n };\n }\n\n /** Get the remaining bytes for a multi-byte character */\n public getBytesRemaining(): number {\n return this.bytes_remaining;\n }\n\n public getRemainingBytesInBuff(buffer: Uint8Array): Uint8Array {\n for (let i = 0; i < this.bytes_remaining; i++) {\n this.temp_buffs[this.bytes_in_sequence][this.bytes_in_sequence - this.bytes_remaining + i] = buffer[i];\n }\n\n const remainingBytes = this.temp_buffs[this.bytes_in_sequence] || alloc(0);\n this.bytes_in_sequence = this.bytes_remaining = 0;\n\n return remainingBytes;\n }\n\n public handleBoundarySplit(i: number, buffer: Uint8Array) {\n for (let j = 0; j <= (buffer.length - 1 - i); j++) {\n this.temp_buffs[this.bytes_in_sequence][j] = buffer[i + j];\n }\n\n this.bytes_remaining = (i + this.bytes_in_sequence) - buffer.length;\n }\n\n /** Set the remaining bytes, ensuring it's not negative */\n public setBytesRemaining(value: number): void {\n if (value < 0) throw new Error(\"bytes_remaining cannot be negative\");\n this.bytes_remaining = value;\n }\n\n /** Get the total bytes in the current UTF-8 character sequence */\n public getBytesInSequence(): number {\n return this.bytes_in_sequence;\n }\n\n /** Set the total bytes, ensuring it's not negative */\n public setBytesInSequence(value: number): void {\n if (value < 0) throw new Error(\"bytes_in_sequence cannot be negative\");\n this.bytes_in_sequence = value;\n }\n\n /** Get the temporary buffers for multi-byte characters */\n public getTempBuffs(): Record<string, Uint8Array> {\n return this.temp_buffs;\n }\n\n /** Set the temporary buffers */\n public setTempBuffs(buffers: Record<string, Uint8Array>): void {\n this.temp_buffs = buffers;\n }\n\n public hasBytesRemaining() {\n return this.bytes_remaining > 0\n }\n}\n","import { TOKENS } from \"@/constants/constants\";\nimport { ErrorHandler } from \"@/handlers/error-handler\";\nimport { EventEmitter } from \"@/handlers/event-emitter\";\nimport { StateHandler } from \"@/handlers/state-handler\";\nimport { StringHandler } from \"@/handlers/string-handler\";\nimport { TokenHandler } from \"@/handlers/token-handler\";\nimport { UTF8Handler } from \"@/handlers/utf8-handler\";\n\nexport abstract class BaseParser {\n /** Event Emitter */\n protected eventEmitter: EventEmitter;\n\n /** Handler for current state */\n protected stateHandler: StateHandler;\n\n /** Handler for strings, including multybytes */\n protected stringHandler: StringHandler;\n\n /** Handler for utf8 symbols */\n protected utf8Handler: UTF8Handler;\n\n protected errorHandler: ErrorHandler;\n\n protected tokenHandler: TokenHandler;\n\n protected encoder: TextEncoder;\n\n constructor() {\n this.eventEmitter = new EventEmitter();\n this.stringHandler = new StringHandler();\n this.stateHandler = new StateHandler(this.eventEmitter, this.stringHandler);\n this.errorHandler = new ErrorHandler(this.eventEmitter, this.stateHandler)\n this.utf8Handler = new UTF8Handler();\n this.tokenHandler = new TokenHandler(this.eventEmitter, this.stateHandler, this.errorHandler)\n this.encoder = new TextEncoder();\n }\n\n /**\n * Parses and processes numeric values.\n * Can be overridden for custom number handling.\n * @param text - The numeric text to parse.\n */\n protected numberReviver(text: string): Error | undefined {\n const result = Number(text);\n\n if (isNaN(result)) {\n const error = new Error(`Invalid number: ${text}`);\n this.eventEmitter.handleError(error);\n return error;\n }\n\n // Check if text is a long numeric string (likely an ID) rather than a safe number\n if (/^[0-9]+$/.test(text) && result.toString() !== text) {\n this.tokenHandler.handleToken(TOKENS.STRING, text) // Emit as a string instead of a number\n } else {\n this.tokenHandler.handleToken(TOKENS.NUMBER, result); // Emit as a valid number\n }\n }\n}\n","import { CHARS, TOKENIZER_STATES, TOKENS, UTF8_BOUNDS } from \"@/constants/constants\";\nimport { BaseParser } from \"@/parsers/base-parser\";\n\nexport class Parser extends BaseParser {\n public getEmitter() {\n return this.eventEmitter\n }\n\n public onToken(callback: (token: number, value: any) => void) {\n this.eventEmitter.onToken(callback)\n }\n\n public onValue(callback: (value: any) => void) {\n this.eventEmitter.onValue(callback)\n }\n\n public onError(callback: (err: Error) => void) {\n this.eventEmitter.onError(callback)\n }\n\n public getLastValue() {\n return this.stateHandler.getLastValue();\n }\n\n public getLastIncompleteValue(omitEmpty = false) {\n return this.stateHandler.getLastIncompleteValue(omitEmpty);\n }\n\n public getCurrentKey() {\n return this.stateHandler.getKey();\n }\n\n public getStack() {\n return this.stateHandler.getStack()\n }\n\n public getOffset() {\n return this.stateHandler.getOffset()\n }\n\n /**\n * Processes an incoming buffer of JSON data.\n * @param buffer - The input JSON chunk.\n */\n public write(buffer: Uint8Array | string): Error | undefined {\n if (typeof buffer === \"string\") {\n buffer = this.encoder.encode(buffer); // Convert to Uint8Array\n }\n\n let n: number;\n let res: number | Error = 0;\n let hasError: Error | undefined;\n for (let i = 0; i < buffer.length; i++) {\n n = buffer[i];\n\n switch (this.stateHandler.getTokenizerState()) {\n case TOKENIZER_STATES.START:\n hasError = this.processStartState(n, buffer, i);\n if (typeof hasError !== \"undefined\") return hasError;\n break;\n case TOKENIZER_STATES.STRING1:\n res = this.processStringStartingState(n, buffer, i);\n if (typeof res !== \"number\") return res;\n i = res;\n break;\n case TOKENIZER_STATES.STRING2:\n res = this.processStringBackslashState(n, buffer, i);\n if (typeof res !== \"number\") return res;\n i = res;\n break;\n case TOKENIZER_STATES.STRING3:\n case TOKENIZER_STATES.STRING4:\n case TOKENIZER_STATES.STRING5:\n case TOKENIZER_STATES.STRING6:\n // unicode hex codes\n res = this.processStringUnicodeState(n, buffer, i);\n if (typeof res !== \"number\") return res;\n i = res;\n break;\n case TOKENIZER_STATES.NUMBER1:\n case TOKENIZER_STATES.NUMBER3:\n res = this.processNumberState(n, i);\n if (typeof res !== \"number\") return res;\n i = res;\n break;\n case TOKENIZER_STATES.TRUE1:\n case TOKENIZER_STATES.TRUE2:\n case TOKENIZER_STATES.TRUE3:\n hasError = this.processTrueState(n, buffer, i);\n if (typeof hasError !== \"undefined\") return hasError;\n break;\n case TOKENIZER_STATES.FALSE1:\n case TOKENIZER_STATES.FALSE2:\n case TOKENIZER_STATES.FALSE3:\n case TOKENIZER_STATES.FALSE4:\n hasError = this.processFalseState(n, buffer, i);\n if (typeof hasError !== \"undefined\") return hasError;\n break;\n case TOKENIZER_STATES.NULL1:\n case TOKENIZER_STATES.NULL2:\n case TOKENIZER_STATES.NULL3:\n this.processNullState(n, buffer, i);\n break;\n default:\n this.errorHandler.charError(buffer, i);\n }\n }\n }\n\n /**\n * Processes tokens at the root level (e.g. `{`, `[`, `:`, `,`).\n */\n private processStartState(n: number, buffer: Uint8Array, i: number): Error | undefined {\n this.stateHandler.incOffset();\n\n if (n === CHARS.LEFT_BRACE) this.tokenHandler.handleToken(TOKENS.LEFT_BRACE, \"{\");\n else if (n === CHARS.RIGHT_BRACE) this.tokenHandler.handleToken(TOKENS.RIGHT_BRACE, \"}\");\n else if (n === CHARS.LEFT_BRACKET) this.tokenHandler.handleToken(TOKENS.LEFT_BRACKET, \"[\");\n else if (n === CHARS.RIGHT_BRACKET) this.tokenHandler.handleToken(TOKENS.RIGHT_BRACKET, \"]\");\n else if (n === CHARS.COLON) this.tokenHandler.handleToken(TOKENS.COLON, \":\");\n else if (n === CHARS.COMMA) this.tokenHandler.handleToken(TOKENS.COMMA, \",\");\n else if (n === CHARS.T) this.stateHandler.setTokenizerState(TOKENIZER_STATES.TRUE1);\n else if (n === CHARS.F) this.stateHandler.setTokenizerState(TOKENIZER_STATES.FALSE1);\n else if (n === CHARS.N) this.stateHandler.setTokenizerState(TOKENIZER_STATES.NULL1);\n else if (n === CHARS.QUOTE) {\n this.stringHandler.reset(\"\")\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.STRING1);\n } else if (n === CHARS.MINUS) {\n this.stringHandler.resetString(\"-\")\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.NUMBER1);\n } else if (n >= CHARS.ZERO && n <= CHARS.NINE) {\n this.stringHandler.setFromCharcode(n)\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.NUMBER3);\n } else if (!CHARS.WHITESPACES.includes(n)) {\n return this.errorHandler.charError(buffer, i);\n }\n }\n\n private processStringUnicodeState(n: number, buffer: Uint8Array, i: number): Error | number {\n const isNumber = n >= CHARS.ZERO && n <= CHARS.NINE\n const isBigHexLetter = n >= CHARS.BIG_A && n <= CHARS.BIG_F\n const isHexLetter = n >= CHARS.A && n <= CHARS.F\n\n if (isNumber || isBigHexLetter || isHexLetter) {\n this.stringHandler.addUnicodeFromCharCode(n);\n const currentState = this.stateHandler.getTokenizerState();\n this.stateHandler.incTokenizerState()\n\n if (currentState === TOKENIZER_STATES.STRING6) {\n const intVal = parseInt(this.stringHandler.getUnicode(), 16);\n this.stringHandler.setUnicode(undefined)\n\n //<56320,57343> - lowSurrogate\n if (\n this.stringHandler.getHighSurrogate() !== undefined &&\n intVal >= UTF8_BOUNDS.LOW_SURROGATE_START &&\n intVal <= UTF8_BOUNDS.LOW_SURROGATE_END\n ) {\n this.stringHandler.appendStringBuf(this.encoder.encode(String.fromCharCode(this.stringHandler.getHighSurrogate() || 0, intVal)));\n this.stringHandler.setHighSurrogate(undefined);\n } else if (\n this.stringHandler.getHighSurrogate() === undefined &&\n intVal >= UTF8_BOUNDS.HIGH_SURROGATE_START &&\n intVal <= UTF8_BOUNDS.HIGH_SURROGATE_END\n ) {\n //<55296,56319> - highSurrogate\n this.stringHandler.setHighSurrogate(intVal);\n } else {\n if (this.stringHandler.getHighSurrogate() !== undefined) {\n this.stringHandler.appendStringBuf(this.encoder.encode(String.fromCharCode(this.stringHandler.getHighSurrogate() || 0)));\n this.stringHandler.setHighSurrogate(undefined);\n }\n\n this.stringHandler.appendStringBuf(this.encoder.encode(String.fromCharCode(intVal)));\n }\n\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.STRING1);\n }\n } else {\n return this.errorHandler.charError(buffer, i);\n }\n\n return i;\n }\n\n private processStringBackslashState(n: number, buffer: Uint8Array, i: number): Error | number {\n n = buffer[i];\n\n if (n === CHARS.QUOTE) {\n this.stringHandler.appendStringChar(n); this.stateHandler.setTokenizerState(TOKENIZER_STATES.STRING1);\n } else if (n === CHARS.BACKSLASH) {\n this.stringHandler.appendStringChar(CHARS.BACKSLASH); this.stateHandler.setTokenizerState(TOKENIZER_STATES.STRING1);\n } else if (n === CHARS.FORWARD_SLASH) {\n this.stringHandler.appendStringChar(CHARS.FORWARD_SLASH);\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.STRING1);\n } else if (n === CHARS.B) {\n this.stringHandler.appendStringChar(CHARS.BACKSPACE);\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.STRING1);\n } else if (n === CHARS.F) {\n this.stringHandler.appendStringChar(CHARS.FORM_FEED);\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.STRING1);\n } else if (n === CHARS.N) {\n this.stringHandler.appendStringChar(CHARS.NEWLINE);\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.STRING1);\n } else if (n === CHARS.R) {\n this.stringHandler.appendStringChar(CHARS.CARRIAGE_RETURN);\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.STRING1);\n } else if (n === CHARS.T) {\n this.stringHandler.appendStringChar(CHARS.TAB);\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.STRING1);\n } else if (n === CHARS.U) {\n this.stringHandler.setUnicode(\"\");\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.STRING3)\n } else {\n return this.errorHandler.charError(buffer, i);\n }\n\n return i;\n }\n\n private processStringStartingState(n: number, buffer: Uint8Array, i: number): Error | number {\n // check for carry over of a multi byte char split between data chunks\n // & fill temp buffer it with start of this data chunk up to the boundary limit set in the last iteration\n if (this.utf8Handler.hasBytesRemaining()) {\n const remainingBytes = this.utf8Handler.getBytesRemaining();\n\n this.stringHandler.appendStringBuf(\n this.utf8Handler.getRemainingBytesInBuff(buffer)\n )\n\n i = i + remainingBytes - 1;\n // else if no remainder bytes carried over, parse multi byte (>=128) chars one at a time\n } else if (!this.utf8Handler.hasBytesRemaining() && n >= UTF8_BOUNDS.MIN_MULTI_BYTE) {\n if (n <= UTF8_BOUNDS.INVALID_LOWER || n > UTF8_BOUNDS.BYTE_4_MAX) {\n const error = new Error(\n \"Invalid UTF-8 character at position \" +\n i + \" in state \" +\n this.stateHandler.getTokenizerStateName() +\n \" with value: \" + n\n );\n\n this.eventEmitter.handleError(error);\n return error;\n }\n\n if ((n >= UTF8_BOUNDS.BYTE_2_MIN) && (n <= UTF8_BOUNDS.BYTE_2_MAX)) this.utf8Handler.setBytesInSequence(2);\n if ((n >= UTF8_BOUNDS.BYTE_3_MIN) && (n <= UTF8_BOUNDS.BYTE_3_MAX)) this.utf8Handler.setBytesInSequence(3);\n if ((n >= UTF8_BOUNDS.BYTE_4_MIN) && (n <= UTF8_BOUNDS.BYTE_4_MAX)) this.utf8Handler.setBytesInSequence(4);\n\n // if bytes needed to complete char fall outside buffer length, we have a boundary split\n if ((this.utf8Handler.getBytesInSequence() + i) > buffer.length) {\n this.utf8Handler.handleBoundarySplit(i, buffer)\n i = buffer.length - 1;\n } else {\n this.stringHandler.appendStringBuf(buffer, i, i + this.utf8Handler.getBytesInSequence())\n i = i + this.utf8Handler.getBytesInSequence() - 1;\n }\n } else if (n === CHARS.QUOTE) {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.START)\n this.stringHandler.flushBuffer()\n this.tokenHandler.handleToken(TOKENS.STRING, this.stringHandler.getString())\n this.stateHandler.addOffset(this.encoder.encode(this.stringHandler.getString()).length + 1)\n this.stringHandler.resetString()\n } else if (n === CHARS.BACKSLASH) {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.STRING2)\n } else if (n >= CHARS.SPACE) {\n this.stringHandler.appendStringChar(n)\n } else {\n return this.errorHandler.charError(buffer, i)\n }\n\n return i;\n }\n\n /**\n * Parses numeric values.\n */\n private processNumberState(n: number, i: number): Error | number {\n if (\n (n >= CHARS.ZERO && n <= CHARS.NINE) ||\n n === CHARS.DOT ||\n n === CHARS.E ||\n n === CHARS.BIG_E ||\n n === CHARS.PLUS ||\n n === CHARS.MINUS\n ) {\n this.stringHandler.addFromCharCode(n);\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.NUMBER3)\n } else {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.START);\n\n const error = this.numberReviver(this.stringHandler.getString())\n\n if (error) {\n return error;\n }\n\n this.stateHandler.addOffset(this.stringHandler.getString().length - 1)\n this.stringHandler.resetString()\n i--;\n }\n\n return i;\n }\n\n private processFalseState(n: number, buffer: Uint8Array, i: number) {\n const tokenizerState = this.stateHandler.getTokenizerState();\n\n if (tokenizerState === TOKENIZER_STATES.FALSE1 && n === CHARS.A) {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.FALSE2)\n return\n }\n\n if (tokenizerState === TOKENIZER_STATES.FALSE2 && n === CHARS.L) {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.FALSE3)\n return\n }\n\n if (tokenizerState === TOKENIZER_STATES.FALSE3 && n === CHARS.S) {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.FALSE4)\n return\n }\n\n if (tokenizerState === TOKENIZER_STATES.FALSE4 && n === CHARS.E) {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.START)\n this.tokenHandler.handleToken(TOKENS.FALSE, false)\n this.stateHandler.addOffset(4)\n return\n }\n\n return this.errorHandler.charError(buffer, i);\n }\n\n private processTrueState(n: number, buffer: Uint8Array, i: number) {\n const tokenizerState = this.stateHandler.getTokenizerState();\n\n if (tokenizerState === TOKENIZER_STATES.TRUE1 && n === CHARS.R) {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.TRUE2)\n return\n }\n\n if (tokenizerState === TOKENIZER_STATES.TRUE2 && n === CHARS.U) {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.TRUE3)\n return\n }\n\n if (tokenizerState === TOKENIZER_STATES.TRUE3 && n === CHARS.E) {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.START)\n this.tokenHandler.handleToken(TOKENS.TRUE, true)\n this.stateHandler.addOffset(3)\n return\n }\n\n return this.errorHandler.charError(buffer, i);\n }\n\n /**\n * Processes `null` value.\n */\n private processNullState(n: number, buffer: Uint8Array, i: number) {\n const tokenizerState = this.stateHandler.getTokenizerState();\n\n if (tokenizerState === TOKENIZER_STATES.NULL1 && n === CHARS.U) {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.NULL2)\n return\n }\n\n if (tokenizerState === TOKENIZER_STATES.NULL2 && n === CHARS.L) {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.NULL3)\n return\n }\n\n if (tokenizerState === TOKENIZER_STATES.NULL3 && n === CHARS.L) {\n this.stateHandler.setTokenizerState(TOKENIZER_STATES.START)\n this.tokenHandler.handleToken(TOKENS.NULL, null)\n this.stateHandler.addOffset(3)\n return\n }\n\n return this.errorHandler.charError(buffer, i);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACO,IAAM,SAAS;AAAA,EAClB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,cAAc;AAAA,EACd,eAAe;AAAA,EACf,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AACZ;AAIO,IAAM,mBAAmB;AAAA,EAC5B,OAAO;AAAA,EACP,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA;AAAA,EACT,SAAS;AAAA;AAAA;AAAA,EAET,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AACb;AAGO,IAAM,gBAAgB;AAAA,EACzB,OAAO;AAAA,EACP,KAAK;AAAA,EACL,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AACX;AAGO,IAAM,eAAe;AAAA,EACxB,QAAQ;AAAA,EACR,OAAO;AACX;AAGO,IAAM,QAAQ;AAAA,EACjB,WAAW;AAAA;AAAA,EACX,eAAe;AAAA;AAAA,EACf,WAAW;AAAA;AAAA,EACX,WAAW;AAAA;AAAA,EACX,SAAS;AAAA;AAAA,EACT,iBAAiB;AAAA;AAAA,EACjB,KAAK;AAAA;AAAA,EACL,OAAO;AAAA;AAAA,EAGP,YAAY;AAAA;AAAA,EACZ,aAAa;AAAA;AAAA,EACb,cAAc;AAAA;AAAA,EACd,eAAe;AAAA;AAAA,EACf,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EAEP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,MAAM;AAAA;AAAA,EAEN,KAAK;AAAA;AAAA,EACL,GAAG;AAAA;AAAA,EACH,OAAO;AAAA;AAAA,EAEP,GAAG;AAAA;AAAA,EACH,GAAG;AAAA;AAAA,EACH,GAAG;AAAA;AAAA,EACH,GAAG;AAAA;AAAA,EACH,GAAG;AAAA;AAAA,EAEH,GAAG;AAAA;AAAA,EACH,GAAG;AAAA;AAAA,EACH,GAAG;AAAA;AAAA,EAEH,GAAG;AAAA;AAAA,EAEH,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EAEP,MAAM;AAAA;AAAA,EACN,MAAM;AAAA;AAAA,EAEN,aAAa;AAAA,IACT;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,EACJ;AACJ;AAEO,IAAM,cAAc;AAAA,EACvB,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,YAAY;AAAA,EAC