spitewaste 0.1.009 → 0.2.01

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,11 +54,43 @@ _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
76
+
77
+ $_to_roman(r, v) {
78
+ push `v` :divmod swap push `r` swap :strrep
79
+ @-2 swap :strcat ^-2
80
+ }
81
+
82
+ ; converts the number N to a string of roman numerals R
83
+ ; [N] => [R]
84
+ ;
85
+ ; [2020] => ["MMXX"]
86
+ ; [1666] => ["MDCLXVI"]
87
+ ; [1337] => ["MCCCXXXVII"]
88
+ ; [94] => ["XCIV"]
89
+ to_roman: push -2,0 store
90
+ $_to_roman("M", 1000) $_to_roman("CM", 900)
91
+ $_to_roman("D", 500) $_to_roman("CD", 400)
92
+ $_to_roman("C", 100) $_to_roman("XC", 90)
93
+ $_to_roman("L", 50) $_to_roman("XL", 40)
94
+ $_to_roman("X", 10) $_to_roman("IX", 9)
95
+ $_to_roman("V", 5) $_to_roman("IV", 4)
96
+ $_to_roman("I", 1) push 2 sub load ret
@@ -1,8 +1,7 @@
1
1
  import array ; arycat, arydup
2
2
  import util ; eq, inc, range
3
3
 
4
- ; returns B raised to the power E
5
- ; ! TODO: support negative exponents (return Rational)
4
+ ; returns B raised to the power E; if E must be negative, use ratpow instead
6
5
  ; [B E] => [B**E]
7
6
  ;
8
7
  ; [0 0] => [1], [0 9] => [0], [9 0] => [1]
@@ -52,7 +51,7 @@ ilog: push -1,0 store ; accumulator at -1
52
51
  _ilog_loop: ; [n b]
53
52
  swap copy 1 div dup jz _ilog_done
54
53
  push -1 :inc swap jump _ilog_loop
55
- _ilog_done: push -1 load slide 2 ret
54
+ _ilog_done: @-1 slide 2 ret
56
55
 
57
56
  ; returns the greatest common divisor of A and B
58
57
  ; [A B] => [gcd(A, B)]
@@ -112,10 +111,7 @@ abs: dup :sign mul ret
112
111
  ; [42 6] => [7 0]
113
112
  ; [ 1 5] => [0 1]
114
113
  ; ! [9 0] => [!!] TODO: find a way to expect exceptions
115
- divmod:
116
- push -1 swap store
117
- dup push -1 load div
118
- swap push -1 load mod ret
114
+ divmod: ^-1 dup @-1 div swap @-1 mod ret
119
115
 
120
116
  ; returns whether N is greater than 0
121
117
  ; [N] => [N > 0]
@@ -139,11 +135,11 @@ neg?: :sign push -1 :eq ret
139
135
  ; [25] => [1 5 25 3] ; no duplicate for perfect squares
140
136
  ; [60] => [1 2 3 4 5 6 60 30 20 15 12 10 12]
141
137
  divisors: ; [n]
142
- dup push -1 swap store ; preserve N because array operations
138
+ dup ^-1 ; preserve N because array operations
143
139
  :isqrt push 1 swap :range dup ; 1..isqrt(N)
144
- reject (push -1 load swap mod) :arydup ; get first half of divisors
145
- map (push -1 load swap div) :arycat ; map first half to second half
146
- 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
147
143
  _divisors_square: slide 1 $-- ret ; de-duplicate when N is a perfect square
148
144
 
149
145
  ; returns the number of ways to choose K elements from a set of N
@@ -1,12 +1,34 @@
1
- import math ; pow
1
+ import math ; pow
2
+ import string ; strtoa
2
3
 
3
- srand: push $seed swap store ret
4
+ ; seeds the random number generator with integer S
5
+ ; [S] => []
6
+ srand: ^$seed ret
4
7
 
8
+ ; returns the next number N in the linear congruential generator (better MINSTD)
9
+ ; [] => [N]
5
10
  rand:
6
11
  push $seed dup dup load
7
- push 3,13,10244807 mul mul mul
8
- push 2,32 :pow mod
12
+ push 48271 mul
13
+ push 2,31 :pow $-- mod
9
14
  store load ret
10
15
 
11
- rand_range: ; [a b]
12
- copy 1 sub :rand swap mod add ret
16
+ ; returns a random integer I between A and B (inclusive)
17
+ ; [A B] => [I]
18
+ rand_range:
19
+ $++ copy 1 sub :rand swap mod add ret
20
+
21
+ ; returns an array A of N random integers between 1 and D (inclusive)
22
+ ; [N D] => [A]
23
+ dice: ^-2 dup ^-1 times (push 1 @-2 :rand_range) @-1 ret
24
+
25
+ ; shuffles the array A in-place using the modern Fisher-Yates algorithm
26
+ ; [A] => [A']
27
+ shuffle: dup $-- ^-3
28
+ _shuffle_loop:
29
+ push 0 @-3 :rand_range @-3 :aryswap
30
+ push -3 :dec @-3 push -1 mul jn _shuffle_loop ret
31
+
32
+ ; shuffles the characters of the string S, producing a random anagram
33
+ ; [S] => [S']
34
+ strfry: :strtoa :shuffle pop :strpack ret
@@ -169,8 +169,37 @@ ratinv:
169
169
  ; [R(4,2) 5] => [R(32,1)]
170
170
  ; [R(-3,7) 0] => [R(1,1)]
171
171
  ; [R(-3,14) 2] => [R(9,196)]
172
- ratpow:
172
+ ; [R(5,1) -1] => [R(1,5)]
173
+ ; [R(2,5) -2] => [R(25,4)]
174
+ ; [R(-3,14) -3] => [R(-2744,27)]
175
+ ratpow: dup jn _ratpow_neg
173
176
  swap push 2 :divmod push 2 mul $--
174
177
  swap $RAT :divmod copy 2 mul
175
178
  copy 3 :pow swap copy 3 :pow
176
179
  swap :to_r :ratsimp slide 2 ret
180
+ _ratpow_neg: push -1 mul swap :ratinv swap :ratpow ret
181
+
182
+ ; converts the rational number R to a "pseudo-float" with a whole (W) part
183
+ ; and a fractional (F) part composed of P digits after the decimal point
184
+ ; [R P] => [W F]
185
+ ;
186
+ ; [R(22,7) 2] => [3 14]
187
+ ; [R(355,113) 6] => [3 141592]
188
+ ; [R(8675,309) 10] => [28 744336569] TODO: leading 0 is lost (bug)
189
+ ; [R(2,4) 3] => [0 500]
190
+ to_f: ^-2 :from_r :divmod push 0 swap
191
+ _to_f_loop:
192
+ @-1 :divmod swap
193
+ copy 2 push 10 mul add
194
+ swap push 10 mul
195
+ push 2 :dig
196
+ push -2 dup :dec load :neg? jz _to_f_loop pop ret
197
+
198
+ ; converts the rational number R to a string S of the form
199
+ ; "<simplified numerator>/<simplified denominator>"
200
+ ; [R] => [S]
201
+ ;
202
+ ; [R(22,7)] => ["22/7"]
203
+ ; [R(2,4)] => ["1/2"]
204
+ ; [R(99,9)] => ["11/1"]
205
+ ratstr: :ratsimp :from_r push 2 map (:to_s) push '/' :strjoinc ret
@@ -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
@@ -207,8 +207,10 @@ center: push ' ' :centerc ret
207
207
  ; ["foobar"] => ["fooba"]
208
208
  ; ["abc"] => ["ab"]
209
209
  ; ["a"] => [""]
210
- ; ! [""] => ERROR TODO: Should just be a no-op.
211
- strchop: dup :strlen push 1 sub push 0 swap :strslice ret
210
+ ; [""] => [""]
211
+ strchop: dup jz _strchop_empty
212
+ dup :strlen push 1 sub push 0 swap :strslice ret
213
+ _strchop_empty: ret
212
214
 
213
215
  ; splits string S on delimiting character C, leaving the resultant substrings
214
216
  ; on the stack as a pseudo-array (length at top of stack)
@@ -222,13 +224,13 @@ strchop: dup :strlen push 1 sub push 0 swap :strslice ret
222
224
  ; ["foo,,bar" ','] => ["foo" "" "bar" 3]
223
225
  ; ["/foo/bar/" '/'] => ["" "foo" "bar" "" 4]
224
226
  strsplit:
225
- push -3 push 1 store ; number of found substrings
226
- 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
227
229
  _strsplit_loop:
228
- dup dup push -2 load
230
+ dup dup @-2
229
231
  :strindex dup jn _strsplit_done ; done when index of delimiter is -1
230
232
  push 0 swap :strslice
231
- swap copy 1 push -3 load
233
+ swap copy 1 @-3
232
234
  swap :strlen
233
235
  swap push -3 swap push 1 add store ; update number of found
234
236
  push 1 add push 128 swap :pow div ; shrink haystack
@@ -246,11 +248,10 @@ lines: push 10 :strsplit ret
246
248
  ; ["foo" "bar" "baz" 3 '--'] => ["foo--bar--baz"]
247
249
  ; ["foo" 1 "?!"] => ["foo"]
248
250
  strjoinc:
249
- dup :strlen pop ; get delimiter length into -1
250
- push -2 swap store
251
- 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
252
253
  swap push 128 copy 1 :strlen
253
- push -2 load :strlen
254
+ @-2 :strlen
254
255
  sub :pow mod swap ; remove delimiter from last and flow into strjoin
255
256
 
256
257
  ; concatenates the pseudo-array of strings A into string S
@@ -287,13 +288,12 @@ _strcountc_done: swap slide 2 ret
287
288
  ; ["eunoia" "aeiou"] => [5]
288
289
  ; ["why" "aeiou"] => [0]
289
290
  strcount:
290
- swap push -2 swap store
291
- :strunpack push 0 :to_a
292
- map (push -2 load swap :strcountc)
291
+ swap ^-2 :strunpack push 0 :to_a
292
+ map (@-2 swap :strcountc)
293
293
  reduce (add) ret
294
294
 
295
295
  ; translates all characters in A to the corresponding characters in B
296
- ; in stirng S, ; similar to the `tr` utility in Unix. A and B must be
296
+ ; in string S, ; similar to the `tr` utility in Unix. A and B must be
297
297
  ; of the same length. TODO: make this smarter (ranges, length mismatch)
298
298
  ; ! clobbers heap addresses -1, -2, and -3
299
299
  ; [S A B] => [S']
@@ -301,16 +301,13 @@ strcount:
301
301
  ; ["abcd" "abc" "xyz"] => ["xyzd"]
302
302
  ; ["foobar" "oba" "ele"] => ["feeler"]
303
303
  ; ["abcdcba" "abcd" "xyz|"] => ["xyz|zyx"]
304
- strtrans:
305
- push -3 swap store
306
- push -2 swap store
307
- dup :strlen push -1 swap store
308
- :strunpack push -1 load
304
+ strtrans: ^-3 ^-2
305
+ dup :strlen ^-1 :strunpack @-1
309
306
  map (:_strtrans) pop :strpack ret
310
307
  _strtrans:
311
- dup push -2 load swap :strindex
308
+ dup @-2 swap :strindex
312
309
  dup jn _strtrans_no
313
- push -3 load swap :charat
310
+ @-3 swap :charat
314
311
  slide 1 ret
315
312
  _strtrans_no: pop ret
316
313
 
@@ -343,17 +340,38 @@ _strsqueeze_loop: ; [s]
343
340
  _strsqueeze_skip: pop jump _strsqueeze_loop
344
341
  _strsqueeze_done: pop :strpack :strrev ret
345
342
 
343
+ $_strdel(cmp) {
344
+ :strunpack @-1 :strlen
345
+ select (@-2 swap :strindex `cmp`)
346
+ pop :strpack ret
347
+ }
348
+
349
+ ; returns the string S with all characters in string T removed, like `tr -d`.
350
+ ; If the first character of T is '^', instead only those characters are kept.
351
+ ; [S T] => [S']
352
+ ;
353
+ ; ["abc123" "abc"] => ["123"]
354
+ ; ["abc123" "123"] => ["abc"]
355
+ ; ["abcba12321" "abc"] => ["12321"]
356
+ ; ["abc12321cba" "^2ac"] => ["ac22ca"]
357
+ ; ["facetious" "^aeiou"] => ["aeiou"]
358
+ strdel: push -1 copy 2 store push -2 copy 1 store
359
+ push 0 :charat push '^' :neq jz _strdel_comp $_strdel(:neg?)
360
+ _strdel_comp: $_strdel(:pos?)
361
+
346
362
  ; returns the sum of the ordinal values of the characters in string S
347
363
  ; [S] => [N]
348
364
  ;
349
365
  ; ["ABC"] => [198]
350
366
  ; ["012"] => [147]
351
367
  ; ["a"] => [97]
352
- ; [""] => [] TODO: bug, should be 0
353
- strsum: :strunpack push 0 :to_a reduce (add) ret
368
+ ; [""] => [0]
369
+ strsum: dup jz _strsum_empty
370
+ :strunpack push 0 :to_a reduce (add) ret
371
+ _strsum_empty: ret
354
372
 
355
373
  ; rotates the string S to the left N times, wrapping
356
- ; [S N]
374
+ ; [S N] => [S']
357
375
  ;
358
376
  ; ["abc" 0] => ["abc"]
359
377
  ; ["abcd" 1] => ["bcda"]
@@ -362,9 +380,22 @@ strsum: :strunpack push 0 :to_a reduce (add) ret
362
380
  strrotl: push 128 swap copy 2 :strlen mod :pow :divmod :strcat ret
363
381
 
364
382
  ; rotates the string S to the right N times, wrapping
365
- ; [S N]
383
+ ; [S N] => [S']
366
384
  ;
367
385
  ; ["abcd" 1] => ["dabc"]
368
386
  ; ["abcd" 5] => ["dabc"]
369
387
  ; ["foodbar" 3] => ["barfood"]
370
- 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
395
+
396
+ ; frobnicates the string S by XORing all its bytes with 42
397
+ ; [S] => [S']
398
+ ;
399
+ ; ["foobar"] => ["LEEHKX"]
400
+ ; ["LEEHKX"] => ["foobar"]
401
+ memfrob: :strtoa map (push 42 :bxor) pop :strpack ret
@@ -0,0 +1,14 @@
1
+ ;;; Miscellaneous terminal-based functionality
2
+
3
+ import util ; hex2rgb
4
+
5
+ $_setg(code) {
6
+ push 91,27 ochr ochr
7
+ push `code` onum push 50,59 ochr ochr
8
+ :hex2rgb each (push 59 ochr onum)
9
+ push 109 ochr ret
10
+ }
11
+
12
+ setfg: $_setg(38)
13
+ setbg: $_setg(48)
14
+ reset: push 109,91,27 ochr ochr ochr 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!
@@ -15,22 +15,24 @@ import string ; charat, strcat, strindex, strlen
15
15
  ; [0 -2] => [0 -1 -2]
16
16
  ; [-3 3] => [-3 -2 -1 0 1 2 3]
17
17
  ; [3 -3] => [3 2 1 0 -1 -2 -3]
18
- ; [4 4] => [4 5] TODO: bug
19
- range: dup copy 2 sub jn _range_down
18
+ ; [4 4] => [4]
19
+ range: dup copy 2 sub jz _range_one dup copy 2 sub jn _range_down
20
20
  copy 1 push 1 add swap
21
21
  copy 1 copy 1 sub jn range pop ret
22
+ _range_one: pop ret
22
23
  _range_down:
23
24
  copy 1 push 1 sub swap
24
25
  dup copy 2 sub jn _range_down pop ret
25
26
 
26
27
  $range_loop(fn, cmp) {
27
28
  `fn`:
28
- copy 1 copy 1 add swap dup
29
- copy 2 add push -1 load `cmp` jz `fn` pop ret
29
+ dup copy 2 add @-1 `cmp` jz _`fn`_done
30
+ copy 1 copy 1 add swap jump `fn`
31
+ _`fn`_done: pop ret
30
32
  }
31
33
 
32
- ; inserts between the top two stack values the intervening consecutive elements,
33
- ; counting by `step` up/down to (but never beyond) J
34
+ ; inserts between I and J the intervening consecutive elements, counting by
35
+ ; step S up/down to (but never beyond) J
34
36
  ; [I J S] => [I I±S ... ~J]
35
37
  ;
36
38
  ; [1 4 1] => [1 2 3 4]
@@ -39,12 +41,19 @@ $range_loop(fn, cmp) {
39
41
  ; [10 2 -3] => [10 7 4]
40
42
  ; [-5 25 10] => [-5 5 15 25]
41
43
  ; [25 -5 -10] => [25 15 5 -5]
42
- steprange: swap push -1 swap store dup copy 2 sub jn _steprange_down_loop
43
- $range_loop(_steprange_loop, :gt)
44
- $range_loop(_steprange_down_loop, :lt)
45
-
46
- ; prints the string at the top of the stack and halts execution
47
- die!: :println exit
44
+ ; [4 20 3] => [4 7 10 13 16 19]
45
+ ; [20 4 -3] => [20 17 14 11 8 5]
46
+ ; [3 9 7] => [3]
47
+ ; [9 3 -7] => [9]
48
+ steprange: swap push -1 copy 1 store copy 2 sub
49
+ jn _steprange_down_loop jump _steprange_loop ; prevent DCE
50
+ $range_loop(_steprange_loop, :lte)
51
+ $range_loop(_steprange_down_loop, :gte)
52
+
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
48
57
 
49
58
  ; for stoi and itos
50
59
  alpha: push "0123456789abcdefghijklmnopqrstuvwxyz" ret
@@ -65,7 +74,7 @@ alpha: push "0123456789abcdefghijklmnopqrstuvwxyz" ret
65
74
  ; ["-123" 10] => [-123]
66
75
  ; ["-ff" 16] => [-255]
67
76
  stoi: swap dup :_stoi_sign swap copy 1 :eq
68
- push 2 mul $-- push -2 swap store push 0
77
+ push 2 mul $-- ^-2 push 0
69
78
  _stoi_loop: ; [b s a]
70
79
  swap dup jz _stoi_done
71
80
  swap copy 2 copy 2
@@ -76,10 +85,11 @@ _stoi_loop: ; [b s a]
76
85
  mul add swap push 128 div swap
77
86
  jump _stoi_loop
78
87
  _stoi_sign: dup push 0 :charat push '-' :eq copy 1 :strlen :strslice ret
79
- _stoi_invalid: pop pop slide 1 swap div push -2 load mul ret
80
- _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
81
90
 
82
91
  ; creature comforts
92
+
83
93
  bin: push 2 :stoi ret
84
94
  oct: push 8 :stoi ret
85
95
  to_i: push 10 :stoi ret
@@ -89,11 +99,13 @@ hex: push 16 :stoi ret
89
99
  ; [N B]
90
100
  ;
91
101
  ; [42 2] => ["101010"]
102
+ ; [-42 2] => ["-101010"]
92
103
  ; [511 8] => ["777"]
93
104
  ; [12345 10] => ["12345"]
105
+ ; [-54321 10] => ["-54321"]
94
106
  ; [57005 16] => ["dead"]
95
107
  ; [81699 17] => ["gabe"]
96
- itos: swap push 0 ; accumulator
108
+ itos: swap push -2 copy 1 :neg? store :abs push 0
97
109
  _itos_loop:
98
110
  swap dup jz _itos_done
99
111
  swap copy 1 copy 3 mod
@@ -101,9 +113,10 @@ _itos_loop:
101
113
  swap :strcat
102
114
  swap copy 2 div
103
115
  swap jump _itos_loop
104
- _itos_done: swap slide 2 ret
116
+ _itos_done: swap slide 2 push 45 @-2 mul swap :strcat ret
105
117
 
106
118
  ; creature comforts
119
+
107
120
  to_bin: push 2 :itos ret
108
121
  to_oct: push 8 :itos ret
109
122
  to_s: push 10 :itos ret
@@ -118,12 +131,10 @@ to_hex: push 16 :itos ret
118
131
  ; [256 16] => [1 0 0 3]
119
132
  digits:
120
133
  copy 1 jz _digits_zero ; special case
121
- push -1 swap store
122
- push -1 swap ; sentinel value
134
+ ^-1 push -1 swap ; sentinel value
123
135
  _digits_loop:
124
136
  dup jz _digits_done
125
- push -1 load :divmod
126
- swap jump _digits_loop
137
+ @-1 :divmod swap jump _digits_loop
127
138
  _digits_zero: dup div ret
128
139
  _digits_done: push 1 sub :to_a ret
129
140
 
@@ -187,6 +198,18 @@ lte: swap ; intentionally flow into gte
187
198
  gte: sub jn _gte_no push 1 ret
188
199
  _gte_no: push 0 ret
189
200
 
201
+ ; returns 1 if the number N is between A and B (inclusive), 0 otherwise
202
+ ; ! A must be <= B for sensible results TODO: bug?
203
+ ; [N A B]
204
+ ;
205
+ ; [5 0 10] => [1]
206
+ ; [11 0 10] => [0]
207
+ ; [4 0 4] => [1]
208
+ ; [-1 0 4] => [0]
209
+ ; [-5 -10 0] => [1]
210
+ ; [3 4 2] => [0]
211
+ between?: copy 2 :gte swap copy 2 :lte mul slide 1 ret
212
+
190
213
  ; Though extremely rare, it's possible that we know a particular value is
191
214
  ; stored in the heap at some key, just not which one. This subroutine takes
192
215
  ; a value V to search for and a starting index I, and either returns the first
@@ -195,3 +218,31 @@ _gte_no: push 0 ret
195
218
  heap_seeking_missile:
196
219
  $++ dup load copy 2 :eq jz heap_search
197
220
  slide 1 ret
221
+
222
+ ; converts the #RRGGBB (leading '#' optional) color string S to
223
+ ; its individual RGB components as integers in the range 0-255
224
+ ; [S] => [R G B]
225
+ ;
226
+ ; ["#000000"] => [0 0 0]
227
+ ; ["ffffff"] => [255 255 255]
228
+ ; ["#102030"] => [16 32 48]
229
+ ; ["c0ffee"] => [192 255 238]
230
+ hex2rgb:
231
+ dup push 0 :charat push '#' :eq push 127 mul $++ div
232
+ push 128,2 :pow :divmod :hex swap
233
+ push 128,2 :pow :divmod :hex swap :hex ret
234
+
235
+ ; converts R, G, and B components to length-6 hexadecimal string S
236
+ ; ! dies if any of the values to convert aren't between 0 and 255
237
+ ; [R G B] => [S]
238
+ ;
239
+ ; [0 0 0] => ["000000"]
240
+ ; [255 255 255] => ["ffffff"]
241
+ ; [16 32 48] => ["102030"]
242
+ ; [192 255 238] => ["c0ffee"]
243
+ rgb2hex:
244
+ push 3 :arydup all (push 0,255 :between?) jz _rgb2hex_invalid
245
+ pop copy 2 push 256,2 :pow mul
246
+ copy 2 push 256 mul add add
247
+ slide 2 :to_hex push 6,48 :rjustc ret
248
+ _rgb2hex_invalid: push "(rgb2hex) invalid RGB" :die!