// Numbering-plan metadata: one row per region, describing how to dial into and
// out of it, which number types it defines, and the rule each type follows.
//
// The table itself is generated (`plan_data.mbt`, from
// `data/numbering-plans.json` by `scripts/gen-metadata.mjs`) out of Google's
// libphonenumber metadata (Apache-2.0). Nothing here is hand-maintained data:
// this file is the lookup and expansion layer over the generated table.
///|
/// One number type inside a plan: the rule a national number must match, and the
/// lengths the plan publishes for that type.
///
/// `national` is the set of possible lengths; `local_only` is the set of lengths
/// that are only possible when dialled locally (kept for completeness and for
/// the data-integrity tests, not used for `possible`).
priv struct PlanType {
kind : NumberType
pattern : String
national : Array[Int]
local_only : Array[Int]
/// The compiled rule, filled in the first time the rule is used. A pattern
/// costs microseconds to compile, and a validation walks several of them, so
/// compiling once per rule (rather than once per call) is the difference
/// between a microsecond and a millisecond. The cell makes the cache survive
/// the value copy of the row it lives in.
cached : @ref.Ref[CompiledPattern?]
}
///|
/// One formatting rule: how a national number of a given shape is split into
/// groups, and what goes between them.
///
/// `pattern` is a sequence of capture groups over digit runs (`(\d{3})(\d{4})`);
/// `format` refers to them as `$1`..`$9`. `leading` holds the plan's published
/// leading-digit patterns for selecting the rule; the last one is the most
/// specific and is the one that decides. `intl_format` overrides `format` when
/// the number is rendered internationally (`"NA"` means the plan has no
/// international rendering). `prefix_rule` is the plan's national-prefix rule,
/// written with `$NP` (the trunk prefix) and `$FG` (the formatted number), and
/// `prefix_optional` says the prefix may be left out.
priv struct PhoneFormat {
pattern : String
leading : Array[String]
format : String
intl_format : String
prefix_rule : String
/// The compiled leading-digit pattern of the rule (the last one published),
/// memoised so rendering does not re-parse it.
leading_rule : @ref.Ref[CompiledPattern?]
}
///|
/// One formatting row, used only by the generated table.
fn plan_format(
pattern : String,
leading : Array[String],
format : String,
intl_format : String,
prefix_rule : String,
) -> PhoneFormat {
{ pattern, leading, format, intl_format, prefix_rule, leading_rule: @ref.Ref(None) }
}
///|
/// A numbering plan.
///
/// `idd` is the prefix dialled to reach another country from inside this one.
/// `national_prefix` is the trunk prefix dialled before a national number; it is
/// empty for plans that have none. `general` is the plan's overall rule, and
/// `types` are the per-type rules. `primary` marks the region a bare calling
/// code resolves to when several regions share it.
priv struct Region {
code : String
name : String
calling_code : Int
idd : String
national_prefix : String
/// The published pattern that recognises a trunk prefix while parsing; empty
/// when the plan only publishes the plain prefix.
prefix_parsing : String
/// The territory's leading-digit pattern, or empty when it publishes none.
leading_digits : String
/// The compiled leading-digit pattern, memoised like the type rules.
leading_rule : @ref.Ref[CompiledPattern?]
/// The compiled trunk-prefix pattern, memoised like the type rules.
prefix_rule : @ref.Ref[CompiledPattern?]
primary : Bool
general : String
general_rule : @ref.Ref[CompiledPattern?]
types : Array[PlanType]
formats : Array[PhoneFormat]
}
///|
/// One type row. Keeping this a function keeps the generated table to one line
/// per type and lets the field order read in the order the data is published.
fn plan_type(
kind : NumberType,
pattern : String,
national : Array[Int],
local_only : Array[Int],
) -> PlanType {
{ kind, pattern, national, local_only, cached: @ref.Ref(None) }
}
///|
/// True when the territory's published leading-digit pattern matches a prefix of
/// the number, or when it publishes none. In a shared calling code this is the
/// cheap first question: the full type rules only run for territories that pass
/// it, which is what keeps a +1 resolution from testing twenty-five allocations.
fn region_leading_matches(r : Region, national_number : String) -> Bool {
if r.leading_digits == "" {
return true
}
let compiled = match r.leading_rule.val {
Some(c) => c
None => {
let c = compile_pattern(r.leading_digits)
r.leading_rule.val = Some(c)
c
}
}
if compiled.start < 0 {
return true
}
run_pattern_prefix(compiled, national_number)
}
///|
/// The compiled trunk-prefix pattern of a region, memoised in the row.
fn region_prefix_compiled(r : Region) -> CompiledPattern {
match r.prefix_rule.val {
Some(c) => c
None => {
let c = compile_pattern(r.prefix_parsing)
r.prefix_rule.val = Some(c)
c
}
}
}
///|
/// True when the plan's overall rule accepts the text. Memoised like a type rule.
fn region_general_matches(r : Region, text : String) -> Bool {
let compiled = match r.general_rule.val {
Some(c) => c
None => {
let c = compile_pattern(r.general)
r.general_rule.val = Some(c)
c
}
}
run_pattern(compiled, text)
}
///|
/// True when the type's rule accepts the text. The compiled rule is memoised in
/// the row, so the cost is paid once per rule rather than once per call.
fn type_rule_matches(t : PlanType, text : String) -> Bool {
let compiled = match t.cached.val {
Some(c) => c
None => {
let c = compile_pattern(t.pattern)
t.cached.val = Some(c)
c
}
}
run_pattern(compiled, text)
}
///|
/// One region row, used only by the generated table.
fn plan_row(
code : String,
name : String,
calling_code : Int,
idd : String,
national_prefix : String,
prefix_parsing : String,
leading_digits : String,
primary : Bool,
general : String,
types : Array[PlanType],
formats : Array[PhoneFormat],
) -> Region {
{
code,
name,
calling_code,
idd,
national_prefix,
prefix_parsing,
leading_digits,
leading_rule: @ref.Ref(None),
prefix_rule: @ref.Ref(None),
primary,
general,
general_rule: @ref.Ref(None),
types,
formats,
}
}
///|
/// Uppercase a region code so lookups are case-insensitive.
fn normalize_code(code : String) -> String {
let buf = StringBuilder()
for c in code {
buf.write_char(upper_ascii(c))
}
buf.to_string()
}
///|
/// Look a region up by its ISO 3166-1 alpha-2 code, case-insensitively.
fn find_region(code : String) -> Region? {
let want = normalize_code(code)
for r in regions {
if r.code == want {
return Some(r)
}
}
None
}
///|
/// Every national-number length any type in the region uses, sorted, without
/// repeats.
fn lengths_of(r : Region) -> Array[Int] {
let out : Array[Int] = []
for t in r.types {
for n in t.national {
push_unique_sorted(out, n)
}
}
out
}
///|
/// Insert `v` into a sorted, unique array, keeping it sorted.
fn push_unique_sorted(a : Array[Int], v : Int) -> Unit {
let mut i = 0
while i < a.length() && a[i] < v {
i = i + 1
}
if i < a.length() && a[i] == v {
return
}
a.insert(i, v)
}
///|
/// The number types whose length rule admits an `n`-digit national number.
fn types_for_length(r : Region, n : Int) -> Array[NumberType] {
let out : Array[NumberType] = []
for t in r.types {
if t.national.contains(n) {
out.push(t.kind)
}
}
out
}
///|
/// The type whose rule the national number matches, in the precedence order
/// libphonenumber uses: the special ranges win over the geographic ones, a
/// number that fits both fixed-line and mobile is reported as the shared type,
/// and a number outside the plan's general rule has no type at all.
fn type_of_national_number(r : Region, national_number : String) -> NumberType? {
if !region_general_matches(r, national_number) {
return None
}
for t in r.types {
if t.kind != FixedLine && t.kind != Mobile && type_rule_matches(t, national_number) {
return Some(t.kind)
}
}
let fixed = fits(r, FixedLine, national_number)
let mobile = fits(r, Mobile, national_number)
if fixed && mobile {
return Some(FixedLineOrMobile)
}
if fixed {
return Some(FixedLine)
}
if mobile {
return Some(Mobile)
}
None
}
///|
/// True when the region publishes a rule of this type and the number matches it.
fn fits(r : Region, kind : NumberType, national_number : String) -> Bool {
for t in r.types {
if t.kind == kind && type_rule_matches(t, national_number) {
return true
}
}
false
}
///|
/// The formatting rules that apply to a region. A territory that shares a
/// calling code and publishes no rules of its own (Canada, Puerto Rico and the
/// other NANP territories under `+1`) inherits the primary region's rules,
/// which is what the upstream metadata means by leaving them out.
fn formats_of(r : Region) -> Array[PhoneFormat] {
if r.formats.length() > 0 {
return r.formats
}
for other in regions {
if other.calling_code == r.calling_code && other.primary {
return other.formats
}
}
[]
}
///|
/// True when the number matches any published rule of the region. This is the
/// pattern-level test behind `is_valid`; the caller has already established that
/// the calling codes agree.
fn region_admits(r : Region, national_number : String) -> Bool {
for t in r.types {
if type_rule_matches(t, national_number) {
return true
}
}
false
}
// ---------------------------------------------------------------------------
// Public lookups
// ---------------------------------------------------------------------------
///|
/// Every region code in the table, sorted.
pub fn region_codes() -> Array[String] {
let out : Array[String] = []
for r in regions {
out.push(r.code)
}
out
}
///|
/// True when the region code is one the table knows.
pub fn region_exists(code : String) -> Bool {
find_region(code) is Some(_)
}
///|
/// The table's own spelling of a region code, or None when it is not known.
/// Useful for echoing a user-supplied code in a canonical form.
pub fn canonical_region_code(code : String) -> String? {
match find_region(code) {
Some(r) => Some(r.code)
None => None
}
}
///|
/// The English name of a region, e.g. `"Japan"`.
pub fn region_name(code : String) -> String? {
match find_region(code) {
Some(r) => Some(r.name)
None => None
}
}
///|
/// The E.164 calling code of a region, without a leading `+`.
pub fn calling_code(code : String) -> Int? {
match find_region(code) {
Some(r) => Some(r.calling_code)
None => None
}
}
///|
/// The international dial-out prefix used inside a region, e.g. `"011"` for the
/// NANP or `"00"` for most of Europe.
pub fn idd_prefix(code : String) -> String? {
match find_region(code) {
Some(r) => Some(r.idd)
None => None
}
}
///|
/// The trunk prefix dialled before a national number, or None when the plan has
/// none (Italy, Spain, Portugal and others).
pub fn national_prefix(code : String) -> String? {
match find_region(code) {
Some(r) => if r.national_prefix == "" { None } else { Some(r.national_prefix) }
None => None
}
}
///|
/// The national-number lengths the region uses, sorted, without repeats.
pub fn possible_lengths(code : String) -> Array[Int]? {
match find_region(code) {
Some(r) => Some(lengths_of(r))
None => None
}
}
///|
/// The lengths a region accepts only for a number dialled locally (no trunk
/// prefix or no area code), sorted and without repeats. Empty when the plan
/// publishes none — which is most of them.
pub fn local_only_lengths(code : String) -> Array[Int]? {
match find_region(code) {
None => None
Some(r) => {
let out : Array[Int] = []
for t in r.types {
for n in t.local_only {
push_unique_sorted(out, n)
}
}
Some(out)
}
}
}
///|
/// One region that uses a calling code, or None when no region does. When
/// several share the code (the whole NANP is `+1`) the region marked primary
/// wins; otherwise the first in code order. Use `regions_by_calling_code` for
/// all of them.
pub fn region_by_calling_code(cc : Int) -> String? {
for r in regions {
if r.calling_code == cc && r.primary {
return Some(r.code)
}
}
for r in regions {
if r.calling_code == cc {
return Some(r.code)
}
}
None
}
///|
/// Every region that shares a calling code, in table order.
pub fn regions_by_calling_code(cc : Int) -> Array[String] {
let out : Array[String] = []
for r in regions {
if r.calling_code == cc {
out.push(r.code)
}
}
out
}
///|
/// The plan's overall rule for its national numbers, e.g. `"[2-9]\\d{9}"` for
/// the NANP. Exposed so a caller can see the rule a verdict was based on.
pub fn plan_pattern(code : String) -> String? {
match find_region(code) {
Some(r) => Some(r.general)
None => None
}
}
///|
/// The rule published for one number type in one region, if the plan has that
/// type. Lets a caller audit `is_valid` / `number_type` against the data.
pub fn type_pattern(code : String, kind : NumberType) -> String? {
match find_region(code) {
None => None
Some(r) => {
for t in r.types {
if t.kind == kind {
return Some(t.pattern)
}
}
None
}
}
}