UNPKG

libphonenumber-js

Version:

A simpler (and smaller) rewrite of Google Android's libphonenumber library in javascript

214 lines (190 loc) 4.58 kB
export default class PatternParser { parse(pattern) { this.context = [{ or: true, instructions: [] }] this.parsePattern(pattern) if (this.context.length !== 1) { throw new Error('Non-finalized contexts left when pattern parse ended') } const { branches, instructions } = this.context[0] if (branches) { return { op: '|', args: branches.concat([ expandSingleElementArray(instructions) ]) } } /* istanbul ignore if */ if (instructions.length === 0) { throw new Error('Pattern is required') } if (instructions.length === 1) { return instructions[0] } return instructions } startContext(context) { this.context.push(context) } endContext() { this.context.pop() } getContext() { return this.context[this.context.length - 1] } parsePattern(pattern) { if (!pattern) { throw new Error('Pattern is required') } const match = pattern.match(OPERATOR) if (!match) { if (ILLEGAL_CHARACTER_REGEXP.test(pattern)) { throw new Error(`Illegal characters found in a pattern: ${pattern}`) } this.getContext().instructions = this.getContext().instructions.concat( pattern.split('') ) return } const operator = match[1] const before = pattern.slice(0, match.index) const rightPart = pattern.slice(match.index + operator.length) switch (operator) { case '(?:': if (before) { this.parsePattern(before) } this.startContext({ or: true, instructions: [], branches: [] }) break case ')': if (!this.getContext().or) { throw new Error('")" operator must be preceded by "(?:" operator') } if (before) { this.parsePattern(before) } if (this.getContext().instructions.length === 0) { throw new Error('No instructions found after "|" operator in an "or" group') } const { branches } = this.getContext() branches.push( expandSingleElementArray( this.getContext().instructions ) ) this.endContext() this.getContext().instructions.push({ op: '|', args: branches }) break case '|': if (!this.getContext().or) { throw new Error('"|" operator can only be used inside "or" groups') } if (before) { this.parsePattern(before) } // The top-level is an implicit "or" group, if required. if (!this.getContext().branches) { // `branches` are not defined only for the root implicit "or" operator. /* istanbul ignore else */ if (this.context.length === 1) { this.getContext().branches = [] } else { throw new Error('"branches" not found in an "or" group context') } } this.getContext().branches.push( expandSingleElementArray( this.getContext().instructions ) ) this.getContext().instructions = [] break case '[': if (before) { this.parsePattern(before) } this.startContext({ oneOfSet: true }) break case ']': if (!this.getContext().oneOfSet) { throw new Error('"]" operator must be preceded by "[" operator') } this.endContext() this.getContext().instructions.push({ op: '[]', args: parseOneOfSet(before) }) break /* istanbul ignore next */ default: throw new Error(`Unknown operator: ${operator}`) } if (rightPart) { this.parsePattern(rightPart) } } } function parseOneOfSet(pattern) { const values = [] let i = 0 while (i < pattern.length) { if (pattern[i] === '-') { if (i === 0 || i === pattern.length - 1) { throw new Error(`Couldn't parse a one-of set pattern: ${pattern}`) } const prevValue = pattern[i - 1].charCodeAt(0) + 1 const nextValue = pattern[i + 1].charCodeAt(0) - 1 let value = prevValue while (value <= nextValue) { values.push(String.fromCharCode(value)) value++ } } else { values.push(pattern[i]) } i++ } return values } const ILLEGAL_CHARACTER_REGEXP = /[\(\)\[\]\?\:\|]/ const OPERATOR = new RegExp( // any of: '(' + // or operator '\\|' + // or '|' + // or group start '\\(\\?\\:' + // or '|' + // or group end '\\)' + // or '|' + // one-of set start '\\[' + // or '|' + // one-of set end '\\]' + ')' ) function expandSingleElementArray(array) { if (array.length === 1) { return array[0] } return array }