// Comparing two numbers: is this the same line as that one?
//
// libphonenumber answers this with `isNumberMatch`, and the distinction matters
// in address books: two entries can be the same line written with and without a
// country code, or one can be a fragment of the other because it was stored
// without its area code.
///|
/// How two numbers relate.
pub(all) enum NumberMatch {
/// Calling code, national number and extension all agree.
ExactMatch
/// National numbers and extensions agree; one calling code is unspecified (0).
NsnMatch
/// One national number is a suffix of the other: the classic "stored without
/// the area code" case.
ShortNsnMatch
NoMatch
} derive(Eq, Debug)
///|
/// `Eq` promotes both methods; name them so the promotion is stated.
pub extend NumberMatch with Eq::{not_equal, equal}
///|
/// `Debug` promotes `to_repr` the same way.
pub extend NumberMatch with @moonbitlang/core/debug.Debug::{to_repr}
///|
pub impl Show for NumberMatch with fn to_string(self : NumberMatch) -> String {
match self {
ExactMatch => "exact"
NsnMatch => "nsn"
ShortNsnMatch => "short-nsn"
NoMatch => "none"
}
}
///|
pub extend NumberMatch with Show::{output, to_string}
///|
/// True when `short` is a proper suffix of `long`.
fn is_suffix(short : String, long : String) -> Bool {
let s = chars_of(short)
let l = chars_of(long)
if s.length() == 0 || s.length() >= l.length() {
return false
}
let offset = l.length() - s.length()
let mut i = 0
while i < s.length() {
if s[i] != l[offset + i] {
return false
}
i = i + 1
}
true
}
///|
/// Compare two parsed numbers, in libphonenumber's four levels.
///
/// Different explicit calling codes never match. Zero means an unspecified
/// calling code. Conflicting explicit extensions never match; a missing
/// extension permits only a short match, following libphonenumber.
pub fn is_number_match(a : PhoneNumber, b : PhoneNumber) -> NumberMatch {
if a.country_code < 0 ||
b.country_code < 0 ||
a.national_number == "" ||
b.national_number == "" ||
digits_of(a.national_number) != a.national_number ||
digits_of(b.national_number) != b.national_number {
return NoMatch
}
if a.extension != "" && b.extension != "" && a.extension != b.extension {
return NoMatch
}
if a.country_code != 0 &&
b.country_code != 0 &&
a.country_code != b.country_code {
return NoMatch
}
if a.national_number == b.national_number && a.extension == b.extension {
return if a.country_code == b.country_code && a.country_code != 0 {
ExactMatch
} else {
NsnMatch
}
}
if a.national_number == b.national_number ||
is_suffix(a.national_number, b.national_number) ||
is_suffix(b.national_number, a.national_number) {
ShortNsnMatch
} else {
NoMatch
}
}
///|
test "number matching" {
let a = PhoneNumber::new(1, "2015550123")
assert_eq(is_number_match(a, PhoneNumber::new(1, "2015550123")), ExactMatch)
assert_eq(is_number_match(a, PhoneNumber::new(44, "2015550123")), NoMatch)
assert_eq(is_number_match(a, PhoneNumber::new(0, "2015550123")), NsnMatch)
assert_eq(is_number_match(a, PhoneNumber::new(1, "5550123")), ShortNsnMatch)
assert_eq(is_number_match(a, PhoneNumber::new(1, "2015550124")), NoMatch)
// Different extensions are different destinations.
assert_eq(is_number_match(a.with_extension("42"), a), ShortNsnMatch)
assert_eq(
is_number_match(
a.with_extension("42"),
PhoneNumber::new(1, "2015550123").with_extension("42"),
),
ExactMatch,
)
}