react-native-executorch 0.10.6 → 0.10.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/lib/module/fetcher/telemetry.js +1 -1
  2. package/package.json +1 -1
  3. package/react-native-executorch.podspec +2 -0
  4. package/src/fetcher/telemetry.ts +1 -1
  5. package/third-party/common/phonemis/CMakeLists.txt +70 -14
  6. package/third-party/common/phonemis/README.md +183 -33
  7. package/third-party/common/phonemis/THIRD_PARTY_NOTICES.md +57 -0
  8. package/third-party/common/phonemis/knowledge/code_style.md +642 -0
  9. package/third-party/common/phonemis/licenses/Unicode-3.0.txt +39 -0
  10. package/third-party/common/phonemis/licenses/misaki-Apache-2.0.txt +201 -0
  11. package/third-party/common/phonemis/licenses/nlohmann-json-MIT.txt +21 -0
  12. package/third-party/common/phonemis/licenses/xsimd-BSD-3-Clause.txt +29 -0
  13. package/third-party/common/phonemis/src/phonemis/base/config.h +6 -0
  14. package/third-party/common/phonemis/src/phonemis/base/ipipeline.cpp +75 -0
  15. package/third-party/common/phonemis/src/phonemis/base/ipipeline.h +21 -0
  16. package/third-party/common/phonemis/src/phonemis/base/phonemizer/constants.h +2 -5
  17. package/third-party/common/phonemis/src/phonemis/base/phonemizer/lexicon_phonemizer.cpp +3 -4
  18. package/third-party/common/phonemis/src/phonemis/base/phonemizer/nn/neural_phonemizer.cpp +17 -8
  19. package/third-party/common/phonemis/src/phonemis/base/phonemizer/nn/neural_phonemizer.h +4 -0
  20. package/third-party/common/phonemis/src/phonemis/base/phonemizer/nn/tokenizer.cpp +21 -6
  21. package/third-party/common/phonemis/src/phonemis/base/phonemizer/nn/tokenizer.h +13 -4
  22. package/third-party/common/phonemis/src/phonemis/base/pipeline.cpp +8 -0
  23. package/third-party/common/phonemis/src/phonemis/base/pipeline.h +19 -0
  24. package/third-party/common/phonemis/src/phonemis/base/processor/common/conditions.cpp +37 -0
  25. package/third-party/common/phonemis/src/phonemis/base/processor/common/conditions.h +45 -0
  26. package/third-party/common/phonemis/src/phonemis/base/processor/common/constants.h +10 -0
  27. package/third-party/common/phonemis/src/phonemis/base/processor/common/context.cpp +105 -0
  28. package/third-party/common/phonemis/src/phonemis/base/processor/common/context.h +49 -0
  29. package/third-party/common/phonemis/src/phonemis/base/processor/common/document.cpp +173 -0
  30. package/third-party/common/phonemis/src/phonemis/base/processor/common/document.h +67 -0
  31. package/third-party/common/phonemis/src/phonemis/base/processor/common/text.cpp +75 -0
  32. package/third-party/common/phonemis/src/phonemis/base/processor/common/text.h +60 -0
  33. package/third-party/common/phonemis/src/phonemis/base/processor/common/types.h +18 -0
  34. package/third-party/common/phonemis/src/phonemis/base/processor/config.h +53 -0
  35. package/third-party/common/phonemis/src/phonemis/base/processor/layer.h +9 -7
  36. package/third-party/common/phonemis/src/phonemis/base/processor/layers/normalize/constants.h +20 -0
  37. package/third-party/common/phonemis/src/phonemis/base/processor/layers/normalize/layer.cpp +89 -0
  38. package/third-party/common/phonemis/src/phonemis/base/processor/layers/normalize/layer.h +35 -0
  39. package/third-party/common/phonemis/src/phonemis/base/processor/layers/normalize/types.h +16 -0
  40. package/third-party/common/phonemis/src/phonemis/base/processor/layers/num2word/kind.h +40 -0
  41. package/third-party/common/phonemis/src/phonemis/base/processor/layers/num2word/layer.h +382 -0
  42. package/third-party/common/phonemis/src/phonemis/base/processor/layers/num2word/notation.h +30 -0
  43. package/third-party/common/phonemis/src/phonemis/base/processor/layers/num2word/numerals.cpp +51 -0
  44. package/third-party/common/phonemis/src/phonemis/base/processor/layers/num2word/numerals.h +31 -0
  45. package/third-party/common/phonemis/src/phonemis/base/processor/layers/num2word/pattern.h +30 -0
  46. package/third-party/common/phonemis/src/phonemis/base/processor/layers/num2word/pattern_set.cpp +17 -0
  47. package/third-party/common/phonemis/src/phonemis/base/processor/layers/num2word/pattern_set.h +31 -0
  48. package/third-party/common/phonemis/src/phonemis/base/processor/layers/num2word/patterns.cpp +811 -0
  49. package/third-party/common/phonemis/src/phonemis/base/processor/layers/num2word/patterns.h +285 -0
  50. package/third-party/common/phonemis/src/phonemis/base/processor/layers/num2word/primitives.cpp +64 -0
  51. package/third-party/common/phonemis/src/phonemis/base/processor/layers/num2word/primitives.h +23 -0
  52. package/third-party/common/phonemis/src/phonemis/base/processor/layers/num2word/types.h +94 -0
  53. package/third-party/common/phonemis/src/phonemis/base/processor/layers/sanitizer/layer.cpp +19 -0
  54. package/third-party/common/phonemis/src/phonemis/base/processor/{sanitizer_layer.h → layers/sanitizer/layer.h} +8 -8
  55. package/third-party/common/phonemis/src/phonemis/base/processor/layers/sym2word/layer.cpp +65 -0
  56. package/third-party/common/phonemis/src/phonemis/base/processor/layers/sym2word/layer.h +29 -0
  57. package/third-party/common/phonemis/src/phonemis/base/processor/layers/term2word/layer.cpp +170 -0
  58. package/third-party/common/phonemis/src/phonemis/base/processor/layers/term2word/layer.h +40 -0
  59. package/third-party/common/phonemis/src/phonemis/base/processor/layers/term2word/types.h +25 -0
  60. package/third-party/common/phonemis/src/phonemis/base/processor/layers/trim/layer.cpp +47 -0
  61. package/third-party/common/phonemis/src/phonemis/base/processor/layers/trim/layer.h +19 -0
  62. package/third-party/common/phonemis/src/phonemis/base/processor/preprocessor.cpp +60 -0
  63. package/third-party/common/phonemis/src/phonemis/base/processor/preprocessor.h +45 -0
  64. package/third-party/common/phonemis/src/phonemis/base/processor/processor.cpp +6 -7
  65. package/third-party/common/phonemis/src/phonemis/base/processor/processor.h +14 -10
  66. package/third-party/common/phonemis/src/phonemis/base/tagger/hmm_tagger.cpp +89 -52
  67. package/third-party/common/phonemis/src/phonemis/base/tagger/hmm_tagger.h +16 -9
  68. package/third-party/common/phonemis/src/phonemis/base/tokenizer/tokenizer.cpp +13 -12
  69. package/third-party/common/phonemis/src/phonemis/lang/de/constants.h +183 -19
  70. package/third-party/common/phonemis/src/phonemis/lang/de/num2word.cpp +355 -309
  71. package/third-party/common/phonemis/src/phonemis/lang/de/num2word.h +42 -17
  72. package/third-party/common/phonemis/src/phonemis/lang/de/patterns.cpp +56 -0
  73. package/third-party/common/phonemis/src/phonemis/lang/de/patterns.h +15 -0
  74. package/third-party/common/phonemis/src/phonemis/lang/de/pipeline.h +3 -3
  75. package/third-party/common/phonemis/src/phonemis/lang/en/constants.h +181 -30
  76. package/third-party/common/phonemis/src/phonemis/lang/en/hmm_tagger.cpp +5 -7
  77. package/third-party/common/phonemis/src/phonemis/lang/en/num2word.cpp +232 -135
  78. package/third-party/common/phonemis/src/phonemis/lang/en/num2word.h +41 -16
  79. package/third-party/common/phonemis/src/phonemis/lang/en/pipeline.h +3 -10
  80. package/third-party/common/phonemis/src/phonemis/lang/es/constants.h +182 -60
  81. package/third-party/common/phonemis/src/phonemis/lang/es/num2word.cpp +247 -212
  82. package/third-party/common/phonemis/src/phonemis/lang/es/num2word.h +38 -18
  83. package/third-party/common/phonemis/src/phonemis/lang/es/patterns.cpp +50 -0
  84. package/third-party/common/phonemis/src/phonemis/lang/es/patterns.h +15 -0
  85. package/third-party/common/phonemis/src/phonemis/lang/es/pipeline.h +3 -3
  86. package/third-party/common/phonemis/src/phonemis/lang/fr/constants.h +152 -26
  87. package/third-party/common/phonemis/src/phonemis/lang/fr/num2word.cpp +252 -179
  88. package/third-party/common/phonemis/src/phonemis/lang/fr/num2word.h +38 -15
  89. package/third-party/common/phonemis/src/phonemis/lang/fr/patterns.cpp +83 -0
  90. package/third-party/common/phonemis/src/phonemis/lang/fr/patterns.h +15 -0
  91. package/third-party/common/phonemis/src/phonemis/lang/fr/pipeline.h +3 -3
  92. package/third-party/common/phonemis/src/phonemis/lang/hi/constants.h +62 -10
  93. package/third-party/common/phonemis/src/phonemis/lang/hi/num2word.cpp +74 -123
  94. package/third-party/common/phonemis/src/phonemis/lang/hi/num2word.h +27 -16
  95. package/third-party/common/phonemis/src/phonemis/lang/hi/pipeline.h +5 -3
  96. package/third-party/common/phonemis/src/phonemis/lang/it/constants.h +122 -42
  97. package/third-party/common/phonemis/src/phonemis/lang/it/num2word.cpp +200 -207
  98. package/third-party/common/phonemis/src/phonemis/lang/it/num2word.h +30 -18
  99. package/third-party/common/phonemis/src/phonemis/lang/it/patterns.cpp +25 -0
  100. package/third-party/common/phonemis/src/phonemis/lang/it/patterns.h +15 -0
  101. package/third-party/common/phonemis/src/phonemis/lang/it/pipeline.h +3 -3
  102. package/third-party/common/phonemis/src/phonemis/lang/pl/constants.h +346 -77
  103. package/third-party/common/phonemis/src/phonemis/lang/pl/num2word.cpp +631 -246
  104. package/third-party/common/phonemis/src/phonemis/lang/pl/num2word.h +47 -15
  105. package/third-party/common/phonemis/src/phonemis/lang/pl/pipeline.h +6 -6
  106. package/third-party/common/phonemis/src/phonemis/lang/pt/constants.h +201 -0
  107. package/third-party/common/phonemis/src/phonemis/lang/pt/num2word.cpp +365 -0
  108. package/third-party/common/phonemis/src/phonemis/lang/pt/num2word.h +53 -0
  109. package/third-party/common/phonemis/src/phonemis/lang/pt/pipeline.h +4 -2
  110. package/third-party/common/phonemis/src/phonemis/main.cpp +42 -11
  111. package/third-party/common/phonemis/src/phonemis/protophone/meta.h +24 -0
  112. package/third-party/common/phonemis/src/phonemis/protophone/numeric.h +26 -0
  113. package/third-party/common/phonemis/src/phonemis/protophone/protophone.cpp +525 -0
  114. package/third-party/common/phonemis/src/phonemis/protophone/protophone.h +80 -0
  115. package/third-party/common/phonemis/src/phonemis/protophone/tensor.h +76 -0
  116. package/third-party/common/phonemis/src/phonemis/protophone/tensor_utils.h +89 -0
  117. package/third-party/common/phonemis/src/phonemis/protophone/tensor_view.h +125 -0
  118. package/third-party/common/phonemis/src/phonemis/protophone/types.h +43 -0
  119. package/third-party/common/phonemis/src/phonemis/utils/strings.h +20 -14
  120. package/third-party/common/phonemis/src/phonemis/utils/unicode.h +15 -1
  121. package/third-party/common/phonemis/src/third-party/xsimd/arch/common/xsimd_common_arithmetic.hpp +267 -0
  122. package/third-party/common/phonemis/src/third-party/xsimd/arch/common/xsimd_common_bit.hpp +232 -0
  123. package/third-party/common/phonemis/src/third-party/xsimd/arch/common/xsimd_common_cast.hpp +43 -0
  124. package/third-party/common/phonemis/src/third-party/xsimd/arch/common/xsimd_common_complex.hpp +112 -0
  125. package/third-party/common/phonemis/src/third-party/xsimd/arch/common/xsimd_common_details.hpp +386 -0
  126. package/third-party/common/phonemis/src/third-party/xsimd/arch/common/xsimd_common_logical.hpp +240 -0
  127. package/third-party/common/phonemis/src/third-party/xsimd/arch/common/xsimd_common_math.hpp +2601 -0
  128. package/third-party/common/phonemis/src/third-party/xsimd/arch/common/xsimd_common_memory.hpp +1006 -0
  129. package/third-party/common/phonemis/src/third-party/xsimd/arch/common/xsimd_common_rounding.hpp +72 -0
  130. package/third-party/common/phonemis/src/third-party/xsimd/arch/common/xsimd_common_swizzle.hpp +260 -0
  131. package/third-party/common/phonemis/src/third-party/xsimd/arch/common/xsimd_common_trigo.hpp +996 -0
  132. package/third-party/common/phonemis/src/third-party/xsimd/arch/utils/shifts.hpp +82 -0
  133. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx.hpp +2111 -0
  134. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx2.hpp +1591 -0
  135. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx2_128.hpp +170 -0
  136. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx512bw.hpp +724 -0
  137. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx512cd.hpp +28 -0
  138. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx512dq.hpp +270 -0
  139. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx512er.hpp +17 -0
  140. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx512f.hpp +2919 -0
  141. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx512ifma.hpp +17 -0
  142. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx512pf.hpp +17 -0
  143. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx512vbmi.hpp +76 -0
  144. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx512vbmi2.hpp +130 -0
  145. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx512vnni_avx512bw.hpp +17 -0
  146. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx512vnni_avx512vbmi2.hpp +17 -0
  147. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avx_128.hpp +164 -0
  148. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_avxvnni.hpp +17 -0
  149. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_common.hpp +26 -0
  150. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_common_fwd.hpp +127 -0
  151. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_constants.hpp +393 -0
  152. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_emulated.hpp +787 -0
  153. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_fma3_avx.hpp +93 -0
  154. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_fma3_avx2.hpp +46 -0
  155. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_fma3_sse.hpp +97 -0
  156. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_fma4.hpp +92 -0
  157. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_i8mm_neon64.hpp +17 -0
  158. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_isa.hpp +148 -0
  159. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_neon.hpp +3580 -0
  160. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_neon64.hpp +1614 -0
  161. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_rvv.hpp +1515 -0
  162. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_scalar.hpp +1210 -0
  163. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_sse2.hpp +2333 -0
  164. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_sse3.hpp +66 -0
  165. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_sse4_1.hpp +510 -0
  166. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_sse4_2.hpp +44 -0
  167. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_ssse3.hpp +186 -0
  168. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_sve.hpp +1186 -0
  169. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_vsx.hpp +947 -0
  170. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_vxe.hpp +787 -0
  171. package/third-party/common/phonemis/src/third-party/xsimd/arch/xsimd_wasm.hpp +1819 -0
  172. package/third-party/common/phonemis/src/third-party/xsimd/config/xsimd_arch.hpp +242 -0
  173. package/third-party/common/phonemis/src/third-party/xsimd/config/xsimd_config.hpp +633 -0
  174. package/third-party/common/phonemis/src/third-party/xsimd/config/xsimd_cpu_features.hpp +50 -0
  175. package/third-party/common/phonemis/src/third-party/xsimd/config/xsimd_cpu_features_arm.hpp +132 -0
  176. package/third-party/common/phonemis/src/third-party/xsimd/config/xsimd_cpu_features_ppc.hpp +54 -0
  177. package/third-party/common/phonemis/src/third-party/xsimd/config/xsimd_cpu_features_riscv.hpp +95 -0
  178. package/third-party/common/phonemis/src/third-party/xsimd/config/xsimd_cpu_features_s390x.hpp +56 -0
  179. package/third-party/common/phonemis/src/third-party/xsimd/config/xsimd_cpu_features_x86.hpp +1121 -0
  180. package/third-party/common/phonemis/src/third-party/xsimd/config/xsimd_cpuid.hpp +144 -0
  181. package/third-party/common/phonemis/src/third-party/xsimd/config/xsimd_getauxval.hpp +159 -0
  182. package/third-party/common/phonemis/src/third-party/xsimd/config/xsimd_macros.hpp +78 -0
  183. package/third-party/common/phonemis/src/third-party/xsimd/math/xsimd_rem_pio2.hpp +707 -0
  184. package/third-party/common/phonemis/src/third-party/xsimd/memory/xsimd_aligned_allocator.hpp +349 -0
  185. package/third-party/common/phonemis/src/third-party/xsimd/memory/xsimd_alignment.hpp +102 -0
  186. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_all_registers.hpp +57 -0
  187. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_api.hpp +3106 -0
  188. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avx2_register.hpp +57 -0
  189. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avx512bw_register.hpp +51 -0
  190. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avx512cd_register.hpp +51 -0
  191. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avx512dq_register.hpp +51 -0
  192. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avx512er_register.hpp +51 -0
  193. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avx512f_register.hpp +77 -0
  194. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avx512ifma_register.hpp +51 -0
  195. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avx512pf_register.hpp +51 -0
  196. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avx512vbmi2_register.hpp +51 -0
  197. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avx512vbmi_register.hpp +51 -0
  198. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avx512vnni_avx512bw_register.hpp +54 -0
  199. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avx512vnni_avx512vbmi2_register.hpp +53 -0
  200. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avx_register.hpp +79 -0
  201. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_avxvnni_register.hpp +44 -0
  202. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_batch.hpp +1666 -0
  203. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_batch_constant.hpp +572 -0
  204. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_batch_fwd.hpp +41 -0
  205. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_common_arch.hpp +49 -0
  206. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_emulated_register.hpp +80 -0
  207. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_fma3_avx2_register.hpp +50 -0
  208. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_fma3_avx_register.hpp +50 -0
  209. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_fma3_sse_register.hpp +50 -0
  210. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_fma4_register.hpp +50 -0
  211. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_i8mm_neon64_register.hpp +55 -0
  212. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_neon64_register.hpp +55 -0
  213. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_neon_register.hpp +130 -0
  214. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_register.hpp +96 -0
  215. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_rvv_register.hpp +491 -0
  216. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_sse2_register.hpp +59 -0
  217. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_sse3_register.hpp +49 -0
  218. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_sse4_1_register.hpp +48 -0
  219. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_sse4_2_register.hpp +48 -0
  220. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_ssse3_register.hpp +48 -0
  221. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_sve_register.hpp +216 -0
  222. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_traits.hpp +409 -0
  223. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_utils.hpp +462 -0
  224. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_vsx_register.hpp +84 -0
  225. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_vxe_register.hpp +86 -0
  226. package/third-party/common/phonemis/src/third-party/xsimd/types/xsimd_wasm_register.hpp +59 -0
  227. package/third-party/common/phonemis/src/third-party/xsimd/utils/bits.hpp +122 -0
  228. package/third-party/common/phonemis/src/third-party/xsimd/utils/xsimd_type_traits.hpp +145 -0
  229. package/third-party/common/phonemis/src/third-party/xsimd/xsimd.hpp +43 -0
  230. package/third-party/common/phonemis/src/phonemis/base/processor/constants.h +0 -11
  231. package/third-party/common/phonemis/src/phonemis/base/processor/num2word/config.h +0 -16
  232. package/third-party/common/phonemis/src/phonemis/base/processor/num2word/layer.cpp +0 -271
  233. package/third-party/common/phonemis/src/phonemis/base/processor/num2word/layer.h +0 -45
  234. package/third-party/common/phonemis/src/phonemis/base/processor/num2word/types.h +0 -29
  235. package/third-party/common/phonemis/src/phonemis/base/processor/sanitizer_layer.cpp +0 -22
  236. package/third-party/common/phonemis/src/phonemis/base/processor/trim_layer.cpp +0 -40
  237. package/third-party/common/phonemis/src/phonemis/base/processor/trim_layer.h +0 -22
@@ -1,326 +1,711 @@
1
- #include "num2word.h"
2
1
  #include "constants.h"
3
- #include <phonemis/utils/conversions.h>
2
+ #include "num2word.h"
3
+ #include <phonemis/base/processor/common/constants.h>
4
+ #include <phonemis/base/processor/layers/num2word/numerals.h>
5
+ #include <phonemis/base/processor/layers/num2word/patterns.h>
6
+ #include <phonemis/base/processor/layers/num2word/primitives.h>
7
+ #include <phonemis/utils/strings.h>
8
+ #include <phonemis/utils/unicode.h>
9
+
4
10
  #include <algorithm>
5
- #include <cmath>
6
- #include <cstdlib>
7
11
  #include <optional>
8
- #include <vector>
12
+ #include <unordered_set>
9
13
 
10
14
  namespace phonemis::pl {
11
15
 
12
- using namespace constants;
13
- using namespace phonemis::utils;
14
- using num2word::PluralForm;
16
+ using namespace constants::num2word;
17
+ using namespace processor;
18
+ using namespace processor::num2word;
19
+ using namespace utils;
15
20
 
16
21
  namespace {
22
+ size_t index(Case c) {
23
+ return static_cast<size_t>(c);
24
+ }
25
+
26
+ uint64_t value(const Number& number) {
27
+ return value_of(number.integer).value_or(0);
28
+ }
29
+
30
+ bool starts_with_any(std::u32string_view word, const std::vector<std::u32string>& stems) {
31
+ return std::ranges::any_of(stems, [word](const auto& stem) { return word.starts_with(stem); });
32
+ }
33
+
34
+ // ------------------------
35
+ // ----- Number words -----
36
+ // ------------------------
37
+
38
+ // The column of kCardinals: nominative and accusative, genitive and locative, dative, instrumental
39
+ size_t column(Case c) {
40
+ switch (c) {
41
+ case Case::NOMINATIVE:
42
+ case Case::ACCUSATIVE:
43
+ return 0;
44
+ case Case::GENITIVE:
45
+ case Case::LOCATIVE:
46
+ return 1;
47
+ case Case::DATIVE:
48
+ return 2;
49
+ default:
50
+ return 3;
51
+ }
52
+ }
53
+
54
+ // The form of a noun after a number: 1 / 2-4 / 5+ in the nominative, the plural of the case otherwise
55
+ const std::u32string& form(const Noun& noun, uint64_t n, Case c, bool fraction = false) {
56
+ if (fraction) {
57
+ return noun.singular[index(Case::GENITIVE)];
58
+ }
59
+ if (n == 1) {
60
+ return noun.singular[index(c)];
61
+ }
62
+ if (c == Case::NOMINATIVE || c == Case::ACCUSATIVE) {
63
+ bool few = n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14);
64
+ return noun.plural[index(few ? Case::NOMINATIVE : Case::GENITIVE)];
65
+ }
66
+ return noun.plural[index(c)];
67
+ }
68
+
69
+ std::u32string below_thousand(uint64_t n, Case c, Gender g) {
70
+ if (n == 0) {
71
+ return std::u32string(kCardinals.at(0)[column(c)]);
72
+ }
17
73
 
18
- // Polish nouns following a numeral take one of three plural forms based on
19
- // the numeral's last one or two digits: singular for 1, "few" (-y/-e endings)
20
- // for 2-4 except 12-14, and "many" (genitive plural) for everything else.
21
- PluralForm pick_plural_form(int32_t n) {
22
- int32_t abs_n = std::abs(n);
23
- if (abs_n == 1) return PluralForm::SINGULAR;
24
- int32_t last_two = abs_n % 100;
25
- int32_t last = abs_n % 10;
26
- if (last_two >= 12 && last_two <= 14) return PluralForm::MANY;
27
- if (last >= 2 && last <= 4) return PluralForm::FEW;
28
- return PluralForm::MANY;
29
- }
30
-
31
- // Inflects an ordinal word from its masculine-nominative form. The form code
32
- // reuses PluralForm to share a single inflection table:
33
- // - SINGULAR -> feminine singular ("druga", "trzecia")
34
- // - FEW -> neuter / non-masc-plural ("drugie", "czwarte")
35
- // - MANY -> genitive plural ("drugich", "czwartych")
36
- // The ending shape depends on the masculine stem:
37
- // - hard stem (-y): drop "y", append plain ending (a / e / ych)
38
- // - soft stem (-i): drop "i", append i-prefixed ending (ia / ie / ich)
39
- // - velar stem (-gi/-ki): drop "i", "a" doesn't get the leading "i"
40
- // Words not ending in -y or -i are returned unchanged so the helper is safe
41
- // to apply across cardinal tokens of a compound ordinal phrase.
42
- std::u32string inflect_ordinal(std::u32string_view word, PluralForm form) {
43
- if (word.empty()) return std::u32string(word);
44
- char32_t last = word.back();
45
- std::u32string stem(word);
46
- stem.pop_back();
47
-
48
- if (last == U'y') {
49
- if (form == PluralForm::SINGULAR) return stem + U"a";
50
- if (form == PluralForm::FEW) return stem + U"e";
51
- return stem + U"ych";
52
- }
53
- if (last == U'i') {
54
- char32_t prev = stem.empty() ? U'\0' : stem.back();
55
- bool velar = (prev == U'g' || prev == U'k');
56
- if (form == PluralForm::SINGULAR) return stem + (velar ? U"a" : U"ia");
57
- if (form == PluralForm::FEW) return stem + U"ie";
58
- return stem + U"ich";
59
- }
60
- return std::u32string(word);
61
- }
62
-
63
- // Builds the masculine-nominative ordinal phrase for `value`. Ordinals beyond
64
- // 100 keep the upper magnitude as a cardinal phrase and only inflect the
65
- // trailing sub-100 portion (e.g. 2026 -> "dwa tysiące dwudziesty szósty").
66
- std::u32string ordinal_masc(int32_t value, const Num2Word& self) {
67
- if (auto it = num2word::kOrdinals.find(value); it != num2word::kOrdinals.end()) {
68
- return it->second;
69
- }
70
-
71
- if (value < 100) {
72
- int32_t tens = (value / 10) * 10;
73
- int32_t units = value % 10;
74
- return num2word::kOrdinals.at(tens) + U" " + num2word::kOrdinals.at(units);
75
- }
76
-
77
- if (value < 1000) {
78
- int32_t hundreds = (value / 100) * 100;
79
- int32_t rest = value % 100;
80
- if (rest == 0) {
81
- // 200-900 lack dedicated ordinal entries; the cardinal form is the
82
- // pragmatic fallback (the literal "dwusetny" family is rarely used).
83
- return num2word::kCardinals.at(hundreds);
74
+ uint64_t r = n % 100;
75
+ std::vector<std::u32string> words;
76
+ for (uint64_t part : {n - r, r < 20 ? r : r - r % 10, r < 20 ? 0 : r % 10}) {
77
+ if (part == 1) {
78
+ words.push_back(U"jeden"); // Inside a compound number "jeden" does not inflect
79
+ } else if (part == 2 && g == Gender::FEMININE &&
80
+ (c == Case::NOMINATIVE || c == Case::ACCUSATIVE || c == Case::INSTRUMENTAL)) {
81
+ words.push_back(c == Case::INSTRUMENTAL ? U"dwiema" : U"dwie");
82
+ } else if (part) {
83
+ words.emplace_back(kCardinals.at(part)[column(c)]);
84
84
  }
85
- return num2word::kCardinals.at(hundreds) + U" " + ordinal_masc(rest, self);
86
- }
87
-
88
- for (auto it = num2word::kLargeCardinals.rbegin(); it != num2word::kLargeCardinals.rend(); ++it) {
89
- int64_t base = it->first;
90
- if (static_cast<int64_t>(value) >= base) {
91
- int32_t high = value / static_cast<int32_t>(base);
92
- int32_t low = value % static_cast<int32_t>(base);
93
- const auto& forms = it->second;
94
- const auto& scale_word = forms[static_cast<std::size_t>(pick_plural_form(high))];
95
-
96
- if (low == 0) {
97
- // 1000 -> "tysięczny", 1000000 -> "milionowy" already in kOrdinals.
98
- // For 2000+ we fall back to "<cardinal high> <scale word>" since
99
- // round multiple ordinals ("dwutysięczny", ...) aren't tabulated.
100
- return self.to_cardinal_int(high) + U" " + scale_word;
101
- }
85
+ }
86
+ return join(words);
87
+ }
102
88
 
103
- std::u32string head = (high == 1 && base == 1000LL) ? U"" : (self.to_cardinal_int(high) + U" ");
104
- return head + scale_word + U" " + ordinal_masc(low, self);
89
+ std::u32string cardinal(uint64_t n, Case c = Case::NOMINATIVE, Gender g = Gender::MASCULINE) {
90
+ if (n == 1) {
91
+ return std::u32string(kOne[static_cast<size_t>(g)][index(c)]);
92
+ }
93
+
94
+ std::vector<std::u32string> parts;
95
+ for (auto [scale, noun] : {std::pair{1000000000ULL, &kBillion}, {1000000ULL, &kMillion}, {1000ULL, &kThousand}}) {
96
+ uint64_t q = n / scale;
97
+ n %= scale;
98
+ if (q) {
99
+ parts.push_back(q == 1 ? form(*noun, q, c) : cardinal(q, c) + U" " + form(*noun, q, c));
105
100
  }
106
101
  }
102
+ if (n || parts.empty()) {
103
+ parts.push_back(below_thousand(n, c, g));
104
+ }
105
+ return join(parts);
106
+ }
107
107
 
108
- return self.to_cardinal_int(value);
108
+ // An ordinal stem with an adjective ending: drug + y -> drugi, trzec + a -> trzecia
109
+ std::u32string adjective(std::u32string_view stem, std::u32string_view ending) {
110
+ std::u32string e(ending);
111
+ if (stem.ends_with(U'g') || stem.ends_with(U'k')) {
112
+ e = e[0] == U'y' ? U"i" + e.substr(1) : e[0] == U'e' ? U"i" + e : e;
113
+ } else if (stem.ends_with(U'c')) {
114
+ e = e[0] == U'y' ? U"i" + e.substr(1) : U"i" + e;
115
+ }
116
+ return std::u32string(stem) + e;
109
117
  }
110
118
 
111
- // Inflects the trailing ordinal tokens of a compound phrase, leaving the
112
- // preceding cardinal part untouched. Tokens are scanned from the end and
113
- // inflected as long as they end in -y or -i (the hallmark of a masculine
114
- // ordinal); the scan stops at the first non-ordinal token (e.g. "tysiące",
115
- // "sto"), which is preserved verbatim.
116
- std::u32string inflect_trailing_ordinals(const std::u32string& phrase, PluralForm form) {
117
- std::vector<std::u32string> parts;
118
- size_t pos = 0;
119
- while (pos <= phrase.size()) {
120
- size_t next = phrase.find(U' ', pos);
121
- if (next == std::u32string::npos) {
122
- parts.push_back(phrase.substr(pos));
123
- break;
119
+ // The form of a number inside a compound: czterdziestopięcio(metrowe), stu(letni)
120
+ std::u32string combining(uint64_t n) {
121
+ uint64_t h = n - n % 100, r = n % 100;
122
+ if (h >= 1000) {
123
+ return cardinal(n);
124
+ }
125
+ std::u32string words(h ? kCombining.at(h) : U"");
126
+ if (r) {
127
+ words += r < 20 || r % 10 == 0 ? std::u32string(kCombining.at(r)) :
128
+ std::u32string(kCombining.at(r - r % 10)) + std::u32string(kCombining.at(r % 10));
129
+ }
130
+ return words;
131
+ }
132
+
133
+ // Only the last two parts inflect: dwa tysiące dwudziestym drugim, tysiąc osiemsetnym
134
+ std::u32string ordinal(uint64_t n, Case c = Case::NOMINATIVE, Gender g = Gender::MASCULINE, bool plural = false) {
135
+ std::u32string_view e = kAdjectiveEndings[plural ? 3 : static_cast<size_t>(g)][index(c)];
136
+
137
+ uint64_t r = n % 100;
138
+ if (r) {
139
+ std::vector<std::u32string> words = {n - r ? cardinal(n - r) : U""};
140
+ if (r < 20 || r % 10 == 0) {
141
+ words.push_back(adjective(kOrdinalStems.at(r), e));
142
+ } else {
143
+ words.push_back(adjective(kOrdinalStems.at(r - r % 10), e));
144
+ words.push_back(adjective(kOrdinalStems.at(r % 10), e));
124
145
  }
125
- parts.push_back(phrase.substr(pos, next - pos));
126
- pos = next + 1;
146
+ return join(words);
127
147
  }
128
148
 
129
- size_t boundary = parts.size();
130
- while (boundary > 0 && !parts[boundary - 1].empty()) {
131
- char32_t back = parts[boundary - 1].back();
132
- if (back != U'y' && back != U'i') break;
133
- boundary--;
149
+ uint64_t h = n % 1000;
150
+ if (h) {
151
+ std::vector<std::u32string> words = {n - h ? cardinal(n - h) : U"", adjective(kOrdinalStems.at(h), e)};
152
+ return join(words);
134
153
  }
135
154
 
136
- std::u32string result;
137
- for (size_t i = 0; i < parts.size(); ++i) {
138
- if (i > 0) result += U" ";
139
- result += (i >= boundary) ? inflect_ordinal(parts[i], form) : parts[i];
155
+ uint64_t t = n / 1000;
156
+ return adjective((t == 1 ? U"" : combining(t)) + U"tysięczn", e);
157
+ }
158
+
159
+ std::u32string digits(std::u32string_view text) {
160
+ std::vector<std::u32string> words;
161
+ for (char32_t c : text) {
162
+ words.emplace_back(c == U'1' ? U"jeden" : kCardinals.at(c - U'0')[0]);
140
163
  }
141
- return result;
164
+ return join(words);
142
165
  }
143
166
 
144
- // Maps an ordinal-suffix's terminal letter to the inflection class. Polish
145
- // ordinal abbreviations encode the gender/number of the inflected ending in
146
- // their last character: -y/-i = masculine nominative singular, -a = feminine
147
- // singular, -e = neuter singular or non-masculine plural.
148
- std::optional<PluralForm> form_from_suffix(std::u32string_view suffix) {
149
- if (suffix.empty()) return std::nullopt;
150
- switch (suffix.back()) {
151
- case U'a': return PluralForm::SINGULAR;
152
- case U'e': return PluralForm::FEW;
153
- default: return std::nullopt;
167
+ // -------------------
168
+ // ----- Context -----
169
+ // -------------------
170
+
171
+ // Inflection of a number: its form, case and gender
172
+ struct Features {
173
+ Form form = Form::CARDINAL;
174
+ Case grammatical_case = Case::NOMINATIVE;
175
+ Gender gender = Gender::MASCULINE;
176
+ };
177
+
178
+ // A noun's case, gender and number, guessed from its ending (as seen after an ordinal): sesji, sesję, roku
179
+ struct NounForm {
180
+ Case grammatical_case = Case::NOMINATIVE;
181
+ Gender gender = Gender::MASCULINE;
182
+ bool plural = false;
183
+ };
184
+
185
+ // Whether a word is made only of given characters (and is not empty).
186
+ bool made_of(std::u32string_view word, std::u32string_view characters) {
187
+ return !word.empty() && std::ranges::all_of(word, [&](char32_t c) { return strings::contains(characters, c); });
188
+ }
189
+
190
+ constexpr std::u32string_view kDigits = U"0123456789";
191
+
192
+ const std::unordered_set<std::u32string>& genitive_plurals() {
193
+ static const std::unordered_set<std::u32string> forms = [] {
194
+ std::unordered_set<std::u32string> result;
195
+ for (const auto* table : {&kUnits, &kCurrencies, &kScales}) {
196
+ for (const auto& [key, noun] : *table) {
197
+ result.insert(noun.plural[index(Case::GENITIVE)]);
198
+ }
199
+ }
200
+ return result;
201
+ }();
202
+ return forms;
203
+ }
204
+
205
+ // A noun that a number counts: osób, metrów, złotych (as opposed to a verb, or nothing after a year)
206
+ bool counted(std::u32string_view word) {
207
+ std::u32string w(word);
208
+ return kCountNouns.contains(w) || genitive_plurals().contains(w) || word.ends_with(U"ów") || word.ends_with(U"ych") ||
209
+ word.ends_with(U"ich");
210
+ }
211
+
212
+ /**
213
+ * The words before a number within its clause (lowercased), and the first word after it, skipping a range
214
+ * or coordination it belongs to (20-30, 3 a 368) and the złote of an amount of grosze (39 532 złotych 20 groszy).
215
+ */
216
+ std::pair<std::vector<std::u32string>, std::u32string> surroundings(const Context& context) {
217
+ std::u32string_view head = context.head();
218
+ size_t clause = 0;
219
+ for (size_t i = 0; i + 1 < head.size(); ++i) {
220
+ if (strings::contains(std::u32string_view(U",;:.!?"), head[i]) && unicode::isspace(head[i + 1])) {
221
+ clause = i + 1;
222
+ }
223
+ }
224
+
225
+ std::vector<std::u32string> before;
226
+ for (auto word : words_of(head.substr(clause))) {
227
+ before.push_back(strings::to_lower(stripped(word)));
228
+ }
229
+ auto range_start = [](std::u32string_view w) {
230
+ return w.size() > 1 && unicode::isdigit(w[0]) && w.back() == U'-' &&
231
+ made_of(w.substr(0, w.size() - 1), U"0123456789,. ");
232
+ };
233
+ while (!before.empty() && range_start(before.back())) {
234
+ before.pop_back();
235
+ }
236
+ while (before.size() >= 2 && kConjunctions.contains(before.back()) &&
237
+ made_of(before[before.size() - 2], U"0123456789,.")) {
238
+ before.resize(before.size() - 2);
239
+ }
240
+ if (!before.empty() && kCurrencyWords.contains(before.back())) {
241
+ before.pop_back();
242
+ while (!before.empty() && made_of(before.back(), kDigits)) {
243
+ before.pop_back();
244
+ }
245
+ }
246
+
247
+ // The first word after, beyond the end of a range (-30)
248
+ std::u32string next;
249
+ std::u32string_view tail = context.tail();
250
+ if (tail.empty() || !strings::contains(processor::constants::kPunctuation, tail[0])) {
251
+ for (auto word : words_of(tail)) {
252
+ std::u32string_view w = stripped(word);
253
+ if (!(w.size() > 1 && w[0] == U'-' && made_of(w.substr(1), U"0123456789,."))) {
254
+ next = strings::to_lower(w);
255
+ break;
256
+ }
257
+ }
154
258
  }
259
+ return {before, next};
155
260
  }
156
261
 
157
- // Builds the masculine-genitive ordinal phrase used for date days. Days fall
158
- // in [1, 31], so a flat lookup with a single tens+units fallback suffices.
159
- std::u32string ordinal_gen(int32_t value) {
160
- if (auto it = num2word::kOrdinalsGen.find(value); it != num2word::kOrdinalsGen.end()) {
161
- return it->second;
262
+ // The context of the number this one continues after a comma (140 g mąki, 130 g mąki): both share a case.
263
+ std::optional<Context> enumerated(const Context& context) {
264
+ std::u32string_view head = context.head();
265
+ size_t end = head.find_last_not_of(U" \t\n\r");
266
+ if (end == std::u32string_view::npos || head[end] != U',') {
267
+ return std::nullopt;
162
268
  }
163
- int32_t tens = (value / 10) * 10;
164
- int32_t units = value % 10;
165
- return num2word::kOrdinalsGen.at(tens) + U" " + num2word::kOrdinalsGen.at(units);
269
+
270
+ // The last number before the comma, at most four words before it
271
+ std::u32string_view text = head.substr(0, end);
272
+ std::optional<Span> number;
273
+ for (size_t i = 0; i < text.size();) {
274
+ if (unicode::isdigit(text[i]) && is_clear_before(text, i, U".,")) {
275
+ number = Span{i, digits_end(text, i)};
276
+ i = number->end;
277
+ } else {
278
+ i++;
279
+ }
280
+ }
281
+ if (!number || words_of(text.substr(number->end)).size() > 4) {
282
+ return std::nullopt;
283
+ }
284
+ return Context(text, *number);
166
285
  }
167
286
 
168
- } // namespace
287
+ // The case of a number governed by an amount noun, a genitive verb or a negation right before it.
288
+ std::optional<Case> governed(const std::vector<std::u32string>& before) {
289
+ std::u32string w1 = before.empty() ? U"" : before.back();
290
+ for (const auto& stem : kAmountNouns) {
291
+ if (w1.starts_with(stem) && kNounEndings.contains(std::u32string(w1.substr(stem.size())))) {
292
+ return Case::GENITIVE; // od kwoty trzystu tysięcy
293
+ }
294
+ }
295
+
296
+ std::vector<std::u32string> window(before.end() - std::min<size_t>(before.size(), 3), before.end());
297
+ std::vector<std::u32string> verbs;
298
+ std::ranges::copy_if(window, std::back_inserter(verbs), [](const auto& w) { return !kClitics.contains(w); });
299
+ if (!verbs.empty() && starts_with_any(verbs.back(), kGenitiveVerbs)) {
300
+ return Case::GENITIVE; // potrzebowali stu czterdziestu gramów, brakuje nam trzech osób
301
+ }
169
302
 
170
- std::u32string Num2Word::to_cardinal_int(int32_t value) const {
171
- if (value < 0) {
172
- return U"minus " + to_cardinal_int(std::abs(value));
303
+ // nie powinno przekraczać pięciu gramów, but not: nie jest to pięć, nie więcej niż pięć
304
+ auto no = std::ranges::find(window, U"nie");
305
+ if (no != window.end() && std::none_of(no + 1, window.end(), [](const auto& w) {
306
+ return kCopulas.contains(w) || w == U"niż" || w == U"tylko";
307
+ })) {
308
+ return Case::GENITIVE;
173
309
  }
310
+ return std::nullopt;
311
+ }
174
312
 
175
- if (auto it = num2word::kCardinals.find(value); it != num2word::kCardinals.end()) {
176
- return it->second;
313
+ NounForm noun_form(std::u32string_view word, std::u32string_view preposition) {
314
+ bool locative = kLocativePrepositions.contains(std::u32string(preposition));
315
+ bool instrumental = preposition == U"z" || preposition == U"ze" || preposition == U"przed" || preposition == U"nad" ||
316
+ preposition == U"pod";
317
+
318
+ const std::vector<std::pair<std::u32string_view, NounForm>> endings = {
319
+ {U"ami", {Case::INSTRUMENTAL, Gender::MASCULINE, true}},
320
+ {U"ach", {Case::LOCATIVE, Gender::MASCULINE, true}},
321
+ {U"ych", {Case::GENITIVE, Gender::MASCULINE, true}},
322
+ {U"ów", {Case::GENITIVE, Gender::MASCULINE, true}},
323
+ {U"iem", {Case::INSTRUMENTAL, Gender::MASCULINE}},
324
+ {U"em", {Case::INSTRUMENTAL, Gender::MASCULINE}},
325
+ {U"ą", {instrumental ? Case::INSTRUMENTAL : Case::ACCUSATIVE, Gender::FEMININE}},
326
+ {U"ę", {Case::ACCUSATIVE, Gender::FEMININE}},
327
+ {U"a", {Case::NOMINATIVE, Gender::FEMININE}},
328
+ {U"i", {locative ? Case::LOCATIVE : Case::GENITIVE, Gender::FEMININE}},
329
+ {U"y", {locative ? Case::LOCATIVE : Case::GENITIVE, Gender::FEMININE}},
330
+ {U"u", {locative ? Case::LOCATIVE : Case::GENITIVE, Gender::MASCULINE}},
331
+ {U"ie", {Case::LOCATIVE, Gender::MASCULINE}},
332
+ {U"e", {Case::NOMINATIVE, Gender::NEUTER}},
333
+ {U"o", {Case::NOMINATIVE, Gender::NEUTER}}};
334
+ for (const auto& [ending, form] : endings) {
335
+ if (word.ends_with(ending)) {
336
+ return form;
337
+ }
177
338
  }
339
+ return {};
340
+ }
178
341
 
179
- if (value < 100) {
180
- // 21..99 (excluding round tens): tens word + units word, no connector.
181
- int32_t tens = (value / 10) * 10;
182
- int32_t units = value % 10;
183
- return num2word::kCardinals.at(tens) + U" " + num2word::kCardinals.at(units);
342
+ // The form, case and gender of a number from its context: an ordinal for years, days and headings.
343
+ Features features(const Context& context, uint64_t n, bool decimal, bool year_abbreviation = false) {
344
+ auto [before, next] = surroundings(context);
345
+ std::u32string w1 = before.empty() ? U"" : before.back();
346
+ std::u32string w2 = before.size() < 2 ? U"" : before[before.size() - 2];
347
+ std::u32string noun = year_abbreviation ? U"roku" : next; // 2020 r.
348
+
349
+ // 1. Years, days and headings are ordinals
350
+ bool locative = kLocativePrepositions.contains(w1);
351
+ if (noun == U"roku" || noun == U"rok" || noun == U"rokiem") {
352
+ Case c = noun == U"rok" ? Case::NOMINATIVE :
353
+ noun == U"rokiem" ? Case::INSTRUMENTAL :
354
+ locative ? Case::LOCATIVE :
355
+ Case::GENITIVE;
356
+ return {Form::ORDINAL, c};
357
+ }
358
+ if (w1 == U"dnia") {
359
+ return {Form::ORDINAL, Case::GENITIVE}; // z dnia 1.
360
+ }
361
+ if (w1 == U"roku" || w1 == U"rok") {
362
+ bool w2_locative = kLocativePrepositions.contains(w2);
363
+ return {Form::ORDINAL, w1 == U"rok" ? Case::NOMINATIVE : w2_locative ? Case::LOCATIVE : Case::GENITIVE};
364
+ }
365
+ if (std::ranges::find(kMonthsGenitive, noun) != kMonthsGenitive.end() && n >= 1 && n <= 31) {
366
+ return {Form::ORDINAL, kCopulas.contains(w1) ? Case::NOMINATIVE : Case::GENITIVE}; // od pierwszego kwietnia
367
+ }
368
+ if (kMonths.contains(w1) && n >= 1000 && n <= 2100) {
369
+ return {Form::ORDINAL, Case::GENITIVE};
370
+ }
371
+ if ((w1 == U"w" || w1 == U"we") && n >= 1000 && n <= 2100 && !genitive_plurals().contains(noun)) {
372
+ return {Form::ORDINAL, Case::LOCATIVE};
373
+ }
374
+ if (auto it = kYearPrepositions.find(w1); it != kYearPrepositions.end() && n >= 1900 && n <= 2100 && !counted(noun)) {
375
+ return {Form::ORDINAL, it->second}; // od dwa tysiące dwudziestego do dwa tysiące dwudziestego drugiego
376
+ }
377
+ if (starts_with_any(w1, kOrdinalHeadings) && n < 100) {
378
+ return {Form::ORDINAL, noun_form(w1, w2).grammatical_case}; // w punkcie drugim
184
379
  }
185
380
 
186
- if (value < 1000) {
187
- int32_t hundreds = (value / 100) * 100;
188
- int32_t rest = value % 100;
189
- return num2word::kCardinals.at(hundreds) + U" " + to_cardinal_int(rest);
381
+ // 2. The case of a cardinal: from a preposition, a governing word or a number it continues
382
+ Features result;
383
+ if (auto it = kPrepositionCases.find(w1); it != kPrepositionCases.end()) {
384
+ result.grammatical_case = it->second;
385
+ } else if (auto it = kComplexPrepositions.find(w1); it != kComplexPrepositions.end() && it->second == w2) {
386
+ result.grammatical_case = Case::GENITIVE;
387
+ } else if (auto governing = decimal ? std::nullopt : governed(before)) {
388
+ result.grammatical_case = *governing;
389
+ } else if (auto earlier = enumerated(context)) {
390
+ result.grammatical_case = features(*earlier, 0, false).grammatical_case;
190
391
  }
191
392
 
192
- // Large-scale decomposition (tysiąc / milion / miliard) in descending order.
193
- for (auto it = num2word::kLargeCardinals.rbegin(); it != num2word::kLargeCardinals.rend(); ++it) {
194
- int64_t base = it->first;
195
- if (static_cast<int64_t>(value) >= base) {
196
- int32_t high = value / static_cast<int32_t>(base);
197
- int32_t low = value % static_cast<int32_t>(base);
393
+ // 3. The noun after it, as it agrees with the number
394
+ bool few = n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14);
395
+ bool nominative = result.grammatical_case == Case::NOMINATIVE || result.grammatical_case == Case::ACCUSATIVE;
396
+ if ((noun.ends_with(U"ami") || noun.ends_with(U"mi")) && noun.size() > 4) {
397
+ result.grammatical_case = Case::INSTRUMENTAL;
398
+ } else if (noun.ends_with(U"ach") && noun.size() > 4) {
399
+ result.grammatical_case = Case::LOCATIVE;
400
+ } else if (nominative && few && counted(noun)) {
401
+ result.grammatical_case = Case::GENITIVE; // trzech osób: the noun is genitive, so the number is too
402
+ }
198
403
 
199
- // Polish drops the leading "jeden" in front of any scale noun; "1000"
200
- // reads as "tysiąc" and "1000000" as "milion".
201
- std::u32string head = (high == 1) ? U"" : (to_cardinal_int(high) + U" ");
202
- const auto& forms = it->second;
203
- const auto& scale_word = forms[static_cast<std::size_t>(pick_plural_form(high))];
404
+ if ((noun.ends_with(U'a') && n == 1) || starts_with_any(noun, kFemininePlurals)) {
405
+ result.gender = Gender::FEMININE;
406
+ } else if (noun == U"euro") {
407
+ result.gender = Gender::NEUTER;
408
+ }
409
+ return result;
410
+ }
204
411
 
205
- std::u32string res = head + scale_word;
206
- if (low > 0) {
207
- res += U" " + to_cardinal_int(low);
208
- }
209
- return res;
412
+ // The word governing a dot ordinal: the one before it, or before the first ordinal of "lat 50. i 60."
413
+ std::u32string ordinal_cue(const Context& context) {
414
+ std::u32string w1 = context.before();
415
+ auto head = words_of(context.head());
416
+ if (w1 != U"czy" && kConjunctions.contains(w1) && head.size() > 1) {
417
+ std::u32string_view previous = head[head.size() - 2];
418
+ if (previous.ends_with(U'.') && made_of(previous.substr(0, previous.size() - 1), kDigits)) {
419
+ return context.before(3);
210
420
  }
211
421
  }
422
+ return w1;
423
+ }
424
+
425
+ // The gender of a counted noun, for the number before it: dwie tony, dwa metry
426
+ Gender gender_of(const Noun& noun) {
427
+ const auto& nominative = noun.singular[index(Case::NOMINATIVE)];
428
+ return nominative.ends_with(U'a') ? Gender::FEMININE : nominative == U"euro" ? Gender::NEUTER : Gender::MASCULINE;
429
+ }
430
+
431
+ // A decimal number in a case: dwa przecinek pięć, półtora, dwa i pół
432
+ std::u32string decimal(const Number& number, Case c) {
433
+ // 801.04, 4.0: a dotted code rather than a decimal
434
+ if (number.decimal_mark == U'.' && (number.fraction.size() == 2 || number.fraction == U"0")) {
435
+ uint64_t f = value_of(number.fraction).value_or(0);
436
+ return cardinal(value(number)) + U" " + (number.fraction[0] == U'0' ? digits(number.fraction) : cardinal(f));
437
+ }
212
438
 
213
- return conversions::utf8_to_u32(std::to_string(value));
439
+ bool zero = !number.integer.empty(); // .5 has no zero before its point
440
+ if (number.integer == U"1" && number.fraction == U"5") {
441
+ return U"półtora";
442
+ }
443
+ std::u32string head = zero ? cardinal(value(number), c) + U" " : U"";
444
+ if (number.fraction == U"5") {
445
+ return head + U"i pół";
446
+ }
447
+
448
+ std::vector<std::u32string> words = {U"przecinek"};
449
+ size_t zeros = number.fraction.find_first_not_of(U'0');
450
+ words.insert(words.end(), std::min(zeros, number.fraction.size()), U"zero");
451
+ if (zeros != std::u32string::npos) {
452
+ words.push_back(cardinal(value_of(number.fraction).value_or(0), c));
453
+ }
454
+ return head + join(words);
214
455
  }
215
456
 
216
- std::u32string Num2Word::to_cardinal_float(float value, std::u32string_view repr) const {
217
- if (value < 0.0f) {
218
- return U"minus " + to_cardinal_float(std::abs(value), repr);
457
+ // A number counting a noun, both in a case: pięćset dwadzieścia cztery kilometry
458
+ std::u32string amount(const Number& number, const Noun& noun, Case c) {
459
+ std::u32string words;
460
+ if (!number.fraction.empty()) {
461
+ words = decimal(number, c);
462
+ } else {
463
+ auto n = value_of(number.integer);
464
+ words = !n || spoken_digits(number, 7) ? digits(number.integer) : cardinal(*n, c, gender_of(noun));
465
+ }
466
+ return words + U" " + form(noun, value(number), c, !number.fraction.empty());
467
+ }
468
+
469
+ bool is_zloty(std::u32string_view currency) {
470
+ return currency == U"zł" || currency == U"PLN";
471
+ }
472
+ } // namespace
473
+
474
+ Num2Word::Num2Word() : Num2WordLayer(kNotation, kJoiners) {
475
+ add_pattern(std::make_unique<DatePattern>(kDateFormat));
476
+ for (const auto& format : kPhoneFormats) {
477
+ add_pattern(std::make_unique<PhonePattern>(format));
478
+ }
479
+ add_pattern(std::make_unique<DotOrdinalPattern>(true, true));
480
+ add_pattern(std::make_unique<SuffixPattern>(Kind::NUMBER, kYearSuffixes, kYearFormat));
481
+ }
482
+
483
+ // -----------------------
484
+ // ----- Resolution -----
485
+ // -----------------------
486
+
487
+ bool Num2Word::accept(const Match& match, const Context& context) const {
488
+ switch (match.kind) {
489
+ case Kind::PHONE:
490
+ // 600 123 456 is a count, unless a word says it is a phone number
491
+ return match.separator != U' ' || kPhoneWords.contains(context.before());
492
+ case Kind::TIME: {
493
+ const auto& minutes = match.numbers[1].integer;
494
+ bool round = minutes == U"00" || minutes == U"15" || minutes == U"30" || minutes == U"45";
495
+ bool decimal = match.separator == U'.' && !round && !kTimeWords.contains(context.before());
496
+ return value(match.numbers[0]) <= 24 && value(match.numbers[1]) <= 59 && !decimal;
497
+ }
498
+ case Kind::ORDINAL: {
499
+ // An ordinal agrees with the noun after it: "29. sesji", but "Punkt 3. Dalej" ends a sentence
500
+ std::u32string w1 = ordinal_cue(context);
501
+ auto words = words_of(context.tail());
502
+ std::u32string_view next = words.empty() ? U"" : stripped(words[0]);
503
+ bool heading = starts_with_any(w1, kHeadings) && value(match.numbers[0]) < 100;
504
+ return kDecadeWords.contains(w1) || heading || (!next.empty() && unicode::islower(next[0]));
505
+ }
506
+ default:
507
+ return true;
219
508
  }
509
+ }
220
510
 
221
- // The base transform normalizes the decimal separator to '.', but accept
222
- // ',' too when this method is invoked directly with a raw representation.
223
- std::string value_str = conversions::u32_to_utf8(repr);
224
- std::replace(value_str.begin(), value_str.end(), ',', '.');
511
+ NumberReading Num2Word::resolve(const Match& match, const Context& context) const {
512
+ NumberReading reading;
513
+
514
+ switch (match.kind) {
515
+ case Kind::DATE:
516
+ reading.grammatical_case = kCopulas.contains(context.before()) ? Case::NOMINATIVE : Case::GENITIVE;
517
+ return reading;
518
+
519
+ case Kind::TIME: {
520
+ // o osiemnastej trzydzieści: the hour is a feminine ordinal (godzina) in the case of the word before
521
+ std::u32string w1 = context.before();
522
+ reading.grammatical_case = w1 == U"o" || w1 == U"godzinie" || w1 == U"po" ? Case::LOCATIVE :
523
+ w1 == U"do" || w1 == U"od" || w1 == U"około" || w1 == U"koło" ? Case::GENITIVE :
524
+ w1 == U"przed" || w1 == U"między" ? Case::INSTRUMENTAL :
525
+ w1 == U"na" || w1 == U"za" ? Case::ACCUSATIVE :
526
+ Case::NOMINATIVE;
527
+ return reading;
528
+ }
225
529
 
226
- // Trim trailing zeros and any dangling decimal point.
227
- auto rit = std::find_if(value_str.rbegin(), value_str.rend(),
228
- [](char c) { return c != '0'; });
229
- value_str = std::string(value_str.begin(), rit.base());
230
- if (!value_str.empty() && value_str.back() == '.') {
231
- value_str.pop_back();
530
+ case Kind::ORDINAL: {
531
+ // "29. sesji": an ordinal agreeing with the noun after it, the decade of "lat 70.", or a heading
532
+ reading.form = Form::ORDINAL;
533
+ std::u32string w1 = ordinal_cue(context);
534
+ if (auto it = kDecadeWords.find(w1); it != kDecadeWords.end()) {
535
+ reading.grammatical_case = it->second;
536
+ reading.plural = true;
537
+ } else if (starts_with_any(w1, kHeadings) && value(match.numbers[0]) < 100) {
538
+ reading.grammatical_case = noun_form(w1, context.before(2)).grammatical_case; // Punkt 3., w punkcie 5.
539
+ } else {
540
+ auto words = words_of(context.tail());
541
+ NounForm next = noun_form(words.empty() ? U"" : strings::to_lower(stripped(words[0])), w1);
542
+ reading.grammatical_case = next.grammatical_case;
543
+ reading.gender = next.gender;
544
+ reading.plural = next.plural;
545
+ }
546
+ return reading;
547
+ }
548
+
549
+ case Kind::DIMENSION: {
550
+ // numerem 3x38: a document number, where "x" stands for a slash
551
+ std::u32string_view head = context.head();
552
+ size_t digits = head.size();
553
+ while (digits > 0 && unicode::isdigit(head[digits - 1])) {
554
+ digits--;
555
+ }
556
+ size_t word_end = digits;
557
+ while (word_end > 0 && unicode::isspace(head[word_end - 1])) {
558
+ word_end--;
559
+ }
560
+ size_t word_begin = word_end;
561
+ while (word_begin > 0 && unicode::isword(head[word_begin - 1])) {
562
+ word_begin--;
563
+ }
564
+ std::u32string_view word = head.substr(word_begin, word_end - word_begin);
565
+ reading.numbered = digits < head.size() && word_end < digits && (word.starts_with(U"numer") || word == U"nr");
566
+ return reading;
567
+ }
568
+
569
+ default:
570
+ break;
571
+ }
572
+
573
+ // A number: its own inflection (the start of a range for amounts), and the case of what it counts
574
+ if (!match.numbers.empty()) {
575
+ const Number& first = match.numbers.front();
576
+ bool year_abbreviation = match.suffix == U"r.";
577
+ Features own = first.fraction.empty() ? features(context, value(first), false, year_abbreviation) :
578
+ features(context, 0, true, year_abbreviation);
579
+ reading.form = own.form;
580
+ reading.grammatical_case = own.grammatical_case;
581
+ reading.gender = own.gender;
582
+
583
+ const Number& last = match.numbers.back();
584
+ bool grosze = match.kind == Kind::MONEY && match.scale.empty() && last.fraction.size() == 2 && is_zloty(match.unit);
585
+ reading.amount_case = features(context, 0, !last.fraction.empty() && !grosze).grammatical_case;
232
586
  }
587
+ return reading;
588
+ }
233
589
 
234
- size_t dot_pos = value_str.find('.');
235
- if (dot_pos == std::string::npos) {
236
- return to_cardinal_int(value_str.empty() ? 0 : std::stoi(value_str));
590
+ // --------------------
591
+ // ----- Readings -----
592
+ // --------------------
593
+
594
+ std::u32string Num2Word::read_number(const Number& number, const NumberReading& reading) const {
595
+ if (!number.fraction.empty()) {
596
+ return decimal(number, reading.grammatical_case);
237
597
  }
238
598
 
239
- std::string integer_part_str = value_str.substr(0, dot_pos);
240
- std::string fractional_part_str = value_str.substr(dot_pos + 1);
241
- int32_t integer_part = integer_part_str.empty() ? 0 : std::stoi(integer_part_str);
599
+ auto n = value_of(number.integer);
600
+ if (!n || spoken_digits(number, 7)) {
601
+ return digits(number.integer);
602
+ }
603
+ if (reading.form == Form::ORDINAL) {
604
+ return ordinal(*n, reading.grammatical_case, reading.gender);
605
+ }
606
+ return cardinal(*n, reading.grammatical_case, reading.gender);
607
+ }
242
608
 
243
- // Leading zeros in the fractional part are spoken as "zero" tokens; the
244
- // remaining digits are verbalized as a single cardinal (e.g. 0,05 ->
245
- // "zero przecinek zero pięć", 123,45 -> "... przecinek czterdzieści pięć").
246
- std::u32string frac_words;
247
- size_t pos = 0;
248
- while (pos < fractional_part_str.size() && fractional_part_str[pos] == '0') {
249
- if (!frac_words.empty()) frac_words += U" ";
250
- frac_words += U"zero";
251
- pos++;
609
+ std::u32string Num2Word::read_code_digits(std::u32string_view text) const {
610
+ auto n = value_of(text);
611
+ if ((text.size() > 1 && text[0] == U'0') || text.size() >= 7 || !n) {
612
+ return digits(text);
252
613
  }
253
- if (pos < fractional_part_str.size()) {
254
- if (!frac_words.empty()) frac_words += U" ";
255
- frac_words += to_cardinal_int(std::stoi(fractional_part_str.substr(pos)));
614
+ return cardinal(*n);
615
+ }
616
+
617
+ std::u32string Num2Word::read_date(const Match& match, const NumberReading& reading) const {
618
+ std::vector<std::u32string> words = {ordinal(value(match.numbers[0]), reading.grammatical_case),
619
+ std::u32string(kMonthsGenitive[value(match.numbers[1]) - 1]),
620
+ ordinal(value(match.numbers[2]), Case::GENITIVE)};
621
+ if (!match.suffix.empty()) {
622
+ words.push_back(U"roku");
256
623
  }
624
+ return join(words);
625
+ }
257
626
 
258
- return to_cardinal_int(integer_part) + U" przecinek " + frac_words;
627
+ std::u32string Num2Word::read_phone(const Match& match, const NumberReading& reading) const {
628
+ std::vector<std::u32string> words;
629
+ // In groups: sześćset sto dwadzieścia trzy; a longer one digit by digit
630
+ for (const auto& group : match.numbers) {
631
+ words.push_back(group.integer.size() > 3 ? digits(group.integer) : cardinal(value(group)));
632
+ }
633
+ return join(words);
259
634
  }
260
635
 
261
- std::u32string Num2Word::to_ordinal_int(int32_t value, std::u32string_view suffix) const {
262
- std::u32string base = ordinal_masc(value, *this);
263
- if (auto form = form_from_suffix(suffix)) {
264
- return inflect_trailing_ordinals(base, *form);
636
+ std::u32string Num2Word::read_time(const Match& match, const NumberReading& reading) const {
637
+ std::u32string hours = ordinal(value(match.numbers[0]), reading.grammatical_case, Gender::FEMININE);
638
+ const auto& minutes = match.numbers[1].integer;
639
+ if (value(match.numbers[1]) == 0) {
640
+ return hours;
265
641
  }
266
- return base;
642
+ return hours + U" " + (minutes[0] == U'0' ? digits(minutes) : cardinal(value(match.numbers[1])));
643
+ }
644
+
645
+ std::u32string Num2Word::read_ordinal(const Match& match, const NumberReading& reading) const {
646
+ return ordinal(value(match.numbers[0]), reading.grammatical_case, reading.gender, reading.plural);
267
647
  }
268
648
 
269
- std::u32string Num2Word::to_currency(char32_t currency, std::variant<int32_t, float> number) const {
270
- auto it = num2word::kCurrencies.find(currency);
271
- if (it == num2word::kCurrencies.end()) {
272
- return std::u32string(1, currency);
649
+ std::u32string Num2Word::read_compound(const Match& match, const NumberReading& reading) const {
650
+ const Number& number = match.numbers[0];
651
+ if (!number.fraction.empty()) {
652
+ return read_number(number, reading) + U"-" + std::u32string(match.suffix);
273
653
  }
274
654
 
275
- PluralForm form = std::visit([](auto&& arg) -> PluralForm {
276
- using T = std::decay_t<decltype(arg)>;
277
- if constexpr (std::is_same_v<T, int32_t>) {
278
- return pick_plural_form(arg);
279
- } else {
280
- // Singular only for amounts that round to exactly 1; otherwise pick
281
- // based on the integer part as a coarse approximation.
282
- if (std::abs(arg - 1.0F) < 1e-6F) return PluralForm::SINGULAR;
283
- return pick_plural_form(static_cast<int32_t>(arg));
655
+ // lat 70-tych: siedemdziesiątych - the first gender and case taking the ending of the suffix
656
+ if (auto it = kOrdinalSuffixes.find(std::u32string(match.suffix)); it != kOrdinalSuffixes.end()) {
657
+ for (size_t g = 0; g < kAdjectiveEndings.size(); ++g) {
658
+ for (size_t c = 0; c < kAdjectiveEndings[g].size(); ++c) {
659
+ if (kAdjectiveEndings[g][c] == it->second) {
660
+ Gender gender = g == 3 ? Gender::MASCULINE : static_cast<Gender>(g);
661
+ return ordinal(value(number), static_cast<Case>(c), gender, g == 3);
662
+ }
663
+ }
284
664
  }
285
- }, number);
665
+ }
666
+
667
+ // 45-metrowe: czterdziestopięciometrowe
668
+ return combining(value(number)) + std::u32string(match.suffix);
669
+ }
286
670
 
287
- return it->second[static_cast<std::size_t>(form)];
671
+ std::u32string Num2Word::read_plain(const Match& match, const NumberReading& reading) const {
672
+ return read_number(match.numbers[0], reading) + (match.suffix == U"r." ? U" roku" : U"");
288
673
  }
289
674
 
290
- std::u32string Num2Word::to_fraction(int32_t numerator, int32_t denominator) const {
291
- // The numerator agrees in gender (feminine) with the elided noun "część"
292
- // that the fraction implicitly modifies; only 1 and 2 have distinct forms
293
- // in this position ("jedna", "dwie"), other numerals are invariable here.
294
- std::u32string num_word;
295
- if (numerator == 1) num_word = U"jedna";
296
- else if (numerator == 2) num_word = U"dwie";
297
- else num_word = to_cardinal_int(numerator);
675
+ std::u32string Num2Word::read_money(const Number& number, const Match& match, const NumberReading& reading) const {
676
+ const Noun& currency = kCurrencies.at(std::u32string(match.unit));
677
+ Case c = reading.amount_case;
678
+ if (!match.scale.empty()) {
679
+ return amount(number, kScales.at(std::u32string(match.scale)), c) + U" " + currency.plural[index(Case::GENITIVE)];
680
+ }
298
681
 
299
- std::u32string ord_phrase = ordinal_masc(denominator, *this);
300
- return num_word + U" " + inflect_trailing_ordinals(ord_phrase, pick_plural_form(numerator));
682
+ // dwanaście złotych dziewięćdziesiąt dziewięć groszy
683
+ if (number.fraction.size() == 2 && is_zloty(match.unit)) {
684
+ uint64_t grosze = value_of(number.fraction).value_or(0);
685
+ Number zlote{.integer = number.integer, .grouped = number.grouped};
686
+ return amount(zlote, currency, c) + U" " + cardinal(grosze, c) + U" " + form(kGrosz, grosze, c);
687
+ }
688
+ return amount(number, currency, c);
301
689
  }
302
690
 
303
- std::u32string Num2Word::to_day(uint32_t day) const {
304
- // Polish dates always render the day as a masculine genitive ordinal.
305
- return ordinal_gen(static_cast<int32_t>(day));
691
+ std::u32string Num2Word::read_scale(const Number& number, const Match& match, const NumberReading& reading) const {
692
+ return amount(number, kScales.at(std::u32string(match.scale)), reading.amount_case);
306
693
  }
307
694
 
308
- std::u32string Num2Word::to_month(uint32_t month) const {
309
- if (month >= 1 && month <= 12) {
310
- return num2word::kMonths[month];
311
- }
312
- return U"";
695
+ std::u32string Num2Word::read_percent(const Number& number, const Match& match, const NumberReading& reading) const {
696
+ std::u32string suffix = match.suffix.empty() ? U"" : U" " + std::u32string(match.suffix);
697
+ return amount(number, kPercent, reading.amount_case) + suffix;
313
698
  }
314
699
 
315
- std::u32string Num2Word::to_year(uint32_t year) const {
316
- // Polish reads years as plain cardinals (e.g. 2026 -> "dwa tysiące
317
- // dwadzieścia sześć"). The fully-inflected form ("dwa tysiące dwudziestego
318
- // szóstego roku") is reserved for formal contexts and not produced here.
319
- return to_cardinal_int(static_cast<int32_t>(year));
700
+ std::u32string Num2Word::read_unit(const Number& number, const Match& match, const NumberReading& reading) const {
701
+ return amount(number, kUnits.at(std::u32string(match.unit)), reading.amount_case);
320
702
  }
321
703
 
322
- bool Num2Word::is_ordinal_suffix(std::u32string_view suffix) const {
323
- return num2word::kOrdinalSuffixes.contains(std::u32string(suffix));
704
+ std::u32string Num2Word::read_joiner(const Match& match, const NumberReading& reading) const {
705
+ if (match.kind == Kind::DIMENSION) {
706
+ return reading.numbered ? U" przez " : U" na ";
707
+ }
708
+ return Num2WordLayer::read_joiner(match, reading);
324
709
  }
325
710
 
326
711
  } // namespace phonemis::pl