siderust-js 0.1.0

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 (272) hide show
  1. package/.github/workflows/ci.yml +166 -0
  2. package/.gitmodules +9 -0
  3. package/CHANGELOG.md +26 -0
  4. package/LICENSE +661 -0
  5. package/README.md +138 -0
  6. package/package.json +12 -0
  7. package/qtty-js/.github/workflows/ci.yml +151 -0
  8. package/qtty-js/.gitmodules +3 -0
  9. package/qtty-js/CHANGELOG.md +31 -0
  10. package/qtty-js/LICENSE +661 -0
  11. package/qtty-js/README.md +132 -0
  12. package/qtty-js/package.json +20 -0
  13. package/qtty-js/qtty/.github/workflows/ci.yml +155 -0
  14. package/qtty-js/qtty/CHANGELOG.md +120 -0
  15. package/qtty-js/qtty/Cargo.lock +1462 -0
  16. package/qtty-js/qtty/Cargo.toml +12 -0
  17. package/qtty-js/qtty/LICENSE +661 -0
  18. package/qtty-js/qtty/README.md +9 -0
  19. package/qtty-js/qtty/qtty/Cargo.toml +41 -0
  20. package/qtty-js/qtty/qtty/README.md +8 -0
  21. package/qtty-js/qtty/qtty/examples/angles.rs +14 -0
  22. package/qtty-js/qtty/qtty/examples/astronomy.rs +17 -0
  23. package/qtty-js/qtty/qtty/examples/dimensional_arithmetic.rs +83 -0
  24. package/qtty-js/qtty/qtty/examples/python_integration.rs +61 -0
  25. package/qtty-js/qtty/qtty/examples/quickstart.rs +15 -0
  26. package/qtty-js/qtty/qtty/examples/ratios.rs +12 -0
  27. package/qtty-js/qtty/qtty/examples/serde_with_unit.rs +234 -0
  28. package/qtty-js/qtty/qtty/examples/serialization.rs +141 -0
  29. package/qtty-js/qtty/qtty/examples/serialization_advanced.rs +155 -0
  30. package/qtty-js/qtty/qtty/src/f32.rs +108 -0
  31. package/qtty-js/qtty/qtty/src/f64.rs +30 -0
  32. package/qtty-js/qtty/qtty/src/i128.rs +111 -0
  33. package/qtty-js/qtty/qtty/src/i16.rs +111 -0
  34. package/qtty-js/qtty/qtty/src/i32.rs +111 -0
  35. package/qtty-js/qtty/qtty/src/i64.rs +111 -0
  36. package/qtty-js/qtty/qtty/src/i8.rs +111 -0
  37. package/qtty-js/qtty/qtty/src/lib.rs +238 -0
  38. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.lock +83 -0
  39. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.toml +10 -0
  40. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/src/lib.rs +7 -0
  41. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.lock +83 -0
  42. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.toml +10 -0
  43. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/src/lib.rs +7 -0
  44. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.lock +83 -0
  45. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.toml +10 -0
  46. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/src/lib.rs +5 -0
  47. package/qtty-js/qtty/qtty/tests/integration_tests.rs +529 -0
  48. package/qtty-js/qtty/qtty/tests/qtty_vec_feature_matrix.rs +58 -0
  49. package/qtty-js/qtty/qtty-core/Cargo.toml +41 -0
  50. package/qtty-js/qtty/qtty-core/README.md +8 -0
  51. package/qtty-js/qtty/qtty-core/examples/diesel_integration.rs +145 -0
  52. package/qtty-js/qtty/qtty-core/examples/quantity_db_serde.rs +215 -0
  53. package/qtty-js/qtty/qtty-core/src/dimension.rs +249 -0
  54. package/qtty-js/qtty/qtty-core/src/feature_diesel.rs +318 -0
  55. package/qtty-js/qtty/qtty-core/src/feature_pyo3.rs +27 -0
  56. package/qtty-js/qtty/qtty-core/src/feature_serde.rs +203 -0
  57. package/qtty-js/qtty/qtty-core/src/feature_tiberius.rs +28 -0
  58. package/qtty-js/qtty/qtty-core/src/lib.rs +744 -0
  59. package/qtty-js/qtty/qtty-core/src/macros.rs +93 -0
  60. package/qtty-js/qtty/qtty-core/src/quantity.rs +810 -0
  61. package/qtty-js/qtty/qtty-core/src/scalar.rs +1742 -0
  62. package/qtty-js/qtty/qtty-core/src/unit.rs +332 -0
  63. package/qtty-js/qtty/qtty-core/src/units/angular.rs +1228 -0
  64. package/qtty-js/qtty/qtty-core/src/units/area.rs +243 -0
  65. package/qtty-js/qtty/qtty-core/src/units/frequency.rs +179 -0
  66. package/qtty-js/qtty/qtty-core/src/units/length.rs +1270 -0
  67. package/qtty-js/qtty/qtty-core/src/units/mass.rs +488 -0
  68. package/qtty-js/qtty/qtty-core/src/units/mod.rs +26 -0
  69. package/qtty-js/qtty/qtty-core/src/units/power.rs +324 -0
  70. package/qtty-js/qtty/qtty-core/src/units/time.rs +667 -0
  71. package/qtty-js/qtty/qtty-core/src/units/unitless.rs +212 -0
  72. package/qtty-js/qtty/qtty-core/src/units/velocity.rs +210 -0
  73. package/qtty-js/qtty/qtty-core/src/units/volume.rs +269 -0
  74. package/qtty-js/qtty/qtty-core/tests/core.rs +628 -0
  75. package/qtty-js/qtty/qtty-core/tests/diesel.rs +461 -0
  76. package/qtty-js/qtty/qtty-core/tests/integers.rs +632 -0
  77. package/qtty-js/qtty/qtty-core/tests/no_cross_unit_ops.rs +35 -0
  78. package/qtty-js/qtty/qtty-core/tests/pyo3.rs +334 -0
  79. package/qtty-js/qtty/qtty-core/tests/quantity_f32.rs +276 -0
  80. package/qtty-js/qtty/qtty-core/tests/scalar_decimal.rs +258 -0
  81. package/qtty-js/qtty/qtty-core/tests/scalar_f32.rs +286 -0
  82. package/qtty-js/qtty/qtty-core/tests/scalar_f64_real.rs +287 -0
  83. package/qtty-js/qtty/qtty-core/tests/scalar_rational.rs +260 -0
  84. package/qtty-js/qtty/qtty-core/tests/serde.rs +256 -0
  85. package/qtty-js/qtty/qtty-core/tests/tiberius.rs +208 -0
  86. package/qtty-js/qtty/qtty-derive/Cargo.toml +23 -0
  87. package/qtty-js/qtty/qtty-derive/README.md +8 -0
  88. package/qtty-js/qtty/qtty-derive/src/lib.rs +340 -0
  89. package/qtty-js/qtty/qtty-ffi/ARCHITECTURE.md +3 -0
  90. package/qtty-js/qtty/qtty-ffi/Cargo.toml +31 -0
  91. package/qtty-js/qtty/qtty-ffi/README.md +9 -0
  92. package/qtty-js/qtty/qtty-ffi/build.rs +326 -0
  93. package/qtty-js/qtty/qtty-ffi/cbindgen.toml +105 -0
  94. package/qtty-js/qtty/qtty-ffi/include/qtty_ffi.h +1126 -0
  95. package/qtty-js/qtty/qtty-ffi/src/ffi.rs +1251 -0
  96. package/qtty-js/qtty/qtty-ffi/src/ffi_serde.rs +294 -0
  97. package/qtty-js/qtty/qtty-ffi/src/helpers.rs +310 -0
  98. package/qtty-js/qtty/qtty-ffi/src/lib.rs +229 -0
  99. package/qtty-js/qtty/qtty-ffi/src/macros.rs +121 -0
  100. package/qtty-js/qtty/qtty-ffi/src/registry.rs +274 -0
  101. package/qtty-js/qtty/qtty-ffi/src/types.rs +620 -0
  102. package/qtty-js/qtty/qtty-ffi/tests/integration_tests.rs +842 -0
  103. package/qtty-js/qtty/qtty-ffi/units.csv +156 -0
  104. package/qtty-js/qtty/qtty-ffi/units.csv.md +3 -0
  105. package/qtty-js/qtty-node/.prettierignore +6 -0
  106. package/qtty-js/qtty-node/.prettierrc.json +6 -0
  107. package/qtty-js/qtty-node/README.md +250 -0
  108. package/qtty-js/qtty-node/c8.config.json +11 -0
  109. package/qtty-js/qtty-node/eslint.config.js +31 -0
  110. package/qtty-js/qtty-node/examples/arithmetic.mjs +64 -0
  111. package/qtty-js/qtty-node/examples/astronomy.mjs +90 -0
  112. package/qtty-js/qtty-node/examples/quickstart.mjs +36 -0
  113. package/qtty-js/qtty-node/examples/serialization.mjs +125 -0
  114. package/qtty-js/qtty-node/examples/unit_factories.mjs +74 -0
  115. package/qtty-js/qtty-node/index.d.ts +219 -0
  116. package/qtty-js/qtty-node/index.js +323 -0
  117. package/qtty-js/qtty-node/lib/DerivedQuantity.js +122 -0
  118. package/qtty-js/qtty-node/lib/Quantity.js +151 -0
  119. package/qtty-js/qtty-node/lib/backend.js +25 -0
  120. package/qtty-js/qtty-node/native.cjs +306 -0
  121. package/qtty-js/qtty-node/package-lock.json +3223 -0
  122. package/qtty-js/qtty-node/package.json +70 -0
  123. package/qtty-js/qtty-node/units.d.ts +299 -0
  124. package/qtty-js/qtty-node/units.js +210 -0
  125. package/qtty-js/qtty-web/Cargo.lock +767 -0
  126. package/qtty-js/qtty-web/Cargo.toml +21 -0
  127. package/qtty-js/qtty-web/index.d.ts +140 -0
  128. package/qtty-js/qtty-web/index.js +20 -0
  129. package/qtty-js/qtty-web/lib/DerivedQuantity.js +58 -0
  130. package/qtty-js/qtty-web/lib/Quantity.js +75 -0
  131. package/qtty-js/qtty-web/lib/backend.js +80 -0
  132. package/qtty-js/qtty-web/package.json +45 -0
  133. package/qtty-js/qtty-web/src/lib.rs +111 -0
  134. package/qtty-js/scripts/ci.sh +73 -0
  135. package/scripts/ci.sh +123 -0
  136. package/siderust-core/Cargo.lock +787 -0
  137. package/siderust-core/Cargo.toml +18 -0
  138. package/siderust-core/DEDUPLICATION.md +124 -0
  139. package/siderust-core/src/body.rs +120 -0
  140. package/siderust-core/src/events.rs +184 -0
  141. package/siderust-core/src/lib.rs +20 -0
  142. package/siderust-core/src/observer.rs +55 -0
  143. package/siderust-core/src/position.rs +213 -0
  144. package/siderust-node/.prettierignore +7 -0
  145. package/siderust-node/.prettierrc.json +6 -0
  146. package/siderust-node/Cargo.lock +906 -0
  147. package/siderust-node/Cargo.toml +29 -0
  148. package/siderust-node/README.md +109 -0
  149. package/siderust-node/__test__/index.test.mjs +248 -0
  150. package/siderust-node/build.rs +5 -0
  151. package/siderust-node/c8.config.json +3 -0
  152. package/siderust-node/eslint.config.js +31 -0
  153. package/siderust-node/examples/01_basic_coordinates.mjs +24 -0
  154. package/siderust-node/examples/02_coordinate_transformations.mjs +25 -0
  155. package/siderust-node/examples/03_all_frames_conversions.mjs +26 -0
  156. package/siderust-node/examples/04_all_center_conversions.mjs +24 -0
  157. package/siderust-node/examples/05_target_tracking.mjs +22 -0
  158. package/siderust-node/examples/06_night_events.mjs +18 -0
  159. package/siderust-node/examples/07_moon_properties.mjs +21 -0
  160. package/siderust-node/examples/08_solar_system.mjs +19 -0
  161. package/siderust-node/examples/09_star_observability.mjs +22 -0
  162. package/siderust-node/examples/10_time_periods.mjs +9 -0
  163. package/siderust-node/examples/11_serialization.mjs +31 -0
  164. package/siderust-node/examples/12_runtime_ephemeris.mjs +27 -0
  165. package/siderust-node/examples/13_coordinate_operations.mjs +20 -0
  166. package/siderust-node/index.d.ts +623 -0
  167. package/siderust-node/index.js +79 -0
  168. package/siderust-node/lib/Observer.js +112 -0
  169. package/siderust-node/lib/Star.js +118 -0
  170. package/siderust-node/lib/backend.js +63 -0
  171. package/siderust-node/lib/wrappers.js +566 -0
  172. package/siderust-node/main.js +20 -0
  173. package/siderust-node/native.cjs +360 -0
  174. package/siderust-node/package-lock.json +3261 -0
  175. package/siderust-node/package.json +71 -0
  176. package/siderust-node/src/body.rs +74 -0
  177. package/siderust-node/src/coordinates.rs +372 -0
  178. package/siderust-node/src/ephemeris.rs +462 -0
  179. package/siderust-node/src/events.rs +577 -0
  180. package/siderust-node/src/lib.rs +43 -0
  181. package/siderust-node/src/observer.rs +132 -0
  182. package/siderust-node/src/phase.rs +218 -0
  183. package/siderust-node/src/position.rs +292 -0
  184. package/siderust-node/src/star.rs +200 -0
  185. package/siderust-web/Cargo.lock +855 -0
  186. package/siderust-web/Cargo.toml +34 -0
  187. package/siderust-web/README.md +100 -0
  188. package/siderust-web/__test__/index.test.mjs +118 -0
  189. package/siderust-web/examples/github-pages/README.md +31 -0
  190. package/siderust-web/examples/github-pages/index.html +135 -0
  191. package/siderust-web/index.d.ts +311 -0
  192. package/siderust-web/index.js +66 -0
  193. package/siderust-web/lib/Observer.js +103 -0
  194. package/siderust-web/lib/Star.js +116 -0
  195. package/siderust-web/lib/backend.js +400 -0
  196. package/siderust-web/lib/wrappers.js +512 -0
  197. package/siderust-web/package.json +55 -0
  198. package/siderust-web/src/body.rs +69 -0
  199. package/siderust-web/src/coordinates.rs +302 -0
  200. package/siderust-web/src/ephemeris.rs +456 -0
  201. package/siderust-web/src/events.rs +520 -0
  202. package/siderust-web/src/lib.rs +51 -0
  203. package/siderust-web/src/observer.rs +117 -0
  204. package/siderust-web/src/phase.rs +190 -0
  205. package/siderust-web/src/position.rs +291 -0
  206. package/siderust-web/src/star.rs +178 -0
  207. package/tempoch-js/.github/workflows/ci.yml +142 -0
  208. package/tempoch-js/.gitmodules +3 -0
  209. package/tempoch-js/CHANGELOG.md +25 -0
  210. package/tempoch-js/LICENSE +661 -0
  211. package/tempoch-js/README.md +126 -0
  212. package/tempoch-js/package.json +20 -0
  213. package/tempoch-js/scripts/ci.sh +73 -0
  214. package/tempoch-js/tempoch/.github/workflows/ci.yml +113 -0
  215. package/tempoch-js/tempoch/CHANGELOG.md +82 -0
  216. package/tempoch-js/tempoch/Cargo.lock +947 -0
  217. package/tempoch-js/tempoch/Cargo.toml +3 -0
  218. package/tempoch-js/tempoch/LICENSE +661 -0
  219. package/tempoch-js/tempoch/README.md +76 -0
  220. package/tempoch-js/tempoch/tempoch/Cargo.toml +27 -0
  221. package/tempoch-js/tempoch/tempoch/examples/periods.rs +45 -0
  222. package/tempoch-js/tempoch/tempoch/examples/quickstart.rs +13 -0
  223. package/tempoch-js/tempoch/tempoch/src/lib.rs +49 -0
  224. package/tempoch-js/tempoch/tempoch/tests/integration.rs +57 -0
  225. package/tempoch-js/tempoch/tempoch-core/Cargo.toml +24 -0
  226. package/tempoch-js/tempoch/tempoch-core/src/delta_t.rs +345 -0
  227. package/tempoch-js/tempoch/tempoch-core/src/instant.rs +811 -0
  228. package/tempoch-js/tempoch/tempoch-core/src/julian_date_ext.rs +142 -0
  229. package/tempoch-js/tempoch/tempoch-core/src/lib.rs +81 -0
  230. package/tempoch-js/tempoch/tempoch-core/src/period.rs +1168 -0
  231. package/tempoch-js/tempoch/tempoch-core/src/scales.rs +779 -0
  232. package/tempoch-js/tempoch/tempoch-ffi/Cargo.lock +889 -0
  233. package/tempoch-js/tempoch/tempoch-ffi/Cargo.toml +26 -0
  234. package/tempoch-js/tempoch/tempoch-ffi/build.rs +24 -0
  235. package/tempoch-js/tempoch/tempoch-ffi/cbindgen.toml +30 -0
  236. package/tempoch-js/tempoch/tempoch-ffi/src/error.rs +19 -0
  237. package/tempoch-js/tempoch/tempoch-ffi/src/lib.rs +82 -0
  238. package/tempoch-js/tempoch/tempoch-ffi/src/period.rs +101 -0
  239. package/tempoch-js/tempoch/tempoch-ffi/src/time.rs +711 -0
  240. package/tempoch-js/tempoch/tempoch-ffi/tests/ffi.rs +265 -0
  241. package/tempoch-js/tempoch-node/.prettierignore +6 -0
  242. package/tempoch-js/tempoch-node/.prettierrc.json +6 -0
  243. package/tempoch-js/tempoch-node/Cargo.lock +496 -0
  244. package/tempoch-js/tempoch-node/Cargo.toml +29 -0
  245. package/tempoch-js/tempoch-node/README.md +265 -0
  246. package/tempoch-js/tempoch-node/__test__/index.test.mjs +598 -0
  247. package/tempoch-js/tempoch-node/build.rs +5 -0
  248. package/tempoch-js/tempoch-node/c8.config.json +3 -0
  249. package/tempoch-js/tempoch-node/eslint.config.js +31 -0
  250. package/tempoch-js/tempoch-node/examples/periods.mjs +79 -0
  251. package/tempoch-js/tempoch-node/examples/quickstart.mjs +71 -0
  252. package/tempoch-js/tempoch-node/examples/timescales.mjs +92 -0
  253. package/tempoch-js/tempoch-node/index.d.ts +280 -0
  254. package/tempoch-js/tempoch-node/index.js +32 -0
  255. package/tempoch-js/tempoch-node/lib/JulianDate.js +176 -0
  256. package/tempoch-js/tempoch-node/lib/ModifiedJulianDate.js +156 -0
  257. package/tempoch-js/tempoch-node/lib/Period.js +133 -0
  258. package/tempoch-js/tempoch-node/lib/backend.js +38 -0
  259. package/tempoch-js/tempoch-node/lib/qttyCompat.js +92 -0
  260. package/tempoch-js/tempoch-node/native.cjs +317 -0
  261. package/tempoch-js/tempoch-node/package-lock.json +3223 -0
  262. package/tempoch-js/tempoch-node/package.json +56 -0
  263. package/tempoch-js/tempoch-node/src/lib.rs +573 -0
  264. package/tempoch-js/tempoch-web/Cargo.toml +23 -0
  265. package/tempoch-js/tempoch-web/index.d.ts +95 -0
  266. package/tempoch-js/tempoch-web/index.js +27 -0
  267. package/tempoch-js/tempoch-web/lib/JulianDate.js +170 -0
  268. package/tempoch-js/tempoch-web/lib/ModifiedJulianDate.js +145 -0
  269. package/tempoch-js/tempoch-web/lib/Period.js +121 -0
  270. package/tempoch-js/tempoch-web/lib/backend.js +118 -0
  271. package/tempoch-js/tempoch-web/package.json +46 -0
  272. package/tempoch-js/tempoch-web/src/lib.rs +184 -0
@@ -0,0 +1,620 @@
1
+ //! ABI-stable FFI types for `qtty` quantities.
2
+ //!
3
+ //! This module defines the `#[repr(C)]` and `#[repr(u32)]` types that form the stable C ABI
4
+ //! for cross-language interoperability. These types are designed to be safe to pass across
5
+ //! FFI boundaries.
6
+ //!
7
+ //! # ABI Stability Contract
8
+ //!
9
+ //! The discriminant values for [`UnitId`] and [`DimensionId`] are part of the ABI contract
10
+ //! and **MUST NEVER CHANGE** once assigned. New variants may be added with new discriminant
11
+ //! values, but existing values must remain stable across all versions.
12
+
13
+ use core::ffi::c_char;
14
+ use serde::{Deserialize, Serialize};
15
+
16
+ // =============================================================================
17
+ // Status Codes
18
+ // =============================================================================
19
+
20
+ /// Success status code.
21
+ pub const QTTY_OK: i32 = 0;
22
+
23
+ /// Error: the provided unit ID is not recognized/valid.
24
+ pub const QTTY_ERR_UNKNOWN_UNIT: i32 = -1;
25
+
26
+ /// Error: conversion requested between incompatible dimensions.
27
+ pub const QTTY_ERR_INCOMPATIBLE_DIM: i32 = -2;
28
+
29
+ /// Error: a required output pointer was null.
30
+ pub const QTTY_ERR_NULL_OUT: i32 = -3;
31
+
32
+ /// Error: the provided value is invalid (reserved for future use).
33
+ pub const QTTY_ERR_INVALID_VALUE: i32 = -4;
34
+
35
+ /// Error: the provided output buffer is too small.
36
+ pub const QTTY_ERR_BUFFER_TOO_SMALL: i32 = -5;
37
+
38
+ // =============================================================================
39
+ // Format Flags (for qtty_quantity_format)
40
+ // =============================================================================
41
+
42
+ /// Format flag: default decimal notation (e.g. `"1234.57 m"`).
43
+ pub const QTTY_FMT_DEFAULT: u32 = 0;
44
+
45
+ /// Format flag: scientific notation with lowercase `e` (e.g. `"1.23e3 m"`).
46
+ pub const QTTY_FMT_LOWER_EXP: u32 = 1;
47
+
48
+ /// Format flag: scientific notation with uppercase `E` (e.g. `"1.23E3 m"`).
49
+ pub const QTTY_FMT_UPPER_EXP: u32 = 2;
50
+
51
+ // =============================================================================
52
+ // Dimension Identifiers
53
+ // =============================================================================
54
+
55
+ /// Dimension identifier for FFI.
56
+ ///
57
+ /// Represents the physical dimension of a quantity. All discriminant values are
58
+ /// explicitly assigned and are part of the ABI contract.
59
+ ///
60
+ /// # ABI Contract
61
+ ///
62
+ /// **Discriminant values must never change.** New dimensions may be added with
63
+ /// new explicit discriminant values.
64
+ #[repr(u32)]
65
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
66
+ pub enum DimensionId {
67
+ /// Length dimension (e.g., meters, kilometers).
68
+ Length = 1,
69
+ /// Time dimension (e.g., seconds, hours).
70
+ Time = 2,
71
+ /// Angle dimension (e.g., radians, degrees).
72
+ Angle = 3,
73
+ /// Mass dimension (e.g., grams, kilograms).
74
+ Mass = 4,
75
+ /// Power dimension (e.g., watts, kilowatts).
76
+ Power = 5,
77
+ }
78
+
79
+ // =============================================================================
80
+ // Unit Identifiers
81
+ // =============================================================================
82
+
83
+ // =============================================================================
84
+ // Unit Identifiers
85
+ // =============================================================================
86
+
87
+ // The UnitId enum is generated by build.rs from qtty-core unit definitions.
88
+ // All discriminant values are explicitly assigned and are part of the ABI contract.
89
+ // Discriminant encoding: DSSCC where D=dimension (1 digit), SS=system (2 digits), CC=counter (2 digits)
90
+ // Units are grouped by dimension with ranges:
91
+ // - Length units: 1xxxx (10000-19999), Time units: 2xxxx (20000-29999), Angle units: 3xxxx (30000-39999)
92
+ // - Mass units: 4xxxx (40000-49999), Power units: 5xxxx (50000-59999)
93
+ include!(concat!(env!("OUT_DIR"), "/unit_id_enum.rs"));
94
+
95
+ impl UnitId {
96
+ /// Returns the unit name as a static NUL-terminated C string.
97
+ ///
98
+ /// This is safe to call from C code and returns a pointer to static memory.
99
+ #[inline]
100
+ pub const fn name_cstr(&self) -> *const c_char {
101
+ include!(concat!(env!("OUT_DIR"), "/unit_names_cstr.rs"))
102
+ }
103
+
104
+ /// Returns the unit name as a Rust string slice.
105
+ #[inline]
106
+ pub const fn name(&self) -> &'static str {
107
+ include!(concat!(env!("OUT_DIR"), "/unit_names.rs"))
108
+ }
109
+
110
+ /// Returns the unit symbol as a Rust string slice (e.g., "m", "km", "s").
111
+ #[inline]
112
+ pub const fn symbol(&self) -> &'static str {
113
+ include!(concat!(env!("OUT_DIR"), "/unit_symbols.rs"))
114
+ }
115
+
116
+ /// Attempts to create a `UnitId` from a raw `u32` discriminant value.
117
+ ///
118
+ /// Returns `None` if the value does not correspond to a valid unit.
119
+ #[inline]
120
+ pub const fn from_u32(value: u32) -> Option<Self> {
121
+ include!(concat!(env!("OUT_DIR"), "/unit_from_u32.rs"))
122
+ }
123
+ }
124
+
125
+ // =============================================================================
126
+ // Quantity Carrier Type
127
+ // =============================================================================
128
+
129
+ /// A POD quantity carrier type suitable for FFI.
130
+ ///
131
+ /// This struct represents a physical quantity as a value paired with its unit.
132
+ /// It is `#[repr(C)]` to ensure a stable, predictable memory layout across
133
+ /// language boundaries.
134
+ ///
135
+ /// # Memory Layout
136
+ ///
137
+ /// - `value`: 8 bytes (f64)
138
+ /// - `unit`: 4 bytes (u32 via UnitId)
139
+ /// - Padding: 4 bytes (for alignment)
140
+ /// - Total: 16 bytes on most platforms
141
+ ///
142
+ /// # Example
143
+ ///
144
+ /// ```rust
145
+ /// use qtty_ffi::{QttyQuantity, UnitId};
146
+ ///
147
+ /// let q = QttyQuantity {
148
+ /// value: 1000.0,
149
+ /// unit: UnitId::Meter,
150
+ /// };
151
+ /// ```
152
+ #[repr(C)]
153
+ #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
154
+ pub struct QttyQuantity {
155
+ /// The numeric value of the quantity.
156
+ pub value: f64,
157
+ /// The unit identifier for this quantity.
158
+ pub unit: UnitId,
159
+ }
160
+
161
+ impl QttyQuantity {
162
+ /// Creates a new quantity with the given value and unit.
163
+ #[inline]
164
+ pub const fn new(value: f64, unit: UnitId) -> Self {
165
+ Self { value, unit }
166
+ }
167
+
168
+ /// Checks if this quantity is compatible with another (same dimension).
169
+ ///
170
+ /// # Example
171
+ ///
172
+ /// ```rust
173
+ /// use qtty_ffi::{QttyQuantity, UnitId};
174
+ ///
175
+ /// let meters = QttyQuantity::new(100.0, UnitId::Meter);
176
+ /// let km = QttyQuantity::new(1.0, UnitId::Kilometer);
177
+ /// let seconds = QttyQuantity::new(60.0, UnitId::Second);
178
+ ///
179
+ /// assert!(meters.compatible(&km));
180
+ /// assert!(!meters.compatible(&seconds));
181
+ /// ```
182
+ #[inline]
183
+ pub fn compatible(&self, other: &Self) -> bool {
184
+ crate::registry::compatible(self.unit, other.unit)
185
+ }
186
+
187
+ /// Returns the dimension of this quantity.
188
+ #[inline]
189
+ pub fn dimension(&self) -> Option<DimensionId> {
190
+ crate::registry::dimension(self.unit)
191
+ }
192
+
193
+ /// Converts this quantity to a different unit.
194
+ ///
195
+ /// Returns `None` if the units are incompatible (different dimensions).
196
+ ///
197
+ /// # Example
198
+ ///
199
+ /// ```rust
200
+ /// use qtty_ffi::{QttyQuantity, UnitId};
201
+ ///
202
+ /// let meters = QttyQuantity::new(1000.0, UnitId::Meter);
203
+ /// let km = meters.convert_to(UnitId::Kilometer).unwrap();
204
+ /// assert!((km.value - 1.0).abs() < 1e-12);
205
+ /// assert_eq!(km.unit, UnitId::Kilometer);
206
+ /// ```
207
+ #[inline]
208
+ pub fn convert_to(&self, target: UnitId) -> Option<Self> {
209
+ crate::registry::convert_value(self.value, self.unit, target)
210
+ .ok()
211
+ .map(|v| Self::new(v, target))
212
+ }
213
+
214
+ /// Adds two quantities, returning result in the left operand's unit.
215
+ ///
216
+ /// Returns `None` if the quantities have different dimensions.
217
+ ///
218
+ /// # Example
219
+ ///
220
+ /// ```rust
221
+ /// use qtty_ffi::{QttyQuantity, UnitId};
222
+ ///
223
+ /// let a = QttyQuantity::new(1.0, UnitId::Kilometer);
224
+ /// let b = QttyQuantity::new(500.0, UnitId::Meter);
225
+ /// let sum = a.add(&b).unwrap();
226
+ /// assert!((sum.value - 1.5).abs() < 1e-12);
227
+ /// assert_eq!(sum.unit, UnitId::Kilometer);
228
+ /// ```
229
+ #[inline]
230
+ pub fn add(&self, other: &Self) -> Option<Self> {
231
+ let other_converted = other.convert_to(self.unit)?;
232
+ Some(Self::new(self.value + other_converted.value, self.unit))
233
+ }
234
+
235
+ /// Subtracts another quantity from this one, returning result in this quantity's unit.
236
+ ///
237
+ /// Returns `None` if the quantities have different dimensions.
238
+ ///
239
+ /// # Example
240
+ ///
241
+ /// ```rust
242
+ /// use qtty_ffi::{QttyQuantity, UnitId};
243
+ ///
244
+ /// let a = QttyQuantity::new(2.0, UnitId::Kilometer);
245
+ /// let b = QttyQuantity::new(500.0, UnitId::Meter);
246
+ /// let diff = a.sub(&b).unwrap();
247
+ /// assert!((diff.value - 1.5).abs() < 1e-12);
248
+ /// assert_eq!(diff.unit, UnitId::Kilometer);
249
+ /// ```
250
+ #[inline]
251
+ pub fn sub(&self, other: &Self) -> Option<Self> {
252
+ let other_converted = other.convert_to(self.unit)?;
253
+ Some(Self::new(self.value - other_converted.value, self.unit))
254
+ }
255
+
256
+ /// Multiplies the quantity by a scalar value.
257
+ ///
258
+ /// # Example
259
+ ///
260
+ /// ```rust
261
+ /// use qtty_ffi::{QttyQuantity, UnitId};
262
+ ///
263
+ /// let q = QttyQuantity::new(5.0, UnitId::Meter);
264
+ /// let result = q.mul_scalar(3.0);
265
+ /// assert!((result.value - 15.0).abs() < 1e-12);
266
+ /// ```
267
+ #[inline]
268
+ pub const fn mul_scalar(&self, scalar: f64) -> Self {
269
+ Self::new(self.value * scalar, self.unit)
270
+ }
271
+
272
+ /// Divides the quantity by a scalar value.
273
+ ///
274
+ /// # Example
275
+ ///
276
+ /// ```rust
277
+ /// use qtty_ffi::{QttyQuantity, UnitId};
278
+ ///
279
+ /// let q = QttyQuantity::new(15.0, UnitId::Meter);
280
+ /// let result = q.div_scalar(3.0);
281
+ /// assert!((result.value - 5.0).abs() < 1e-12);
282
+ /// ```
283
+ #[inline]
284
+ pub const fn div_scalar(&self, scalar: f64) -> Self {
285
+ Self::new(self.value / scalar, self.unit)
286
+ }
287
+
288
+ /// Negates the quantity value.
289
+ #[inline]
290
+ pub const fn neg(&self) -> Self {
291
+ Self::new(-self.value, self.unit)
292
+ }
293
+ }
294
+
295
+ impl Default for QttyQuantity {
296
+ fn default() -> Self {
297
+ Self {
298
+ value: 0.0,
299
+ unit: UnitId::Meter,
300
+ }
301
+ }
302
+ }
303
+
304
+ // =============================================================================
305
+ // QttyDerivedQuantity - Compound quantities like velocity (m/s)
306
+ // =============================================================================
307
+
308
+ /// A derived quantity representing a compound unit (numerator/denominator).
309
+ ///
310
+ /// This is useful for quantities like velocity (m/s), frequency (rad/s), etc.
311
+ ///
312
+ /// # ABI Stability
313
+ ///
314
+ /// This struct has `#[repr(C)]` layout:
315
+ /// - `value` at offset 0 (8 bytes)
316
+ /// - `numerator` at offset 8 (4 bytes)
317
+ /// - `denominator` at offset 12 (4 bytes)
318
+ /// - Total size: 16 bytes
319
+ ///
320
+ /// # Example
321
+ ///
322
+ /// ```rust
323
+ /// use qtty_ffi::{QttyDerivedQuantity, UnitId};
324
+ ///
325
+ /// // Create a velocity: 100 m/s
326
+ /// let velocity = QttyDerivedQuantity::new(100.0, UnitId::Meter, UnitId::Second);
327
+ /// assert_eq!(velocity.value, 100.0);
328
+ /// assert_eq!(velocity.numerator, UnitId::Meter);
329
+ /// assert_eq!(velocity.denominator, UnitId::Second);
330
+ /// ```
331
+ #[repr(C)]
332
+ #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
333
+ pub struct QttyDerivedQuantity {
334
+ /// The numeric value of the derived quantity.
335
+ pub value: f64,
336
+ /// The numerator unit identifier.
337
+ pub numerator: UnitId,
338
+ /// The denominator unit identifier.
339
+ pub denominator: UnitId,
340
+ }
341
+
342
+ impl QttyDerivedQuantity {
343
+ /// Creates a new derived quantity with the given value and units.
344
+ #[inline]
345
+ pub const fn new(value: f64, numerator: UnitId, denominator: UnitId) -> Self {
346
+ Self {
347
+ value,
348
+ numerator,
349
+ denominator,
350
+ }
351
+ }
352
+
353
+ /// Returns the symbol string for this derived quantity (e.g., "m/s").
354
+ #[inline]
355
+ pub fn symbol(&self) -> String {
356
+ format!("{}/{}", self.numerator.symbol(), self.denominator.symbol())
357
+ }
358
+
359
+ /// Converts this derived quantity to different units.
360
+ ///
361
+ /// Returns `None` if the numerator or denominator dimensions are incompatible.
362
+ ///
363
+ /// # Example
364
+ ///
365
+ /// ```rust
366
+ /// use qtty_ffi::{QttyDerivedQuantity, UnitId};
367
+ ///
368
+ /// // Convert 100 m/s to km/h
369
+ /// let velocity = QttyDerivedQuantity::new(100.0, UnitId::Meter, UnitId::Second);
370
+ /// let converted = velocity.convert_to(UnitId::Kilometer, UnitId::Hour).unwrap();
371
+ /// // 100 m/s = 360 km/h
372
+ /// assert!((converted.value - 360.0).abs() < 1e-9);
373
+ /// ```
374
+ pub fn convert_to(&self, target_num: UnitId, target_den: UnitId) -> Option<Self> {
375
+ // Check dimensional compatibility
376
+ let num_dim = crate::registry::dimension(self.numerator)?;
377
+ let den_dim = crate::registry::dimension(self.denominator)?;
378
+ let target_num_dim = crate::registry::dimension(target_num)?;
379
+ let target_den_dim = crate::registry::dimension(target_den)?;
380
+
381
+ if num_dim != target_num_dim || den_dim != target_den_dim {
382
+ return None;
383
+ }
384
+
385
+ // Convert numerator: e.g., 100 m -> ? km
386
+ let num_converted =
387
+ crate::registry::convert_value(self.value, self.numerator, target_num).ok()?;
388
+
389
+ // Convert denominator scale: e.g., 1 s -> ? h (0.000278 h)
390
+ // If 1 s = 0.000278 h, then dividing by that gives us the factor
391
+ let den_converted =
392
+ crate::registry::convert_value(1.0, self.denominator, target_den).ok()?;
393
+
394
+ // Result = (num in new units) / (den scale factor)
395
+ // 100 m = 0.1 km, 1 s = 1/3600 h
396
+ // 100 m/s = 0.1 km / (1/3600 h) = 0.1 * 3600 km/h = 360 km/h
397
+ let result_value = num_converted / den_converted;
398
+
399
+ Some(Self::new(result_value, target_num, target_den))
400
+ }
401
+
402
+ /// Multiplies the derived quantity by a scalar.
403
+ #[inline]
404
+ pub const fn mul_scalar(&self, scalar: f64) -> Self {
405
+ Self::new(self.value * scalar, self.numerator, self.denominator)
406
+ }
407
+
408
+ /// Divides the derived quantity by a scalar.
409
+ #[inline]
410
+ pub const fn div_scalar(&self, scalar: f64) -> Self {
411
+ Self::new(self.value / scalar, self.numerator, self.denominator)
412
+ }
413
+
414
+ /// Negates the derived quantity.
415
+ #[inline]
416
+ pub const fn neg(&self) -> Self {
417
+ Self::new(-self.value, self.numerator, self.denominator)
418
+ }
419
+ }
420
+
421
+ impl Default for QttyDerivedQuantity {
422
+ fn default() -> Self {
423
+ Self {
424
+ value: 0.0,
425
+ numerator: UnitId::Meter,
426
+ denominator: UnitId::Second,
427
+ }
428
+ }
429
+ }
430
+
431
+ #[cfg(test)]
432
+ mod tests {
433
+ use super::*;
434
+
435
+ #[test]
436
+ fn unit_id_discriminants_are_stable() {
437
+ // These values are part of the ABI contract and must never change
438
+ assert_eq!(UnitId::Meter as u32, 10011); // Length, SI, #11
439
+ assert_eq!(UnitId::Kilometer as u32, 10014); // Length, SI, #14
440
+ assert_eq!(UnitId::Second as u32, 20008); // Time, SI, #8
441
+ assert_eq!(UnitId::Minute as u32, 21000); // Time, Common, #0
442
+ assert_eq!(UnitId::Hour as u32, 21001); // Time, Common, #1
443
+ assert_eq!(UnitId::Day as u32, 21002); // Time, Common, #2
444
+ assert_eq!(UnitId::Radian as u32, 30001); // Angle, Radian, #1
445
+ assert_eq!(UnitId::Degree as u32, 31004); // Angle, Degree, #4
446
+ }
447
+
448
+ #[test]
449
+ fn dimension_id_discriminants_are_stable() {
450
+ // These values are part of the ABI contract and must never change
451
+ assert_eq!(DimensionId::Length as u32, 1);
452
+ assert_eq!(DimensionId::Time as u32, 2);
453
+ assert_eq!(DimensionId::Angle as u32, 3);
454
+ }
455
+
456
+ #[test]
457
+ fn unit_id_from_u32_roundtrips() {
458
+ for unit in [
459
+ UnitId::Meter,
460
+ UnitId::Kilometer,
461
+ UnitId::Second,
462
+ UnitId::Minute,
463
+ UnitId::Hour,
464
+ UnitId::Day,
465
+ UnitId::Radian,
466
+ UnitId::Degree,
467
+ ] {
468
+ let value = unit as u32;
469
+ assert_eq!(UnitId::from_u32(value), Some(unit));
470
+ }
471
+ }
472
+
473
+ #[test]
474
+ fn unit_id_from_u32_rejects_invalid() {
475
+ assert_eq!(UnitId::from_u32(0), None);
476
+ assert_eq!(UnitId::from_u32(9999), None);
477
+ assert_eq!(UnitId::from_u32(60000), None);
478
+ assert_eq!(UnitId::from_u32(99999), None);
479
+ }
480
+
481
+ #[test]
482
+ fn unit_names_are_not_empty() {
483
+ for unit in [
484
+ UnitId::Meter,
485
+ UnitId::Kilometer,
486
+ UnitId::Second,
487
+ UnitId::Minute,
488
+ UnitId::Hour,
489
+ UnitId::Day,
490
+ UnitId::Radian,
491
+ UnitId::Degree,
492
+ ] {
493
+ assert!(!unit.name().is_empty());
494
+ }
495
+ }
496
+
497
+ // ─── QttyQuantity method coverage ────────────────────────────────────────
498
+
499
+ #[test]
500
+ fn qtty_quantity_default() {
501
+ let q = QttyQuantity::default();
502
+ assert_eq!(q.value, 0.0);
503
+ assert_eq!(q.unit, UnitId::Meter);
504
+ }
505
+
506
+ #[test]
507
+ fn qtty_quantity_neg() {
508
+ let q = QttyQuantity::new(5.0, UnitId::Meter);
509
+ let n = q.neg();
510
+ assert_eq!(n.value, -5.0);
511
+ assert_eq!(n.unit, UnitId::Meter);
512
+ }
513
+
514
+ #[test]
515
+ fn qtty_quantity_mul_scalar() {
516
+ let q = QttyQuantity::new(4.0, UnitId::Kilometer);
517
+ let r = q.mul_scalar(2.5);
518
+ assert_eq!(r.value, 10.0);
519
+ assert_eq!(r.unit, UnitId::Kilometer);
520
+ }
521
+
522
+ #[test]
523
+ fn qtty_quantity_div_scalar() {
524
+ let q = QttyQuantity::new(15.0, UnitId::Second);
525
+ let r = q.div_scalar(3.0);
526
+ assert_eq!(r.value, 5.0);
527
+ assert_eq!(r.unit, UnitId::Second);
528
+ }
529
+
530
+ #[test]
531
+ fn qtty_quantity_dimension() {
532
+ let q = QttyQuantity::new(1.0, UnitId::Meter);
533
+ assert_eq!(q.dimension(), Some(DimensionId::Length));
534
+
535
+ let t = QttyQuantity::new(1.0, UnitId::Second);
536
+ assert_eq!(t.dimension(), Some(DimensionId::Time));
537
+ }
538
+
539
+ #[test]
540
+ fn qtty_quantity_compatible() {
541
+ let a = QttyQuantity::new(1.0, UnitId::Meter);
542
+ let b = QttyQuantity::new(1.0, UnitId::Kilometer);
543
+ let c = QttyQuantity::new(1.0, UnitId::Second);
544
+
545
+ assert!(a.compatible(&b));
546
+ assert!(!a.compatible(&c));
547
+ }
548
+
549
+ #[test]
550
+ fn qtty_quantity_add_incompatible_returns_none() {
551
+ let a = QttyQuantity::new(1.0, UnitId::Meter);
552
+ let b = QttyQuantity::new(1.0, UnitId::Second);
553
+ assert!(a.add(&b).is_none());
554
+ }
555
+
556
+ #[test]
557
+ fn qtty_quantity_sub_incompatible_returns_none() {
558
+ let a = QttyQuantity::new(1.0, UnitId::Meter);
559
+ let b = QttyQuantity::new(1.0, UnitId::Second);
560
+ assert!(a.sub(&b).is_none());
561
+ }
562
+
563
+ // ─── QttyDerivedQuantity method coverage ─────────────────────────────────
564
+
565
+ #[test]
566
+ fn qtty_derived_quantity_default() {
567
+ let d = QttyDerivedQuantity::default();
568
+ assert_eq!(d.value, 0.0);
569
+ assert_eq!(d.numerator, UnitId::Meter);
570
+ assert_eq!(d.denominator, UnitId::Second);
571
+ }
572
+
573
+ #[test]
574
+ fn qtty_derived_quantity_symbol() {
575
+ let d = QttyDerivedQuantity::new(1.0, UnitId::Meter, UnitId::Second);
576
+ let sym = d.symbol();
577
+ // format is "numerator_symbol/denominator_symbol"
578
+ assert!(sym.contains('/'), "symbol should contain '/'");
579
+ assert!(!sym.is_empty());
580
+ // Should look like "m/s"
581
+ assert_eq!(
582
+ sym,
583
+ format!("{}/{}", UnitId::Meter.symbol(), UnitId::Second.symbol())
584
+ );
585
+ }
586
+
587
+ #[test]
588
+ fn qtty_derived_quantity_mul_scalar() {
589
+ let d = QttyDerivedQuantity::new(10.0, UnitId::Meter, UnitId::Second);
590
+ let r = d.mul_scalar(3.0);
591
+ assert_eq!(r.value, 30.0);
592
+ assert_eq!(r.numerator, UnitId::Meter);
593
+ assert_eq!(r.denominator, UnitId::Second);
594
+ }
595
+
596
+ #[test]
597
+ fn qtty_derived_quantity_div_scalar() {
598
+ let d = QttyDerivedQuantity::new(30.0, UnitId::Kilometer, UnitId::Hour);
599
+ let r = d.div_scalar(2.0);
600
+ assert_eq!(r.value, 15.0);
601
+ assert_eq!(r.numerator, UnitId::Kilometer);
602
+ assert_eq!(r.denominator, UnitId::Hour);
603
+ }
604
+
605
+ #[test]
606
+ fn qtty_derived_quantity_neg() {
607
+ let d = QttyDerivedQuantity::new(5.0, UnitId::Meter, UnitId::Second);
608
+ let n = d.neg();
609
+ assert_eq!(n.value, -5.0);
610
+ assert_eq!(n.numerator, UnitId::Meter);
611
+ assert_eq!(n.denominator, UnitId::Second);
612
+ }
613
+
614
+ #[test]
615
+ fn qtty_derived_quantity_convert_to_incompatible_returns_none() {
616
+ // m/s → kg/h: numerator dimension mismatch
617
+ let d = QttyDerivedQuantity::new(1.0, UnitId::Meter, UnitId::Second);
618
+ assert!(d.convert_to(UnitId::Kilogram, UnitId::Hour).is_none());
619
+ }
620
+ }