Phone number parsing, validation and formatting for MoonBit: 245 numbering plans behind E.164 and RFC 3966, a pattern engine that interprets each plan's published rules, valid versus possible verdicts, number types, the full NANP, grouping by each plan's own formatting rules, and a generated table with a reproducible data pipeline
| Level | Question | Answer comes from |
|---|---|---|
| is_possible | known calling code, length the plan uses? | published lengths (and the NANP's shape rules for +1) |
| is_valid | does the number match a published rule? | the plan's per-type patterns, interpreted by the engine |
| number_type | which rule matched? | the same walk, in libphonenumber's precedence order |
///|
let pn = @dialnumber.parse("+1 204-254-5000", "")
///|
match pn {
Some(n) => {
@dialnumber.region_of_number(n) // Some("CA")
@dialnumber.is_possible(n) // true
@dialnumber.is_valid(n) // true
@dialnumber.number_type(n) // Some(FixedLineOrMobile)
}
None => ()
}///|
let us = @dialnumber.PhoneNumber::new(1, "2015550123")
@dialnumber.format_national(us, "US") // Some("(201) 555-0123")
@dialnumber.format_international(us) // "+1 201-555-0123"
///|
let gb = @dialnumber.PhoneNumber::new(44, "2079460958")
@dialnumber.format_national(gb, "GB") // Some("020 7946 0958")
@dialnumber.format_international(gb) // "+44 20 7946 0958"///|
@dialnumber.matches_pattern("[2-9]\\d{9}", "2042545000") // true
@dialnumber.matches_pattern("[2-9]\\d{9}", "204254500") // false: full match only
@dialnumber.pattern_well_formed("[2-9]\\d{9}") // true
@dialnumber.plan_pattern("DK") // Some("[2-9]\\d{7}")PhoneNumberMetadata.xml upstream, Apache-2.0, pinned by SHA-256
| scripts/update-metadata.mjs fetch, extract, validate the grammar
v
data/numbering-plans.json one record per region: prefixes, types,
| patterns, lengths, example numbers,
| and the formatting rules
| scripts/gen-metadata.mjs emit MoonBit
v
plan_data.mbt the table the library compiles
plan_examples_test.mbt 1 132 published example numbers as golden tests
plan_corpus_test.mbt the engine cross-checked against a regex enginenode scripts/update-metadata.mjs # fetch a fresh copy of the upstream XML
node scripts/gen-metadata.mjs # regenerate the table and the example tests
node scripts/gen-corpus.mjs # regenerate the differential corpus
node scripts/check-docs.mjs # verify the docs still match real output///|
@dialnumber.nanp_region_codes().length() // 25
@dialnumber.nanp_region_of("2042545000") // Some("CA")
@dialnumber.nanp_region_of("7872345678") // Some("PR")
@dialnumber.nanp_region_of("2011550123") // None: NXX starts with 1moon add sssssurf/dialnumber///|
let pn = @dialnumber.parse("011 44 20 7946 0958", "US")
///|
match pn {
Some(n) => {
@dialnumber.format_e164(n) // "+442079460958"
@dialnumber.format_national(n, "GB") // Some("020 7946 0958")
@dialnumber.is_possible(n) // true
@dialnumber.is_valid(n) // true
@dialnumber.region_of_number(n) // Some("GB")
}
None => ()
}moon run cmd/main -- parse "+81 90-1234-5678"
moon run cmd/main -- valid "+1 787-234-5678"
moon run cmd/main -- possible "090-1234-5678" JP
moon run cmd/main -- format "090-1234-5678" JP
moon run cmd/main -- types "+86 131 2345 6789"
moon run cmd/main -- region "+442079460958"
moon run cmd/main -- info JP
moon run cmd/main -- cc de
moon run cmd/main -- tel "tel:555-0123;phone-context=+1-919;ext=42"
moon run cmd/main -- match "\d{8}" 12345678
moon run cmd/main -- list$ moon run cmd/main -- parse "+81 90-1234-5678"
country_code: 81
national_number: 9012345678
e164: +819012345678
region: JP
$ moon run cmd/main -- valid "+1 787-234-5678"
valid: yes
type: fixed-line-or-mobile
region: PR
$ moon run cmd/main -- types "+86 131 2345 6789"
length candidates: fixed-line,mobile,shared-cost
pattern verdict: mobile
$ moon run cmd/main -- info JP
region: JP
name: Japan
calling_code: 81
idd: 010
national_prefix: 0
lengths: 8,9,10,11,12,13,14,15,16,17
$ moon run cmd/main -- tel "tel:555-0123;phone-context=+1-919;ext=42"
global_number: (none)
local_number: 5550123
phone_context: +1919
extension: 42
isub: (none)
resolved: +19195550123
$ moon run cmd/main -- match "\d{8}" 12345678
well_formed: yes
matches: yes| Function | Purpose |
|---|---|
| parse(input, region) | read any written form into a PhoneNumber? |
| parse_e164(input) | read only a +-prefixed number |
| parse_national(input, region) | read a number as dialled in a region |
| is_possible(pn) | known code and a length the plan uses, plus NANP shape for +1 |
| is_possible_in_region(pn, region) | the same, against one named region |
| is_possible_number(input, region) | parse then check, in one call |
| is_valid(pn) | the national number matches a published rule |
| is_valid_in_region(pn, region) | the same, against one named region |
| is_valid_number(input, region) | parse then validate, in one call |
| number_type(pn) | the type the rules report, in libphonenumber's precedence |
| number_type_in_region(pn, region) | the same, within one named region |
| possible_types(pn) | number types whose length admits the number |
| possible_types_in_region(pn, region) | the same, within one region |
| region_of_number(pn) | the region the number resolves to |
| Function | Purpose |
|---|---|
| matches_pattern(pattern, text) | full-match under the numbering-plan grammar |
| pattern_well_formed(pattern) | is the rule inside the supported grammar |
| plan_pattern(code) | the plan's overall rule, as published |
| type_pattern(code, kind) | the rule published for one number type |
| Function | Purpose |
|---|---|
| format_e164(pn) | +8613123456789 |
| format_international(pn) | +86 131 2345 6789, grouped by the plan |
| format_national(pn, region) | trunk prefix plus grouped national number |
| format_dialling(pn, from_region) | 011 442079460958, or the national form at home |
| format_rfc3966(pn) | tel:+8613123456789;ext=42 |
| parse_tel_uri(uri) | parse and resolve a tel: URI |
| parse_tel_uri_full(uri) | parse into a TelUri? without resolving |
| to_tel_uri_full(t) | serialise a TelUri canonically |
| resolve_tel_uri(t) | resolve parsed parts, when the context allows |
| to_tel_uri(pn) | write a tel: URI for a number |
| Function | Purpose |
|---|---|
| nanp_possible(nsn) | ten-digit NANP structure |
| nanp_region_of(nsn) | the territory a +1 number belongs to |
| nanp_region_codes() | every region in the NANP |
| nanp_area_code(nsn) | the first three digits of a NANP number |
| region_codes() | every region code in the table |
| region_exists(code) | is the code in the table |
| canonical_region_code(code) | the table's spelling of a code |
| region_name(code) | English name |
| calling_code(code) | E.164 calling code |
| idd_prefix(code) | international dial-out prefix |
| national_prefix(code) | trunk prefix, or None |
| possible_lengths(code) | national-number lengths |
| local_only_lengths(code) | lengths accepted only when dialled locally |
| region_by_calling_code(cc) | one region for a calling code |
| regions_by_calling_code(cc) | every region for a calling code |
| Operation | Time |
|---|---|
| is_possible | 0.13 µs |
| format_e164 | 0.10 µs |
| format_national | 3.9 µs |
| is_valid (a single-plan number) | 4.5 µs |
| parse (E.164) | 4.9 µs |
| parse (tel: URI) | 9.1 µs |
| region_of_number | 12.8 µs |
| parse (national) | 15.8 µs |
| is_valid (a NANP number, 25 territories to disambiguate) | 15.9 µs |
| format_international | 18.7 µs |
moon testimpl Show for NumberTypeimpl Show for PhoneNumberfn calling_code(code : String) -> Int?fn canonical_region_code(code : String) -> String?fn idd_prefix(code : String) -> String?fn is_possible_number(input : String, region : String) -> Boolfn is_valid_number(input : String, region : String) -> Boolfn matches_pattern(pattern : String, text : String) -> Boolfn nanp_area_code(national_number : String) -> String?fn nanp_possible(national_number : String) -> Boolfn nanp_region_of(national_number : String) -> String?fn national_prefix(code : String) -> String?fn pattern_well_formed(pattern : String) -> Boolfn plan_pattern(code : String) -> String?fn region_by_calling_code(cc : Int) -> String?fn region_exists(code : String) -> Boolfn region_name(code : String) -> String?Install
Download zipPhone number parsing, validation and formatting for MoonBit: 245 numbering plans behind E.164 and RFC 3966, a pattern engine that interprets each plan's published rules, valid versus possible verdicts, number types, the full NANP, grouping by each plan's own formatting rules, and a generated table with a reproducible data pipeline