string.h 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. /**
  2. * @file string.h
  3. * Furi string primitive
  4. */
  5. #pragma once
  6. #include <stdbool.h>
  7. #include <stdint.h>
  8. #include <stddef.h>
  9. #include <stdarg.h>
  10. #include <m-core.h>
  11. #ifdef __cplusplus
  12. extern "C" {
  13. #endif
  14. /**
  15. * @brief Furi string failure constant.
  16. */
  17. #define FURI_STRING_FAILURE ((size_t)-1)
  18. /**
  19. * @brief Furi string primitive.
  20. */
  21. typedef struct FuriString FuriString;
  22. //---------------------------------------------------------------------------
  23. // Constructors
  24. //---------------------------------------------------------------------------
  25. /**
  26. * @brief Allocate new FuriString.
  27. * @return FuriString*
  28. */
  29. FuriString* furi_string_alloc();
  30. /**
  31. * @brief Allocate new FuriString and set it to string.
  32. * Allocate & Set the string a to the string.
  33. * @param source
  34. * @return FuriString*
  35. */
  36. FuriString* furi_string_alloc_set(const FuriString* source);
  37. /**
  38. * @brief Allocate new FuriString and set it to C string.
  39. * Allocate & Set the string a to the C string.
  40. * @param cstr_source
  41. * @return FuriString*
  42. */
  43. FuriString* furi_string_alloc_set_str(const char cstr_source[]);
  44. /**
  45. * @brief Allocate new FuriString and printf to it.
  46. * Initialize and set a string to the given formatted value.
  47. * @param format
  48. * @param ...
  49. * @return FuriString*
  50. */
  51. FuriString* furi_string_alloc_printf(const char format[], ...);
  52. /**
  53. * @brief Allocate new FuriString and printf to it.
  54. * Initialize and set a string to the given formatted value.
  55. * @param format
  56. * @param args
  57. * @return FuriString*
  58. */
  59. FuriString* furi_string_alloc_vprintf(const char format[], va_list args);
  60. /**
  61. * @brief Allocate new FuriString and move source string content to it.
  62. * Allocate the string, set it to the other one, and destroy the other one.
  63. * @param source
  64. * @return FuriString*
  65. */
  66. FuriString* furi_string_alloc_move(FuriString* source);
  67. //---------------------------------------------------------------------------
  68. // Destructors
  69. //---------------------------------------------------------------------------
  70. /**
  71. * @brief Free FuriString.
  72. * @param string
  73. */
  74. void furi_string_free(FuriString* string);
  75. //---------------------------------------------------------------------------
  76. // String memory management
  77. //---------------------------------------------------------------------------
  78. /**
  79. * @brief Reserve memory for string.
  80. * Modify the string capacity to be able to handle at least 'alloc' characters (including final null char).
  81. * @param string
  82. * @param size
  83. */
  84. void furi_string_reserve(FuriString* string, size_t size);
  85. /**
  86. * @brief Reset string.
  87. * Make the string empty.
  88. * @param s
  89. */
  90. void furi_string_reset(FuriString* string);
  91. /**
  92. * @brief Swap two strings.
  93. * Swap the two strings string_1 and string_2.
  94. * @param string_1
  95. * @param string_2
  96. */
  97. void furi_string_swap(FuriString* string_1, FuriString* string_2);
  98. /**
  99. * @brief Move string_2 content to string_1.
  100. * Set the string to the other one, and destroy the other one.
  101. * @param string_1
  102. * @param string_2
  103. */
  104. void furi_string_move(FuriString* string_1, FuriString* string_2);
  105. /**
  106. * @brief Compute a hash for the string.
  107. * @param string
  108. * @return size_t
  109. */
  110. size_t furi_string_hash(const FuriString* string);
  111. /**
  112. * @brief Get string size (usually length, but not for UTF-8)
  113. * @param string
  114. * @return size_t
  115. */
  116. size_t furi_string_size(const FuriString* string);
  117. /**
  118. * @brief Check that string is empty or not
  119. * @param string
  120. * @return bool
  121. */
  122. bool furi_string_empty(const FuriString* string);
  123. //---------------------------------------------------------------------------
  124. // Getters
  125. //---------------------------------------------------------------------------
  126. /**
  127. * @brief Get the character at the given index.
  128. * Return the selected character of the string.
  129. * @param string
  130. * @param index
  131. * @return char
  132. */
  133. char furi_string_get_char(const FuriString* string, size_t index);
  134. /**
  135. * @brief Return the string view a classic C string.
  136. * @param string
  137. * @return const char*
  138. */
  139. const char* furi_string_get_cstr(const FuriString* string);
  140. //---------------------------------------------------------------------------
  141. // Setters
  142. //---------------------------------------------------------------------------
  143. /**
  144. * @brief Set the string to the other string.
  145. * Set the string to the source string.
  146. * @param string
  147. * @param source
  148. */
  149. void furi_string_set(FuriString* string, FuriString* source);
  150. /**
  151. * @brief Set the string to the other C string.
  152. * Set the string to the source C string.
  153. * @param string
  154. * @param source
  155. */
  156. void furi_string_set_str(FuriString* string, const char source[]);
  157. /**
  158. * @brief Set the string to the n first characters of the C string.
  159. * @param string
  160. * @param source
  161. * @param length
  162. */
  163. void furi_string_set_strn(FuriString* string, const char source[], size_t length);
  164. /**
  165. * @brief Set the character at the given index.
  166. * @param string
  167. * @param index
  168. * @param c
  169. */
  170. void furi_string_set_char(FuriString* string, size_t index, const char c);
  171. /**
  172. * @brief Set the string to the n first characters of other one.
  173. * @param string
  174. * @param source
  175. * @param offset
  176. * @param length
  177. */
  178. void furi_string_set_n(FuriString* string, const FuriString* source, size_t offset, size_t length);
  179. /**
  180. * @brief Format in the string the given printf format
  181. * @param string
  182. * @param format
  183. * @param ...
  184. * @return int
  185. */
  186. int furi_string_printf(FuriString* string, const char format[], ...);
  187. /**
  188. * @brief Format in the string the given printf format
  189. * @param string
  190. * @param format
  191. * @param args
  192. * @return int
  193. */
  194. int furi_string_vprintf(FuriString* string, const char format[], va_list args);
  195. //---------------------------------------------------------------------------
  196. // Appending
  197. //---------------------------------------------------------------------------
  198. /**
  199. * @brief Append a character to the string.
  200. * @param string
  201. * @param c
  202. */
  203. void furi_string_push_back(FuriString* string, char c);
  204. /**
  205. * @brief Append a string to the string.
  206. * Concatenate the string with the other string.
  207. * @param string_1
  208. * @param string_2
  209. */
  210. void furi_string_cat(FuriString* string_1, const FuriString* string_2);
  211. /**
  212. * @brief Append a C string to the string.
  213. * Concatenate the string with the C string.
  214. * @param string_1
  215. * @param cstring_2
  216. */
  217. void furi_string_cat_str(FuriString* string_1, const char cstring_2[]);
  218. /**
  219. * @brief Append to the string the formatted string of the given printf format.
  220. * @param string
  221. * @param format
  222. * @param ...
  223. * @return int
  224. */
  225. int furi_string_cat_printf(FuriString* string, const char format[], ...);
  226. /**
  227. * @brief Append to the string the formatted string of the given printf format.
  228. * @param string
  229. * @param format
  230. * @param args
  231. * @return int
  232. */
  233. int furi_string_cat_vprintf(FuriString* string, const char format[], va_list args);
  234. //---------------------------------------------------------------------------
  235. // Comparators
  236. //---------------------------------------------------------------------------
  237. /**
  238. * @brief Compare two strings and return the sort order.
  239. * @param string_1
  240. * @param string_2
  241. * @return int
  242. */
  243. int furi_string_cmp(const FuriString* string_1, const FuriString* string_2);
  244. /**
  245. * @brief Compare string with C string and return the sort order.
  246. * @param string_1
  247. * @param cstring_2
  248. * @return int
  249. */
  250. int furi_string_cmp_str(const FuriString* string_1, const char cstring_2[]);
  251. /**
  252. * @brief Compare two strings (case insensitive according to the current locale) and return the sort order.
  253. * Note: doesn't work with UTF-8 strings.
  254. * @param string_1
  255. * @param string_2
  256. * @return int
  257. */
  258. int furi_string_cmpi(const FuriString* string_1, const FuriString* string_2);
  259. /**
  260. * @brief Compare string with C string (case insensitive according to the current locale) and return the sort order.
  261. * Note: doesn't work with UTF-8 strings.
  262. * @param string_1
  263. * @param cstring_2
  264. * @return int
  265. */
  266. int furi_string_cmpi_str(const FuriString* string_1, const char cstring_2[]);
  267. //---------------------------------------------------------------------------
  268. // Search
  269. //---------------------------------------------------------------------------
  270. /**
  271. * @brief Search the first occurrence of the needle in the string from the position start.
  272. * Return STRING_FAILURE if not found.
  273. * By default, start is zero.
  274. * @param string
  275. * @param needle
  276. * @param start
  277. * @return size_t
  278. */
  279. size_t furi_string_search(const FuriString* string, const FuriString* needle, size_t start);
  280. /**
  281. * @brief Search the first occurrence of the needle in the string from the position start.
  282. * Return STRING_FAILURE if not found.
  283. * @param string
  284. * @param needle
  285. * @param start
  286. * @return size_t
  287. */
  288. size_t furi_string_search_str(const FuriString* string, const char needle[], size_t start);
  289. /**
  290. * @brief Search for the position of the character c from the position start (include) in the string.
  291. * Return STRING_FAILURE if not found.
  292. * By default, start is zero.
  293. * @param string
  294. * @param c
  295. * @param start
  296. * @return size_t
  297. */
  298. size_t furi_string_search_char(const FuriString* string, char c, size_t start);
  299. /**
  300. * @brief Reverse search for the position of the character c from the position start (include) in the string.
  301. * Return STRING_FAILURE if not found.
  302. * By default, start is zero.
  303. * @param string
  304. * @param c
  305. * @param start
  306. * @return size_t
  307. */
  308. size_t furi_string_search_rchar(const FuriString* string, char c, size_t start);
  309. //---------------------------------------------------------------------------
  310. // Equality
  311. //---------------------------------------------------------------------------
  312. /**
  313. * @brief Test if two strings are equal.
  314. * @param string_1
  315. * @param string_2
  316. * @return bool
  317. */
  318. bool furi_string_equal(const FuriString* string_1, const FuriString* string_2);
  319. /**
  320. * @brief Test if the string is equal to the C string.
  321. * @param string_1
  322. * @param cstring_2
  323. * @return bool
  324. */
  325. bool furi_string_equal_str(const FuriString* string_1, const char cstring_2[]);
  326. //---------------------------------------------------------------------------
  327. // Replace
  328. //---------------------------------------------------------------------------
  329. /**
  330. * @brief Replace in the string the sub-string at position 'pos' for 'len' bytes into the C string 'replace'.
  331. * @param string
  332. * @param pos
  333. * @param len
  334. * @param replace
  335. */
  336. void furi_string_replace_at(FuriString* string, size_t pos, size_t len, const char replace[]);
  337. /**
  338. * @brief Replace a string 'needle' to string 'replace' in a string from 'start' position.
  339. * By default, start is zero.
  340. * Return STRING_FAILURE if 'needle' not found or replace position.
  341. * @param string
  342. * @param needle
  343. * @param replace
  344. * @param start
  345. * @return size_t
  346. */
  347. size_t
  348. furi_string_replace(FuriString* string, FuriString* needle, FuriString* replace, size_t start);
  349. /**
  350. * @brief Replace a C string 'needle' to C string 'replace' in a string from 'start' position.
  351. * By default, start is zero.
  352. * Return STRING_FAILURE if 'needle' not found or replace position.
  353. * @param string
  354. * @param needle
  355. * @param replace
  356. * @param start
  357. * @return size_t
  358. */
  359. size_t furi_string_replace_str(
  360. FuriString* string,
  361. const char needle[],
  362. const char replace[],
  363. size_t start);
  364. /**
  365. * @brief Replace all occurrences of 'needle' string into 'replace' string.
  366. * @param string
  367. * @param needle
  368. * @param replace
  369. */
  370. void furi_string_replace_all(
  371. FuriString* string,
  372. const FuriString* needle,
  373. const FuriString* replace);
  374. /**
  375. * @brief Replace all occurrences of 'needle' C string into 'replace' C string.
  376. * @param string
  377. * @param needle
  378. * @param replace
  379. */
  380. void furi_string_replace_all_str(FuriString* string, const char needle[], const char replace[]);
  381. //---------------------------------------------------------------------------
  382. // Start / End tests
  383. //---------------------------------------------------------------------------
  384. /**
  385. * @brief Test if the string starts with the given string.
  386. * @param string
  387. * @param start
  388. * @return bool
  389. */
  390. bool furi_string_start_with(const FuriString* string, const FuriString* start);
  391. /**
  392. * @brief Test if the string starts with the given C string.
  393. * @param string
  394. * @param start
  395. * @return bool
  396. */
  397. bool furi_string_start_with_str(const FuriString* string, const char start[]);
  398. /**
  399. * @brief Test if the string ends with the given string.
  400. * @param string
  401. * @param end
  402. * @return bool
  403. */
  404. bool furi_string_end_with(const FuriString* string, const FuriString* end);
  405. /**
  406. * @brief Test if the string ends with the given C string.
  407. * @param string
  408. * @param end
  409. * @return bool
  410. */
  411. bool furi_string_end_with_str(const FuriString* string, const char end[]);
  412. //---------------------------------------------------------------------------
  413. // Trim
  414. //---------------------------------------------------------------------------
  415. /**
  416. * @brief Trim the string left to the first 'index' bytes.
  417. * @param string
  418. * @param index
  419. */
  420. void furi_string_left(FuriString* string, size_t index);
  421. /**
  422. * @brief Trim the string right from the 'index' position to the last position.
  423. * @param string
  424. * @param index
  425. */
  426. void furi_string_right(FuriString* string, size_t index);
  427. /**
  428. * @brief Trim the string from position index to size bytes.
  429. * See also furi_string_set_n.
  430. * @param string
  431. * @param index
  432. * @param size
  433. */
  434. void furi_string_mid(FuriString* string, size_t index, size_t size);
  435. /**
  436. * @brief Trim a string from the given set of characters (default is " \n\r\t").
  437. * @param string
  438. * @param chars
  439. */
  440. void furi_string_trim(FuriString* string, const char chars[]);
  441. //---------------------------------------------------------------------------
  442. // UTF8
  443. //---------------------------------------------------------------------------
  444. /**
  445. * @brief An unicode value.
  446. */
  447. typedef unsigned int FuriStringUnicodeValue;
  448. /**
  449. * @brief Compute the length in UTF8 characters in the string.
  450. * @param string
  451. * @return size_t
  452. */
  453. size_t furi_string_utf8_length(FuriString* string);
  454. /**
  455. * @brief Push unicode into string, encoding it in UTF8.
  456. * @param string
  457. * @param unicode
  458. */
  459. void furi_string_utf8_push(FuriString* string, FuriStringUnicodeValue unicode);
  460. /**
  461. * @brief State of the UTF8 decoding machine state.
  462. */
  463. typedef enum {
  464. FuriStringUTF8StateStarting,
  465. FuriStringUTF8StateDecoding1,
  466. FuriStringUTF8StateDecoding2,
  467. FuriStringUTF8StateDecoding3,
  468. FuriStringUTF8StateError
  469. } FuriStringUTF8State;
  470. /**
  471. * @brief Main generic UTF8 decoder.
  472. * It takes a character, and the previous state and the previous value of the unicode value.
  473. * It updates the state and the decoded unicode value.
  474. * A decoded unicode encoded value is valid only when the state is FuriStringUTF8StateStarting.
  475. * @param c
  476. * @param state
  477. * @param unicode
  478. */
  479. void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnicodeValue* unicode);
  480. //---------------------------------------------------------------------------
  481. // Lasciate ogne speranza, voi ch’entrate
  482. //---------------------------------------------------------------------------
  483. /**
  484. *
  485. * Select either the string function or the str function depending on
  486. * the b operand to the function.
  487. * func1 is the string function / func2 is the str function.
  488. */
  489. /**
  490. * @brief Select for 1 argument
  491. */
  492. #define FURI_STRING_SELECT1(func1, func2, a) \
  493. _Generic((a), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a)
  494. /**
  495. * @brief Select for 2 arguments
  496. */
  497. #define FURI_STRING_SELECT2(func1, func2, a, b) \
  498. _Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b)
  499. /**
  500. * @brief Select for 3 arguments
  501. */
  502. #define FURI_STRING_SELECT3(func1, func2, a, b, c) \
  503. _Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b, c)
  504. /**
  505. * @brief Select for 4 arguments
  506. */
  507. #define FURI_STRING_SELECT4(func1, func2, a, b, c, d) \
  508. _Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b, c, d)
  509. /**
  510. * @brief Allocate new FuriString and set it content to string (or C string).
  511. * ([c]string)
  512. */
  513. #define furi_string_alloc_set(a) \
  514. FURI_STRING_SELECT1(furi_string_alloc_set, furi_string_alloc_set_str, a)
  515. /**
  516. * @brief Set the string content to string (or C string).
  517. * (string, [c]string)
  518. */
  519. #define furi_string_set(a, b) FURI_STRING_SELECT2(furi_string_set, furi_string_set_str, a, b)
  520. /**
  521. * @brief Compare string with string (or C string) and return the sort order.
  522. * Note: doesn't work with UTF-8 strings.
  523. * (string, [c]string)
  524. */
  525. #define furi_string_cmp(a, b) FURI_STRING_SELECT2(furi_string_cmp, furi_string_cmp_str, a, b)
  526. /**
  527. * @brief Compare string with string (or C string) (case insensitive according to the current locale) and return the sort order.
  528. * Note: doesn't work with UTF-8 strings.
  529. * (string, [c]string)
  530. */
  531. #define furi_string_cmpi(a, b) FURI_STRING_SELECT2(furi_string_cmpi, furi_string_cmpi_str, a, b)
  532. /**
  533. * @brief Test if the string is equal to the string (or C string).
  534. * (string, [c]string)
  535. */
  536. #define furi_string_equal(a, b) FURI_STRING_SELECT2(furi_string_equal, furi_string_equal_str, a, b)
  537. /**
  538. * @brief Replace all occurrences of string into string (or C string to another C string) in a string.
  539. * (string, [c]string, [c]string)
  540. */
  541. #define furi_string_replace_all(a, b, c) \
  542. FURI_STRING_SELECT3(furi_string_replace_all, furi_string_replace_all_str, a, b, c)
  543. /**
  544. * @brief Search for a string (or C string) in a string
  545. * (string, [c]string[, start=0])
  546. */
  547. #define furi_string_search(v, ...) \
  548. M_APPLY( \
  549. FURI_STRING_SELECT3, \
  550. furi_string_search, \
  551. furi_string_search_str, \
  552. v, \
  553. M_IF_DEFAULT1(0, __VA_ARGS__))
  554. /**
  555. * @brief Search for a C string in a string
  556. * (string, cstring[, start=0])
  557. */
  558. #define furi_string_search_str(v, ...) \
  559. M_APPLY(furi_string_search_str, v, M_IF_DEFAULT1(0, __VA_ARGS__))
  560. /**
  561. * @brief Test if the string starts with the given string (or C string).
  562. * (string, [c]string)
  563. */
  564. #define furi_string_start_with(a, b) \
  565. FURI_STRING_SELECT2(furi_string_start_with, furi_string_start_with_str, a, b)
  566. /**
  567. * @brief Test if the string ends with the given string (or C string).
  568. * (string, [c]string)
  569. */
  570. #define furi_string_end_with(a, b) \
  571. FURI_STRING_SELECT2(furi_string_end_with, furi_string_end_with_str, a, b)
  572. /**
  573. * @brief Append a string (or C string) to the string.
  574. * (string, [c]string)
  575. */
  576. #define furi_string_cat(a, b) FURI_STRING_SELECT2(furi_string_cat, furi_string_cat_str, a, b)
  577. /**
  578. * @brief Trim a string from the given set of characters (default is " \n\r\t").
  579. * (string[, set=" \n\r\t"])
  580. */
  581. #define furi_string_trim(...) M_APPLY(furi_string_trim, M_IF_DEFAULT1(" \n\r\t", __VA_ARGS__))
  582. /**
  583. * @brief Search for a character in a string.
  584. * (string, character[, start=0])
  585. */
  586. #define furi_string_search_char(v, ...) \
  587. M_APPLY(furi_string_search_char, v, M_IF_DEFAULT1(0, __VA_ARGS__))
  588. /**
  589. * @brief Reverse Search for a character in a string.
  590. * (string, character[, start=0])
  591. */
  592. #define furi_string_search_rchar(v, ...) \
  593. M_APPLY(furi_string_search_rchar, v, M_IF_DEFAULT1(0, __VA_ARGS__))
  594. /**
  595. * @brief Replace a string to another string (or C string to another C string) in a string.
  596. * (string, [c]string, [c]string[, start=0])
  597. */
  598. #define furi_string_replace(a, b, ...) \
  599. M_APPLY( \
  600. FURI_STRING_SELECT4, \
  601. furi_string_replace, \
  602. furi_string_replace_str, \
  603. a, \
  604. b, \
  605. M_IF_DEFAULT1(0, __VA_ARGS__))
  606. /**
  607. * @brief Replace a C string to another C string in a string.
  608. * (string, cstring, cstring[, start=0])
  609. */
  610. #define furi_string_replace_str(a, b, ...) \
  611. M_APPLY(furi_string_replace_str, a, b, M_IF_DEFAULT1(0, __VA_ARGS__))
  612. /**
  613. * @brief INIT OPLIST for FuriString.
  614. */
  615. #define F_STR_INIT(a) ((a) = furi_string_alloc())
  616. /**
  617. * @brief INIT SET OPLIST for FuriString.
  618. */
  619. #define F_STR_INIT_SET(a, b) ((a) = furi_string_alloc_set(b))
  620. /**
  621. * @brief OPLIST for FuriString.
  622. */
  623. #define FURI_STRING_OPLIST \
  624. (INIT(F_STR_INIT), \
  625. INIT_SET(F_STR_INIT_SET), \
  626. SET(furi_string_set), \
  627. INIT_MOVE(furi_string_alloc_move), \
  628. MOVE(furi_string_move), \
  629. SWAP(furi_string_swap), \
  630. RESET(furi_string_reset), \
  631. EMPTY_P(furi_string_empty), \
  632. CLEAR(furi_string_free), \
  633. HASH(furi_string_hash), \
  634. EQUAL(furi_string_equal), \
  635. CMP(furi_string_cmp), \
  636. TYPE(FuriString*))
  637. #ifdef __cplusplus
  638. }
  639. #endif