spitewaste 0.2.0 → 0.2.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.
@@ -36,6 +36,12 @@ _isop_resume: sub add swap push 128 div jump _isop_loop
36
36
  _isop_no: dup jump _isop_resume
37
37
  _isop_done: pop ret
38
38
 
39
+ $_do_collatz() {
40
+ dup push 2 mod
41
+ swap copy 1 push 2 mul $++ mul
42
+ push 2 copy 2 sub div add
43
+ }
44
+
39
45
  ; returns the elements of the Collatz sequence for integer N as a pseudo-array
40
46
  ; ! may run forever on some as-yet-unknown input
41
47
  ; [N] => [A]
@@ -48,18 +54,29 @@ _isop_done: pop ret
48
54
  collatz: push 1 ; sequence length
49
55
  _collatz_loop:
50
56
  copy 1 dup push 1 sub jz _collatz_done
51
- dup push 2 mod
52
- swap copy 1 push 2 mul $++ mul
53
- push 2 copy 2 sub div add
54
- swap $++ jump _collatz_loop
57
+ $_do_collatz() swap $++ jump _collatz_loop
55
58
  _collatz_done: pop ret
56
59
 
60
+ ; returns the length L of the Collatz sequence for integer N
61
+ ; ! may run forever on some as-yet-unknown input
62
+ ; [N] => [L]
63
+ ;
64
+ ; [1] => [1]
65
+ ; [4] => [3]
66
+ ; [7] => [17]
67
+ ; [189] => [107]
68
+ collatz_len: push 1 swap
69
+ _collatz_len_loop:
70
+ dup $-- jz _collatz_done
71
+ $_do_collatz() swap $++ swap jump _collatz_len_loop
72
+ copy 1 dup push 1 sub jz _collatz_done
73
+
57
74
  ; ruby:
58
75
  ; push "'" :strcat push "ruby -e 'p " swap :strcat shell ret
59
76
 
60
77
  $_to_roman(r, v) {
61
78
  push `v` :divmod swap push `r` swap :strrep
62
- push -2 load swap :strcat push -2 swap store
79
+ @-2 swap :strcat ^-2
63
80
  }
64
81
 
65
82
  ; converts the number N to a string of roman numerals R
@@ -77,3 +94,17 @@ to_roman: push -2,0 store
77
94
  $_to_roman("X", 10) $_to_roman("IX", 9)
78
95
  $_to_roman("V", 5) $_to_roman("IV", 4)
79
96
  $_to_roman("I", 1) push 2 sub load ret
97
+
98
+ ; applies the ROT13 "cipher" to the string S
99
+ ; [S] => [S']
100
+ ;
101
+ ; ["gnat"] => ["tang"]
102
+ ; ["purely"] => ["cheryl"]
103
+ ; ["cat.PNG"] => ["png.CAT"]
104
+ ; ["Hello, world."] => ["Uryyb, jbeyq."]
105
+ ; ["a123z"] => ["n123m"]
106
+ ROT13:
107
+ push "AZ" :strexpand push "az" :strexpand :strcat
108
+ push "NZ" :strexpand push "AM" :strexpand
109
+ push "nz" :strexpand push "am" :strexpand
110
+ :strcat :strcat :strcat :strtrans ret
@@ -1,6 +1,7 @@
1
1
  import string ; strcat, strpack, strrev, strunpack
2
2
 
3
3
  ; prints the character at the top of the stack until terminating zero
4
+ ; [0 ... C] => []
4
5
  print: :strunpack
5
6
  _print_loop:
6
7
  dup jz _print_done
@@ -8,34 +9,35 @@ _print_loop:
8
9
  _print_done: pop ret
9
10
 
10
11
  ; print with newline
12
+ ; [0 ... C] => []
11
13
  println: :print push 10 ochr ret
12
14
 
13
- ;;;
14
-
15
15
  ; reads a line of input onto the top of the stack as a packed string
16
16
  ; ! clobbers heap address -1
17
+ ; [] => [L]
17
18
  getline: push 0 ; terminator for strpack
18
-
19
- ; read characters onto the stack until newline (10) or EOF (-1)
20
19
  _getline_loop:
21
20
  push -1 dup ichr load
22
21
  dup jn _getline_eof
23
22
  dup push 10 sub jz _getline_done
24
23
  jump _getline_loop
25
-
26
24
  _getline_eof: pop
27
25
  _getline_done: :strpack :strrev ret
28
26
 
29
- ;;;
30
-
27
+ ; displays the string S then reads a line of input
28
+ ; [S] => [L]
31
29
  prompt: :print :getline ret
32
30
 
33
- ;;;
34
-
35
- ; consume stdin until EOF
31
+ ; consume stdin until EOF into the string S
32
+ ; [] => [S]
36
33
  readall: push 0 ; accumulated string
37
34
  _readall_loop:
38
35
  :getline dup jz _readall_done
39
36
  :strcat jump _readall_loop
40
-
41
37
  _readall_done: pop ret
38
+
39
+ ; returns the contents C of the file at path P (a string)
40
+ ; NON-STANDARD! This function makes use of the `shell` instruction, which is
41
+ ; only(?) available in the Spiceweight Whitespace interpreter.
42
+ ; [P] => [C]
43
+ readfile: push "cat " swap :strcat shell ret
@@ -51,7 +51,7 @@ ilog: push -1,0 store ; accumulator at -1
51
51
  _ilog_loop: ; [n b]
52
52
  swap copy 1 div dup jz _ilog_done
53
53
  push -1 :inc swap jump _ilog_loop
54
- _ilog_done: push -1 load slide 2 ret
54
+ _ilog_done: @-1 slide 2 ret
55
55
 
56
56
  ; returns the greatest common divisor of A and B
57
57
  ; [A B] => [gcd(A, B)]
@@ -111,10 +111,7 @@ abs: dup :sign mul ret
111
111
  ; [42 6] => [7 0]
112
112
  ; [ 1 5] => [0 1]
113
113
  ; ! [9 0] => [!!] TODO: find a way to expect exceptions
114
- divmod:
115
- push -1 swap store
116
- dup push -1 load div
117
- swap push -1 load mod ret
114
+ divmod: ^-1 dup @-1 div swap @-1 mod ret
118
115
 
119
116
  ; returns whether N is greater than 0
120
117
  ; [N] => [N > 0]
@@ -138,11 +135,11 @@ neg?: :sign push -1 :eq ret
138
135
  ; [25] => [1 5 25 3] ; no duplicate for perfect squares
139
136
  ; [60] => [1 2 3 4 5 6 60 30 20 15 12 10 12]
140
137
  divisors: ; [n]
141
- dup push -1 swap store ; preserve N because array operations
138
+ dup ^-1 ; preserve N because array operations
142
139
  :isqrt push 1 swap :range dup ; 1..isqrt(N)
143
- reject (push -1 load swap mod) :arydup ; get first half of divisors
144
- map (push -1 load swap div) :arycat ; map first half to second half
145
- push -1 load copy 2 dup mul sub jz _divisors_square ret
140
+ reject (@-1 swap mod) :arydup ; get first half of divisors
141
+ map (@-1 swap div) :arycat ; map first half to second half
142
+ @-1 copy 2 dup mul sub jz _divisors_square ret
146
143
  _divisors_square: slide 1 $-- ret ; de-duplicate when N is a perfect square
147
144
 
148
145
  ; returns the number of ways to choose K elements from a set of N
@@ -1,16 +1,39 @@
1
- import math ; pow
1
+ import array ; sorted?
2
+ import math ; pow
3
+ import string ; strtoa
2
4
 
3
- srand: push $seed swap store ret
5
+ ; seeds the random number generator with integer S
6
+ ; [S] => []
7
+ srand: ^$seed ret
4
8
 
9
+ ; returns the next number N in the linear congruential generator (better MINSTD)
10
+ ; [] => [N]
5
11
  rand:
6
12
  push $seed dup dup load
7
- push 3,13,10244807 mul mul mul
8
- push 2,32 :pow mod
13
+ push 48271 mul
14
+ push 2,31 :pow $-- mod
9
15
  store load ret
10
16
 
11
- rand_range: ; [a b]
12
- copy 1 sub :rand swap mod add ret
17
+ ; returns a random integer I between A and B (inclusive)
18
+ ; [A B] => [I]
19
+ rand_range:
20
+ $++ copy 1 sub :rand swap mod add ret
13
21
 
14
- dice:
15
- push -2 swap store push 1 :aryfill
16
- map (push -2 load :rand_range) ret
22
+ ; returns an array A of N random integers between 1 and D (inclusive)
23
+ ; [N D] => [A]
24
+ dice: ^-2 dup ^-1 times (push 1 @-2 :rand_range) @-1 ret
25
+
26
+ ; shuffles the array A in-place using the modern Fisher-Yates algorithm
27
+ ; [A] => [A']
28
+ shuffle: dup $-- ^-3
29
+ _shuffle_loop:
30
+ push 0 @-3 :rand_range @-3 :aryswap
31
+ push -3 :dec @-3 push -1 mul jn _shuffle_loop ret
32
+
33
+ ; shuffles the characters of the string S, producing a random anagram
34
+ ; [S] => [S']
35
+ strfry: :strtoa :shuffle pop :strpack ret
36
+
37
+ ; sorts the array A if you're lucky
38
+ ; [A] => [A']
39
+ bogosort: :shuffle :arydup :sorted? jz bogosort ret
@@ -187,9 +187,9 @@ _ratpow_neg: push -1 mul swap :ratinv swap :ratpow ret
187
187
  ; [R(355,113) 6] => [3 141592]
188
188
  ; [R(8675,309) 10] => [28 744336569] TODO: leading 0 is lost (bug)
189
189
  ; [R(2,4) 3] => [0 500]
190
- to_f: push -2 swap store :from_r :divmod push 0 swap
190
+ to_f: ^-2 :from_r :divmod push 0 swap
191
191
  _to_f_loop:
192
- push -1 load :divmod swap
192
+ @-1 :divmod swap
193
193
  copy 2 push 10 mul add
194
194
  swap push 10 mul
195
195
  push 2 :dig
@@ -1,6 +1,6 @@
1
1
  ;;; Heavy-handed stack manipulation
2
2
 
3
- ; These subrtouines do some pretty intricate stack-based operations, relying
3
+ ; These subroutines do some pretty intricate stack-based operations, relying
4
4
  ; heavily on clobbering the heap in order to maintain their "bookkeeping".
5
5
  ; Many of them use heap addresses -10 and lower, unbounded, so they're only
6
6
  ; meant to be used in a pinch or when there just isn't much of an alternative.
@@ -16,15 +16,13 @@ import util ; dec
16
16
  ; [1 2 3 4 5 3] => [1 3 4 5 2]
17
17
  roll:
18
18
  push -10 dup store ; current heap index kept at -10
19
- _roll_keep: ; [n]
19
+ _roll_keep:
20
20
  dup jz _roll_remove
21
21
  push -10 :dec
22
- swap push -10 load swap store
22
+ swap @-10 swap store
23
23
  push 1 sub jump _roll_keep
24
- _roll_remove:
25
- push 10 sub load
26
- swap push -10 swap store
27
- _roll_restore: ; i
24
+ _roll_remove: push 10 sub load swap ^-10
25
+ _roll_restore:
28
26
  dup load swap push 1 add
29
27
  dup push 10 add jz _roll_done
30
28
  jump _roll_restore
@@ -37,15 +35,15 @@ _roll_done: load ret
37
35
  ; [1 2 3 4 5 8 5] => [8 1 2 3 4 5]
38
36
  bury:
39
37
  push -10 dup store ; current heap index kept at -10
40
- swap push -9 swap store ; preserve element to bury
38
+ swap ^-9 ; preserve element to bury
41
39
  _bury_keep: ; [n]
42
40
  dup jz _bury_restore
43
41
  push -10 :dec
44
- swap push -10 load swap store
42
+ swap @-10 swap store
45
43
  push 1 sub jump _bury_keep
46
44
  _bury_restore:
47
45
  push 9 sub load
48
- push -10 load :_roll_restore pop ret
46
+ @-10 :_roll_restore pop ret
49
47
 
50
48
 
51
49
  ; "digs" out the Ith element of the stack and discards it
@@ -63,14 +61,14 @@ dig: :roll pop ret
63
61
  ;
64
62
  ; [-1 9 8 7 -1] => [7 8 9 3]
65
63
  ; [0 'c' 'b' 'a' 0] => ['a' 'b' 'c' 3]
66
- to_a: push -1 swap store push -10 dup store
64
+ to_a: ^-1 push -10 dup store
67
65
  _to_a_loop:
68
- dup push -1 load sub jz _to_a_sentinel
66
+ dup @-1 sub jz _to_a_sentinel
69
67
  push -10 dup :dec load
70
68
  swap store jump _to_a_loop
71
69
  _to_a_sentinel: pop push -10
72
70
  _to_a_restore:
73
- dup push -10 load sub jz _to_a_done
71
+ dup @-10 sub jz _to_a_done
74
72
  push 1 sub dup load swap
75
73
  jump _to_a_restore
76
74
  _to_a_done: push -10 swap sub ret
@@ -88,7 +86,7 @@ _npop_done: pop ret
88
86
  ;
89
87
  ; [1 2 3 4 5 2] => [1 2 5]
90
88
  ; [1 2 3 4 1] => [1 2 4]
91
- nslide: swap push -1 swap store :npop push -1 load ret
89
+ nslide: swap ^-1 :npop @-1 ret
92
90
 
93
91
  ; copies the Nth element to the top of the stack; this does exactly what
94
92
  ; a `copy N` instruction would do, but we don't always know N in advance
@@ -106,3 +104,14 @@ _ncopy_restore:
106
104
  dup push 9 add jz _ncopy_done
107
105
  dup load swap push 1 add jump _ncopy_restore
108
106
  _ncopy_done: pop ret
107
+
108
+ ; swaps the two arrays at the top of the stack
109
+ ; [A B] => [B A]
110
+ ;
111
+ ; [1 2 3 3 3 2 1 3] => [3 2 1 3 1 2 3 3]
112
+ ; [5 6 2 9 7 5 3 1 5] => [9 7 5 3 1 5 5 6 2]
113
+ ; [0 0 2 4 2 0 3] => [4 2 0 3 0 0 2]
114
+ ; [1 1 2 1] => [2 1 1 1]
115
+ swapary:
116
+ push -10 :aryheap push -11 @-9 sub :aryheap
117
+ push -10 :heapary push -11 @-9 sub :heapary ret
@@ -224,13 +224,13 @@ _strchop_empty: ret
224
224
  ; ["foo,,bar" ','] => ["foo" "" "bar" 3]
225
225
  ; ["/foo/bar/" '/'] => ["" "foo" "bar" "" 4]
226
226
  strsplit:
227
- push -3 push 1 store ; number of found substrings
228
- push -2 swap store ; stash delimiter to allow some stack juggling
227
+ push -3,1 store ; number of found substrings
228
+ ^-2 ; stash delimiter to allow some stack juggling
229
229
  _strsplit_loop:
230
- dup dup push -2 load
230
+ dup dup @-2
231
231
  :strindex dup jn _strsplit_done ; done when index of delimiter is -1
232
232
  push 0 swap :strslice
233
- swap copy 1 push -3 load
233
+ swap copy 1 @-3
234
234
  swap :strlen
235
235
  swap push -3 swap push 1 add store ; update number of found
236
236
  push 1 add push 128 swap :pow div ; shrink haystack
@@ -248,11 +248,10 @@ lines: push 10 :strsplit ret
248
248
  ; ["foo" "bar" "baz" 3 '--'] => ["foo--bar--baz"]
249
249
  ; ["foo" 1 "?!"] => ["foo"]
250
250
  strjoinc:
251
- dup :strlen pop ; get delimiter length into -1
252
- push -2 swap store
253
- map (push -2 load :strcat) ; add delimiter to all elements
251
+ dup :strlen pop ^-2 ; get delimiter length into -1
252
+ map (@-2 :strcat) ; add delimiter to all elements
254
253
  swap push 128 copy 1 :strlen
255
- push -2 load :strlen
254
+ @-2 :strlen
256
255
  sub :pow mod swap ; remove delimiter from last and flow into strjoin
257
256
 
258
257
  ; concatenates the pseudo-array of strings A into string S
@@ -289,9 +288,8 @@ _strcountc_done: swap slide 2 ret
289
288
  ; ["eunoia" "aeiou"] => [5]
290
289
  ; ["why" "aeiou"] => [0]
291
290
  strcount:
292
- swap push -2 swap store
293
- :strunpack push 0 :to_a
294
- map (push -2 load swap :strcountc)
291
+ swap ^-2 :strunpack push 0 :to_a
292
+ map (@-2 swap :strcountc)
295
293
  reduce (add) ret
296
294
 
297
295
  ; translates all characters in A to the corresponding characters in B
@@ -303,16 +301,13 @@ strcount:
303
301
  ; ["abcd" "abc" "xyz"] => ["xyzd"]
304
302
  ; ["foobar" "oba" "ele"] => ["feeler"]
305
303
  ; ["abcdcba" "abcd" "xyz|"] => ["xyz|zyx"]
306
- strtrans:
307
- push -3 swap store
308
- push -2 swap store
309
- dup :strlen push -1 swap store
310
- :strunpack push -1 load
304
+ strtrans: ^-3 ^-2
305
+ dup :strlen ^-1 :strunpack @-1
311
306
  map (:_strtrans) pop :strpack ret
312
307
  _strtrans:
313
- dup push -2 load swap :strindex
308
+ dup @-2 swap :strindex
314
309
  dup jn _strtrans_no
315
- push -3 load swap :charat
310
+ @-3 swap :charat
316
311
  slide 1 ret
317
312
  _strtrans_no: pop ret
318
313
 
@@ -346,8 +341,8 @@ _strsqueeze_skip: pop jump _strsqueeze_loop
346
341
  _strsqueeze_done: pop :strpack :strrev ret
347
342
 
348
343
  $_strdel(cmp) {
349
- :strunpack push -1 load :strlen
350
- select (push -2 load swap :strindex `cmp`)
344
+ :strunpack @-1 :strlen
345
+ select (@-2 swap :strindex `cmp`)
351
346
  pop :strpack ret
352
347
  }
353
348
 
@@ -390,13 +385,39 @@ strrotl: push 128 swap copy 2 :strlen mod :pow :divmod :strcat ret
390
385
  ; ["abcd" 1] => ["dabc"]
391
386
  ; ["abcd" 5] => ["dabc"]
392
387
  ; ["foodbar" 3] => ["barfood"]
393
- strrotr: push 0 swap sub :strrotl ret
388
+ strrotr: push -1 mul :strrotl ret
389
+
390
+ ; gets the characters of the string S onto the stack as a pseudo-array, but
391
+ ; with a leading 0 on the assumption that it'll eventually be repacked
392
+ ;
393
+ ; ["abc"] => [0 99 98 97 3]
394
+ strtoa: dup :strlen pop :strunpack @-1 $++ ret
394
395
 
395
396
  ; frobnicates the string S by XORing all its bytes with 42
396
397
  ; [S] => [S']
397
398
  ;
398
399
  ; ["foobar"] => ["LEEHKX"]
399
400
  ; ["LEEHKX"] => ["foobar"]
400
- memfrob:
401
- dup :strlen pop :strunpack push -1 load $++
402
- map (push 42 :bxor) pop :strpack ret
401
+ memfrob: :strtoa map (push 42 :bxor) pop :strpack ret
402
+
403
+ ; returns 1 if the string S begins with substring T, 0 otherwise
404
+ ; [S T] => [0 | 1]
405
+ ;
406
+ ; ["foobar" "foo"] => [1]
407
+ ; ["foobar" "boo"] => [0]
408
+ ; ["abc123" "123"] => [0]
409
+ ; [" foo" " "] = [1]
410
+ strbegins?:
411
+ dup :strlen copy 2 swap push 0 swap
412
+ :strslice :eq slide 1 ret
413
+
414
+ ; returns 1 if the string S ends with substring T, 0 otherwise
415
+ ; [S T] => [0 | 1]
416
+ ;
417
+ ; ["foobar" "bar"] => [1]
418
+ ; ["foobar" "foo"] => [0]
419
+ ; ["abc123" "abc"] => [0]
420
+ ; ["foo " " "] = [1]
421
+ strends?:
422
+ :strrev dup :strlen copy 2 :strrev swap push 0 swap
423
+ :strslice :eq slide 1 ret
@@ -8,3 +8,6 @@ assert_eq:
8
8
  push ", got " :print onum
9
9
  push " for test " swap :strcat :die!
10
10
  _assert_eq_yes: pop pop pop ret
11
+
12
+ assert_nz: jz _assert_nz_no pop ret
13
+ _assert_nz_no: push "expected nonzero for test " swap :strcat :die!
@@ -26,7 +26,7 @@ _range_down:
26
26
 
27
27
  $range_loop(fn, cmp) {
28
28
  `fn`:
29
- dup copy 2 add push -1 load `cmp` jz _`fn`_done
29
+ dup copy 2 add @-1 `cmp` jz _`fn`_done
30
30
  copy 1 copy 1 add swap jump `fn`
31
31
  _`fn`_done: pop ret
32
32
  }
@@ -50,8 +50,10 @@ steprange: swap push -1 copy 1 store copy 2 sub
50
50
  $range_loop(_steprange_loop, :lte)
51
51
  $range_loop(_steprange_down_loop, :gte)
52
52
 
53
- ; prints the string at the top of the stack and halts execution
54
- die!: :println exit
53
+ ; prints the string at the top of the stack and halts execution after pushing
54
+ ; something onto the stack to signal abnormal termination/unclean exit
55
+ ; [...] => [... 1]
56
+ die!: :println push 1 exit
55
57
 
56
58
  ; for stoi and itos
57
59
  alpha: push "0123456789abcdefghijklmnopqrstuvwxyz" ret
@@ -72,7 +74,7 @@ alpha: push "0123456789abcdefghijklmnopqrstuvwxyz" ret
72
74
  ; ["-123" 10] => [-123]
73
75
  ; ["-ff" 16] => [-255]
74
76
  stoi: swap dup :_stoi_sign swap copy 1 :eq
75
- push 2 mul $-- push -2 swap store push 0
77
+ push 2 mul $-- ^-2 push 0
76
78
  _stoi_loop: ; [b s a]
77
79
  swap dup jz _stoi_done
78
80
  swap copy 2 copy 2
@@ -83,8 +85,8 @@ _stoi_loop: ; [b s a]
83
85
  mul add swap push 128 div swap
84
86
  jump _stoi_loop
85
87
  _stoi_sign: dup push 0 :charat push '-' :eq copy 1 :strlen :strslice ret
86
- _stoi_invalid: pop pop slide 1 swap div push -2 load mul ret
87
- _stoi_done: swap slide 2 push -2 load mul ret
88
+ _stoi_invalid: pop pop slide 1 swap div @-2 mul ret
89
+ _stoi_done: swap slide 2 @-2 mul ret
88
90
 
89
91
  ; creature comforts
90
92
 
@@ -111,7 +113,7 @@ _itos_loop:
111
113
  swap :strcat
112
114
  swap copy 2 div
113
115
  swap jump _itos_loop
114
- _itos_done: swap slide 2 push 45,-2 load mul swap :strcat ret
116
+ _itos_done: swap slide 2 push 45 @-2 mul swap :strcat ret
115
117
 
116
118
  ; creature comforts
117
119
 
@@ -129,12 +131,10 @@ to_hex: push 16 :itos ret
129
131
  ; [256 16] => [1 0 0 3]
130
132
  digits:
131
133
  copy 1 jz _digits_zero ; special case
132
- push -1 swap store
133
- push -1 swap ; sentinel value
134
+ ^-1 push -1 swap ; sentinel value
134
135
  _digits_loop:
135
136
  dup jz _digits_done
136
- push -1 load :divmod
137
- swap jump _digits_loop
137
+ @-1 :divmod swap jump _digits_loop
138
138
  _digits_zero: dup div ret
139
139
  _digits_done: push 1 sub :to_a ret
140
140
 
@@ -215,8 +215,8 @@ between?: copy 2 :gte swap copy 2 :lte mul slide 1 ret
215
215
  ; a value V to search for and a starting index I, and either returns the first
216
216
  ; key associated with that value or loops forever. Probably don't touch.
217
217
  ; [V I]
218
- heap_seeking_missile:
219
- $++ dup load copy 2 :eq jz heap_search
218
+ heap-seeking_missile:
219
+ $++ dup load copy 2 :eq jz heap-seeking_missile
220
220
  slide 1 ret
221
221
 
222
222
  ; converts the #RRGGBB (leading '#' optional) color string S to
@@ -246,3 +246,31 @@ rgb2hex:
246
246
  copy 2 push 256 mul add add
247
247
  slide 2 :to_hex push 6,48 :rjustc ret
248
248
  _rgb2hex_invalid: push "(rgb2hex) invalid RGB" :die!
249
+
250
+ ; stashes the array A in negative heap space starting at index I
251
+ ; [A I] => []
252
+ aryheap:
253
+ $++ dup copy 2 store
254
+ swap times ($-- swap copy 1 swap store) pop ret
255
+
256
+ ; restores the heaped array starting at index I
257
+ ; [I] => [A]
258
+ heapary:
259
+ $++ dup load swap copy 1 sub swap
260
+ times (dup load swap $++) load ret
261
+
262
+ ; swaps the elements in the heap at indices I and J
263
+ ; ! TODO: make heap effects doctest-able
264
+ ; [I J] => []
265
+ heapswap: dup load swap copy 2 load store store ret
266
+
267
+ ; returns the number of nanoseconds N since the Unix epoch
268
+ ; [] => [N]
269
+ time: push "date +%s%N" shell :to_i ret
270
+
271
+ $bench(insns) {
272
+ #insns :println
273
+ :time ^0 `insns` :time @0 sub
274
+ push 10,9 :pow :divmod swap onum
275
+ push '.' ochr :to_s push 9,48 :rjustc :println
276
+ }