@fraczak/k 6.10.4 → 6.10.5

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/CHANGELOG.md CHANGED
@@ -5,6 +5,41 @@ All notable changes to this project will be documented here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
  This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [6.10.5] — 2026-10-08 — Adaptive Division and VFS Codecs
9
+
10
+ ### Arithmetic & Algorithms
11
+
12
+ - **Adaptive Hybrid Division (`Examples/arithmetics.k`)**:
13
+ - Implemented hybrid integer division that dynamically chooses between
14
+ down-shift division ($|x| < 2|y|$) and restoring long division
15
+ ($|x| \ge 2|y|$).
16
+ - Down-shift division scales divisor $y$ up to match length of $x$,
17
+ performing trial subtraction and down-shifting in $O(1)$ per step.
18
+ - Crossover condition is evaluated structurally in $O(\min(|y|, |x|/2))$
19
+ without prior length computations.
20
+ - Added native number-theory operations: `mod`, Euclidean `gcd`, and
21
+ `lcm`.
22
+ - Refactored `arithmetics.k` to focus purely on arithmetic operations.
23
+
24
+ ### Polymorphic Lists
25
+
26
+ - **Polymorphic List Library (`Examples/poly.k`)**:
27
+ - Encapsulated list constructors and destructors (`list?`, `nil`,
28
+ `cons`, `car`, `cdr`, `singleton`, `nil?`) directly in `poly.k`.
29
+ - Updated benchmark suites (`harness/suites/poly.mjs`,
30
+ `scripts/perf-poly.mjs`) to import only arithmetic symbols from
31
+ `arithmetics.k`.
32
+
33
+ ### Web REPL & Codecs
34
+
35
+ - **VFS Codec Subfolder Organization**:
36
+ - Reorganized all codec files in the Virtual File System under the
37
+ `codecs/` subfolder (`codecs/int.mjs`, `codecs/json.mjs`, etc.).
38
+ - Added backward-compatible resolution in `repl-codecs.mjs`.
39
+ - Added `karatsuba-mult.k` to the VFS and example picker in `repl.html`.
40
+
41
+ ---
42
+
8
43
  ## [6.10.4] — 2026-10-06 — Karatsuba Multiplication, Multi-Backend Harness, and CLI Help
9
44
 
10
45
  ### Arithmetic & Algorithms
@@ -4,14 +4,6 @@ swap_yes_no = < /yes |no, /no |yes > $yes_no;
4
4
  yes = {}|yes $yes_no;
5
5
  no = yes swap_yes_no;
6
6
 
7
- list? = ?< {} nil, {X car, Y cdr} cons > = Y;
8
- nil = {}|nil list?;
9
- singleton = {() car, nil cdr} |cons list?;
10
- cons = {.car car, .cdr cdr} |cons list?;
11
- nil? = list? / nil nil;
12
- car = list? /cons .car;
13
- cdr = list? /cons .cdr;
14
-
15
7
  $ bits = < {} _, bits 0, bits 1 >;
16
8
 
17
9
  0b = {} | _;
@@ -70,29 +62,10 @@ inOrder_ = ${bits x, bits y, yes_no inOrder}
70
62
 
71
63
  GT = {inOrder /no if, () then} . then;
72
64
 
73
- EQ = GT {.x y, .y x} GT;
74
-
75
- toBitList = {inv x, nil r} _toBitList;
76
- _toBitList = <
77
- {.x empty? if, .r then}.then,
78
- <
79
- {.x /0 x, {0 car, .r cdr} cons r},
80
- {.x /1 x, {1 car, .r cdr} cons r}
81
- > _toBitList
82
- >;
83
- fromBitList = {() l, 0b r} _fromBitList inv $bits;
84
- _fromBitList =
85
- <
86
- {.l nil? if, .r then} .then,
87
- <
88
- {.l car /0 if, {.l cdr l, .r |0 r} then} .then,
89
- {.l car /1 if, {.l cdr l, .r |1 r} then} .then
90
- > _fromBitList
91
- >;
65
+ EQ = {inOrder /yes if, {.x y, .y x} inOrder /yes and, () then} .then;
92
66
 
93
67
  --------------------------------------
94
68
  $ int = < bits '+', bits '-' >;
95
- $ ints = <{int car, ints cdr} cons, {} nil>;
96
69
 
97
70
  remove_leading_zeros = <
98
71
  /0 remove_leading_zeros,
@@ -102,7 +75,7 @@ nat = remove_leading_zeros <empty? 0, ()>;
102
75
  int = nat | '+' $int;
103
76
  abs = $int </'+',/'-'>;
104
77
 
105
- pos? = $int {dec /'+' if, () then} .then;
78
+ pos? = $int {/'+' remove_leading_zeros /1 if, () then} .then;
106
79
  neg? = $int {/'-' if, () then} .then;
107
80
  zero_int? = $int {/'+' /0 empty? if, () then} .then;
108
81
  minus_one? = $int {/'-' /1 empty? if, () then} .then;
@@ -125,12 +98,12 @@ dec = $int <
125
98
  /'-' {inv x, 0b y} _inc nat |'-'
126
99
  > $int;
127
100
 
128
- _dec = ?{X x, X y, ...} <
101
+ _dec = ${bits x, bits y} <
129
102
  {.x /1 x, .y |0 y} concat_,
130
103
  {.x /0 x, .y |1 y} _dec
131
- > ?X;
104
+ >;
132
105
 
133
- $carry = <{} 0, {} 1>;
106
+ $ carry = <{} 0, {} 1>;
134
107
  c0 = {}|0 $carry;
135
108
  c1 = {}|1 $carry;
136
109
 
@@ -218,7 +191,7 @@ times = ${int x, int y}
218
191
  sign_minus
219
192
  > sign,
220
193
  {
221
- 0 result,
194
+ 0b result,
222
195
  .x abs inv _x,
223
196
  .y abs y
224
197
  } _times result
@@ -249,105 +222,244 @@ _times =
249
222
  ;
250
223
 
251
224
 
252
- mod =
253
- ${bits x, bits y}
254
- -- _log!
255
- <
256
- -- if y > x then (x mod y) = x
257
- {.y x, .x y} GT .y,
258
- -- else, i.e.
259
- -- if x >= y then (x mod y) = ((x-y mod 2y) mod y)
260
- {
261
- {
262
- {.x int x, .y int y} minus /"+" x,
263
- .y inv |0 inv y
264
- } mod x,
265
- .y y
266
- } mod
267
- >
268
- ;
269
225
 
270
226
 
271
- // gratest common divisor
272
- gcd = ${bits x, bits y}
273
- <
274
- {.y x, .x y} GT,
275
- ()
276
- > _gcd
277
- ;
278
227
 
279
- _gcd = <
280
- { .y /0 if, .x then } .then,
281
- { .y x, mod y } _gcd
282
- >;
228
+ // ----------------------------------------------------------------------
229
+ // Integer Division: Adaptive Hybrid Algorithm
230
+ //
231
+ // Combines two complementary strategies based on the bit-length
232
+ // comparison between dividend x and divisor y (|x| vs |y|):
233
+ //
234
+ // 1. Down-Shift Division (|x| < 2|y|, y is large relative to x):
235
+ // - Scales y up to y_max = y * 2^k to match length(x) via 'y inv'
236
+ // and prepending k zeros (|0).
237
+ // - In each step: trial subtraction x = x - y_scaled, then down-shifts
238
+ // y_scaled by 1 bit in O(1) by popping the outer zero (/_0).
239
+ // - Runs in exactly (|x| - |y| + 1) iterations. When y ~= x, this
240
+ // terminates in 1-2 steps rather than looping through all bits of x.
241
+ //
242
+ // 2. Restoring Division (|x| >= 2|y|, y is small relative to x):
243
+ // - Consumes bits of x from MSB to LSB into remainder accumulator r.
244
+ // - Remainder r stays small (r <= 2y), so trial subtractions operate
245
+ // on tiny numbers of length |y| rather than full-length |x|.
246
+ //
247
+ // The crossover condition |x| < 2|y| is evaluated structurally in
248
+ // O(min(|y|, |x|/2)) by consuming 2 bits of x for every 1 bit of y.
249
+ // ----------------------------------------------------------------------
250
+
251
+ // Helpers for Restoring Division (shift-and-subtract)
252
+ shift_in_0 = inv |0 inv nat;
253
+ shift_in_1 = inv |1 inv nat;
254
+ sub_bits = ${bits x, bits y} {.x inv x, .y inv y, c0 c} _minus inv nat;
255
+
256
+ _div_restoring =
257
+ ${bits x, bits y, bits r, bits q}
258
+ <
259
+ {.x empty? if, {.q inv int div, .r rem} then} .then,
260
+ <
261
+ {.x /0 x, .r shift_in_0 r, .y y, .q q},
262
+ {.x /1 x, .r shift_in_1 r, .y y, .q q}
263
+ >
264
+ {
265
+ .x x,
266
+ .y y,
267
+ .q q,
268
+ .r r,
269
+ {.y x, .r y} inOrder test
270
+ }
271
+ <
272
+ {.test /yes if, {.x x, .y y, .q |1 q, {.r x, .y y} sub_bits r} then} .then,
273
+ {.test /no if, {.x x, .y y, .q |0 q, .r r} then} .then
274
+ >
275
+ _div_restoring
276
+ >;
283
277
 
278
+ // Helpers for Down-Shift Division (align and shift divisor down)
279
+ // Phase 1: pad _y with |0 to match length of x, counting shift steps
280
+ _pad_down = ${bits x, bits _y, bits steps}
281
+ <
282
+ {.x empty? if, {._y _y, .steps steps} then} .then,
283
+ {
284
+ .x </0, /1> x,
285
+ ._y |0 _y,
286
+ .steps |0 steps
287
+ } _pad_down
288
+ >;
284
289
 
290
+ _align_down = ${bits x, bits y, bits _y}
291
+ <
292
+ // Both x and y have bits remaining: advance both
293
+ {
294
+ .x </0, /1> x,
295
+ .y </0, /1> y,
296
+ ._y _y
297
+ } _align_down,
285
298
 
286
- // div
299
+ // y is empty while x remains: pad _y with shift zeros
300
+ {
301
+ .y empty? if,
302
+ {.x x, ._y _y, 0b steps} _pad_down then
303
+ } .then,
287
304
 
288
- log2_floor =
289
- ${bits x, bits step}
290
- -- finds the largest k, such that 'step * 2^k <= x',
291
- -- returns k1_, i.e., 0 if x < step, and k1_ otherwise.
292
- {.x x, .step step, 0 log} log2_floor_;
305
+ // Both x and y are empty: lengths equal (0 shift steps)
306
+ {
307
+ .x empty? if,
308
+ .y empty? and,
309
+ {._y _y, 0b steps} then
310
+ } .then
311
+ >;
293
312
 
294
- log2_floor_ = <
295
- {{.step x,.x y} GT if, .log then} .then,
296
- {.x x, {.step x,0 y} GT {.x x,2 y} {.x int x, .y int y} times abs step, .log int inc abs log} log2_floor_
297
- >;
313
+ // Phase 2: down-shift trial subtraction
314
+ _div_down = ${bits x, bits _y, bits steps, bits q}
315
+ {
316
+ .x x,
317
+ ._y _y,
318
+ .steps steps,
319
+ .q q,
320
+ ._y inv nat y_scaled
321
+ }
322
+ {
323
+ .x x,
324
+ ._y _y,
325
+ .steps steps,
326
+ .q q,
327
+ .y_scaled y_scaled,
328
+ {.y_scaled x, .x y} inOrder test
329
+ }
330
+ <
331
+ // If x >= y_scaled, subtract and emit quotient bit 1
332
+ {
333
+ .test /yes if,
334
+ {
335
+ {.x int x, .y_scaled int y} minus /+ x,
336
+ .q |1 q,
337
+ ._y _y,
338
+ .steps steps
339
+ } then
340
+ } .then,
341
+ // If x < y_scaled, keep x and emit quotient bit 0
342
+ {
343
+ .test /no if,
344
+ {.x x, .q |0 q, ._y _y, .steps steps} then
345
+ } .then
346
+ >
347
+ <
348
+ // If no more shift steps remaining, division is complete
349
+ {
350
+ .steps empty? if,
351
+ {.q inv int div, .x rem} then
352
+ } .then,
353
+ // Otherwise down-shift: remove the last 0 from _y and steps
354
+ {
355
+ .x x,
356
+ ._y /0 _y,
357
+ .steps /0 steps,
358
+ .q q
359
+ } _div_down
360
+ >;
298
361
 
362
+ $ choose_div = < {} down_shift, {} restore >;
299
363
 
300
- x_times_2_to_k =
301
- ${bits x, bits k}
302
- {.x inv _x, .k int k} _x_times_2_to_the_power_of_k inv
303
- ;
364
+ // Evaluates whether |x| < 2|y| by advancing 2 bits of x per 1 bit of y
365
+ _check_2y = ${bits x, bits y}
366
+ <
367
+ // x is empty -> |x| < 2|y| (choose down_shift)
368
+ {.x empty? if, {} | down_shift $choose_div then} .then,
369
+ // y is empty while x remains -> |x| >= 2|y| (choose restore)
370
+ {.y empty? if, {} | restore $choose_div then} .then,
371
+ // x has only 1 bit left while y remains -> |x| < 2|y|
372
+ {.x </0, /1> empty? if, {} | down_shift $choose_div then} .then,
373
+ // Advance 2 bits from x and 1 bit from y
374
+ {
375
+ .x </0, /1> </0, /1> x,
376
+ .y </0, /1> y
377
+ } _check_2y
378
+ >;
304
379
 
305
- _x_times_2_to_the_power_of_k = <
306
- {.k zero_int? if, ._x then} .then,
307
- {._x |0 _x, .k dec k} _x_times_2_to_the_power_of_k
308
- >
309
- ;
310
-
311
- div =
312
- ${int x, int y}
313
- // remember sign
380
+ // Public division interface
381
+ div = ${int x, int y}
314
382
  {
315
383
  <
316
384
  { .x/'+' if, .y get_sign then } .then,
317
- { .x/'-' if, .y swap_sign get_sign then } .then
385
+ { .x/'-' if, .y swap_sign get_sign then } .then
318
386
  > sign,
319
- {.x abs x, .y abs step, 0 int res} _div res_wo_sign
387
+ .x abs remove_leading_zeros x_norm,
388
+ .y abs remove_leading_zeros y_norm
320
389
  }
321
- <
322
- {.sign/'+' if, {.res_wo_sign.div div, .res_wo_sign.rem rem} then} .then,
323
- { .res_wo_sign.div swap_sign div, .res_wo_sign.rem rem }
324
- >
325
- ;
326
-
327
- _div =
328
- {
329
- .x x,
330
- .step step,
331
- .res res,
332
- {.x x, .step step} log2_floor log
333
- }
334
- <
335
- { .log int zero_int? if, {.res div, .x rem} then } .then,
336
- {
337
- {1 x, .log int dec abs k} x_times_2_to_k factor,
338
- () i
339
- }
390
+ <
391
+ // Case 1: x == 0 -> quotient 0, remainder 0
392
+ {
393
+ .x_norm empty? if,
394
+ { 0 int div, 0 rem } then
395
+ } .then,
396
+ // Case 2: |x| < |y| -> x < y, quotient 0, remainder x
397
+ {
398
+ {.y_norm x, .x_norm y} GT if,
399
+ { 0 int div, .x_norm rem } then
400
+ } .then,
401
+ // Case 3: general division via adaptive hybrid choice
340
402
  {
341
- {.i.x int x, {.i.step int x, .factor int y} times y} minus abs x,
342
- .i.step step,
343
- {.i.res x, .factor int y} plus res
344
- } _div
403
+ .sign sign,
404
+ {
405
+ .x_norm x,
406
+ .y_norm y,
407
+ {.x_norm x, .y_norm y} _check_2y choice
408
+ }
409
+ <
410
+ // If |x| < 2|y|, use down-shift division
411
+ {
412
+ .choice /down_shift if,
413
+ {
414
+ .x x,
415
+ {.x x, .y y, .y inv _y} _align_down aligned
416
+ }
417
+ {
418
+ .x x,
419
+ .aligned._y _y,
420
+ .aligned.steps steps,
421
+ 0b q
422
+ } _div_down then
423
+ } .then,
424
+ // If |x| >= 2|y|, use restoring long division
425
+ {
426
+ .choice /restore if,
427
+ {.x x, .y y, 0 r, 0b q} _div_restoring then
428
+ } .then
429
+ > res
430
+ }
431
+ <
432
+ {.sign/'+' if, {.res.div div, .res.rem rem} then} .then,
433
+ { .res.div swap_sign div, .res.rem rem }
434
+ >
345
435
  >;
346
436
 
347
437
 
438
+ // ----------------------------------------------------------------------
439
+ // Number Theory: Modulo, Greatest Common Divisor, Least Common Multiple
440
+ // ----------------------------------------------------------------------
441
+
442
+ // Modulo: remainder of x divided by y in $bits, delegated to div
443
+ mod = ${bits x, bits y}
444
+ {{.x int x, .y int y} div .rem rem} .rem;
445
+
446
+ // Greatest common divisor (Euclidean algorithm)
447
+ gcd = ${bits x, bits y}
448
+ <
449
+ {.y x, .x y} GT,
450
+ ()
451
+ > _gcd
452
+ ;
453
+
454
+ _gcd = <
455
+ { .y remove_leading_zeros empty? if, .x nat then } .then,
456
+ { .y x, mod y } _gcd
457
+ >;
458
+
459
+ // Least common multiple: (x / gcd(x, y)) * y
348
460
  lcm =
349
461
  ${bits x, bits y}
350
- {{.x int x, .y int y} times x, gcd int y} div .div abs
462
+ {{.x int x, gcd int y} div .div x, .y int y} times abs
351
463
  ;
352
464
 
353
465
  factorial = {()n, 1 int acc} factorial_;
package/Examples/poly.k CHANGED
@@ -1,8 +1,16 @@
1
1
  -- Examples/poly.k: Polymorphic list functions in k
2
- -- Uses list constructors/destructors from Examples/list.k
3
- -- and arbitrary-precision integer representations from Examples/arithmetics.k.
4
-
5
- -- Decrement an integer and normalize its representation (stripping leading zeros)
2
+ -- Uses arbitrary-precision integers from Examples/arithmetics.k.
3
+
4
+ -- List constructors and destructors
5
+ list? = ?< {} nil, {X car, Y cdr} cons > = Y;
6
+ nil = {} | nil list?;
7
+ singleton = {() car, nil cdr} | cons list?;
8
+ cons = {.car car, .cdr cdr} | cons list?;
9
+ nil? = list? / nil nil;
10
+ car = list? /cons .car;
11
+ cdr = list? /cons .cdr;
12
+
13
+ -- Decrement an integer and normalize (stripping leading zeros)
6
14
  pred_int = dec /'+' nat | '+';
7
15
 
8
16
  -- Concatenate two lists: {xs, ys} -> list
@@ -82,7 +90,8 @@ gt? =
82
90
  >
83
91
  ;
84
92
 
85
- -- Split list into two halves by alternating elements: xs -> {first, second}
93
+ -- Split list into two halves by alternating elements:
94
+ -- xs -> {first, second}
86
95
  split = {() xs, nil first, nil second} _split;
87
96
  _split = <
88
97
  {.xs /nil if, {.first first, .second second} then} .then,
@@ -25,7 +25,10 @@ function cleanPath(p) {
25
25
  }
26
26
 
27
27
  export function setVfsFile(path, content) {
28
- const cleaned = cleanPath(path);
28
+ let cleaned = cleanPath(path);
29
+ if (cleaned.endsWith(".mjs") && !cleaned.startsWith("codecs/")) {
30
+ cleaned = `codecs/${cleaned}`;
31
+ }
29
32
  vfs.set(cleaned, content);
30
33
  }
31
34
 
@@ -53,11 +56,13 @@ const CURATED_VFS_FILES = [
53
56
  "core.k",
54
57
  "arithmetics.k",
55
58
  "ieee.k",
56
- "json.mjs",
57
- "utf8.mjs",
58
- "int.mjs",
59
- "unit.mjs",
60
- "ieee.mjs"
59
+ "poly.k",
60
+ "karatsuba-mult.k",
61
+ "codecs/json.mjs",
62
+ "codecs/utf8.mjs",
63
+ "codecs/int.mjs",
64
+ "codecs/unit.mjs",
65
+ "codecs/ieee.mjs"
61
66
  ];
62
67
 
63
68
  export function getAllVfsFiles() {
@@ -69,7 +74,7 @@ export function getAllVfsFiles() {
69
74
  }
70
75
  for (const key of vfs.keys()) {
71
76
  if (key.endsWith("runtime.wat") || key.startsWith("backends/")) continue;
72
- if (key.startsWith("Examples/") || key.startsWith("codecs/")) continue;
77
+ if (key.startsWith("Examples/")) continue;
73
78
  result.add(key);
74
79
  }
75
80
  return Array.from(result);