| /* §10.10 phase 2: merge convertible same-base unit buckets into the |
| first-encountered unit (px absorbs cm/in/pt/pc, deg absorbs |
| rad/grad/turn, …). Buckets with `base === null` (relative or unknown |
| units) keep their own slot. |
| */ |
| |
| import { convert } from '../convertUnits.js'; |
| |
| /** |
| * @typedef {object} UnitBucket |
| * @property {string} unit |
| * @property {number} total |
| * @property {number} scale largest |term| accumulated into `total`, for noise detection |
| * @property {import('../convertUnits.js').BaseType | null} base |
| * @property {number} order |
| */ |
| |
| /** Mutates `buckets` in place — totals of survivor buckets accumulate the |
| * converted values of merged neighbors. Caller must not reuse the input. |
| * @param {UnitBucket[]} buckets |
| * @return {UnitBucket[]} |
| */ |
| function mergeConvertibleBuckets(buckets) { |
| const ordered = buckets.sort((a, b) => a.order - b.order); |
| /** @type {Set<string>} */ const merged = new Set(); |
| /** @type {UnitBucket[]} */ const out = []; |
| for (const b of ordered) { |
| const keyB = b.unit.toLowerCase(); |
| if (merged.has(keyB)) { |
| continue; |
| } |
| merged.add(keyB); |
| if (b.base !== null) { |
| for (const other of ordered) { |
| const keyO = other.unit.toLowerCase(); |
| if (merged.has(keyO)) { |
| continue; |
| } |
| if (other.base !== b.base) { |
| continue; |
| } |
| const converted = convert(other.total, other.unit, b.unit); |
| if (converted !== null) { |
| b.total += converted; |
| merged.add(keyO); |
| } |
| } |
| } |
| out.push(b); |
| } |
| return out; |
| } |
| |
| export { mergeConvertibleBuckets }; |