git.iko.soy/typeable/libphonenumber
on main help sign in
Initial commit
commitee957b0677bc346a147ce8d5e1b9d90b48588efc authormniip <mniip@mniip.com> dateFri 17 Mar 2023, 11:00 (4 years ago)
14 files changed+2325
.gitignore+18 cxxbits/c_phonenumber.cpp+105 cxxbits/c_phonenumberutil.cpp+620 include/c_phonenumber.h+62 include/c_phonenumberutil.h+130 include/common.h+57 libphonenumber.cabal+84 src/Data/PhoneNumber/Internal/Common.chs+20 src/Data/PhoneNumber/Internal/Number.chs+186 src/Data/PhoneNumber/Internal/Util.chs+302 src/Data/PhoneNumber/Number.hs+112 src/Data/PhoneNumber/Util.hs+489 test/Leak.hs+118 test/libphonenumber.supp+22
.gitignorenew file+18
@@ -0,0 +1,18 @@
1+*~
2+*.swp
3+*.swo
4+\#*\#
5+dist
6+dist-*
7+*.o
8+*.dyn_o
9+*.hi
10+*.dyn_hi
11+*.hie
12+*.chi
13+*.chs.h
14+*.log
15+*.eventlog
16+.ghc.environment.*
17+cabal.project.local
18+.vscode
cxxbits/c_phonenumber.cppnew file+105
@@ -0,0 +1,105 @@
1+#include <string>
2+#include <cstdlib>
3+#include <phonenumbers/phonenumberutil.h>
4+
5+#include "common.h"
6+extern "C" {
7+#include "c_phonenumber.h"
8+}
9+
10+using i18n::phonenumbers::PhoneNumber;
11+
12+static PhoneNumber::CountryCodeSource marshal_country_code_source(country_code_source value)
13+{
14+ static_assert(is_same_enum<
15+ enum_values<country_code_source,
16+ UNSPECIFIED,
17+ FROM_NUMBER_WITH_PLUS_SIGN,
18+ FROM_NUMBER_WITH_IDD,
19+ FROM_NUMBER_WITHOUT_PLUS_SIGN,
20+ FROM_DEFAULT_COUNTRY
21+ >,
22+ enum_values<PhoneNumber::CountryCodeSource,
23+ PhoneNumber::UNSPECIFIED,
24+ PhoneNumber::FROM_NUMBER_WITH_PLUS_SIGN,
25+ PhoneNumber::FROM_NUMBER_WITH_IDD,
26+ PhoneNumber::FROM_NUMBER_WITHOUT_PLUS_SIGN,
27+ PhoneNumber::FROM_DEFAULT_COUNTRY
28+ >
29+ >::value,
30+ "CountryCodeSource enum changed"
31+ );
32+ return static_cast<PhoneNumber::CountryCodeSource>(value);
33+}
34+
35+static country_code_source unmarshal_country_code_source(PhoneNumber::CountryCodeSource value)
36+{
37+ static_assert(is_same_enum<
38+ enum_values<country_code_source,
39+ UNSPECIFIED,
40+ FROM_NUMBER_WITH_PLUS_SIGN,
41+ FROM_NUMBER_WITH_IDD,
42+ FROM_NUMBER_WITHOUT_PLUS_SIGN,
43+ FROM_DEFAULT_COUNTRY
44+ >,
45+ enum_values<PhoneNumber::CountryCodeSource,
46+ PhoneNumber::UNSPECIFIED,
47+ PhoneNumber::FROM_NUMBER_WITH_PLUS_SIGN,
48+ PhoneNumber::FROM_NUMBER_WITH_IDD,
49+ PhoneNumber::FROM_NUMBER_WITHOUT_PLUS_SIGN,
50+ PhoneNumber::FROM_DEFAULT_COUNTRY
51+ >
52+ >::value,
53+ "CountryCodeSource enum changed"
54+ );
55+ return static_cast<country_code_source>(value);
56+}
57+
58+extern "C" PhoneNumber *c_phone_number_marshal(phone_number const *c_number)
59+{
60+ PhoneNumber *number = new PhoneNumber();
61+ if (c_number->extension != nullptr)
62+ number->set_extension(c_number->extension, c_number->extension_size);
63+ if (c_number->raw_input != nullptr)
64+ number->set_raw_input(c_number->raw_input, c_number->raw_input_size);
65+ if (c_number->preferred_domestic_carrier_code != nullptr)
66+ number->set_preferred_domestic_carrier_code(c_number->preferred_domestic_carrier_code, c_number->preferred_domestic_carrier_code_size);
67+ number->set_national_number(c_number->national_number);
68+ number->set_country_code(c_number->country_code);
69+ if (c_number->has_italian_leading_zero)
70+ number->set_italian_leading_zero(c_number->italian_leading_zero);
71+ if (c_number->has_country_code_source)
72+ number->set_country_code_source(marshal_country_code_source(c_number->country_code_source));
73+ if (c_number->has_number_of_leading_zeros)
74+ number->set_number_of_leading_zeros(c_number->number_of_leading_zeros);
75+ return number;
76+}
77+
78+extern "C" void c_phone_number_unmarshal(PhoneNumber const *number, phone_number *c_number)
79+{
80+ if (number->has_extension())
81+ unmarshal_string_size(number->extension(), c_number->extension, c_number->extension_size);
82+ else
83+ c_number->extension = nullptr;
84+ if (number->has_raw_input())
85+ unmarshal_string_size(number->raw_input(), c_number->raw_input, c_number->raw_input_size);
86+ else
87+ c_number->raw_input = nullptr;
88+ if (number->has_preferred_domestic_carrier_code())
89+ unmarshal_string_size(number->preferred_domestic_carrier_code(), c_number->preferred_domestic_carrier_code, c_number->preferred_domestic_carrier_code_size);
90+ else
91+ c_number->preferred_domestic_carrier_code = nullptr;
92+ c_number->national_number = number->national_number();
93+ c_number->country_code = number->country_code();
94+ if (c_number->has_italian_leading_zero = number->has_italian_leading_zero())
95+ c_number->italian_leading_zero = number->italian_leading_zero();
96+ if (c_number->has_country_code_source = number->has_country_code_source())
97+ c_number->country_code_source = unmarshal_country_code_source(number->country_code_source());
98+ if (c_number->has_number_of_leading_zeros = number->has_number_of_leading_zeros())
99+ c_number->number_of_leading_zeros = number->number_of_leading_zeros();
100+}
101+
102+extern "C" void c_phone_number_free(PhoneNumber *number)
103+{
104+ delete number;
105+}
cxxbits/c_phonenumberutil.cppnew file+620
@@ -0,0 +1,620 @@
1+#include <cstdlib>
2+#include <list>
3+#include <set>
4+#include <string>
5+#include <phonenumbers/phonenumberutil.h>
6+
7+#include "common.h"
8+extern "C" {
9+#include "c_phonenumberutil.h"
10+}
11+
12+using i18n::phonenumbers::PhoneNumber, i18n::phonenumbers::PhoneNumberUtil;
13+
14+static PhoneNumberUtil::PhoneNumberFormat marshal_phone_number_format(phone_number_format value)
15+{
16+ static_assert(is_same_enum<
17+ enum_values<phone_number_format,
18+ E164,
19+ INTERNATIONAL,
20+ NATIONAL,
21+ RFC3966
22+ >,
23+ enum_values<PhoneNumberUtil::PhoneNumberFormat,
24+ PhoneNumberUtil::E164,
25+ PhoneNumberUtil::INTERNATIONAL,
26+ PhoneNumberUtil::NATIONAL,
27+ PhoneNumberUtil::RFC3966
28+ >
29+ >::value,
30+ "PhoneNumberFormat enum changed"
31+ );
32+ return static_cast<PhoneNumberUtil::PhoneNumberFormat>(value);
33+}
34+
35+static PhoneNumberUtil::PhoneNumberType marshal_phone_number_type(phone_number_type value)
36+{
37+ static_assert(is_same_enum<
38+ enum_values<phone_number_type,
39+ MOBILE,
40+ FIXED_LINE_OR_MOBILE,
41+ TOLL_FREE,
42+ PREMIUM_RATE,
43+ SHARED_COST,
44+ VOIP,
45+ PERSONAL_NUMBER,
46+ PAGER,
47+ UAN,
48+ VOICEMAIL,
49+ UNKNOWN
50+ >,
51+ enum_values<PhoneNumberUtil::PhoneNumberType,
52+ PhoneNumberUtil::MOBILE,
53+ PhoneNumberUtil::FIXED_LINE_OR_MOBILE,
54+ PhoneNumberUtil::TOLL_FREE,
55+ PhoneNumberUtil::PREMIUM_RATE,
56+ PhoneNumberUtil::SHARED_COST,
57+ PhoneNumberUtil::VOIP,
58+ PhoneNumberUtil::PERSONAL_NUMBER,
59+ PhoneNumberUtil::PAGER,
60+ PhoneNumberUtil::UAN,
61+ PhoneNumberUtil::VOICEMAIL,
62+ PhoneNumberUtil::UNKNOWN
63+ >
64+ >::value,
65+ "PhoneNumberType enum changed"
66+ );
67+ return static_cast<PhoneNumberUtil::PhoneNumberType>(value);
68+}
69+
70+static phone_number_type unmarshal_phone_number_type(PhoneNumberUtil::PhoneNumberType value)
71+{
72+ static_assert(is_same_enum<
73+ enum_values<phone_number_type,
74+ MOBILE,
75+ FIXED_LINE_OR_MOBILE,
76+ TOLL_FREE,
77+ PREMIUM_RATE,
78+ SHARED_COST,
79+ VOIP,
80+ PERSONAL_NUMBER,
81+ PAGER,
82+ UAN,
83+ VOICEMAIL,
84+ UNKNOWN
85+ >,
86+ enum_values<PhoneNumberUtil::PhoneNumberType,
87+ PhoneNumberUtil::MOBILE,
88+ PhoneNumberUtil::FIXED_LINE_OR_MOBILE,
89+ PhoneNumberUtil::TOLL_FREE,
90+ PhoneNumberUtil::PREMIUM_RATE,
91+ PhoneNumberUtil::SHARED_COST,
92+ PhoneNumberUtil::VOIP,
93+ PhoneNumberUtil::PERSONAL_NUMBER,
94+ PhoneNumberUtil::PAGER,
95+ PhoneNumberUtil::UAN,
96+ PhoneNumberUtil::VOICEMAIL,
97+ PhoneNumberUtil::UNKNOWN
98+ >
99+ >::value,
100+ "PhoneNumberType enum changed"
101+ );
102+ return static_cast<phone_number_type>(value);
103+}
104+
105+static match_type unmarshal_match_type(PhoneNumberUtil::MatchType value)
106+{
107+ static_assert(is_same_enum<
108+ enum_values<match_type,
109+ INVALID_NUMBER,
110+ NO_MATCH,
111+ SHORT_NSN_MATCH,
112+ NSN_MATCH,
113+ EXACT_MATCH
114+ >,
115+ enum_values<PhoneNumberUtil::MatchType,
116+ PhoneNumberUtil::INVALID_NUMBER,
117+ PhoneNumberUtil::NO_MATCH,
118+ PhoneNumberUtil::SHORT_NSN_MATCH,
119+ PhoneNumberUtil::NSN_MATCH,
120+ PhoneNumberUtil::EXACT_MATCH
121+ >
122+ >::value,
123+ "MatchType enum changed"
124+ );
125+ return static_cast<match_type>(value);
126+}
127+
128+static error_type unmarshal_error_type(PhoneNumberUtil::ErrorType value)
129+{
130+ static_assert(is_same_enum<
131+ enum_values<error_type,
132+ NO_PARSING_ERROR,
133+ INVALID_COUNTRY_CODE_ERROR,
134+ NOT_A_NUMBER,
135+ TOO_SHORT_AFTER_IDD,
136+ TOO_SHORT_NSN,
137+ TOO_LONG_NSN
138+ >,
139+ enum_values<PhoneNumberUtil::ErrorType,
140+ PhoneNumberUtil::NO_PARSING_ERROR,
141+ PhoneNumberUtil::INVALID_COUNTRY_CODE_ERROR,
142+ PhoneNumberUtil::NOT_A_NUMBER,
143+ PhoneNumberUtil::TOO_SHORT_AFTER_IDD,
144+ PhoneNumberUtil::TOO_SHORT_NSN,
145+ PhoneNumberUtil::TOO_LONG_NSN
146+ >
147+ >::value,
148+ "ErrorType enum changed"
149+ );
150+ return static_cast<error_type>(value);
151+}
152+
153+static validation_result unmarshal_validation_result(PhoneNumberUtil::ValidationResult value)
154+{
155+ static_assert(is_same_enum<
156+ enum_values<validation_result,
157+ IS_POSSIBLE,
158+ IS_POSSIBLE_LOCAL_ONLY,
159+ INVALID_COUNTRY_CODE,
160+ TOO_SHORT,
161+ INVALID_LENGTH,
162+ TOO_LONG
163+ >,
164+ enum_values<PhoneNumberUtil::ValidationResult,
165+ PhoneNumberUtil::IS_POSSIBLE,
166+ PhoneNumberUtil::IS_POSSIBLE_LOCAL_ONLY,
167+ PhoneNumberUtil::INVALID_COUNTRY_CODE,
168+ PhoneNumberUtil::TOO_SHORT,
169+ PhoneNumberUtil::INVALID_LENGTH,
170+ PhoneNumberUtil::TOO_LONG
171+ >
172+ >::value,
173+ "ValidationResult enum changed"
174+ );
175+ return static_cast<validation_result>(value);
176+}
177+
178+static void unmarshal_c_string(std::string const &string, c_string &c_string)
179+{
180+ unmarshal_string_size(string, c_string.string, c_string.string_size);
181+}
182+
183+template<typename T>
184+static void unmarshal_int_list(T ints, int *&c_ints, size_t &c_ints_size)
185+{
186+ c_ints = static_cast<int *>(calloc(ints.size(), sizeof(int)));
187+ c_ints_size = ints.size();
188+ size_t i = 0;
189+ for (auto x : ints)
190+ c_ints[i++] = x;
191+}
192+
193+template<typename T>
194+static void unmarshal_type_list(T types, phone_number_type *&c_types, size_t &c_types_size)
195+{
196+ c_types = static_cast<phone_number_type *>(calloc(types.size(), sizeof(phone_number_type)));
197+ c_types_size = types.size();
198+ size_t i = 0;
199+ for (auto t : types)
200+ c_types[i++] = unmarshal_phone_number_type(t);
201+}
202+
203+template<typename T>
204+static void unmarshal_string_list(T strings, c_string *&c_strings, size_t &c_strings_size)
205+{
206+ c_strings = static_cast<c_string *>(calloc(strings.size(), sizeof *c_strings));
207+ c_strings_size = strings.size();
208+ size_t i = 0;
209+ for (auto const &string : strings)
210+ {
211+ unmarshal_c_string(string, c_strings[i]);
212+ i++;
213+ }
214+}
215+
216+extern "C" void c_phone_number_util_get_supported_regions(
217+ c_string **c_regions,
218+ size_t *c_regions_size
219+)
220+{
221+ std::set<std::string> regions;
222+ PhoneNumberUtil::GetInstance()->GetSupportedRegions(&regions);
223+ unmarshal_string_list(regions, *c_regions, *c_regions_size);
224+}
225+
226+extern "C" void c_phone_number_util_get_supported_global_network_calling_codes(
227+ int **c_calling_codes,
228+ size_t *c_calling_codes_size
229+)
230+{
231+ std::set<int> calling_codes;
232+ PhoneNumberUtil::GetInstance()->GetSupportedGlobalNetworkCallingCodes(&calling_codes);
233+ unmarshal_int_list(calling_codes, *c_calling_codes, *c_calling_codes_size);
234+}
235+
236+extern "C" void c_phone_number_util_get_supported_calling_codes(
237+ int **c_calling_codes,
238+ size_t *c_calling_codes_size
239+)
240+{
241+ std::set<int> calling_codes;
242+ PhoneNumberUtil::GetInstance()->GetSupportedCallingCodes(&calling_codes);
243+ unmarshal_int_list(calling_codes, *c_calling_codes, *c_calling_codes_size);
244+}
245+
246+extern "C" void c_phone_number_util_get_supported_types_for_region(
247+ char const *c_region,
248+ size_t c_region_size,
249+ phone_number_type **c_types,
250+ size_t *c_types_size
251+)
252+{
253+ std::set<PhoneNumberUtil::PhoneNumberType> types;
254+ std::string const region(c_region, c_region_size);
255+ PhoneNumberUtil::GetInstance()->GetSupportedTypesForRegion(region, &types);
256+ unmarshal_type_list(types, *c_types, *c_types_size);
257+}
258+
259+extern "C" void c_phone_number_util_get_supported_types_for_non_geo_entity(
260+ int calling_code,
261+ phone_number_type **c_types,
262+ size_t *c_types_size
263+)
264+{
265+ std::set<PhoneNumberUtil::PhoneNumberType> types;
266+ PhoneNumberUtil::GetInstance()->GetSupportedTypesForNonGeoEntity(calling_code, &types);
267+ unmarshal_type_list(types, *c_types, *c_types_size);
268+}
269+
270+extern "C" int c_phone_number_util_is_alpha_number(
271+ char const *c_number,
272+ size_t c_number_size
273+)
274+{
275+ std::string const number(c_number, c_number_size);
276+ return PhoneNumberUtil::GetInstance()->IsAlphaNumber(number);
277+}
278+
279+extern "C" void c_phone_number_util_convert_alpha_characters_in_number(
280+ char const *c_input,
281+ size_t c_input_size,
282+ c_string *c_output
283+)
284+{
285+ std::string number(c_input, c_input_size);
286+ PhoneNumberUtil::GetInstance()->ConvertAlphaCharactersInNumber(&number);
287+ unmarshal_c_string(number, *c_output);
288+}
289+
290+extern "C" void c_phone_number_util_normalize_digits_only(
291+ char const *c_input,
292+ size_t c_input_size,
293+ c_string *c_output
294+)
295+{
296+ std::string number(c_input, c_input_size);
297+ PhoneNumberUtil::GetInstance()->NormalizeDigitsOnly(&number);
298+ unmarshal_c_string(number, *c_output);
299+}
300+
301+extern "C" void c_phone_number_util_normalize_dialable_chars_only(
302+ char const *c_input,
303+ size_t c_input_size,
304+ c_string *c_output
305+)
306+{
307+ std::string number(c_input, c_input_size);
308+ PhoneNumberUtil::GetInstance()->NormalizeDiallableCharsOnly(&number);
309+ unmarshal_c_string(number, *c_output);
310+}
311+
312+extern "C" void c_phone_number_util_get_national_significant_number(
313+ PhoneNumber const *number,
314+ c_string *c_nsn
315+)
316+{
317+ std::string nsn;
318+ PhoneNumberUtil::GetInstance()->GetNationalSignificantNumber(*number, &nsn);
319+ unmarshal_c_string(nsn, *c_nsn);
320+}
321+
322+extern "C" void c_phone_number_util_get_country_mobile_token(
323+ int calling_code,
324+ c_string *c_token
325+)
326+{
327+ std::string token;
328+ PhoneNumberUtil::GetInstance()->GetCountryMobileToken(calling_code, &token);
329+ unmarshal_c_string(token, *c_token);
330+}
331+
332+extern "C" void c_phone_number_util_format(
333+ PhoneNumber const *number,
334+ phone_number_format format,
335+ c_string *c_string
336+)
337+{
338+ std::string string;
339+ PhoneNumberUtil::GetInstance()->Format(*number, marshal_phone_number_format(format), &string);
340+ unmarshal_c_string(string, *c_string);
341+}
342+
343+extern "C" void c_phone_number_util_format_national_number_with_carrier_code(
344+ PhoneNumber const *number,
345+ char const *c_carrier_code,
346+ size_t c_carrier_code_size,
347+ c_string *c_string
348+)
349+{
350+ std::string const carrier_code(c_carrier_code, c_carrier_code_size);
351+ std::string string;
352+ PhoneNumberUtil::GetInstance()->FormatNationalNumberWithCarrierCode(*number, carrier_code, &string);
353+ unmarshal_c_string(string, *c_string);
354+}
355+
356+extern "C" void c_phone_number_util_format_national_number_with_preferred_carrier_code(
357+ PhoneNumber const *number,
358+ char const *c_carrier_code,
359+ size_t c_carrier_code_size,
360+ c_string *c_string
361+)
362+{
363+ std::string const carrier_code(c_carrier_code, c_carrier_code_size);
364+ std::string string;
365+ PhoneNumberUtil::GetInstance()->FormatNationalNumberWithPreferredCarrierCode(*number, carrier_code, &string);
366+ unmarshal_c_string(string, *c_string);
367+}
368+
369+extern "C" void c_phone_number_util_format_number_for_mobile_dialing(
370+ PhoneNumber const *number,
371+ char const *c_region,
372+ size_t c_region_size,
373+ int with_formatting,
374+ c_string *c_string
375+)
376+{
377+ std::string const region(c_region, c_region_size);
378+ std::string string;
379+ PhoneNumberUtil::GetInstance()->FormatNumberForMobileDialing(*number, region, with_formatting, &string);
380+ unmarshal_c_string(string, *c_string);
381+}
382+
383+extern "C" void c_phone_number_util_format_out_of_country_calling_number(
384+ PhoneNumber const *number,
385+ char const *c_region,
386+ size_t c_region_size,
387+ c_string *c_string
388+)
389+{
390+ std::string const region(c_region, c_region_size);
391+ std::string string;
392+ PhoneNumberUtil::GetInstance()->FormatOutOfCountryCallingNumber(*number, region, &string);
393+ unmarshal_c_string(string, *c_string);
394+}
395+
396+extern "C" void c_phone_number_util_format_in_original_format(
397+ PhoneNumber const *number,
398+ char const *c_region,
399+ size_t c_region_size,
400+ c_string *c_string
401+)
402+{
403+ std::string const region(c_region, c_region_size);
404+ std::string string;
405+ PhoneNumberUtil::GetInstance()->FormatInOriginalFormat(*number, region, &string);
406+ unmarshal_c_string(string, *c_string);
407+}
408+
409+extern "C" void c_phone_number_util_format_out_of_country_keeping_alpha_chars(
410+ PhoneNumber const *number,
411+ char const *c_region,
412+ size_t c_region_size,
413+ c_string *c_string
414+)
415+{
416+ std::string const region(c_region, c_region_size);
417+ std::string string;
418+ PhoneNumberUtil::GetInstance()->FormatOutOfCountryKeepingAlphaChars(*number, region, &string);
419+ unmarshal_c_string(string, *c_string);
420+}
421+
422+extern "C" PhoneNumber *c_phone_number_util_truncate_too_long_number(PhoneNumber const *input)
423+{
424+ auto *output = new PhoneNumber(*input);
425+ if (PhoneNumberUtil::GetInstance()->TruncateTooLongNumber(output))
426+ {
427+ return output;
428+ }
429+ else
430+ {
431+ delete output;
432+ return nullptr;
433+ }
434+}
435+
436+extern "C" phone_number_type c_phone_number_util_get_number_type(PhoneNumber const *number)
437+{
438+ return unmarshal_phone_number_type(
439+ PhoneNumberUtil::GetInstance()->GetNumberType(*number)
440+ );
441+}
442+
443+extern "C" int c_phone_number_util_is_valid_number(PhoneNumber const *number)
444+{
445+ return PhoneNumberUtil::GetInstance()->IsValidNumber(*number);
446+}
447+
448+extern "C" int c_phone_number_util_is_valid_number_for_region(
449+ PhoneNumber const *number,
450+ char const *c_region,
451+ size_t c_region_size
452+)
453+{
454+ std::string const region(c_region, c_region_size);
455+ return PhoneNumberUtil::GetInstance()->IsValidNumberForRegion(*number, region);
456+}
457+
458+extern "C" void c_phone_number_util_get_region_code_for_number(
459+ PhoneNumber const *number,
460+ c_string *c_region
461+)
462+{
463+ std::string region;
464+ PhoneNumberUtil::GetInstance()->GetRegionCodeForNumber(*number, &region);
465+ unmarshal_c_string(region, *c_region);
466+}
467+
468+extern "C" int c_phone_number_util_get_country_code_for_region(
469+ char const *c_region,
470+ size_t c_region_size
471+)
472+{
473+ std::string const region(c_region, c_region_size);
474+ return PhoneNumberUtil::GetInstance()->GetCountryCodeForRegion(region);
475+}
476+
477+extern "C" void c_phone_number_util_get_region_code_for_country_code(
478+ int country_code,
479+ c_string *c_region
480+)
481+{
482+ std::string region;
483+ PhoneNumberUtil::GetInstance()->GetRegionCodeForCountryCode(country_code, &region);
484+ unmarshal_c_string(region, *c_region);
485+}
486+
487+extern "C" void c_phone_number_util_get_region_codes_for_country_calling_code(
488+ int country_code,
489+ c_string **c_regions,
490+ size_t *c_regions_size
491+)
492+{
493+ std::list<std::string> regions;
494+ PhoneNumberUtil::GetInstance()->GetRegionCodesForCountryCallingCode(country_code, &regions);
495+ unmarshal_string_list(regions, *c_regions, *c_regions_size);
496+}
497+
498+extern "C" int c_phone_number_util_is_nanpa_country(
499+ char const *c_region,
500+ size_t c_region_size
501+)
502+{
503+ std::string const region(c_region, c_region_size);
504+ return PhoneNumberUtil::GetInstance()->IsNANPACountry(region);
505+}
506+
507+extern "C" void c_phone_number_util_get_ndd_prefix_for_region(
508+ char const *c_region,
509+ size_t c_region_size,
510+ int strip_non_digits,
511+ c_string *c_prefix
512+)
513+{
514+ std::string const region(c_region, c_region_size);
515+ std::string prefix;
516+ PhoneNumberUtil::GetInstance()->GetNddPrefixForRegion(region, strip_non_digits, &prefix);
517+ unmarshal_c_string(prefix, *c_prefix);
518+}
519+
520+extern "C" validation_result c_phone_number_util_is_possible_number_for_type_with_reason(
521+ PhoneNumber const *number,
522+ phone_number_type ntype
523+)
524+{
525+ return unmarshal_validation_result(
526+ PhoneNumberUtil::GetInstance()->IsPossibleNumberForTypeWithReason(
527+ *number,
528+ marshal_phone_number_type(ntype)
529+ )
530+ );
531+}
532+
533+extern "C" int c_phone_number_util_can_be_internationally_dialed(PhoneNumber const *number)
534+{
535+ return PhoneNumberUtil::GetInstance()->CanBeInternationallyDialled(*number);
536+}
537+
538+extern "C" int c_phone_number_util_is_number_geographical_1(PhoneNumber const *number)
539+{
540+ return PhoneNumberUtil::GetInstance()->IsNumberGeographical(*number);
541+}
542+
543+extern "C" int c_phone_number_util_is_number_geographical_2(
544+ phone_number_type ntype,
545+ int calling_code
546+)
547+{
548+ return PhoneNumberUtil::GetInstance()->IsNumberGeographical(
549+ marshal_phone_number_type(ntype),
550+ calling_code
551+ );
552+}
553+
554+extern "C" error_type c_phone_number_util_parse(
555+ char const *c_string,
556+ size_t c_string_size,
557+ char const *c_region,
558+ size_t c_region_size,
559+ PhoneNumber **number
560+)
561+{
562+ std::string const string(c_string, c_string_size);
563+ std::string const region(c_region, c_region_size);
564+ *number = new PhoneNumber();
565+ return unmarshal_error_type(
566+ PhoneNumberUtil::GetInstance()->Parse(string, region, *number)
567+ );
568+}
569+
570+extern "C" error_type c_phone_number_util_parse_and_keep_raw_input(
571+ char const *c_string,
572+ size_t c_string_size,
573+ char const *c_region,
574+ size_t c_region_size,
575+ PhoneNumber **number
576+)
577+{
578+ std::string const string(c_string, c_string_size);
579+ std::string const region(c_region, c_region_size);
580+ *number = new PhoneNumber();
581+ return unmarshal_error_type(
582+ PhoneNumberUtil::GetInstance()->ParseAndKeepRawInput(string, region, *number)
583+ );
584+}
585+
586+extern "C" match_type c_phone_number_util_is_number_match(
587+ PhoneNumber const *number1,
588+ PhoneNumber const *number2
589+)
590+{
591+ return unmarshal_match_type(
592+ PhoneNumberUtil::GetInstance()->IsNumberMatch(*number1, *number2)
593+ );
594+}
595+
596+extern "C" match_type c_phone_number_util_is_number_match_with_two_strings(
597+ char const *c_number1,
598+ size_t c_number1_size,
599+ char const *c_number2,
600+ size_t c_number2_size
601+)
602+{
603+ std::string const number1(c_number1, c_number1_size);
604+ std::string const number2(c_number2, c_number2_size);
605+ return unmarshal_match_type(
606+ PhoneNumberUtil::GetInstance()->IsNumberMatchWithTwoStrings(number1, number2)
607+ );
608+}
609+
610+extern "C" match_type c_phone_number_util_is_number_match_with_one_string(
611+ PhoneNumber const *number1,
612+ char const *c_number2,
613+ size_t c_number2_size
614+)
615+{
616+ std::string const number2(c_number2, c_number2_size);
617+ return unmarshal_match_type(
618+ PhoneNumberUtil::GetInstance()->IsNumberMatchWithOneString(*number1, number2)
619+ );
620+}
include/c_phonenumber.hnew file+62
@@ -0,0 +1,62 @@
1+#ifndef c_phonenumber_h
2+#define c_phonenumber_h
3+
4+#include <stdint.h>
5+#include <stddef.h>
6+
7+// Copied from PhoneNumber::CountryCodeSource.
8+// is_same_enum verifies that these values didn't change
9+enum country_code_source
10+{
11+ UNSPECIFIED = 0,
12+ FROM_NUMBER_WITH_PLUS_SIGN = 1,
13+ FROM_NUMBER_WITH_IDD = 5,
14+ FROM_NUMBER_WITHOUT_PLUS_SIGN = 10,
15+ FROM_DEFAULT_COUNTRY = 20,
16+};
17+
18+// PhoneNumber is not a POD (not trivially constructible, not trivially
19+// moveable), and all of its data is accessed through methods. To access its
20+// data from haskell, we convert a PhoneNumber into an auxiliary struct all at
21+// once. The struct is a POD and can be parsed by haskell.
22+typedef struct
23+{
24+ // Strings are allocated by malloc and it is haskell's responsibility to
25+ // free them. nullptr indicates the string is absent, in which case its
26+ // corresponding size is uninitialized.
27+ char *extension;
28+ size_t extension_size;
29+ char *raw_input;
30+ size_t raw_input_size;
31+ char *preferred_domestic_carrier_code;
32+ size_t preferred_domestic_carrier_code_size;
33+ uint64_t national_number;
34+ int32_t country_code;
35+ // Uninitialized if !has_number_of_leading_zeros
36+ int32_t number_of_leading_zeros;
37+ // Unininitialized if !has_country_code_source
38+ enum country_code_source country_code_source;
39+ // Unininitialized if !has_italian_leading_zero
40+ char italian_leading_zero;
41+ char has_italian_leading_zero;
42+ char has_country_code_source;
43+ char has_number_of_leading_zeros;
44+} phone_number;
45+
46+#ifdef __cplusplus
47+typedef i18n::phonenumbers::PhoneNumber PhoneNumber;
48+#else
49+typedef struct PhoneNumber PhoneNumber;
50+#endif
51+
52+// The returned object is allocated by `new` and managed by C++ RAII, and it is
53+// haskell's responsibility to call its finalizer (c_phone_number_free).
54+PhoneNumber *c_phone_number_marshal(phone_number const *);
55+
56+// phone_number * points to an area which will be filled with the data of the
57+// given PhoneNumber.
58+void c_phone_number_unmarshal(PhoneNumber const *, phone_number *);
59+
60+void c_phone_number_free(PhoneNumber *);
61+
62+#endif
include/c_phonenumberutil.hnew file+130
@@ -0,0 +1,130 @@
1+#ifndef c_phonenumberutil_h
2+#define c_phonenumberutil_h
3+
4+#include <stddef.h>
5+
6+#include "c_phonenumber.h"
7+
8+// Copied from PhoneNumberUtil::PhoneNumberFormat
9+// is_same_enum verifies that these values didn't change
10+enum phone_number_format {
11+ E164,
12+ INTERNATIONAL,
13+ NATIONAL,
14+ RFC3966,
15+};
16+
17+// Copied from PhoneNumberUtil::PhoneNumberType
18+// is_same_enum verifies that these values didn't change
19+enum phone_number_type
20+{
21+ FIXED_LINE,
22+ MOBILE,
23+ FIXED_LINE_OR_MOBILE,
24+ TOLL_FREE,
25+ PREMIUM_RATE,
26+ SHARED_COST,
27+ VOIP,
28+ PERSONAL_NUMBER,
29+ PAGER,
30+ UAN,
31+ VOICEMAIL,
32+ UNKNOWN,
33+};
34+
35+// Copied from PhoneNumberUtil::MatchType
36+// is_same_enum verifies that these values didn't change
37+enum match_type {
38+ INVALID_NUMBER,
39+ NO_MATCH,
40+ SHORT_NSN_MATCH,
41+ NSN_MATCH,
42+ EXACT_MATCH,
43+};
44+
45+// Copied from PhoneNumberUtil::ErrorType
46+// is_same_enum verifies that these values didn't change
47+enum error_type
48+{
49+ NO_PARSING_ERROR,
50+ INVALID_COUNTRY_CODE_ERROR,
51+ NOT_A_NUMBER,
52+ TOO_SHORT_AFTER_IDD,
53+ TOO_SHORT_NSN,
54+ TOO_LONG_NSN,
55+};
56+
57+// Copied from PhoneNumberUtil::ValidationResult
58+// is_same_enum verifies that these values didn't change
59+enum validation_result
60+{
61+ IS_POSSIBLE,
62+ IS_POSSIBLE_LOCAL_ONLY,
63+ INVALID_COUNTRY_CODE,
64+ TOO_SHORT,
65+ INVALID_LENGTH,
66+ TOO_LONG,
67+};
68+
69+// A string allocated by malloc, might not include the null terminator.
70+// It is haskell's responsibility to free it.
71+typedef struct
72+{
73+ char *string;
74+ size_t string_size;
75+} c_string;
76+
77+// In the functions below, if an output parameter is a string, we malloc a
78+// buffer and write its pointer and size into the given c_string *.
79+//
80+// If an input parameter is a string, we "unpack" the c_string into a pair of
81+// char const * + size_t, because c2hs cannot natively pass structs by value.
82+//
83+// If the output is a list of T, we malloc a buffer and write its pointer into
84+// the given T **, and its size (in T's) into the given size_t *.
85+// If the output is a list of strings, we do the same as if T=c_string.
86+//
87+// If the output is a PhoneNumber, we either return a PhoneNumber *, or write
88+// it into a provided PhoneNumber **. In both cases the PhoneNumber object is
89+// allocated by `new` and managed by C++ RAII. In all cases it is haskell's
90+// responsibility to call its finalizer (c_phone_number_free);
91+void c_phone_number_util_get_supported_regions(c_string **, size_t *);
92+void c_phone_number_util_get_supported_global_network_calling_codes(int **, size_t *);
93+void c_phone_number_util_get_supported_calling_codes(int **, size_t *);
94+void c_phone_number_util_get_supported_types_for_region(char const *, size_t, enum phone_number_type **, size_t *);
95+void c_phone_number_util_get_supported_types_for_non_geo_entity(int, enum phone_number_type **, size_t *);
96+int c_phone_number_util_is_alpha_number(char const *, size_t);
97+void c_phone_number_util_convert_alpha_characters_in_number(char const *, size_t, c_string *);
98+void c_phone_number_util_normalize_digits_only(char const *, size_t, c_string *);
99+void c_phone_number_util_normalize_dialable_chars_only(char const *, size_t, c_string *);
100+void c_phone_number_util_get_national_significant_number(PhoneNumber const *, c_string *);
101+void c_phone_number_util_get_country_mobile_token(int, c_string *);
102+void c_phone_number_util_format(PhoneNumber const *, enum phone_number_format, c_string *);
103+void c_phone_number_util_format_national_number_with_carrier_code(PhoneNumber const *, char const *, size_t, c_string *);
104+void c_phone_number_util_format_national_number_with_preferred_carrier_code(PhoneNumber const *, char const *, size_t, c_string *);
105+void c_phone_number_util_format_number_for_mobile_dialing(PhoneNumber const *, char const *, size_t, int, c_string *);
106+void c_phone_number_util_format_out_of_country_calling_number(PhoneNumber const *, char const *, size_t, c_string *);
107+void c_phone_number_util_format_in_original_format(PhoneNumber const *, char const *, size_t, c_string *);
108+void c_phone_number_util_format_out_of_country_keeping_alpha_chars(PhoneNumber const *, char const *, size_t, c_string *);
109+// /!\ May return NULL
110+PhoneNumber *c_phone_number_util_truncate_too_long_number(PhoneNumber const *);
111+enum phone_number_type c_phone_number_util_get_number_type(PhoneNumber const *);
112+int c_phone_number_util_is_valid_number(PhoneNumber const *);
113+int c_phone_number_util_is_valid_number_for_region(PhoneNumber const *, char const *, size_t);
114+void c_phone_number_util_get_region_code_for_number(PhoneNumber const *, c_string *);
115+int c_phone_number_util_get_country_code_for_region(char const *, size_t);
116+void c_phone_number_util_get_region_code_for_country_code(int, c_string *);
117+void c_phone_number_util_get_region_codes_for_country_calling_code(int, c_string **, size_t *);
118+int c_phone_number_util_is_nanpa_country(char const *, size_t);
119+void c_phone_number_util_get_ndd_prefix_for_region(char const *, size_t, int, c_string *);
120+enum validation_result c_phone_number_util_is_possible_number_for_type_with_reason(PhoneNumber const *, enum phone_number_type);
121+int c_phone_number_util_can_be_internationally_dialed(PhoneNumber const *);
122+int c_phone_number_util_is_number_geographical_1(PhoneNumber const *);
123+int c_phone_number_util_is_number_geographical_2(enum phone_number_type, int);
124+enum error_type c_phone_number_util_parse(char const *, size_t, char const *, size_t, PhoneNumber **);
125+enum error_type c_phone_number_util_parse_and_keep_raw_input(char const *, size_t, char const *, size_t, PhoneNumber **);
126+enum match_type c_phone_number_util_is_number_match(PhoneNumber const *, PhoneNumber const *);
127+enum match_type c_phone_number_util_is_number_match_with_two_strings(char const *, size_t, char const *, size_t);
128+enum match_type c_phone_number_util_is_number_match_with_one_string(PhoneNumber const *, char const *, size_t);
129+
130+#endif
include/common.hnew file+57
@@ -0,0 +1,57 @@
1+#pragma once
2+
3+#include <string>
4+#include <cstddef>
5+#include <cstring>
6+#include <type_traits>
7+
8+// Given `enum T { A, B, C }` and `enum S { X, Y, Z }`, we can use
9+// `is_same_enum<enum_values<T, A, B, C>, enum_values<S, X, Y, Z>>::value`
10+// to statically assert that the values A, B, C correspond respectively to the
11+// values X, Y, Z (i.e. that their underlying numerical values are the same),
12+// and thus that the listed members can be static_cast.
13+//
14+// We use this to redeclare C++ enums as C enums (which can then in turn be
15+// parsed by a C parser such as c2hs), and then ensure that our redeclaration
16+// matches the original. If the original were to be modified in a way that
17+// changes the numerical values of the listed members, we would get a
18+// static_assert. It's impossible to verify that no members other than the ones
19+// listed have been added, but such members will have to map to numerical values
20+// that are distinct, and thus not present in the other enum, thus in the worst
21+// case we get a `toEnum` error in haskell code.
22+template<typename T, T... Vals>
23+struct enum_values
24+{
25+ static_assert(std::is_enum<T>::value, "Must be an enum");
26+};
27+
28+template<typename T, typename S>
29+struct is_same_enum: std::false_type
30+{};
31+
32+template<typename T, T... TVals, typename S, S... SVals>
33+struct is_same_enum<enum_values<T, TVals...>, enum_values<S, SVals...>>:
34+ std::integral_constant<bool, std::is_same_v<
35+ std::integer_sequence<
36+ typename std::common_type<
37+ typename std::underlying_type<T>::type,
38+ typename std::underlying_type<S>::type
39+ >::type,
40+ TVals...
41+ >,
42+ std::integer_sequence<
43+ typename std::common_type<
44+ typename std::underlying_type<T>::type,
45+ typename std::underlying_type<S>::type
46+ >::type,
47+ SVals...
48+ >
49+ >>
50+{};
51+
52+inline void unmarshal_string_size(std::string const &string, char *&c_string, size_t &c_string_size)
53+{
54+ c_string_size = string.size();
55+ c_string = static_cast<char *>(malloc(string.size()));
56+ std::memcpy(c_string, string.data(), string.size());
57+}
libphonenumber.cabalnew file+84
@@ -0,0 +1,84 @@
1+cabal-version: 3.0
2+name: libphonenumber
3+version: 0.0.0.0
4+category: Data
5+synopsis: Parsing, formatting, and validating international phone
6+ numbers
7+description:
8+ This package provides bindings for the C++ version of the
9+ <https://github.com/google/libphonenumber libphonenumber library>.
10+ The bindings currently do not include @AsYouTypeFormatter@
11+ and @ShortNumberInfo@.
12+
13+ The interface largely resembles that of the original unified
14+ C++, Java, and JavaScript version of the library, with some
15+ minor changes to make the interface more Haskell-like.
16+
17+ The provided functions are pure, under the assumption that
18+ we are the only user of the C++ library, i.e. that no one
19+ else has installed a global logger which could observe
20+ side-effects from library calls.
21+
22+build-type: Simple
23+
24+extra-source-files:
25+ include/*.h
26+ test/libphonenumber.supp
27+
28+library
29+ exposed-modules:
30+ Data.PhoneNumber.Number
31+ Data.PhoneNumber.Util
32+ other-modules:
33+ Data.PhoneNumber.Internal.Common
34+ Data.PhoneNumber.Internal.Number
35+ Data.PhoneNumber.Internal.Util
36+ build-depends:
37+ base >= 4.12 && < 4.18
38+ , bytestring
39+ , containers
40+ , deepseq
41+ , transformers
42+ build-tool-depends: c2hs:c2hs
43+ include-dirs: include
44+ includes:
45+ c_phonenumber.h
46+ c_phonenumberutil.h
47+ cxx-sources:
48+ cxxbits/c_phonenumber.cpp
49+ cxxbits/c_phonenumberutil.cpp
50+ extra-libraries:
51+ phonenumber
52+ protobuf
53+ if impl(ghc >= 9.4)
54+ build-depends: system-cxx-std-lib
55+ else
56+ extra-libraries: stdc++
57+ hs-source-dirs: src
58+ default-language: Haskell2010
59+ default-extensions:
60+ DeriveAnyClass
61+ DeriveDataTypeable
62+ DeriveGeneric
63+ DerivingStrategies
64+ GeneralizedNewtypeDeriving
65+ LambdaCase
66+ NamedFieldPuns
67+ OverloadedStrings
68+ PatternSynonyms
69+ StandaloneDeriving
70+ RecordWildCards
71+ ViewPatterns
72+ if impl(ghc >= 9.2)
73+ default-extensions: NoFieldSelectors
74+ ghc-options: -Wall -Wextra -Werror
75+
76+test-suite leak_test
77+ type: exitcode-stdio-1.0
78+ build-depends:
79+ base
80+ , libphonenumber
81+ , process
82+ main-is: Leak.hs
83+ hs-source-dirs: test
84+ default-language: Haskell2010
src/Data/PhoneNumber/Internal/Common.chsnew file+20
@@ -0,0 +1,20 @@
1+module Data.PhoneNumber.Internal.Common where
2+
3+import Control.Arrow
4+import Data.ByteString (ByteString)
5+import Data.ByteString.Unsafe
6+import Foreign
7+import Foreign.C.Types
8+
9+#include <stddef.h>
10+
11+alloca2 :: (Storable a, Storable b) => ((Ptr a, Ptr b) -> IO c) -> IO c
12+alloca2 k = alloca $ \p1 -> alloca $ \p2 -> k (p1, p2)
13+
14+withByteString :: ByteString -> ((Ptr CChar, {#type size_t#}) -> IO a) -> IO a
15+withByteString bs k = unsafeUseAsCStringLen bs $ k . second fromIntegral
16+
17+-- Any #fun using acquireCString must be wrapped in a mask, so that an exception
18+-- cannot arrive before we assign a finalizer to the string
19+acquireCString :: Ptr CChar -> {#type size_t#} -> IO ByteString
20+acquireCString = curry $ unsafePackMallocCStringLen . second fromIntegral
src/Data/PhoneNumber/Internal/Number.chsnew file+186
@@ -0,0 +1,186 @@
1+module Data.PhoneNumber.Internal.Number where
2+
3+import Control.DeepSeq
4+import Control.Monad.Trans.Class
5+import Control.Monad.Trans.Cont
6+import Data.ByteString (ByteString)
7+import Data.Data
8+import Data.PhoneNumber.Internal.Common
9+import Data.List
10+import Foreign
11+import GHC.Generics (Generic)
12+import GHC.IO
13+import qualified GHC.Read as P
14+import qualified Text.ParserCombinators.ReadPrec as P
15+import qualified Text.Read.Lex as P
16+
17+#include "c_phonenumber.h"
18+
19+{#pointer *phone_number foreign -> CPhoneNumber nocode#}
20+
21+data CPhoneNumber = CPhoneNumber
22+ { extension :: !(Maybe ByteString)
23+ , rawInput :: !(Maybe ByteString)
24+ , preferredDomesticCarrierCode :: !(Maybe ByteString)
25+ , nationalNumber :: !{#type uint64_t#}
26+ , countryCode :: !{#type int32_t#}
27+ , italianLeadingZero :: !(Maybe Bool)
28+ , countryCodeSource :: !(Maybe CountryCodeSource)
29+ , numberOfLeadingZeros :: !(Maybe {#type int32_t#})
30+ }
31+ deriving (Eq, Ord, Show, Read)
32+
33+-- | Indicates what information was used to fill the
34+-- 'Data.PhoneNumber.Number.countryCode' field of t'PhoneNumber'.
35+{#enum country_code_source as CountryCodeSource {underscoreToCase}
36+ deriving (Eq, Ord, Show, Read, Data, Generic)#}
37+
38+deriving anyclass instance NFData CountryCodeSource
39+
40+withCPhoneNumber :: CPhoneNumber -> (Ptr CPhoneNumber -> IO a) -> IO a
41+withCPhoneNumber CPhoneNumber{..} = runContT $ do
42+ p <- ContT $ allocaBytes {#sizeof phone_number#}
43+ withMaybeCString extension
44+ ({#set phone_number->extension#} p)
45+ ({#set phone_number->extension_size#} p)
46+ withMaybeCString rawInput
47+ ({#set phone_number->raw_input#} p)
48+ ({#set phone_number->raw_input_size#} p)
49+ withMaybeCString preferredDomesticCarrierCode
50+ ({#set phone_number->preferred_domestic_carrier_code#} p)
51+ ({#set phone_number->preferred_domestic_carrier_code_size#} p)
52+ lift $ {#set phone_number->national_number#} p nationalNumber
53+ lift $ {#set phone_number->country_code#} p countryCode
54+ lift $ {#set phone_number->has_italian_leading_zero#} p
55+ =<< case italianLeadingZero of
56+ Nothing -> pure 0
57+ Just ilz -> 1 <$ {#set phone_number->italian_leading_zero#} p (fromIntegral $ fromEnum ilz)
58+ lift $ {#set phone_number->has_country_code_source#} p
59+ =<< case countryCodeSource of
60+ Nothing -> pure 0
61+ Just ccs -> 1 <$ {#set phone_number->country_code_source#} p (fromIntegral $ fromEnum ccs)
62+ lift $ {#set phone_number->has_number_of_leading_zeros#} p
63+ =<< case numberOfLeadingZeros of
64+ Nothing -> pure 0
65+ Just nlz -> 1 <$ {#set phone_number->number_of_leading_zeros#} p (fromIntegral $ fromEnum nlz)
66+ pure p
67+ where
68+ withMaybeCString Nothing setP _ = do
69+ lift $ setP nullPtr
70+ withMaybeCString (Just bs) setP setSZ = do
71+ (p, sz) <- ContT $ withByteString bs
72+ () <- lift $ setP p
73+ lift $ setSZ sz
74+
75+-- Any #fun using acquireCPhoneNumber must be wrapped in a mask, so that an
76+-- exception cannot arrive before we assign finalizers to the strings inside
77+acquireCPhoneNumber :: Ptr CPhoneNumber -> IO CPhoneNumber
78+acquireCPhoneNumber p = do
79+ extension <- acquireMaybeCString
80+ ({#get phone_number->extension#} p)
81+ ({#get phone_number->extension_size#} p)
82+ rawInput <- acquireMaybeCString
83+ ({#get phone_number->raw_input#} p)
84+ ({#get phone_number->raw_input_size#} p)
85+ preferredDomesticCarrierCode <- acquireMaybeCString
86+ ({#get phone_number->preferred_domestic_carrier_code#} p)
87+ ({#get phone_number->preferred_domestic_carrier_code_size#} p)
88+ nationalNumber <- {#get phone_number->national_number#} p
89+ countryCode <- {#get phone_number->country_code#} p
90+ italianLeadingZero <- {#get phone_number->has_italian_leading_zero#} p
91+ >>= \case
92+ 0 -> pure Nothing
93+ _ -> Just . toEnum . fromIntegral <$> {#get phone_number->italian_leading_zero#} p
94+ countryCodeSource <- {#get phone_number->has_country_code_source#} p
95+ >>= \case
96+ 0 -> pure Nothing
97+ _ -> Just . toEnum . fromIntegral <$> {#get phone_number->country_code_source#} p
98+ numberOfLeadingZeros <- {#get phone_number->has_number_of_leading_zeros#} p
99+ >>= \case
100+ 0 -> pure Nothing
101+ _ -> Just <$> {#get phone_number->number_of_leading_zeros#} p
102+ pure $ CPhoneNumber{..}
103+ where
104+ acquireMaybeCString getP getSZ = do
105+ string <- getP
106+ if string == nullPtr
107+ then pure Nothing
108+ else Just <$> (acquireCString string =<< getSZ)
109+
110+-- Any #fun returning PhoneNumber must be wrapped in a mask, so that an
111+-- exception cannot arrive before we assign a finalizer
112+-- | A decoded phone number. While internally it is a handle for the
113+-- corresponding C++ object, for most intents and purposes it can be used as
114+-- a record (using the v'Data.PhoneNumber.Number.PhoneNumber' record pattern
115+-- synonym) with the following structure:
116+--
117+-- @
118+-- v'Data.PhoneNumber.Number.PhoneNumber'
119+-- { 'Data.PhoneNumber.Number.extension' :: !('Maybe' 'ByteString')
120+-- , 'Data.PhoneNumber.Number.rawInput' :: !('Maybe' 'ByteString')
121+-- , 'Data.PhoneNumber.Number.preferredDomesticCarrierCode' :: !('Maybe' 'ByteString')
122+-- , 'Data.PhoneNumber.Number.nationalNumber' :: !'Word'
123+-- , 'Data.PhoneNumber.Number.countryCode' :: ! t'Data.PhoneNumber.Number.CountryCode'
124+-- , 'Data.PhoneNumber.Number.italianLeadingZero' :: !('Maybe' 'Bool')
125+-- , 'Data.PhoneNumber.Number.countryCodeSource' :: !('Maybe' 'CountryCodeSource')
126+-- , 'Data.PhoneNumber.Number.numberOfLeadingZeros' :: !('Maybe' 'Int')
127+-- }
128+-- @
129+{#pointer *PhoneNumber foreign finalizer c_phone_number_free newtype#}
130+
131+{#fun unsafe c_phone_number_marshal
132+ { withCPhoneNumber* `CPhoneNumber'
133+ } -> `PhoneNumber'#}
134+
135+{#fun unsafe c_phone_number_unmarshal
136+ { `PhoneNumber'
137+ , allocaCPhoneNumber- `CPhoneNumber' acquireCPhoneNumber*
138+ } -> `()' #}
139+ where
140+ allocaCPhoneNumber :: (Ptr CPhoneNumber -> IO a) -> IO a
141+ allocaCPhoneNumber = allocaBytes {#sizeof phone_number#}
142+
143+{-# INLINABLE toCPhoneNumber #-}
144+toCPhoneNumber :: PhoneNumber -> CPhoneNumber
145+toCPhoneNumber = unsafeDupablePerformIO . mask_ . c_phone_number_unmarshal
146+
147+{-# INLINABLE fromCPhoneNumber #-}
148+fromCPhoneNumber :: CPhoneNumber -> PhoneNumber
149+fromCPhoneNumber = unsafeDupablePerformIO . mask_ . c_phone_number_marshal
150+
151+-- These would ideally be defined in @Data.PhoneNumber.Number@ but that would
152+-- make them orphan instances.
153+
154+-- | Compares all the data fields, consider 'Data.PhoneNumber.Util.matchNumbers'
155+-- instead
156+instance Eq PhoneNumber where
157+ p1 == p2 = toCPhoneNumber p1 == toCPhoneNumber p2
158+
159+instance Ord PhoneNumber where
160+ p1 `compare` p2 = toCPhoneNumber p1 `compare` toCPhoneNumber p2
161+ p1 <= p2 = toCPhoneNumber p1 <= toCPhoneNumber p2
162+
163+instance Show PhoneNumber where
164+ showsPrec d p = mangleConParen . showsPrec d (toCPhoneNumber p)
165+ where
166+ mangleConParen xs
167+ | Just xs' <- stripPrefix "(" xs = "(" ++ mangleCon xs'
168+ | otherwise = mangleCon xs
169+ mangleCon xs
170+ | Just xs' <- stripPrefix "CPhoneNumber " xs = "PhoneNumber " ++ xs'
171+ | otherwise = xs
172+
173+instance Read PhoneNumber where
174+ readPrec = P.parens $ P.prec 11 $ do
175+ P.expectP (P.Ident "PhoneNumber")
176+ fromCPhoneNumber <$> P.readS_to_Prec
177+ (\d xs -> readsPrec d $ "CPhoneNumber" ++ xs)
178+
179+-- | No internal structure
180+instance Data PhoneNumber where
181+ toConstr _ = error "Data.PhoneNumber.Number.Number.toConstr"
182+ gunfold _ _ = error "Data.PhoneNumber.Number.Number.gunfold"
183+ dataTypeOf _ = mkNoRepType "Data.PhoneNumber.Number.Number"
184+
185+instance NFData PhoneNumber where
186+ rnf pn = pn `seq` ()
src/Data/PhoneNumber/Internal/Util.chsnew file+302
@@ -0,0 +1,302 @@
1+{-# OPTIONS_GHC -Wno-unused-imports #-}
2+module Data.PhoneNumber.Internal.Util where
3+
4+import Control.Applicative
5+import Data.ByteString (ByteString)
6+import Data.PhoneNumber.Internal.Common
7+{#import Data.PhoneNumber.Internal.Number#}
8+import Foreign
9+import Foreign.C.Types
10+import GHC.Generics (Generic)
11+
12+#include <stdlib.h>
13+#include "c_phonenumberutil.h"
14+
15+newtype CString = CString (Ptr CChar, {#type size_t#})
16+
17+unCString :: CString -> (Ptr CChar, {#type size_t#})
18+unCString (CString pair) = pair
19+
20+instance Storable CString where
21+ sizeOf _ = {#sizeof c_string#}
22+ alignment _ = {#alignof c_string#}
23+ peek p = CString <$> liftA2 (,)
24+ ({#get c_string->string#} p)
25+ ({#get c_string->string_size#} p)
26+ poke p (CString (str, sz)) = do
27+ {#set c_string->string#} p str
28+ {#set c_string->string_size#} p sz
29+
30+{#pointer *c_string foreign -> CString nocode#}
31+
32+{#enum phone_number_format as PhoneNumberFormat {underscoreToCase}#}
33+
34+-- | Type of a phone number.
35+--
36+-- 'FixedLineOrMobile' designates cases where it is impossible to distinguish
37+-- between fixed-line and mobile numbers by looking at the phone number itself
38+-- (e.g. the USA).
39+--
40+-- 'TollFree' designates freephone lines.
41+--
42+-- 'SharedCost' designates numbers where the cost of the call is shared between
43+-- the caller and the recipient, and is hence typically less than for
44+-- 'PremiumRate'. See http://en.wikipedia.org/wiki/Shared_Cost_Service for more
45+-- information.
46+--
47+-- 'Voip' designates Voice over IP numbers. This includes TSoIP (Telephony
48+-- Service over IP).
49+--
50+-- 'Uan' designates "Universal Access Numbers" or "Company Numbers". They may be
51+-- further routed to specific offices, but allow one number to be used for a
52+-- company.
53+--
54+-- 'Voicemail' designates "Voice Mail Access Numbers".
55+{#enum phone_number_type as PhoneNumberType {underscoreToCase}
56+ deriving (Eq, Ord, Show, Generic)#}
57+
58+-- | Types of phone number matches. See 'Data.PhoneNumber.Util.matchNumbers'.
59+{#enum match_type as MatchType {underscoreToCase}
60+ deriving (Eq, Show, Generic)#}
61+
62+{#enum error_type as ErrorType {underscoreToCase}#}
63+
64+-- | Possible outcomes when testing if a t'PhoneNumber' is possible.
65+--
66+-- 'IsPossible' means the number length matches that of valid numbers for this
67+-- region.
68+--
69+-- 'IsPossibleLocalOnly' means the number length matches that of local numbers
70+-- for this region only (i.e. numbers that may be able to be dialled within an
71+-- area, but do not have all the information to be dialled from anywhere inside
72+-- or outside the country).
73+--
74+-- 'InvalidCountryCode' means the number has an invalid country calling code.
75+--
76+-- 'TooShort' means the number is shorter than all valid numbers for this
77+-- region.
78+--
79+-- 'TooLong' means the number is longer than all valid numbers for this region.
80+--
81+-- 'InvalidLength' means the number is longer than the shortest valid numbers
82+-- for this region, shorter than the longest valid numbers for this region, and
83+-- does not itself have a number length that matches valid numbers for this
84+-- region. This can also be returned in the case when there are no numbers of a
85+-- specific type at all for this region.
86+{#enum validation_result as ValidationResult {underscoreToCase}
87+ deriving (Eq, Show, Generic)#}
88+
89+{#fun unsafe c_phone_number_util_get_supported_regions
90+ { alloca2- `[ByteString]'& peekAcquireCStringList*
91+ } -> `()'#}
92+
93+{#fun unsafe c_phone_number_util_get_supported_global_network_calling_codes
94+ { alloca2- `[CInt]'& peekAcquireList*
95+ } -> `()'#}
96+
97+{#fun unsafe c_phone_number_util_get_supported_calling_codes
98+ { alloca2- `[CInt]'& peekAcquireList*
99+ } -> `()'#}
100+
101+{#fun unsafe c_phone_number_util_get_supported_types_for_region
102+ { withByteString* `ByteString'&
103+ , alloca2- `[PhoneNumberType]'& peekAcquireEnumList*
104+ } -> `()'#}
105+
106+{#fun unsafe c_phone_number_util_get_supported_types_for_non_geo_entity
107+ { `CInt'
108+ , alloca2- `[PhoneNumberType]'& peekAcquireEnumList*
109+ } -> `()'#}
110+
111+{#fun unsafe c_phone_number_util_is_alpha_number
112+ { withByteString* `ByteString'&
113+ } -> `Bool'#}
114+
115+{#fun unsafe c_phone_number_util_convert_alpha_characters_in_number
116+ { withByteString* `ByteString'&
117+ , alloca- `ByteString' peekAcquireCString*
118+ } -> `()'#}
119+
120+{#fun unsafe c_phone_number_util_normalize_digits_only
121+ { withByteString* `ByteString'&
122+ , alloca- `ByteString' peekAcquireCString*
123+ } -> `()'#}
124+
125+{#fun unsafe c_phone_number_util_normalize_dialable_chars_only
126+ { withByteString* `ByteString'&
127+ , alloca- `ByteString' peekAcquireCString*
128+ } -> `()'#}
129+
130+{#fun unsafe c_phone_number_util_get_national_significant_number
131+ { `PhoneNumber'
132+ , alloca- `ByteString' peekAcquireCString*
133+ } -> `()'#}
134+
135+
136+{#fun unsafe c_phone_number_util_get_country_mobile_token
137+ { `CInt'
138+ , alloca- `ByteString' peekAcquireCString*
139+ } -> `()'#}
140+
141+{#fun unsafe c_phone_number_util_format
142+ { `PhoneNumber'
143+ , `PhoneNumberFormat'
144+ , alloca- `ByteString' peekAcquireCString*
145+ } -> `()'#}
146+
147+{#fun unsafe c_phone_number_util_format_national_number_with_carrier_code
148+ { `PhoneNumber'
149+ , withByteString* `ByteString'&
150+ , alloca- `ByteString' peekAcquireCString*
151+ } -> `()'#}
152+
153+{#fun unsafe c_phone_number_util_format_national_number_with_preferred_carrier_code
154+ { `PhoneNumber'
155+ , withByteString* `ByteString'&
156+ , alloca- `ByteString' peekAcquireCString*
157+ } -> `()'#}
158+
159+{#fun unsafe c_phone_number_util_format_number_for_mobile_dialing
160+ { `PhoneNumber'
161+ , withByteString* `ByteString'&
162+ , `Bool'
163+ , alloca- `ByteString' peekAcquireCString*
164+ } -> `()'#}
165+
166+{#fun unsafe c_phone_number_util_format_out_of_country_calling_number
167+ { `PhoneNumber'
168+ , withByteString* `ByteString'&
169+ , alloca- `ByteString' peekAcquireCString*
170+ } -> `()'#}
171+
172+{#fun unsafe c_phone_number_util_format_in_original_format
173+ { `PhoneNumber'
174+ , withByteString* `ByteString'&
175+ , alloca- `ByteString' peekAcquireCString*
176+ } -> `()'#}
177+
178+{#fun unsafe c_phone_number_util_format_out_of_country_keeping_alpha_chars
179+ { `PhoneNumber'
180+ , withByteString* `ByteString'&
181+ , alloca- `ByteString' peekAcquireCString*
182+ } -> `()'#}
183+
184+{#fun unsafe c_phone_number_util_truncate_too_long_number
185+ { `PhoneNumber'
186+ } -> `Maybe PhoneNumber' acquireMaybePhoneNumber*#}
187+ where
188+ acquireMaybePhoneNumber p
189+ | p == nullPtr = pure Nothing
190+ | otherwise = Just <$> acquirePhoneNumber p
191+
192+{#fun unsafe c_phone_number_util_get_number_type
193+ { `PhoneNumber'
194+ } -> `PhoneNumberType'#}
195+
196+{#fun unsafe c_phone_number_util_is_valid_number
197+ { `PhoneNumber'
198+ } -> `Bool'#}
199+
200+{#fun unsafe c_phone_number_util_is_valid_number_for_region
201+ { `PhoneNumber'
202+ , withByteString* `ByteString'&
203+ } -> `Bool'#}
204+
205+{#fun unsafe c_phone_number_util_get_region_code_for_number
206+ { `PhoneNumber'
207+ , alloca- `ByteString' peekAcquireCString*
208+ } -> `()'#}
209+
210+{#fun unsafe c_phone_number_util_get_country_code_for_region
211+ { withByteString* `ByteString'&
212+ } -> `CInt'#}
213+
214+{#fun unsafe c_phone_number_util_get_region_code_for_country_code
215+ { `Int'
216+ , alloca- `ByteString' peekAcquireCString*
217+ } -> `()'#}
218+
219+{#fun unsafe c_phone_number_util_get_region_codes_for_country_calling_code
220+ { `Int'
221+ , alloca2- `[ByteString]'& peekAcquireCStringList*
222+ } -> `()'#}
223+
224+{#fun unsafe c_phone_number_util_is_nanpa_country
225+ { withByteString* `ByteString'&
226+ } -> `Bool'#}
227+
228+{#fun unsafe c_phone_number_util_get_ndd_prefix_for_region
229+ { withByteString* `ByteString'&
230+ , `Bool'
231+ , alloca- `ByteString' peekAcquireCString*
232+ } -> `()'#}
233+
234+{#fun unsafe c_phone_number_util_is_possible_number_for_type_with_reason
235+ { `PhoneNumber'
236+ , `PhoneNumberType'
237+ } -> `ValidationResult'#}
238+
239+{#fun unsafe c_phone_number_util_can_be_internationally_dialed
240+ { `PhoneNumber'
241+ } -> `Bool'#}
242+
243+{#fun unsafe c_phone_number_util_is_number_geographical_1
244+ { `PhoneNumber'
245+ } -> `Bool'#}
246+
247+{#fun unsafe c_phone_number_util_is_number_geographical_2
248+ { `PhoneNumberType'
249+ , `CInt'
250+ } -> `Bool'#}
251+
252+{#fun unsafe c_phone_number_util_parse
253+ { withByteString* `ByteString'&
254+ , withByteString* `ByteString'&
255+ , alloca- `PhoneNumber' peekAcquirePhoneNumber*
256+ } -> `ErrorType'#}
257+
258+{#fun unsafe c_phone_number_util_parse_and_keep_raw_input
259+ { withByteString* `ByteString'&
260+ , withByteString* `ByteString'&
261+ , alloca- `PhoneNumber' peekAcquirePhoneNumber*
262+ } -> `ErrorType'#}
263+
264+{#fun unsafe c_phone_number_util_is_number_match
265+ { `PhoneNumber'
266+ , `PhoneNumber'
267+ } -> `MatchType'#}
268+
269+{#fun unsafe c_phone_number_util_is_number_match_with_two_strings
270+ { withByteString* `ByteString'&
271+ , withByteString* `ByteString'&
272+ } -> `MatchType'#}
273+
274+{#fun unsafe c_phone_number_util_is_number_match_with_one_string
275+ { `PhoneNumber'
276+ , withByteString* `ByteString'&
277+ } -> `MatchType'#}
278+
279+peekAcquireList :: Storable a => Ptr (Ptr a) -> Ptr {#type size_t#} -> IO [a]
280+peekAcquireList pList pSize = do
281+ size <- peek pSize
282+ list <- peek pList
283+ xs <- peekArray (fromIntegral size) list
284+ {#call unsafe free as stdlibFree#} $ castPtr list
285+ pure xs
286+
287+peekAcquireCStringList :: Ptr (Ptr CString) -> Ptr {#type size_t#} -> IO [ByteString]
288+peekAcquireCStringList pList pSize =
289+ traverse (uncurry acquireCString . unCString) =<< peekAcquireList pList pSize
290+
291+peekAcquireEnumList :: Enum a => Ptr (Ptr CInt) -> Ptr {#type size_t#} -> IO [a]
292+peekAcquireEnumList pList pSize =
293+ map (toEnum . fromIntegral) <$> peekAcquireList pList pSize
294+
295+peekAcquireCString :: Ptr CString -> IO ByteString
296+peekAcquireCString p = uncurry acquireCString . unCString =<< peek p
297+
298+acquirePhoneNumber :: Ptr PhoneNumber -> IO PhoneNumber
299+acquirePhoneNumber p = PhoneNumber <$> newForeignPtr c_phone_number_free p
300+
301+peekAcquirePhoneNumber :: Ptr (Ptr PhoneNumber) -> IO PhoneNumber
302+peekAcquirePhoneNumber p = acquirePhoneNumber =<< peek p
src/Data/PhoneNumber/Number.hsnew file+112
@@ -0,0 +1,112 @@
1+{-# LANGUAGE CPP #-}
2+#if __GLASGOW_HASKELL__ >= 902
3+{-# LANGUAGE FieldSelectors #-}
4+#else
5+{-# OPTIONS_GHC -Wno-name-shadowing #-}
6+#endif
7+-- | Data fields of t'PhoneNumber' objects.
8+module Data.PhoneNumber.Number
9+ ( PhoneNumber
10+ , pattern PhoneNumber
11+ , extension
12+ , rawInput
13+ , preferredDomesticCarrierCode
14+ , nationalNumber
15+ , countryCode
16+ , italianLeadingZero
17+ , countryCodeSource
18+ , numberOfLeadingZeros
19+ , CountryCode(..)
20+ , CountryCodeSource(..)
21+ ) where
22+
23+import Data.ByteString (ByteString)
24+import Data.PhoneNumber.Internal.Number hiding
25+ ( pattern PhoneNumber
26+ , extension
27+ , rawInput
28+ , preferredDomesticCarrierCode
29+ , nationalNumber
30+ , countryCode
31+ , italianLeadingZero
32+ , countryCodeSource
33+ , numberOfLeadingZeros )
34+import qualified Data.PhoneNumber.Internal.Number as I
35+
36+-- | A country calling code (International Subscriber Dialing code, ISD code),
37+-- e.g. @34@ for Spain.
38+--
39+-- Contrary to the name, doesn't always correspond to a unique country (e.g.
40+-- @7@ could be either Russia or Kazakhstan), or a country at all, and instead a
41+-- non-geographical entity (e.g. @800@ is a Universal International Freephone
42+-- Service dialing code).
43+newtype CountryCode = CountryCode Int
44+ deriving newtype (Eq, Ord, Show, Num)
45+
46+-- | Record pattern synonym for accessing data fields of the underlying C++
47+-- object. If on GHC 9.2+, it can be used for record construction, record
48+-- update, and record pattern match. See t'PhoneNumber'.
49+pattern PhoneNumber
50+ :: Maybe ByteString
51+ -> Maybe ByteString
52+ -> Maybe ByteString
53+ -> Word
54+ -> CountryCode
55+ -> Maybe Bool
56+ -> Maybe CountryCodeSource
57+ -> Maybe Int
58+ -> PhoneNumber
59+pattern PhoneNumber
60+#if __GLASGOW_HASKELL__ >= 902
61+ { extension
62+ , rawInput
63+ , preferredDomesticCarrierCode
64+ , nationalNumber
65+ , countryCode
66+ , italianLeadingZero
67+ , countryCodeSource
68+ , numberOfLeadingZeros
69+ }
70+#else
71+ extension rawInput preferredDomesticCarrierCode nationalNumber countryCode
72+ italianLeadingZero countryCodeSource numberOfLeadingZeros
73+#endif
74+ <- (toCPhoneNumber -> CPhoneNumber
75+ { I.nationalNumber = fromIntegral -> nationalNumber
76+ , I.countryCode = fromIntegral -> CountryCode -> countryCode
77+ , I.numberOfLeadingZeros = fmap fromIntegral -> numberOfLeadingZeros
78+ , .. })
79+ where
80+ PhoneNumber extension rawInput preferredDomesticCarrierCode
81+ (fromIntegral -> nationalNumber)
82+ (CountryCode (fromIntegral -> countryCode)) italianLeadingZero
83+ countryCodeSource (fmap fromIntegral -> numberOfLeadingZeros)
84+ = fromCPhoneNumber $ CPhoneNumber{..}
85+
86+#if __GLASGOW_HASKELL__ < 902
87+extension :: PhoneNumber -> Maybe ByteString
88+extension (PhoneNumber x _ _ _ _ _ _ _) = x
89+
90+rawInput :: PhoneNumber -> Maybe ByteString
91+rawInput (PhoneNumber _ x _ _ _ _ _ _) = x
92+
93+preferredDomesticCarrierCode :: PhoneNumber -> Maybe ByteString
94+preferredDomesticCarrierCode (PhoneNumber _ _ x _ _ _ _ _) = x
95+
96+nationalNumber :: PhoneNumber -> Word
97+nationalNumber (PhoneNumber _ _ _ x _ _ _ _) = x
98+
99+countryCode :: PhoneNumber -> CountryCode
100+countryCode (PhoneNumber _ _ _ _ x _ _ _) = x
101+
102+italianLeadingZero :: PhoneNumber -> Maybe Bool
103+italianLeadingZero (PhoneNumber _ _ _ _ _ x _ _) = x
104+
105+countryCodeSource :: PhoneNumber -> Maybe CountryCodeSource
106+countryCodeSource (PhoneNumber _ _ _ _ _ _ x _) = x
107+
108+numberOfLeadingZeros :: PhoneNumber -> Maybe Int
109+numberOfLeadingZeros (PhoneNumber _ _ _ _ _ _ _ x) = x
110+#endif
111+
112+{-# COMPLETE PhoneNumber #-}
src/Data/PhoneNumber/Util.hsnew file+489
@@ -0,0 +1,489 @@
1+-- | Utilities for international phone numbers
2+module Data.PhoneNumber.Util
3+ ( -- * Parsing
4+ parseNumber
5+ , ParseMode(..)
6+ , ErrorType(..)
7+ -- * Formatting
8+ , formatNumber
9+ , PhoneNumberFormat(..)
10+ -- * Analysis
11+ , matchNumbers
12+ , MatchType(..)
13+ , regionForNumber
14+ , nationalSignificantNumber
15+ , isValidNumber
16+ , numberType
17+ , PhoneNumberType(..)
18+ , possibleNumber
19+ , ValidationResult(..)
20+ , canBeInternationallyDialed
21+ , isGeographicalNumber
22+ , isGeographicalNumberType
23+ , isAlphaNumber
24+ -- * Library Support
25+ , supportedRegions
26+ , supportedGlobalNetworkCallingCodes
27+ , supportedCallingCodes
28+ , supportedTypesForRegion
29+ , supportedTypesForNonGeoEntity
30+ -- * 'Region'/'CountryCode' Queries
31+ , Region(..)
32+ , noRegion
33+ , CountryCode(..)
34+ , countryCodeForRegion
35+ , regionForCountryCode
36+ , regionsForCountryCode
37+ , isNANPACountry
38+ , countryMobileToken
39+ , nddPrefixForRegion
40+ -- * Miscellaneous
41+ , truncateTooLongNumber
42+ , convertAlphaNumber
43+ , normalizeNumber
44+ , Normalize(..)
45+ -- * Re-exports
46+ , PhoneNumber
47+ )
48+ where
49+
50+import Control.DeepSeq
51+import Data.ByteString (ByteString)
52+import Data.Coerce
53+import Data.Data
54+import Data.PhoneNumber.Number
55+import Data.PhoneNumber.Internal.Util hiding (ErrorType(..), PhoneNumberFormat(..))
56+import qualified Data.PhoneNumber.Internal.Util as I
57+import qualified Data.Set as S
58+import GHC.Generics
59+import GHC.Exts
60+import GHC.IO
61+
62+-- | Most of the time an ISO 3166-1 alpha-2 country code in upper case, except
63+-- in some cases the UN M.49 code @"001"@ (meaning the world) used to represent
64+-- non-geographical entities.
65+--
66+-- In some situations, the 'noRegion' value corresponds to a default or
67+-- unspecified region.
68+newtype Region = Region ByteString
69+ deriving newtype (Eq, Ord, Show, Read, IsString, NFData)
70+ deriving stock (Data, Generic)
71+
72+-- | A default or unspecified region, the user-assigned alpha-2 code @\"ZZ\"@
73+noRegion :: Region
74+noRegion = "ZZ"
75+
76+-- | All geographical regions the library has metadata for
77+supportedRegions :: S.Set Region
78+supportedRegions = S.fromList $ unsafeDupablePerformIO $ mask_ $
79+ coerce c_phone_number_util_get_supported_regions
80+
81+-- | All global network calling codes (country calling codes for
82+-- non-geographical entities) the library has metadata for
83+supportedGlobalNetworkCallingCodes :: S.Set CountryCode
84+supportedGlobalNetworkCallingCodes = S.fromList $ unsafeDupablePerformIO $ mask_ $
85+ map (CountryCode . fromIntegral) <$>
86+ c_phone_number_util_get_supported_global_network_calling_codes
87+
88+-- | All country calling codes the library has metadata for, covering both
89+-- non-geographical entities (global network calling codes) and those used for
90+-- geographical entities. This could be used to populate a drop-down box of
91+-- country calling codes for a phone-number widget, for instance.
92+supportedCallingCodes :: S.Set CountryCode
93+supportedCallingCodes = S.fromList $ unsafeDupablePerformIO $ mask_ $
94+ map (CountryCode . fromIntegral) <$>
95+ c_phone_number_util_get_supported_calling_codes
96+
97+-- | Returns the types for a given region which the library has metadata for.
98+-- Will not include 'FixedLineOrMobile' (if numbers for this non-geographical
99+-- entity could be classified as 'FixedLineOrMobile', both 'FixedLine' and
100+-- 'Mobile' would be present) and 'Unknown'.
101+--
102+-- No types will be returned for invalid or unknown region codes.
103+supportedTypesForRegion :: Region -> S.Set PhoneNumberType
104+supportedTypesForRegion (Region region) = S.fromList $ unsafeDupablePerformIO $ mask_ $
105+ c_phone_number_util_get_supported_types_for_region region
106+
107+-- | Returns the types for a country-code belonging to a non-geographical entity
108+-- which the library has metadata for. Will not include 'FixedLineOrMobile' (if
109+-- numbers for this non-geographical entity could be classified as
110+-- 'FixedLineOrMobile', both 'FixedLine' and 'Mobile' would be present) and
111+-- 'Unknown'.
112+--
113+-- No types will be returned for country calling codes that do not map to a
114+-- known non-geographical entity.
115+supportedTypesForNonGeoEntity :: CountryCode -> S.Set PhoneNumberType
116+supportedTypesForNonGeoEntity (CountryCode cc) = S.fromList $ unsafeDupablePerformIO $ mask_ $
117+ c_phone_number_util_get_supported_types_for_non_geo_entity $ fromIntegral cc
118+
119+-- | Returns true if the number is a valid vanity (alpha) number such as
120+-- @"800 MICROSOFT"@. A valid vanity number will start with at least 3 digits
121+-- and will have three or more alpha characters. This does not do
122+-- region-specific checks - to work out if this number is actually valid for a
123+-- region, you should use 'parseNumber' and 'possibleNumber'/'isValidNumber'.
124+isAlphaNumber :: ByteString -> Bool
125+isAlphaNumber bs = unsafeDupablePerformIO $
126+ c_phone_number_util_is_alpha_number bs
127+
128+-- | Converts all alpha characters in a number to their respective digits on
129+-- a keypad, but retains existing formatting
130+convertAlphaNumber :: ByteString -> ByteString
131+convertAlphaNumber bs = unsafeDupablePerformIO $ mask_ $
132+ c_phone_number_util_convert_alpha_characters_in_number bs
133+
134+-- | How 'normalizeNumber' should normalize a phone number
135+data Normalize
136+ = Digits -- ^ Convert wide-ascii and arabic-indic numerals to European
137+ -- numerals, and strip punctuation and alpha characters
138+ | Dialable -- ^ Strip all characters which are not diallable on a mobile phone
139+ -- keypad (including all non-ASCII digits)
140+ deriving stock (Eq, Ord, Show, Read, Data, Generic)
141+ deriving anyclass (NFData)
142+
143+-- | Normalizes a string of characters representing a phone number. See
144+-- 'Normalize'.
145+normalizeNumber :: Normalize -> ByteString -> ByteString
146+normalizeNumber Digits bs = unsafeDupablePerformIO $ mask_ $
147+ c_phone_number_util_normalize_digits_only bs
148+normalizeNumber Dialable bs = unsafeDupablePerformIO $ mask_ $
149+ c_phone_number_util_normalize_dialable_chars_only bs
150+
151+-- | Gets the National Significant Number (NSN) of a phone number. Note an NSN
152+-- doesn't contain a national prefix or any formatting.
153+nationalSignificantNumber :: PhoneNumber -> ByteString
154+nationalSignificantNumber pn = unsafeDupablePerformIO $ mask_ $
155+ c_phone_number_util_get_national_significant_number pn
156+
157+-- | Returns the mobile token for the provided country calling code if it has
158+-- one, otherwise returns an empty string. A mobile token is a number inserted
159+-- before the area code when dialing a mobile number from that country from
160+-- abroad.
161+countryMobileToken :: CountryCode -> ByteString
162+countryMobileToken (CountryCode cc) = unsafeDupablePerformIO $ mask_ $
163+ c_phone_number_util_get_country_mobile_token $ fromIntegral cc
164+
165+-- | How 'formatNumber' should format a phone number
166+data PhoneNumberFormat
167+ = International -- ^ Consistent with the definition in ITU-T Recommendation
168+ -- E.123. However we follow local conventions such as using @\'-\'@
169+ -- instead of whitespace as separators. E.g. @"+41 44 668 1800"@.
170+ | National -- ^ Consistent with E.123, and also following local conventions
171+ -- for separators. E.g. @"044 668 1800"@.
172+ | E164 -- ^ Same as 'International' but with no formatting, e.g.
173+ -- @"+41446681800"@. See https://en.wikipedia.org/wiki/E.164.
174+ | RFC3966 -- ^ Same as 'International' but with all separating symbols
175+ -- replaced with a hyphen, and with any phone number extension appended
176+ -- with @";ext="@. It will also have a prefix of @"tel:"@ added, e.g.
177+ -- @"tel:+41-44-668-1800"@.
178+ | NationalWithCarrierCodeOverride ByteString -- ^ Same as 'National' but
179+ -- for dialing using the specified domestic carrier code.
180+ | NationalWithCarrierCodeFallback ByteString -- ^ Same as 'National' but
181+ -- use the phone number's 'preferredDomesticCarrierCode' (which is only
182+ -- set if parsed with 'KeepRawInput'). If a preferred carrier code is
183+ -- absent, the provided string is used as fallback.
184+ | ForMobileDialing -- ^ Format in such a way that it can be dialed from a
185+ -- mobile phone in a specific region. If the number cannot be reached from
186+ -- the region (e.g. some countries block toll-free numbers from being
187+ -- called outside of the country), will format to an empty string.
188+ { from :: Region
189+ , withFormatting :: Bool -- ^ Whether to strip formatting as in 'E164'.
190+ }
191+ | OutOfCountry -- ^ Format for out-of-country dialing purposes. This takes
192+ -- care of the case of calling inside of NANPA and between Russia and
193+ -- Kazakhstan (who share the same country calling code). In those cases,
194+ -- no international prefix is used. For regions which have multiple
195+ -- international prefixes, formats as 'International'.
196+ { from :: Region
197+ , keepAlphaChars :: Bool -- ^ Attempt to keep alpha chars and grouping
198+ -- information, if 'rawInput' is available. Setting this to 'True' comes
199+ -- with a number of caveats:
200+ --
201+ -- 1. This will not produce good results if the country calling code is
202+ -- both present in 'rawInput' /and/ is the start of the national number.
203+ -- This is not a problem in the regions which typically use alpha numbers.
204+ --
205+ -- 2. This will also not produce good results if 'rawInput' has any
206+ -- grouping information within the first three digits of the national
207+ -- number, and if the function needs to strip preceding digits/words in
208+ -- 'rawInput' before these digits. Normally people group the first three
209+ -- digits together so this is not a huge problem.
210+ }
211+ | Original -- ^ Use 'rawInput' verbatim if present, otherwise infer
212+ -- 'National', 'International', or 'OutOfCountry' based on
213+ -- 'countryCodeSource'.
214+ { from :: Region
215+ }
216+
217+-- | Formats a phone number in the specified 'PhoneNumberFormat' using default
218+-- rules. Note that this does not promise to produce a phone number that the
219+-- user can dial from where they are - as we do not currently support a more
220+-- abbreviated format, such as for users in the same area who could potentially
221+-- dial the number without area code.
222+formatNumber :: PhoneNumberFormat -> PhoneNumber -> ByteString
223+formatNumber fmt pn = case fmt of
224+ E164 -> unsafeDupablePerformIO $ mask_ $
225+ c_phone_number_util_format pn I.E164
226+ International -> unsafeDupablePerformIO $ mask_ $
227+ c_phone_number_util_format pn I.International
228+ National -> unsafeDupablePerformIO $ mask_ $
229+ c_phone_number_util_format pn I.National
230+ RFC3966 -> unsafeDupablePerformIO $ mask_ $
231+ c_phone_number_util_format pn I.Rfc3966
232+ NationalWithCarrierCodeOverride cc -> unsafeDupablePerformIO $ mask_ $
233+ c_phone_number_util_format_national_number_with_carrier_code pn cc
234+ NationalWithCarrierCodeFallback cc -> unsafeDupablePerformIO $ mask_ $
235+ c_phone_number_util_format_national_number_with_preferred_carrier_code pn cc
236+ ForMobileDialing (Region region) keepFmt -> unsafeDupablePerformIO $ mask_ $
237+ c_phone_number_util_format_number_for_mobile_dialing pn region keepFmt
238+ Original (Region region) -> unsafeDupablePerformIO $ mask_ $
239+ c_phone_number_util_format_in_original_format pn region
240+ OutOfCountry (Region region) False -> unsafeDupablePerformIO $ mask_ $
241+ c_phone_number_util_format_out_of_country_calling_number pn region
242+ OutOfCountry (Region region) True -> unsafeDupablePerformIO $ mask_ $
243+ c_phone_number_util_format_out_of_country_keeping_alpha_chars pn region
244+
245+-- | Attempts to extract a valid number from a phone number that is too long to
246+-- be valid. Returns 'Nothing' if no valid number could be extracted.
247+truncateTooLongNumber :: PhoneNumber -> Maybe PhoneNumber
248+truncateTooLongNumber pn = unsafeDupablePerformIO $ mask_ $
249+ c_phone_number_util_truncate_too_long_number pn
250+
251+-- | Gets the phone number type. Returns 'Unknown' if invalid.
252+numberType :: PhoneNumber -> PhoneNumberType
253+numberType pn = unsafeDupablePerformIO $
254+ c_phone_number_util_get_number_type pn
255+
256+-- | Tests whether a phone number is valid for a certain region (if unspecified,
257+-- the region the number is from). Note this doesn't verify the number is
258+-- actually in use, which is impossible to tell by just looking at a number
259+-- itself.
260+--
261+-- If the country calling code is not the same as the country calling code for
262+-- the provided region, this immediately returns 'False'. After this, the
263+-- specific number pattern rules for the region are examined.
264+--
265+-- Specifying a region may be useful for determining for example whether a
266+-- particular number is valid for Canada, rather than just a valid NANPA number.
267+-- On the other hand this may lead to undesirable results, for example numbers
268+-- from British Crown dependencies such as the Isle of Man are considered
269+-- invalid for the region @\"GB\"@ (United Kingdom), since it has its own region
270+-- code, @\"IM\"@.
271+--
272+-- Note that it only verifies whether the parsed, canonicalised number is valid:
273+-- not whether a particular series of digits entered by the user is dialable
274+-- from the region provided when parsing. For example, the number
275+-- @+41 (0) 78 927 2696@ can be parsed into a number with country code @"41"@
276+-- and National Significant Number @"789272696"@. This is valid, while the
277+-- original string is not dialable.
278+isValidNumber :: Maybe Region -> PhoneNumber -> Bool
279+isValidNumber Nothing pn = unsafeDupablePerformIO $
280+ c_phone_number_util_is_valid_number pn
281+isValidNumber (Just (Region region)) pn = unsafeDupablePerformIO $
282+ c_phone_number_util_is_valid_number_for_region pn region
283+
284+-- | Returns the region where a phone number is from. This could be used for
285+-- geocoding at the region level. Only guarantees correct results for valid,
286+-- full numbers (not short-codes, or invalid numbers).
287+regionForNumber :: PhoneNumber -> Region
288+regionForNumber pn = unsafeDupablePerformIO $ mask_ $
289+ coerce $ c_phone_number_util_get_region_code_for_number pn
290+
291+-- | Returns the country calling code for a specific region. For example, this
292+-- would be @1@ for the United States, and @64@ for New Zealand.
293+countryCodeForRegion :: Region -> CountryCode
294+countryCodeForRegion (Region region) = unsafeDupablePerformIO $
295+ CountryCode . fromIntegral <$>
296+ c_phone_number_util_get_country_code_for_region region
297+
298+-- | Returns the region code that matches the specific country code. Note that
299+-- it is possible that several regions share the same country calling code
300+-- (e.g. US and Canada), and in that case, only one of the regions (normally the
301+-- one with the largest population) is returned. If the country calling code
302+-- entered is valid but doesn't match a specific region (such as in the case of
303+-- non-geographical calling codes like @800@) the @'Region' "001"@ will be
304+-- returned.
305+regionForCountryCode :: CountryCode -> Region
306+regionForCountryCode (CountryCode cc) = unsafeDupablePerformIO $ mask_ $
307+ coerce $ c_phone_number_util_get_region_code_for_country_code $ fromIntegral cc
308+
309+-- | Returns a list of the region codes that match the specific country calling
310+-- code. For non-geographical country calling codes, the region code @"001"@ is
311+-- returned. Also, in the case of no region code being found, the list is empty.
312+regionsForCountryCode :: CountryCode -> [Region]
313+regionsForCountryCode (CountryCode cc) = unsafeDupablePerformIO $ mask_ $
314+ coerce $ c_phone_number_util_get_region_codes_for_country_calling_code $ fromIntegral cc
315+
316+-- | Checks if this is a region under the North American Numbering Plan
317+-- Administration (NANPA).
318+isNANPACountry :: Region -> Bool
319+isNANPACountry (Region region) = unsafeDupablePerformIO $
320+ c_phone_number_util_is_nanpa_country region
321+
322+-- | Returns the National Direct Dialling prefix (NDD prefix) for a specific
323+-- region. For example, this would be @"1"@ for the United States, and @"0"@ for
324+-- New Zealand. Note that this may contain symbols like @\'~\'@ (which indicates
325+-- a wait for a dialing tone). Returns an empty string if no national prefix is
326+-- present.
327+nddPrefixForRegion
328+ :: Bool -- ^ Whether to strip non-digits like @\'~\'@
329+ -> Region
330+ -> ByteString
331+nddPrefixForRegion strip (Region region) = unsafeDupablePerformIO $ mask_ $
332+ c_phone_number_util_get_ndd_prefix_for_region region strip
333+
334+-- | Check whether a phone number is a possible number of a particular type.
335+-- Pass the type 'Unknown' to check whether a number is possible at all.
336+--
337+-- For more specific types that don't exist in a particular region, this will
338+-- return a result that isn't so useful; it is recommended that you use
339+-- 'supportedTypesForRegion' or 'supportedTypesForNonGeoEntity' respectively
340+-- before calling this function to determine you should pass a more specific
341+-- type instead of 'Unknown'.
342+--
343+-- This function provides a more lenient check than 'isValidNumber' in the
344+-- following sense:
345+--
346+-- 1. It only checks the length of phone numbers. In particular, it doesn't
347+-- check starting digits of the number.
348+--
349+-- 2. If 'Unknown' is provided, it doesn't attempt to figure out the type of the
350+-- number, but uses general rules which apply to all types of phone numbers in a
351+-- region. Therefore, it is much faster than 'isValidNumber'.
352+--
353+-- 3. For some numbers (particularly fixed-line), many regions have the concept
354+-- of area code, which together with subscriber number constitute the National
355+-- Significant Number. It is sometimes okay to dial only the subscriber number
356+-- when dialing in the same area. This function will return IsPossibleLocalOnly
357+-- if the subscriber-number-only version is passed in. On the other hand,
358+-- because 'isValidNumber' validates using information on both starting digits
359+-- (for fixed line numbers, that would most likely be area codes) and length
360+-- (obviously includes the length of area codes for fixed line numbers), it will
361+-- return 'False' for the subscriber-number-only version.
362+possibleNumber :: PhoneNumberType -> PhoneNumber -> ValidationResult
363+possibleNumber ntype pn = unsafeDupablePerformIO $
364+ c_phone_number_util_is_possible_number_for_type_with_reason pn ntype
365+
366+-- | Returns 'True' if the number can be dialed from outside the region, or
367+-- unknown. If the number can only be dialled from within the region, returns
368+-- 'False'. Does not check the number is a valid number. Note that, at the
369+-- moment, this method does not handle short numbers (which are currently all
370+-- presumed to not be diallable from outside their country).
371+canBeInternationallyDialed :: PhoneNumber -> Bool
372+canBeInternationallyDialed pn = unsafeDupablePerformIO $
373+ c_phone_number_util_can_be_internationally_dialed pn
374+
375+-- | Tests whether a phone number has a geographical association. It checks if
376+-- the number is associated with a certain region in the country to which it
377+-- belongs. Note that this doesn't verify if the number is actually in use.
378+isGeographicalNumber :: PhoneNumber -> Bool
379+isGeographicalNumber pn = unsafeDupablePerformIO $
380+ c_phone_number_util_is_number_geographical_1 pn
381+
382+-- | A less expensive version of 'isGeographicalNumber' if we already know the
383+-- 'PhoneNumberType'
384+isGeographicalNumberType :: PhoneNumberType -> CountryCode -> Bool
385+isGeographicalNumberType ntype (CountryCode cc) = unsafeDupablePerformIO $
386+ c_phone_number_util_is_number_geographical_2 ntype $ fromIntegral cc
387+
388+-- | Phone number parsing error
389+data ErrorType
390+ = InvalidCountryCodeError -- ^ The number did not contain a country code and
391+ -- there was no default region supplied, or the number contained an invalid
392+ -- country code
393+ | NotANumber -- ^ Does not look like a phone number
394+ | TooShortAfterIdd -- ^ Input starts with an International Direct Dialing
395+ -- prefix, but ends too shortly thereafter
396+ | TooShortNsn -- ^ The National Significant Number is too short
397+ | TooLongNsn -- ^ The National Significant Number is too long
398+ deriving stock (Eq, Ord, Show, Read, Data, Generic)
399+ deriving anyclass (NFData)
400+
401+-- | How much information to retain when parsing
402+data ParseMode
403+ = Canonicalize -- ^ Canonicalize the phone number such that different
404+ -- representations can be easily compared, no matter what form it was
405+ -- originally entered in (e.g. national, international)
406+ | KeepRawInput -- ^ Record context about the number being parsed, such as
407+ -- 'rawInput', 'countryCodeSource', and 'preferredDomesticCarrierCode'
408+ deriving stock (Eq, Ord, Show, Read, Data, Generic)
409+ deriving anyclass (NFData)
410+
411+-- | Parse a phone number.
412+--
413+-- The function is quite lenient and looks for a number in the input
414+-- 'ByteString' and does not check whether the string is definitely only a phone
415+-- number. To do this, it ignores punctuation and white-space, as well as any
416+-- text before the number (e.g. a leading @"Tel: "@) and trims the non-number
417+-- bits. It will accept a number in any format (E164, national, international
418+-- etc.), assuming it can be interpreted with the default t'Region' supplied. It
419+-- also attempts to convert any alpha characters into digits if it thinks this
420+-- is a vanity number of the type @"1800 MICROSOFT"@.
421+--
422+-- The input can contain formatting such as @+@, @(@ and @-@, as well as a phone
423+-- number extension. It can also be provided in RFC3966 format.
424+--
425+-- Note that validation of whether the number is actually a valid number for a
426+-- particular region is not performed. This can be done separately with
427+-- 'isValidNumber'.
428+--
429+-- Returns an error if the string is not considered to be a viable phone number
430+-- (e.g. too few or too many digits) or if 'noRegion' was supplied and the
431+-- number is not in international format (does not start with @\'+\'@).
432+parseNumber
433+ :: ParseMode
434+ -> Region -- ^ Default region, the country that we are expecting the number to
435+ -- be dialed from, which affects national and international dialing
436+ -- prefixes. This is only used if the number being parsed is not written in
437+ -- international format. In such cases the 'countryCode' of the number would
438+ -- be that of the default region supplied. If the number is guaranteed to
439+ -- start with a @\'+\'@ followed by the country calling code, then
440+ -- 'noRegion' can be supplied.
441+ -> ByteString -- ^ Input.
442+ -> Either ErrorType PhoneNumber
443+parseNumber mode (Region region) number = case err of
444+ I.NoParsingError -> Right pn
445+ I.InvalidCountryCodeError -> Left InvalidCountryCodeError
446+ I.NotANumber -> Left NotANumber
447+ I.TooShortAfterIdd -> Left TooShortAfterIdd
448+ I.TooShortNsn -> Left TooShortNsn
449+ I.TooLongNsn -> Left TooLongNsn
450+ where
451+ (err, pn) = case mode of
452+ Canonicalize -> unsafeDupablePerformIO $ mask_ $
453+ c_phone_number_util_parse number region
454+ KeepRawInput -> unsafeDupablePerformIO $ mask_ $
455+ c_phone_number_util_parse_and_keep_raw_input number region
456+
457+-- | Compares two numbers for equality. A number can be provided as a string, in
458+-- which case it is parsed without assuming its region.
459+--
460+-- Returns 'ExactMatch' if the country calling code, National Significant
461+-- Number (NSN), presence of a leading zero for Italian numbers and any
462+-- extension present are the same.
463+--
464+-- Returns 'NsnMatch' if either or both has no country calling code specified,
465+-- and the NSNs and extensions are the same.
466+--
467+-- Returns 'ShortNsnMatch' if either or both has no country calling code
468+-- specified, or the country calling code specified is the same, and one NSN
469+-- could be a shorter version of the other number. This includes the case where
470+-- one has an extension specified, and the other does not.
471+--
472+-- Returns 'InvalidNumber' if a number that was provided as a string could not
473+-- be parsed.
474+--
475+-- Returns 'NoMatch' otherwise.
476+--
477+-- For example, the numbers @1 345 657 1234@ and @657 1234@ are a
478+-- 'ShortNsnMatch'. The numbers @1 345 657 1234@ and @345 657@ are a 'NoMatch'.
479+-- Note that none of these numbers can be parsed by 'parseNumber' without
480+-- assuming a region.
481+matchNumbers :: Either ByteString PhoneNumber -> Either ByteString PhoneNumber -> MatchType
482+matchNumbers (Left pn1) (Left pn2) = unsafeDupablePerformIO $
483+ c_phone_number_util_is_number_match_with_two_strings pn1 pn2
484+matchNumbers (Left pn1) (Right pn2) = unsafeDupablePerformIO $
485+ c_phone_number_util_is_number_match_with_one_string pn2 pn1
486+matchNumbers (Right pn1) (Left pn2) = unsafeDupablePerformIO $
487+ c_phone_number_util_is_number_match_with_one_string pn1 pn2
488+matchNumbers (Right pn1) (Right pn2) = unsafeDupablePerformIO $
489+ c_phone_number_util_is_number_match pn1 pn2
test/Leak.hsnew file+118
@@ -0,0 +1,118 @@
1+{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE LambdaCase #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE RecordWildCards #-}
5+{-# OPTIONS_GHC -with-rtsopts=-V0 -with-rtsopts=-C0 #-}
6+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
7+
8+import Control.Exception
9+import Control.Monad
10+import Data.PhoneNumber.Number
11+import Data.PhoneNumber.Util
12+import System.Environment
13+import System.Exit
14+import System.Mem
15+import System.Process
16+import System.Timeout
17+
18+-- If not currently running under valrind, start ourselves under valgrind and
19+-- exit immediately after that process exits.
20+restartWithValgrind :: IO ()
21+restartWithValgrind = lookupEnv envVar >>= \case
22+ Nothing -> do
23+ setEnv envVar "1"
24+ prog <- getExecutablePath
25+ args <- getArgs
26+ (_, _, _, hdl) <- createProcess (proc "valgrind" $ valgrindOpts ++ prog:args)
27+ { delegate_ctlc = True
28+ }
29+ exitWith =<< waitForProcess hdl
30+ Just _ -> pure ()
31+ where
32+ envVar = "RUNNING_IN_VALGRIND"
33+ valgrindOpts =
34+ [ "--leak-check=full"
35+ , "--show-leak-kinds=all"
36+ , "--errors-for-leak-kinds=all"
37+ , "--num-callers=30"
38+ , "--suppressions=test/libphonenumber.supp"
39+ , "--error-exitcode=1"
40+ ]
41+
42+-- Repeatedly re-run the given computation a given number of times, attempting
43+-- to interrupt it with an async exception with various delays. The delays are
44+-- roughly uniformly distributed over a range at the end of which the
45+-- computation stops timing out.
46+--
47+-- If there was a place where an unmasked async exception could fire while we
48+-- have pointers to allocated data that have not been accounted for by the
49+-- haskell side, this has a good chance to catch it.
50+{-# NOINLINE hammerIO #-}
51+hammerIO :: Int -> IO a -> IO ()
52+hammerIO total act = go total 1 1
53+ where
54+ go 0 !_ !_ = pure ()
55+ go tot limit cur = timeout cur (void act) >>= \case
56+ Nothing
57+ | cur == limit -> go (tot - 1) (limit + 1) (cur + 1)
58+ | otherwise -> go (tot - 1) limit (cur + 1)
59+ Just ()
60+ | cur == limit -> go (tot - 1) limit 1
61+ | otherwise -> go (tot - 1) limit (cur + 1)
62+
63+{-# NOINLINE hammer #-}
64+hammer :: Int -> (a -> b) -> a -> IO ()
65+hammer total f x = hammerIO total $ evaluate . f =<< evaluate x
66+
67+main :: IO ()
68+main = do
69+ restartWithValgrind
70+ let
71+ iter = 100000 -- starts catching stuff at around 1000
72+ region = "US"
73+ countryCallingCode = 1
74+ carrierCode = "10"
75+ nonGeoCode = 800
76+ phoneString = "+41 44 668 1800"
77+ Right phoneNumber = parseNumber KeepRawInput noRegion phoneString
78+ hammer iter (\(PhoneNumber a b c d e f g h) -> PhoneNumber a b c d e f g h) phoneNumber
79+ hammer iter supportedTypesForRegion region
80+ hammer iter supportedTypesForNonGeoEntity nonGeoCode
81+ hammer iter isAlphaNumber phoneString
82+ hammer iter convertAlphaNumber phoneString
83+ hammer iter (normalizeNumber Digits) phoneString
84+ hammer iter (normalizeNumber Dialable) phoneString
85+ hammer iter nationalSignificantNumber phoneNumber
86+ hammer iter countryMobileToken countryCallingCode
87+ hammer iter (formatNumber E164) phoneNumber
88+ hammer iter (formatNumber International) phoneNumber
89+ hammer iter (formatNumber National) phoneNumber
90+ hammer iter (formatNumber RFC3966) phoneNumber
91+ hammer iter (formatNumber (NationalWithCarrierCodeOverride carrierCode)) phoneNumber
92+ hammer iter (formatNumber (NationalWithCarrierCodeFallback carrierCode)) phoneNumber
93+ hammer iter (formatNumber (ForMobileDialing region True)) phoneNumber
94+ hammer iter (formatNumber (Original region)) phoneNumber
95+ hammer iter (formatNumber (OutOfCountry region False)) phoneNumber
96+ hammer iter (formatNumber (OutOfCountry region False)) phoneNumber
97+ hammer iter truncateTooLongNumber phoneNumber
98+ hammer iter numberType phoneNumber
99+ hammer iter (isValidNumber (Just region)) phoneNumber
100+ hammer iter regionForNumber phoneNumber
101+ hammer iter countryCodeForRegion region
102+ hammer iter regionForCountryCode countryCallingCode
103+ hammer iter regionsForCountryCode countryCallingCode
104+ hammer iter isNANPACountry region
105+ hammer iter (nddPrefixForRegion False) region
106+ hammer iter (possibleNumber Unknown) phoneNumber
107+ hammer iter canBeInternationallyDialed phoneNumber
108+ hammer iter isGeographicalNumber phoneNumber
109+ hammer iter (isGeographicalNumberType FixedLine) countryCallingCode
110+ hammer iter (parseNumber Canonicalize region) phoneString
111+ hammer iter (parseNumber Canonicalize region) ""
112+ hammer iter (parseNumber KeepRawInput region) phoneString
113+ hammer iter (parseNumber KeepRawInput region) ""
114+ hammer iter (matchNumbers (Left phoneString)) (Left phoneString)
115+ hammer iter (matchNumbers (Left phoneString)) (Right phoneNumber)
116+ hammer iter (matchNumbers (Right phoneNumber)) (Left phoneString)
117+ hammer iter (matchNumbers (Right phoneNumber)) (Right phoneNumber)
118+ performGC
test/libphonenumber.suppnew file+22
@@ -0,0 +1,22 @@
1+{
2+ leaks during initialization
3+ Memcheck:Leak
4+ ...
5+ fun:_ZN4i18n12phonenumbers15PhoneNumberUtil11GetInstanceEv
6+ ...
7+}
8+{
9+ leaks during initialization
10+ Memcheck:Leak
11+ match-leak-kinds: reachable
12+ ...
13+ fun:_dl_init
14+ ...
15+}
16+{
17+ leaks due to cache
18+ Memcheck:Leak
19+ ...
20+ fun:_ZN4i18n12phonenumbers11RegExpCache9GetRegExpERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
21+ ...
22+}