import type { Schema, PrimitiveSchema, TupleSchema, ArraySchema } from './types'; import { ParseError } from './parser'; class ValueParser { private input: string; private pos: number = 0; constructor(input: string) { this.input = input; } private peek(): string { return this.input[this.pos] || ''; } private consume(): string { return this.input[this.pos++] || ''; } private skipWhitespace(): void { while (this.pos < this.input.length && /\s/.test(this.input[this.pos])) { this.pos++; } } private consumeStr(str: string): boolean { if (this.input.slice(this.pos, this.pos + str.length) === str) { this.pos += str.length; return true; } return false; } parseValue(schema: Schema, allowOmitBrackets: boolean = false): unknown { this.skipWhitespace(); switch (schema.type) { case 'string': return this.parseStringValue(); case 'number': return this.parseNumberValue(); case 'boolean': return this.parseBooleanValue(); case 'tuple': return this.parseTupleValue(schema, allowOmitBrackets); case 'array': return this.parseArrayValue(schema, allowOmitBrackets); default: throw new ParseError(`Unknown schema type: ${(schema as { type: string }).type}`, this.pos); } } private parseStringValue(): string { let result = ''; while (this.pos < this.input.length) { const char = this.peek(); if (char === '\\') { this.consume(); const nextChar = this.consume(); if (nextChar === ';' || nextChar === '[' || nextChar === ']' || nextChar === '\\') { result += nextChar; } else { result += '\\' + nextChar; } } else if (char === ';' || char === ']') { break; } else { result += this.consume(); } } return result.trim(); } private parseNumberValue(): number { let numStr = ''; while (this.pos < this.input.length && /[\d.\-+eE]/.test(this.peek())) { numStr += this.consume(); } const num = parseFloat(numStr); if (isNaN(num)) { throw new ParseError('Invalid number', this.pos - numStr.length); } return num; } private parseBooleanValue(): boolean { if (this.consumeStr('true')) { return true; } if (this.consumeStr('false')) { return false; } throw new ParseError('Expected true or false', this.pos); } private parseTupleValue(schema: TupleSchema, allowOmitBrackets: boolean): unknown[] { let hasOpenBracket = false; if (this.peek() === '[') { this.consume(); hasOpenBracket = true; } else if (!allowOmitBrackets) { throw new ParseError('Expected [', this.pos); } this.skipWhitespace(); if (this.peek() === ']' && hasOpenBracket) { this.consume(); return []; } const result: unknown[] = []; for (let i = 0; i < schema.elements.length; i++) { this.skipWhitespace(); result.push(this.parseValue(schema.elements[i], false)); this.skipWhitespace(); if (i < schema.elements.length - 1) { if (!this.consumeStr(';')) { throw new ParseError('Expected ;', this.pos); } } } this.skipWhitespace(); if (hasOpenBracket) { if (!this.consumeStr(']')) { throw new ParseError('Expected ]', this.pos); } } return result; } private parseArrayValue(schema: ArraySchema, allowOmitBrackets: boolean): unknown[] { let hasOpenBracket = false; const elementIsTupleOrArray = schema.element.type === 'tuple' || schema.element.type === 'array'; if (this.peek() === '[') { if (!elementIsTupleOrArray) { this.consume(); hasOpenBracket = true; } else if (this.input[this.pos + 1] === '[') { this.consume(); hasOpenBracket = true; } } if (!hasOpenBracket && !allowOmitBrackets && !elementIsTupleOrArray) { throw new ParseError('Expected [', this.pos); } this.skipWhitespace(); if (this.peek() === ']' && hasOpenBracket) { this.consume(); return []; } const result: unknown[] = []; while (true) { this.skipWhitespace(); result.push(this.parseValue(schema.element, false)); this.skipWhitespace(); if (!this.consumeStr(';')) { break; } } this.skipWhitespace(); if (hasOpenBracket) { if (!this.consumeStr(']')) { throw new ParseError('Expected ]', this.pos); } } return result; } getPosition(): number { return this.pos; } getInputLength(): number { return this.input.length; } } export function parseValue(schema: Schema, valueString: string): unknown { const parser = new ValueParser(valueString.trim()); const allowOmitBrackets = schema.type === 'tuple' || schema.type === 'array'; const value = parser.parseValue(schema, allowOmitBrackets); if (parser.getPosition() < parser.getInputLength()) { throw new ParseError('Unexpected input after value', parser.getPosition()); } return value; } export function createValidator(schema: Schema): (value: unknown) => boolean { return function validate(value: unknown): boolean { switch (schema.type) { case 'string': return typeof value === 'string'; case 'number': return typeof value === 'number' && !isNaN(value); case 'boolean': return typeof value === 'boolean'; case 'tuple': if (!Array.isArray(value)) return false; if (value.length !== schema.elements.length) return false; return schema.elements.every((elementSchema, index) => createValidator(elementSchema)(value[index]) ); case 'array': if (!Array.isArray(value)) return false; return value.every((item) => createValidator(schema.element)(item)); default: return false; } }; }