UNPKG

@sinclair/typebox

Version:

Json Schema Type Builder with Static Type Resolution for TypeScript

175 lines (173 loc) 6.46 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TemplateLiteralParserError = void 0; exports.TemplateLiteralParse = TemplateLiteralParse; exports.TemplateLiteralParseExact = TemplateLiteralParseExact; const index_1 = require("../error/index"); // ------------------------------------------------------------------ // TemplateLiteralParserError // ------------------------------------------------------------------ class TemplateLiteralParserError extends index_1.TypeBoxError { } exports.TemplateLiteralParserError = TemplateLiteralParserError; // ------------------------------------------------------------------- // Unescape // // Unescape for these control characters specifically. Note that this // function is only called on non union group content, and where we // still want to allow the user to embed control characters in that // content. For review. // ------------------------------------------------------------------- // prettier-ignore function Unescape(pattern) { return pattern .replace(/\\\$/g, '$') .replace(/\\\*/g, '*') .replace(/\\\^/g, '^') .replace(/\\\|/g, '|') .replace(/\\\(/g, '(') .replace(/\\\)/g, ')'); } // ------------------------------------------------------------------- // Control Characters // ------------------------------------------------------------------- function IsNonEscaped(pattern, index, char) { return pattern[index] === char && pattern.charCodeAt(index - 1) !== 92; } function IsOpenParen(pattern, index) { return IsNonEscaped(pattern, index, '('); } function IsCloseParen(pattern, index) { return IsNonEscaped(pattern, index, ')'); } function IsSeparator(pattern, index) { return IsNonEscaped(pattern, index, '|'); } // ------------------------------------------------------------------- // Control Groups // ------------------------------------------------------------------- function IsGroup(pattern) { if (!(IsOpenParen(pattern, 0) && IsCloseParen(pattern, pattern.length - 1))) return false; let count = 0; for (let index = 0; index < pattern.length; index++) { if (IsOpenParen(pattern, index)) count += 1; if (IsCloseParen(pattern, index)) count -= 1; if (count === 0 && index !== pattern.length - 1) return false; } return true; } // prettier-ignore function InGroup(pattern) { return pattern.slice(1, pattern.length - 1); } // prettier-ignore function IsPrecedenceOr(pattern) { let count = 0; for (let index = 0; index < pattern.length; index++) { if (IsOpenParen(pattern, index)) count += 1; if (IsCloseParen(pattern, index)) count -= 1; if (IsSeparator(pattern, index) && count === 0) return true; } return false; } // prettier-ignore function IsPrecedenceAnd(pattern) { for (let index = 0; index < pattern.length; index++) { if (IsOpenParen(pattern, index)) return true; } return false; } // prettier-ignore function Or(pattern) { let [count, start] = [0, 0]; const expressions = []; for (let index = 0; index < pattern.length; index++) { if (IsOpenParen(pattern, index)) count += 1; if (IsCloseParen(pattern, index)) count -= 1; if (IsSeparator(pattern, index) && count === 0) { const range = pattern.slice(start, index); if (range.length > 0) expressions.push(TemplateLiteralParse(range)); start = index + 1; } } const range = pattern.slice(start); if (range.length > 0) expressions.push(TemplateLiteralParse(range)); if (expressions.length === 0) return { type: 'const', const: '' }; if (expressions.length === 1) return expressions[0]; return { type: 'or', expr: expressions }; } // prettier-ignore function And(pattern) { function Group(value, index) { if (!IsOpenParen(value, index)) throw new TemplateLiteralParserError(`TemplateLiteralParser: Index must point to open parens`); let count = 0; for (let scan = index; scan < value.length; scan++) { if (IsOpenParen(value, scan)) count += 1; if (IsCloseParen(value, scan)) count -= 1; if (count === 0) return [index, scan]; } throw new TemplateLiteralParserError(`TemplateLiteralParser: Unclosed group parens in expression`); } function Range(pattern, index) { for (let scan = index; scan < pattern.length; scan++) { if (IsOpenParen(pattern, scan)) return [index, scan]; } return [index, pattern.length]; } const expressions = []; for (let index = 0; index < pattern.length; index++) { if (IsOpenParen(pattern, index)) { const [start, end] = Group(pattern, index); const range = pattern.slice(start, end + 1); expressions.push(TemplateLiteralParse(range)); index = end; } else { const [start, end] = Range(pattern, index); const range = pattern.slice(start, end); if (range.length > 0) expressions.push(TemplateLiteralParse(range)); index = end - 1; } } return ((expressions.length === 0) ? { type: 'const', const: '' } : (expressions.length === 1) ? expressions[0] : { type: 'and', expr: expressions }); } // ------------------------------------------------------------------ // TemplateLiteralParse // ------------------------------------------------------------------ /** Parses a pattern and returns an expression tree */ function TemplateLiteralParse(pattern) { // prettier-ignore return (IsGroup(pattern) ? TemplateLiteralParse(InGroup(pattern)) : IsPrecedenceOr(pattern) ? Or(pattern) : IsPrecedenceAnd(pattern) ? And(pattern) : { type: 'const', const: Unescape(pattern) }); } // ------------------------------------------------------------------ // TemplateLiteralParseExact // ------------------------------------------------------------------ /** Parses a pattern and strips forward and trailing ^ and $ */ function TemplateLiteralParseExact(pattern) { return TemplateLiteralParse(pattern.slice(1, pattern.length - 1)); }