libphonenumber-js
Version:
A simpler (and smaller) rewrite of Google Android's libphonenumber library in javascript
184 lines (182 loc) • 4.86 kB
JavaScript
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;
}
//# sourceMappingURL=AsYouTypeFormatter.PatternParser.js.map