@sc-voice/tools 3.33.0 → 3.35.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.
package/index.mjs CHANGED
@@ -1,16 +1,20 @@
1
1
  import { Assert } from './src/js/assert.mjs';
2
- export const JS = {
3
- Assert,
2
+ import UUIDV7 from './src/js/uuidv7.mjs';
3
+ export const JS = { Assert, UUIDV7,
4
4
  }
5
5
 
6
6
  import { Activation } from './src/math/activation.mjs';
7
7
  import { Fraction } from './src/math/fraction.mjs';
8
+ import { Units } from './src/math/units.mjs';
8
9
  import { Interval } from './src/math/interval.mjs';
10
+ import { Hadamard } from './src/math/hadamard.mjs';
9
11
 
10
12
  export const ScvMath = {
11
13
  Activation,
12
14
  Fraction,
13
- Interval,
15
+ Hadamard,
16
+ Interval,
17
+ Units,
14
18
  };
15
19
 
16
20
  import { BilaraPath } from './src/text/bilara-path.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sc-voice/tools",
3
- "version": "3.33.0",
3
+ "version": "3.35.0",
4
4
  "description": "Utilities for SC-Voice",
5
5
  "main": "index.mjs",
6
6
  "files": [
@@ -11,29 +11,33 @@
11
11
  "src"
12
12
  ],
13
13
  "scripts": {
14
- "test": "mocha --inline-diffs --recursive",
15
- "test:test": "mocha --config test/mocha-config.json --recursive"
14
+ "test": "vitest run --config test/vitest.config.mjs",
15
+ "test:test": "vitest --config test/vitest.config.mjs"
16
16
  },
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "git+https://github.com/sc-voice/tools.git"
19
+ "url": "git+https://github.com/sc-voice/nx-scv.git"
20
20
  },
21
21
  "keywords": [
22
- "SC-Voice tools",
23
- "merkle-json",
22
+ "SC-Voice",
23
+ "nx-scv",
24
+ "Tools",
24
25
  "Javascript"
25
26
  ],
26
27
  "author": "Karl Lew",
27
28
  "license": "MIT",
28
29
  "bugs": {
29
- "url": "https://github.com/sc-voice/tools/issues"
30
+ "url": "https://github.com/sc-voice/nx-scv/issues"
30
31
  },
31
- "homepage": "https://github.com/sc-voice/tools#readme",
32
+ "homepage": "https://github.com/sc-voice/nx-scv/pkg/tools/#readme",
32
33
  "devDependencies": {
33
34
  "@biomejs/biome": "1.9.4",
35
+ "avro-js": "^1.12.0",
34
36
  "deepl-node": "^1.15.0",
35
37
  "eslint": "^9.17.0",
36
- "mocha": "^11.0.1",
37
- "should": "^13.2.3"
38
+ "@sc-voice/vitest": "^4.0.0"
39
+ },
40
+ "dependencies": {
41
+ "uuid": "^11.1.0"
38
42
  }
39
43
  }
package/src/defines.mjs CHANGED
@@ -1,20 +1,49 @@
1
+ const COLOR_CONSOLE = {
2
+ AS_STRING: 0,
3
+ INSPECT: 0,
4
+ SRC: 0,
5
+ TEST: 0,
6
+ PROPS_NEXT: 0,
7
+ };
8
+
9
+ const FRACTION = {
10
+ REDUCE: 0,
11
+ TEST: 0,
12
+ };
13
+
1
14
  export const DBG = {
15
+ COLOR_CONSOLE,
16
+ FRACTION,
17
+
2
18
  ALIGN_ALL: 0,
3
19
  ALIGN_LINE: 0,
4
- L2T_TO_STRING: 0,
5
- COLOR_CONSOLE: 0,
6
- C10E_INSPECT: 0,
7
- I6L_CONTAINS: 0,
8
- I6L_OVERLAPS: 0,
9
- ML_DOC_VECTORS: 0, // 'mn8:3.4',
10
- MN8_MOHAN: 0,
11
20
  DEEPL_ADAPTER: 0,
12
21
  DEEPL_MOCK: 0, // use mock-deepl
13
22
  DEEPL_MOCK_XLT: 0, // use mock translation
14
23
  DEEPL_TEST_API: 0, // test with live DeepL API ($$$)
15
24
  DEEPL_XLT: 0, // test live translation
25
+ I6L_CONTAINS: 0,
26
+ I6L_OVERLAPS: 0,
27
+ L2T_TO_STRING: 0,
16
28
  L7C_FETCH_LEGACY: 0,
17
29
  L7C_FETCH_LEGACY_SC: 0, // ignore test cache and use SC
30
+ ML_DOC_VECTORS: 0, // 'mn8:3.4'
31
+ MN8_MOHAN: 0,
32
+ M2H: {
33
+ H6D: {
34
+ FWHT: 0,
35
+ ENCODE: 0,
36
+ DECODE: 0,
37
+ },
38
+ },
18
39
  W7E_BOW_OF_TEXT: 0,
19
40
  WORD_MAP_TRANFORMER: 0,
41
+ S5H: {
42
+ U3S: {
43
+ CONVERT_FRACTION: 0,
44
+ },
45
+ },
46
+ T2T: {
47
+ HADAMARD: 0,
48
+ },
20
49
  };
@@ -0,0 +1,171 @@
1
+ import { randomBytes } from 'crypto';
2
+
3
+ // ============================================================================
4
+ // MonotonicityState - Ensures successive UUIDs are always strictly increasing
5
+ // ============================================================================
6
+
7
+ class MonotonicityState {
8
+ constructor() {
9
+ this.previousTimestamp = 0n;
10
+ this.sequence = 0;
11
+ this.offset = 0n;
12
+ }
13
+
14
+ nextMillisWithSequence(timeIntervalMs) {
15
+ // Convert to BigInt if needed
16
+ const currentTime = BigInt(Math.floor(timeIntervalMs));
17
+ let currentMillis = currentTime + this.offset;
18
+
19
+ if (this.previousTimestamp === currentMillis) {
20
+ // Same millisecond: increment sequence counter
21
+ this.sequence += 1; // Don't mask yet - check for overflow
22
+ } else if (currentMillis < this.previousTimestamp) {
23
+ // Clock went backward: increment sequence and adjust offset
24
+ this.sequence += 1;
25
+ this.offset = this.previousTimestamp - currentMillis;
26
+ currentMillis = this.previousTimestamp;
27
+ } else {
28
+ // Time advanced: reset sequence
29
+ this.offset = 0n;
30
+ this.sequence = 0;
31
+ }
32
+
33
+ // If sequence overflows 12 bits, increment time and reset sequence
34
+ if (this.sequence > 0xFFF) {
35
+ this.sequence = 0;
36
+ currentMillis += 1n;
37
+ }
38
+
39
+ // Now apply 12-bit mask for the returned value
40
+ const maskedSequence = this.sequence & 0xFFF;
41
+
42
+ this.previousTimestamp = currentMillis;
43
+ return { millis: currentMillis, sequence: maskedSequence };
44
+ }
45
+ }
46
+
47
+ // Global monotonicity state (thread-safe via single JS event loop)
48
+ const monotonicityState = new MonotonicityState();
49
+
50
+ // ============================================================================
51
+ // UUIDV7 Class
52
+ // ============================================================================
53
+
54
+ class UUIDV7 {
55
+ /**
56
+ * Creates a monotonically increasing UUIDV7.
57
+ * Two successive calls will always produce different, strictly increasing UUIDs.
58
+ *
59
+ * @returns {string} UUID in format: 019cc8b2-e91e-7000-8669-15ac4a704757
60
+ */
61
+ static create() {
62
+ const now = Date.now(); // milliseconds since epoch
63
+ const { millis, sequence } = monotonicityState.nextMillisWithSequence(now);
64
+
65
+ // Generate random bytes for the UUID
66
+ const randomBuf = randomBytes(16); // Get 16 random bytes
67
+
68
+ // Build UUID bytes (16 bytes total)
69
+ const uuid = Buffer.alloc(16);
70
+
71
+ // Bytes 0-5: timestamp in milliseconds (48 bits)
72
+ // Convert BigInt to bytes in big-endian
73
+ const millisBigInt = millis;
74
+ uuid[0] = Number((millisBigInt >> 40n) & 0xFFn);
75
+ uuid[1] = Number((millisBigInt >> 32n) & 0xFFn);
76
+ uuid[2] = Number((millisBigInt >> 24n) & 0xFFn);
77
+ uuid[3] = Number((millisBigInt >> 16n) & 0xFFn);
78
+ uuid[4] = Number((millisBigInt >> 8n) & 0xFFn);
79
+ uuid[5] = Number(millisBigInt & 0xFFn);
80
+
81
+ // Bytes 6-8: 12-bit sequence counter + version + variant
82
+ // Place 12-bit sequence as UInt16 big-endian in bytes 6-7, then apply version/variant masks
83
+ // Sequence is 12 bits: bit11-bit0, represented as 0x0000-0x0FFF
84
+ // When converted to big-endian bytes:
85
+ // bytes[6] = (sequence >> 8) & 0xFF (contains bits 11-8 in lower nibble, upper bits are 0)
86
+ // bytes[7] = sequence & 0xFF (contains bits 7-0)
87
+ uuid[6] = ((sequence >> 8) & 0x0F) | 0x70; // Upper 4 bits of sequence + version 0x7
88
+ uuid[7] = sequence & 0xFF; // Lower 8 bits of sequence
89
+
90
+ // Bytes 9-15: random data
91
+ randomBuf.copy(uuid, 9, 0, 7); // Copy 7 bytes from randomBuf[0:7] to uuid[9:16]
92
+
93
+ // Byte 8: variant (2 bits) + remaining random bits
94
+ uuid[8] = (randomBuf[7] & 0x3F) | 0x80; // Keep lower 6 bits of random + variant 0b10
95
+
96
+ return bytesToUuidString(uuid);
97
+ }
98
+
99
+ /**
100
+ * Compare two UUIDs lexicographically.
101
+ * Returns: -1 if a < b, 0 if a === b, 1 if a > b
102
+ *
103
+ * @param {string} a - UUID
104
+ * @param {string} b - UUID
105
+ * @returns {number} -1, 0, or 1
106
+ */
107
+ static compare(a, b) {
108
+ const aBytes = typeof a === 'string' ? uuidStringToBytes(a) : a;
109
+ const bBytes = typeof b === 'string' ? uuidStringToBytes(b) : b;
110
+
111
+ for (let i = 0; i < 16; i++) {
112
+ if (aBytes[i] < bBytes[i]) return -1;
113
+ if (aBytes[i] > bBytes[i]) return 1;
114
+ }
115
+ return 0;
116
+ }
117
+
118
+ /**
119
+ * Check if UUID a < UUID b
120
+ *
121
+ * @param {string} a - UUID
122
+ * @param {string} b - UUID
123
+ * @returns {boolean}
124
+ */
125
+ static isLessThan(a, b) {
126
+ return UUIDV7.compare(a, b) < 0;
127
+ }
128
+
129
+ /**
130
+ * Check if UUID a > UUID b
131
+ *
132
+ * @param {string} a - UUID
133
+ * @param {string} b - UUID
134
+ * @returns {boolean}
135
+ */
136
+ static isGreaterThan(a, b) {
137
+ return UUIDV7.compare(a, b) > 0;
138
+ }
139
+ }
140
+
141
+ export default UUIDV7;
142
+
143
+ // ============================================================================
144
+ // UUID String Formatting
145
+ // ============================================================================
146
+
147
+ /**
148
+ * Convert UUID bytes to standard string format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
149
+ */
150
+ function bytesToUuidString(bytes) {
151
+ const hex = bytes.toString('hex');
152
+ return [
153
+ hex.slice(0, 8),
154
+ hex.slice(8, 12),
155
+ hex.slice(12, 16),
156
+ hex.slice(16, 20),
157
+ hex.slice(20),
158
+ ].join('-');
159
+ }
160
+
161
+ /**
162
+ * Convert UUID string to bytes.
163
+ *
164
+ * @param {string} uuidString - UUID in format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
165
+ * @returns {Buffer} 16 bytes
166
+ */
167
+ function uuidStringToBytes(uuidString) {
168
+ const hex = uuidString.replace(/-/g, '');
169
+ return Buffer.from(hex, 'hex');
170
+ }
171
+
@@ -59,8 +59,7 @@ export class Activation {
59
59
  }
60
60
 
61
61
  static createRareN(a = 100, b = 1) {
62
- let fEval = (x, a) =>
63
- x < 1 ? 1 : 1 - Math.exp(((x - a) / x) * b);
62
+ let fEval = (x, a) => (x < 1 ? 1 : 1 - Math.exp(((x - a) / x) * b));
64
63
  let dEval = (x, a) =>
65
64
  x < 1 ? 0 : -(a * b * fEval(x, a, b)) / (x * x);
66
65
  return new Activation({ a, b, fEval, dEval });
@@ -1,17 +1,41 @@
1
+ import { DBG } from '../defines.mjs';
2
+ import { ColorConsole } from '../text/color-console.mjs';
3
+ import { Unicode } from '../text/unicode.mjs';
4
+ const { CHECKMARK: UOK } = Unicode;
5
+ const { FRACTION: F6N } = DBG;
6
+ const { cc } = ColorConsole;
7
+
1
8
  export class Fraction {
9
+ #isNull;
2
10
  constructor(...args) {
3
- //constructor(numerator, denominator = 1, units = undefined) {
4
11
  const msg = 'Fraction.ctor:';
5
- if (args[0] instanceof Fraction) {
6
- let { numerator: n, denominator: d, units: u } = args[0];
7
- args = [n, d, u];
12
+ let cfg = args[0];
13
+ let numerator;
14
+ let denominator;
15
+ let units;
16
+ let isNull;
17
+
18
+ if (cfg && typeof cfg === 'object') {
19
+ let { isNull: i4l, numerator: n, denominator: d, units: u } = cfg;
20
+ isNull = i4l;
21
+ numerator = n;
22
+ denominator = d;
23
+ units = u;
24
+ } else {
25
+ let [n, d = 1, u = ''] = args;
26
+ isNull = false;
27
+ numerator = n;
28
+ denominator = d;
29
+ units = u;
8
30
  }
9
- let [numerator, denominator = 1, units = undefined] = args;
10
31
 
11
- Object.assign(this, {
12
- numerator,
13
- denominator,
14
- units,
32
+ this.put({ isNull, numerator, denominator, units });
33
+
34
+ Object.defineProperty(this, 'isNull', {
35
+ enumerable: true,
36
+ get() {
37
+ return this.#isNull;
38
+ },
15
39
  });
16
40
  }
17
41
 
@@ -22,6 +46,29 @@ export class Fraction {
22
46
  return Fraction.gcd(b, a % b);
23
47
  }
24
48
 
49
+ put(json = {}) {
50
+ let { isNull, numerator, denominator = 1, units = '' } = json;
51
+
52
+ if (isNull || numerator == null) {
53
+ this.#isNull = true;
54
+ numerator = 0;
55
+ denominator = 1;
56
+ } else {
57
+ this.#isNull = false;
58
+ }
59
+ Object.assign(this, { numerator, denominator, units });
60
+ }
61
+
62
+ patch(json = {}) {
63
+ let {
64
+ numerator = this.n,
65
+ denominator = this.d,
66
+ units = this.units,
67
+ } = json;
68
+
69
+ this.put({ numerator, denominator, units });
70
+ }
71
+
25
72
  get remainder() {
26
73
  let { n, d } = this;
27
74
 
@@ -54,8 +101,8 @@ export class Fraction {
54
101
  }
55
102
 
56
103
  get value() {
57
- let { numerator, denominator } = this;
58
- return numerator / denominator;
104
+ let { isNull, numerator, denominator } = this;
105
+ return isNull ? null : numerator / denominator;
59
106
  }
60
107
 
61
108
  increment(delta = 1) {
@@ -64,6 +111,8 @@ export class Fraction {
64
111
  }
65
112
 
66
113
  reduce() {
114
+ const msg = 'f6n.reduce';
115
+ const dbg = F6N.REDUCE;
67
116
  let { numerator: n, denominator: d, units } = this;
68
117
  if (Number.isInteger(n) && Number.isInteger(d)) {
69
118
  let g = Fraction.gcd(n, d);
@@ -71,22 +120,46 @@ export class Fraction {
71
120
  this.numerator /= g;
72
121
  this.denominator /= g;
73
122
  }
123
+ dbg && cc.ok1(msg + UOK, this.n, '/', this.d);
124
+ } else {
125
+ // Is this useful?
126
+ for (let i = 0; i < 20; i++) {
127
+ n *= 10;
128
+ d *= 10;
129
+ if (Number.isInteger(n) && Number.isInteger(d)) {
130
+ this.n = n;
131
+ this.d = d;
132
+ this.reduce();
133
+ dbg && cc.ok1(msg + UOK, n, '/', d);
134
+ return this;
135
+ }
136
+ }
137
+ throw new Error(
138
+ `${msg} Why are you reducing non-integers? ${n}/${d}`,
139
+ );
74
140
  }
75
141
  return this;
76
142
  }
77
143
 
78
- toString() {
79
- let { units, numerator: n, denominator: d, value } = this;
80
- if (n == null || d == null) {
81
- return `${n}/${d}`;
82
- }
83
- if (d < 0) {
84
- d = -d;
85
- n = -n;
144
+ toString(cfg = {}) {
145
+ let { isNull, units, numerator: n, denominator: d, value } = this;
146
+ let s;
147
+ if (isNull) {
148
+ s = '?';
149
+ } else {
150
+ let { asRange, fixed = 2 } = cfg;
151
+ if (asRange == null) {
152
+ let sFraction = `${n}/${d}`;
153
+ let sValue = value.toString();
154
+ let sFixed = value.toFixed(fixed);
155
+ s = sValue.length < sFixed.length ? sValue : sFixed;
156
+ s = s.length < sFraction.length ? s : sFraction;
157
+ } else {
158
+ let sRange = `${n}${asRange}${d}`;
159
+ s = sRange;
160
+ }
86
161
  }
87
- let s = d === 1 ? `${n}` : `${n}/${d}`;
88
-
89
- return units ? `${s} ${units}` : s;
162
+ return units ? `${s}${units}` : s;
90
163
  }
91
164
 
92
165
  add(f) {
@@ -94,9 +167,9 @@ export class Fraction {
94
167
  let { numerator: n1, denominator: d1, units: u1 } = this;
95
168
  let { numerator: n2, denominator: d2, units: u2 } = f;
96
169
  if (this.units !== f.units) {
97
- throw new Error(`${msg} units? ${u1}? ${u2}?`);
170
+ throw new Error(`${msg} units? "${u1}" vs. "${u2}"`);
98
171
  }
99
172
 
100
- return new Fraction(n1 * d2 + n2 * d1, d1 * d2).reduce();
173
+ return new this.constructor(n1 * d2 + n2 * d1, d1 * d2).reduce();
101
174
  }
102
175
  }
@@ -0,0 +1,81 @@
1
+ import { DBG } from '../defines.mjs';
2
+ import { ColorConsole } from '../text/color-console.mjs';
3
+ import { Unicode } from '../text/unicode.mjs';
4
+ const { CHECKMARK: UOK } = Unicode;
5
+ const { H6D } = DBG.M2H;
6
+ const { cc } = ColorConsole;
7
+
8
+ const SQRT2 = Math.sqrt(2);
9
+
10
+ export class Hadamard {
11
+ static #ctor = false;
12
+ constructor(cfg = {}) {
13
+ const msg = 'h6d.ctor:';
14
+ if (!Hadamard.#ctor) {
15
+ throw new Error(`${msg} try: encode(signal)`);
16
+ }
17
+
18
+ Object.assign(this, cfg);
19
+ }
20
+
21
+ static fwht(input) {
22
+ // Fast Walsh-Hadamard Transform
23
+ const msg = 'h6d.fwht';
24
+ const dbg = H6D.FWHT;
25
+ const length = input.length;
26
+ let h = 1;
27
+ let scale = 1;
28
+ let output = [...input];
29
+ dbg && cc.ok(msg, 'input:', ...input);
30
+ while (h < length) {
31
+ let hNext = h * 2;
32
+ for (let i = 0; i < length; i += hNext) {
33
+ for (let j = i; j < i + h; j++) {
34
+ let x = output[j];
35
+ let y = output[j + h];
36
+ output[j] = x + y;
37
+ output[j + h] = x - y;
38
+ dbg > 2 &&
39
+ cc.fyi(msg, { h, length, i, j, ih: i + h, jh: j + h });
40
+ }
41
+ }
42
+ dbg > 1 && cc.ok(msg, 'h:', h, 'output:', ...output);
43
+ output = output.map((v) => v / SQRT2);
44
+ h = hNext;
45
+ }
46
+ dbg && cc.ok1(msg + UOK, 'output:', ...output);
47
+
48
+ return output;
49
+ }
50
+
51
+ static encode(signal) {
52
+ const msg = 'h6d.encode';
53
+ const dbg = H6D.ENCODE;
54
+ let length = signal.length;
55
+ let n = Math.ceil(Math.log2(length));
56
+ let fullLength = Math.pow(2, n);
57
+ let zeros = new Array(fullLength - length).fill(0);
58
+ let input = [...signal, ...zeros];
59
+ let output = Hadamard.fwht(input);
60
+
61
+ let h6d;
62
+ try {
63
+ Hadamard.#ctor = true;
64
+ h6d = new Hadamard({ length, signal: output });
65
+ } finally {
66
+ Hadamard.#ctor = false;
67
+ }
68
+ dbg > 1 && cc.ok(msg, 'output:', ...output);
69
+ dbg && cc.ok1(msg, { length, n, fullLength });
70
+ return h6d;
71
+ }
72
+
73
+ decode() {
74
+ const msg = 'h6d.decode';
75
+ const dbg = H6D.DECODE;
76
+ let { length, signal } = this;
77
+ let output = Hadamard.fwht(signal).slice(0, length);
78
+ dbg && cc.ok1(msg + UOK, ...output);
79
+ return output;
80
+ }
81
+ }
@@ -170,18 +170,8 @@ export class Interval {
170
170
  overlaps(iv2) {
171
171
  const msg = 'i6l.overlaps';
172
172
  const dbg = DBG.I6L_OVERLAPS;
173
- let {
174
- lo: lo1,
175
- hi: hi1,
176
- leftOpen: lOpen1,
177
- rightOpen: rOpen1,
178
- } = this;
179
- let {
180
- lo: lo2,
181
- hi: hi2,
182
- leftOpen: lOpen2,
183
- rightOpen: rOpen2,
184
- } = iv2;
173
+ let { lo: lo1, hi: hi1, leftOpen: lOpen1, rightOpen: rOpen1 } = this;
174
+ let { lo: lo2, hi: hi2, leftOpen: lOpen2, rightOpen: rOpen2 } = iv2;
185
175
 
186
176
  //console.log(msg, {lo1, lo2, hi1, hi2});
187
177
  if (!lOpen1 && iv2.contains(lo1)) {
@@ -0,0 +1,230 @@
1
+ import { DBG } from '../../src/defines.mjs';
2
+ const { U3S } = DBG.S5H;
3
+ import { Unicode } from '../text/unicode.mjs';
4
+ import { Fraction } from './fraction.mjs';
5
+ const { CHECKMARK: UOK, RIGHT_ARROW: URA } = Unicode;
6
+ import { ColorConsole } from '../text/color-console.mjs';
7
+ const { cc } = ColorConsole;
8
+
9
+ const DEFAULT_LENGTH = {
10
+ in: {
11
+ aliases: ['inch'],
12
+ from: {
13
+ ft: [12, 0, 0, 1],
14
+ mm: [10, 0, 0, 254],
15
+ cm: [100, 0, 0, 254],
16
+ m: [100 * 100, 0, 0, 254],
17
+ },
18
+ },
19
+ ft: {
20
+ aliases: ['foot', 'feet'],
21
+ from: {
22
+ in: [1, 0, 0, 12],
23
+ mm: [100, 0, 0, 10 * 254 * 12],
24
+ cm: [100, 0, 0, 254 * 12],
25
+ m: [100 * 100, 0, 0, 254 * 12],
26
+ },
27
+ },
28
+ m: {
29
+ aliases: ['meter', 'metre'],
30
+ from: {
31
+ mm: [1, 0, 0, 1000],
32
+ cm: [1, 0, 0, 100],
33
+ ft: [12 * 254, 0, 0, 100 * 100],
34
+ in: [254, 0, 0, 100 * 100],
35
+ },
36
+ },
37
+ cm: {
38
+ aliases: ['centimeter', 'centimetre'],
39
+ from: {
40
+ in: [254, 0, 0, 100],
41
+ ft: [12 * 254, 0, 0, 100],
42
+ mm: [1, 0, 0, 10],
43
+ m: [1, 0, 0, 100],
44
+ },
45
+ },
46
+ mm: {
47
+ aliases: ['millimeter', 'millimetre'],
48
+ },
49
+ }; // DEFAULT_LENGTH
50
+
51
+ const DEFAULT_TEMPERATURE = {
52
+ F: {
53
+ aliases: ['Fahrenheit'],
54
+ from: {
55
+ C: [9, 160, 0, 5],
56
+ },
57
+ },
58
+ C: {
59
+ aliases: ['Centigrade'],
60
+ from: {
61
+ F: [5, -5 * 32, 0, 9],
62
+ },
63
+ },
64
+ };
65
+
66
+ const DEFAULT_TIME = {
67
+ ms: {
68
+ aliases: ['millisecond', 'milliseconds'],
69
+ from: {
70
+ s: [1000, 0, 0, 1],
71
+ min: [60 * 1000, 0, 0, 1],
72
+ h: [60 * 60 * 1000, 0, 0, 1],
73
+ d: [24 * 60 * 60 * 1000, 0, 0, 1],
74
+ },
75
+ },
76
+ s: {
77
+ aliases: ['seconds'],
78
+ from: {
79
+ ms: [1, 0, 0, 1000],
80
+ min: [60, 0, 0, 1],
81
+ h: [60 * 60, 0, 0, 1],
82
+ d: [24 * 60 * 60, 0, 0, 1],
83
+ },
84
+ },
85
+ min: {
86
+ aliases: ['minutes'],
87
+ from: {
88
+ ms: [1, 0, 0, 60 * 1000],
89
+ s: [1, 0, 0, 60],
90
+ h: [60, 0, 0, 1],
91
+ d: [24 * 60, 0, 0, 1],
92
+ },
93
+ },
94
+ h: {
95
+ aliases: ['hour', 'hours', 'hr'],
96
+ from: {
97
+ ms: [1, 0, 0, 60 * 60 * 1000],
98
+ s: [1, 0, 0, 60 * 60],
99
+ min: [1, 0, 0, 60],
100
+ d: [24, 0, 0, 1],
101
+ },
102
+ },
103
+ d: {
104
+ aliases: ['day', 'days'],
105
+ from: {
106
+ ms: [1, 0, 0, 24 * 60 * 60 * 1000],
107
+ s: [1, 0, 0, 24 * 60 * 60],
108
+ min: [1, 0, 0, 24 * 60],
109
+ h: [1, 0, 0, 24],
110
+ },
111
+ },
112
+ }; // DEFAULT_TIME
113
+
114
+ const G_OZ = new Fraction(10000000000, 352739619);
115
+
116
+ const DEFAULT_WEIGHT = {
117
+ mg: {
118
+ aliases: ['milligram', 'milligrams'],
119
+ from: {
120
+ kg: [1000 * 1000, 0, 0, 1],
121
+ g: [1000, 0, 0, 1],
122
+ oz: [G_OZ.n * 1000, 0, 0, G_OZ.d],
123
+ lb: [16 * G_OZ.n * 1000, 0, 0, G_OZ.d],
124
+ },
125
+ },
126
+ g: {
127
+ aliases: ['gram', 'grams'],
128
+ from: {
129
+ kg: [1000, 0, 0, 1],
130
+ mg: [1, 0, 0, 1000],
131
+ oz: [G_OZ.n, 0, 0, G_OZ.d],
132
+ lb: [16 * G_OZ.n, 0, 0, G_OZ.d],
133
+ },
134
+ },
135
+ kg: {
136
+ aliases: ['kilogram', 'kilograms'],
137
+ from: {
138
+ g: [1, 0, 0, 1000],
139
+ mg: [1, 0, 0, 1000 * 1000],
140
+ oz: [G_OZ.n, 0, 0, 1000 * G_OZ.d],
141
+ lb: [16 * G_OZ.n, 0, 0, 1000 * G_OZ.d],
142
+ },
143
+ },
144
+ oz: {
145
+ aliases: ['ounce', 'ounces'],
146
+ from: {
147
+ g: [G_OZ.d, 0, 0, G_OZ.n],
148
+ kg: [G_OZ.d * 1000, 0, 0, G_OZ.n],
149
+ lb: [16, 0, 0, 1],
150
+ },
151
+ },
152
+ lb: {
153
+ aliases: ['pound', 'pounds', 'lbs'],
154
+ from: {
155
+ oz: [1, 0, 0, 16],
156
+ g: [G_OZ.d, 0, 0, 16 * G_OZ.n],
157
+ kg: [1000 * G_OZ.d, 0, 0, 16 * G_OZ.n],
158
+ },
159
+ },
160
+ };
161
+
162
+ const DEFAULT_UNITS = Object.assign(
163
+ {},
164
+ DEFAULT_LENGTH,
165
+ DEFAULT_TEMPERATURE,
166
+ DEFAULT_TIME,
167
+ DEFAULT_WEIGHT,
168
+ );
169
+
170
+ export class Units {
171
+ #abbrMap;
172
+ #unitMap;
173
+ constructor(cfg = {}) {
174
+ let { unitMap = DEFAULT_UNITS } = cfg;
175
+ this.#unitMap = unitMap;
176
+ this.#abbrMap = Object.entries(unitMap).reduce((a, entry) => {
177
+ const [abbr, def] = entry;
178
+ const { aliases } = def;
179
+ a[abbr] = abbr;
180
+ aliases.forEach((n) => {
181
+ a[n] = abbr;
182
+ });
183
+ return a;
184
+ }, {});
185
+ }
186
+
187
+ abbreviation(unit) {
188
+ let d9t = this.#abbrMap[unit];
189
+ return d9t == null ? unit : d9t;
190
+ }
191
+
192
+ convertFraction(vSrc) {
193
+ const msg = 'u3s.convertFraction';
194
+ const dbg = U3S.CONVERT_FRACTION;
195
+ let { n, d, units: uSrc } = vSrc;
196
+ let srcAbbr = this.abbreviation(uSrc);
197
+ let convertTo = (uDst) => {
198
+ let dstAbbr = this.abbreviation(uDst);
199
+ let vDst;
200
+ if (dstAbbr === srcAbbr) {
201
+ vDst = new Fraction(n, d, uDst);
202
+ dbg && cc.ok1(msg + 1 + UOK, vSrc, URA, vDst);
203
+ } else {
204
+ let dstBase = this.#unitMap[dstAbbr];
205
+ let srcMatrix = dstBase?.from?.[srcAbbr];
206
+ if (srcMatrix) {
207
+ let nDst = srcMatrix[0] * n + srcMatrix[1] * d;
208
+ let dDst = srcMatrix[2] * n + srcMatrix[3] * d;
209
+ vDst = new Fraction(nDst, dDst, uDst).reduce();
210
+ dbg && cc.ok1(msg + 2 + UOK, vSrc, URA, vDst);
211
+ } // srcMatrix
212
+ } // dstAbbr !== srcAbbr
213
+ if (vDst == null) {
214
+ vDst = vSrc;
215
+ dbg && cc.ok1(msg + 3 + UOK, vSrc, URA, vDst);
216
+ }
217
+ return vDst;
218
+ };
219
+
220
+ return { to: convertTo };
221
+ } // convertFraction
222
+
223
+ convert(value) {
224
+ if (value instanceof Fraction) {
225
+ return this.convertFraction(value);
226
+ }
227
+
228
+ throw new Error(`value?${value}`);
229
+ } // convert
230
+ } // Units
@@ -6,9 +6,7 @@ export class BilaraPath {
6
6
  static htmlPath(mid) {
7
7
  let lang = 'pli';
8
8
  let auth = 'ms';
9
- return ['html', lang, `${auth}/sutta`, `${mid}_html.json`].join(
10
- '/',
11
- );
9
+ return ['html', lang, `${auth}/sutta`, `${mid}_html.json`].join('/');
12
10
  }
13
11
 
14
12
  static variantPath(mid) {
@@ -1,5 +1,9 @@
1
1
  import util from 'node:util';
2
+ import { DBG } from '../defines.mjs';
2
3
  import { Unicode } from './unicode.mjs';
4
+ const { COLOR_CONSOLE: C10E } = DBG;
5
+
6
+ const { RED_X: URX, CHECKMARK: UOK } = Unicode;
3
7
 
4
8
  const {
5
9
  BLACK,
@@ -23,6 +27,74 @@ const {
23
27
 
24
28
  let CC;
25
29
 
30
+ class Props {
31
+ constructor(obj) {
32
+ let entries = Object.entries(obj);
33
+
34
+ Object.assign(this, {
35
+ obj,
36
+ entries,
37
+ i: 0,
38
+ done: false,
39
+ value: undefined,
40
+ key: undefined,
41
+ emitKey: true,
42
+ });
43
+ }
44
+
45
+ [Symbol.iterator]() {
46
+ return this;
47
+ }
48
+
49
+ next() {
50
+ const msg = 'p3s.next';
51
+ const dbg = C10E.PROPS_NEXT;
52
+ let { entries, i, emitKey, done } = this;
53
+ let value;
54
+ let entry = entries[i];
55
+ if (i < entries.length) {
56
+ if (emitKey) {
57
+ value = entry[0] + ':';
58
+ } else {
59
+ value = entry[1];
60
+ dbg > 1 && CC.ok(msg, 'emitKey', value);
61
+ switch (typeof value) {
62
+ case 'object': {
63
+ if (value instanceof Array) {
64
+ dbg > 1 && CC.ok(msg, 'Array', value);
65
+ value = value === null ? null : JSON.stringify(value);
66
+ } else if (value && value.toString !== {}.toString) {
67
+ dbg > 1 && CC.ok(msg, 'toString', value);
68
+ value = value.toString();
69
+ } else {
70
+ dbg > 1 && CC.ok(msg, 'object', value);
71
+ value = value === null ? null : JSON.stringify(value);
72
+ }
73
+ break;
74
+ }
75
+ case 'function':
76
+ dbg > 1 && CC.ok(msg, 'function', value);
77
+ value = `[Function ${value.name}]`;
78
+ break;
79
+ default:
80
+ dbg > 1 && CC.ok(msg, 'default', value);
81
+ break;
82
+ }
83
+ }
84
+ if (!emitKey) {
85
+ this.i++;
86
+ }
87
+ this.emitKey = !this.emitKey;
88
+ } else {
89
+ done = true;
90
+ }
91
+ this.value = value;
92
+ dbg && CC.ok1(msg + UOK, { done, value });
93
+
94
+ return { done, value };
95
+ }
96
+ } // Props
97
+
26
98
  export class ColorConsole {
27
99
  constructor(opts = {}) {
28
100
  let {
@@ -58,6 +130,14 @@ export class ColorConsole {
58
130
  });
59
131
  }
60
132
 
133
+ static get URX() {
134
+ return URX;
135
+ }
136
+
137
+ static get UOK() {
138
+ return UOK;
139
+ }
140
+
61
141
  static get cc() {
62
142
  CC = CC || new ColorConsole();
63
143
  return CC;
@@ -102,6 +182,10 @@ export class ColorConsole {
102
182
  }
103
183
  }
104
184
 
185
+ props(obj) {
186
+ return new Props(obj);
187
+ }
188
+
105
189
  writeColor(color, rest) {
106
190
  let { styles, defaultOptions } = util?.inspect || {};
107
191
  if (styles) {
@@ -136,31 +220,60 @@ export class ColorConsole {
136
220
  tf = thing;
137
221
  }
138
222
 
139
- let v = this.valueOf(thing);
223
+ let v = this.asString(thing);
140
224
  let color = tf ? this.okColor2 : this.badColor2;
141
225
  return color + v;
142
226
  }
143
227
 
144
- valueOf(thing) {
145
- const msg = 'c10e.valueOf';
146
- let { precision } = this;
228
+ asString(thing) {
229
+ const msg = 'c10e.asString';
230
+ const dbg = C10E.AS_STRING;
231
+ let { okColor1, okColor2, precision } = this;
232
+ dbg > 2 && console.log(okColor2, msg, 'thing:', thing);
147
233
  switch (typeof thing) {
148
234
  case 'undefined':
149
235
  return 'undefined';
150
236
  case 'object': {
151
237
  if (thing == null) {
238
+ dbg > 1 && console.log(okColor2, msg, 'null');
152
239
  return '' + thing;
153
240
  }
154
241
  if (thing instanceof Date) {
155
242
  return this.dateFormat.format(thing);
156
243
  }
157
- if (
158
- thing.constructor !== Object &&
159
- typeof thing.toString === 'function'
160
- ) {
244
+ let ownToString =
245
+ typeof thing.toString === 'function' &&
246
+ thing.toString !== Array.prototype.toString &&
247
+ thing.toString !== Object.prototype.toString;
248
+ dbg > 1 && this.ok(okColor2, msg, 'ownToString:', ownToString);
249
+ if (ownToString) {
250
+ let s = thing.toString();
251
+ dbg > 1 && this.ok(okColor2, msg, 'ownToString1:', s);
161
252
  return thing.toString();
162
253
  }
163
- return thing;
254
+ if (thing instanceof Array) {
255
+ let s =
256
+ '[' +
257
+ thing.map((item) => this.asString(item)).join(', ') +
258
+ ']';
259
+ dbg > 1 && this.ok(okColor2, msg, 'array:', s);
260
+ return s;
261
+ }
262
+
263
+ // Generic Object
264
+ let sEntries = Object.entries(thing)
265
+ .map((kv) => kv[0] + ':' + this.asString(kv[1]))
266
+ .join(', ');
267
+ let cname = thing.constructor?.name;
268
+ if (cname === 'Object') {
269
+ cname = '';
270
+ } else if (cname === 'anonymous') {
271
+ cname = Unicode.INVERSE_BULLET;
272
+ }
273
+ let s = cname + '{' + sEntries + '}';
274
+ dbg > 1 &&
275
+ console.log(okColor2, msg, { ownToString }, 'object:', s);
276
+ return s;
164
277
  }
165
278
  case 'string':
166
279
  return thing;
@@ -171,10 +284,13 @@ export class ColorConsole {
171
284
  }
172
285
  return v;
173
286
  }
287
+ case 'function': {
288
+ return thing.name;
289
+ }
174
290
  default:
175
291
  return JSON.stringify(thing);
176
292
  }
177
- } // valueOf
293
+ } // asString
178
294
 
179
295
  color(textColor, ...things) {
180
296
  let { valueColor } = this;
@@ -184,20 +300,15 @@ export class ColorConsole {
184
300
  let eol;
185
301
  return things.reduce((a, thing) => {
186
302
  let newLabel = '';
187
- let v = this.valueOf(thing);
303
+ let v = this.asString(thing);
188
304
  let aLast = a.at(-1);
189
- if (
190
- typeof aLast === 'string' &&
191
- aLast.endsWith('\n' + endColor)
192
- ) {
305
+ if (typeof aLast === 'string' && aLast.endsWith('\n' + endColor)) {
193
306
  if (aLast === textColor + '\n' + endColor) {
194
307
  a.pop();
195
308
  } else {
196
309
  const iLast = aLast.lastIndexOf('\n');
197
310
  a.pop();
198
- a.push(
199
- aLast.substring(0, iLast) + aLast.substring(iLast + 1),
200
- );
311
+ a.push(aLast.substring(0, iLast) + aLast.substring(iLast + 1));
201
312
  }
202
313
  v = '\n' + v;
203
314
  }
package/src/text/list.mjs CHANGED
@@ -180,8 +180,7 @@ export class ListFactory {
180
180
  separator: rowSeparator,
181
181
  precision,
182
182
  });
183
- let newRow = (separator) =>
184
- this.createRow({ separator, precision });
183
+ let newRow = (separator) => this.createRow({ separator, precision });
185
184
  name = name || singleList.name;
186
185
  switch (order) {
187
186
  case 'col-major':
@@ -159,8 +159,7 @@ function rhex(n) {
159
159
  j = 0;
160
160
  for (; j < 4; j++)
161
161
  s +=
162
- hex_chr[(n >> (j * 8 + 4)) & 0x0f] +
163
- hex_chr[(n >> (j * 8)) & 0x0f];
162
+ hex_chr[(n >> (j * 8 + 4)) & 0x0f] + hex_chr[(n >> (j * 8)) & 0x0f];
164
163
  return s;
165
164
  }
166
165
 
@@ -237,9 +236,7 @@ export class MerkleJson {
237
236
  stringify(value) {
238
237
  if (value instanceof Array) {
239
238
  let body = value.reduce((a, v) => {
240
- return a
241
- ? `${a},${this.stringify(v)}`
242
- : `${this.stringify(v)}`;
239
+ return a ? `${a},${this.stringify(v)}` : `${this.stringify(v)}`;
243
240
  }, '');
244
241
  return `[${body}]`;
245
242
  } else if (value instanceof Date) {
@@ -124,9 +124,7 @@ export class SuttaCentralId {
124
124
  n0 = Number(c0dig);
125
125
  n1 = c0let.charCodeAt(0) - 'a'.charCodeAt(0) + 1;
126
126
  if (Number.isNaN(n0) || Number.isNaN(n1)) {
127
- throw new Error(
128
- `partNumber() cannot parse ${part} in ${id}`,
129
- );
127
+ throw new Error(`partNumber() cannot parse ${part} in ${id}`);
130
128
  }
131
129
  } else {
132
130
  n0 = Number(c0);
@@ -142,10 +140,7 @@ export class SuttaCentralId {
142
140
  static scidNumbersLow(id_or_path) {
143
141
  let scid = BilaraPath.pathParts(id_or_path).suid;
144
142
  let colonParts = scid.replace(/^[-a-z]*/, '').split(':');
145
- let dotParts = colonParts.reduce(
146
- (a, c) => a.concat(c.split('.')),
147
- [],
148
- );
143
+ let dotParts = colonParts.reduce((a, c) => a.concat(c.split('.')), []);
149
144
  let nums = dotParts.reduce((a, n) => {
150
145
  let lowPart = n.split('-')[0];
151
146
  return a.concat(SuttaCentralId.partNumber(lowPart, id_or_path));
@@ -156,15 +151,10 @@ export class SuttaCentralId {
156
151
  static scidNumbersHigh(id_or_path) {
157
152
  let scid = BilaraPath.pathParts(id_or_path).suid;
158
153
  let colonParts = scid.replace(/^[-a-z]*/, '').split(':');
159
- let dotParts = colonParts.reduce(
160
- (a, c) => a.concat(c.split('.')),
161
- [],
162
- );
154
+ let dotParts = colonParts.reduce((a, c) => a.concat(c.split('.')), []);
163
155
  let nums = dotParts.reduce((a, n) => {
164
156
  let highPart = n.split('-').pop();
165
- return a.concat(
166
- SuttaCentralId.partNumber(highPart, id_or_path),
167
- );
157
+ return a.concat(SuttaCentralId.partNumber(highPart, id_or_path));
168
158
  }, []);
169
159
  return nums;
170
160
  }
@@ -1,7 +1,7 @@
1
1
  import { DBG } from '../defines.mjs';
2
+ import { ColorConsole } from './color-console.mjs';
2
3
  import { Corpus } from './corpus.mjs';
3
4
  import { WordVector } from './word-vector.mjs';
4
- import { ColorConsole } from './color-console.mjs';
5
5
  const { cc } = ColorConsole;
6
6
 
7
7
  // The golden ratio is pretty.
@@ -52,17 +52,17 @@ export class TfidfSpace {
52
52
 
53
53
  // Create wordWeight function that weighs the first words
54
54
  // of a document more than the remainder
55
- static wordWeightFromPrefix(prefixLength, prefixBias=0.5) {
55
+ static wordWeightFromPrefix(prefixLength, prefixBias = 0.5) {
56
56
  const msg = 't8e.wordWeightFromPrefix';
57
57
 
58
- let wordWeight = (w,i,nWords) => {
58
+ let wordWeight = (w, i, nWords) => {
59
59
  const nWeighted = Math.min(nWords, prefixLength);
60
60
  const nUnweighted = nWords - nWeighted;
61
61
  const wf = nUnweighted ? prefixBias : 1;
62
- return i < nWeighted
63
- ? wf * nWords / nWeighted
64
- : (1 - wf) * nWords / nUnweighted;
65
- }
62
+ return i < nWeighted
63
+ ? (wf * nWords) / nWeighted
64
+ : ((1 - wf) * nWords) / nUnweighted;
65
+ };
66
66
  return wordWeight;
67
67
  }
68
68
 
@@ -110,9 +110,7 @@ export class TfidfSpace {
110
110
  const msg = 'w7e.idfTunable:';
111
111
  // NOTE: This is NOT the usual formula
112
112
  // Map to [0:ignore..1:important]
113
- return nDocs
114
- ? 1 - Math.exp(((wdc - nDocs) / wdc) * idfWeight)
115
- : 1;
113
+ return nDocs ? 1 - Math.exp(((wdc - nDocs) / wdc) * idfWeight) : 1;
116
114
  }
117
115
 
118
116
  idf(word, idfWeight = this.idfWeight) {
@@ -208,22 +206,20 @@ export class TfidfSpace {
208
206
  return { bow, words };
209
207
  }
210
208
 
211
- bowOfText(text, opts={}) {
209
+ bowOfText(text, opts = {}) {
212
210
  const msg = 'w7e.bowOfText:';
213
211
  let dbg = DBG.W7E_BOW_OF_TEXT;
214
212
  if (text == null) {
215
213
  throw new Error(`${msg} text?`);
216
214
  }
217
- let {
218
- wordWeight = (word,i,n) => 1,
219
- } = opts;
215
+ let { wordWeight = (word, i, n) => 1 } = opts;
220
216
  let sNorm = this.normalizeText(text);
221
217
  let words = sNorm.split(' ');
222
218
  let nWords = words.length;
223
219
  let bow = words.reduce((a, word, i) => {
224
220
  let ww = wordWeight(word, i, nWords);
225
221
  a[word] = (a[word] || 0) + ww;
226
- dbg && cc.fyi1(msg+0.1, {i, word, ww, sum:a[word]});
222
+ dbg && cc.fyi1(msg + 0.1, { i, word, ww, sum: a[word] });
227
223
  return a;
228
224
  }, new WordVector());
229
225
 
@@ -235,6 +235,24 @@ export class Unicode {
235
235
  static get u_MACRON() {
236
236
  return '\u016d'; /* UTF-8 c5ab */
237
237
  }
238
+ static get WHITE_CIRCLE() {
239
+ return '\u25CB';
240
+ }
241
+ static get INVERSE_BULLET() {
242
+ return '\u25D8';
243
+ }
244
+ static get CIRCLED_BULLET() {
245
+ return '\u29BF';
246
+ }
247
+ static get FISHEYE() {
248
+ return '\u25C9';
249
+ }
250
+ static get WHITE_BULLET() {
251
+ return '\u25E6';
252
+ }
253
+ static get BULLET() {
254
+ return '\u2022';
255
+ }
238
256
  static get LEFT_ARROW() {
239
257
  return '\u2190';
240
258
  }
@@ -1,8 +1,6 @@
1
1
  import { DBG } from '../defines.mjs';
2
2
  import { Unicode } from './unicode.mjs';
3
- const {
4
- ELLIPSIS,
5
- } = Unicode;
3
+ const { ELLIPSIS } = Unicode;
6
4
 
7
5
  // The golden ratio is pretty.
8
6
  // 1.6180339887498948482045868343656381177203091798057628621354;
@@ -106,6 +104,7 @@ export class WordVector extends Object {
106
104
  }
107
105
 
108
106
  multiply(vec2) {
107
+ // Hadabard product: multipy element by element
109
108
  const msg = 'w8r.multiply:';
110
109
  let keys = Object.keys(vec2);
111
110
  return keys.reduce((a, k) => {