spitewaste 0.1.003 → 0.1.004

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.
@@ -1,77 +1,101 @@
1
+ ;;; Heavy-handed stack manipulation
2
+
3
+ ; These subrtouines do some pretty intricate stack-based operations, relying
4
+ ; heavily on clobbering the heap in order to maintain their "bookkeeping".
5
+ ; Many of them use heap addresses -10 and lower, unbounded, so they're only
6
+ ; meant to be used in a pinch or when there just isn't much of an alternative.
7
+
8
+ import util ; dec
9
+
10
+ ; rolls the Ith element (counting from 0) to the top of the stack,
11
+ ; shifting the elements above it down by 1.
12
+ ; [I]
13
+ ;
14
+ ; [1 2 3 2] => [2 3 1]
15
+ ; [1 2 3 4 1] => [1 2 4 3]
16
+ ; [1 2 3 4 5 3] => [1 3 4 5 2]
1
17
  roll:
2
18
  push -10 dup store ; current heap index kept at -10
3
-
4
19
  _roll_keep: ; [n]
5
20
  dup jz _roll_remove
6
21
  push -10 :dec
7
22
  swap push -10 load swap store
8
23
  push 1 sub jump _roll_keep
9
-
10
24
  _roll_remove:
11
25
  push 10 sub load
12
26
  swap push -10 swap store
13
-
14
27
  _roll_restore: ; i
15
28
  dup load swap push 1 add
16
29
  dup push 10 add jz _roll_done
17
30
  jump _roll_restore
18
-
19
31
  _roll_done: load ret
20
32
 
21
- ;;;
22
-
33
+ ; "buries" X in the stack at index I, counting from 0
34
+ ; [X I]
35
+ ;
36
+ ; [1 2 3 4 2] => [1 4 2 3] ; 2nd element of stack now 4
37
+ ; [1 2 3 4 5 8 5] => [8 1 2 3 4 5]
23
38
  bury:
24
39
  push -10 dup store ; current heap index kept at -10
25
40
  swap push -9 swap store ; preserve element to bury
26
-
27
41
  _bury_keep: ; [n]
28
42
  dup jz _bury_restore
29
43
  push -10 :dec
30
44
  swap push -10 load swap store
31
45
  push 1 sub jump _bury_keep
32
-
33
46
  _bury_restore:
34
47
  push 9 sub load
35
48
  push -10 load :_roll_restore pop ret
36
49
 
37
- ;;;
38
50
 
51
+ ; "digs" out the Ith element of the stack and discards it
52
+ ; [I]
53
+ ;
54
+ ; [1 2 3 4 5 2] => [1 2 4 5]
55
+ ; [1 2 3 4 5 4] => [2 3 4 5]
39
56
  dig: :roll pop ret
40
57
 
41
- ;;;
42
-
43
- to_a:
44
- push -1 swap store
45
- push -10 dup store
46
-
58
+ ; pops elements off the stack until it hits the specified sentinel value S,
59
+ ; pushing them to the resulting pseudo-array. It's often more convenient to
60
+ ; build up a collection in reverse order, and we often don't know in advance
61
+ ; how many elements we'll meet, but we do know to stop at the sentinel.
62
+ ; [S En ... E1 S] => [E1 ... En n]
63
+ ;
64
+ ; [-1 9 8 7 -1] => [7 8 9 3]
65
+ ; [0 'c' 'b' 'a' 0] => ['a' 'b' 'c' 3]
66
+ to_a: push -1 swap store push -10 dup store
47
67
  _to_a_loop:
48
68
  dup push -1 load sub jz _to_a_sentinel
49
69
  push -10 dup :dec load
50
70
  swap store jump _to_a_loop
51
-
52
71
  _to_a_sentinel: pop push -10
53
-
54
72
  _to_a_restore:
55
73
  dup push -10 load sub jz _to_a_done
56
74
  push 1 sub dup load swap
57
75
  jump _to_a_restore
58
-
59
76
  _to_a_done: push -10 swap sub ret
60
77
 
61
- ;;;
62
-
63
- ; dynamic pop ; [n]
64
- npop:
65
- dup jz _npop_done
66
- swap pop push 1 sub jump npop
67
-
78
+ ; pops N elements off the top of the stack
79
+ ; [N]
80
+ ;
81
+ ; [1 2 3 4 5 3] => [1 2]
82
+ ; [1 2 3 4 0] => [1 2 3 4]
83
+ npop: dup jz _npop_done swap pop push 1 sub jump npop
68
84
  _npop_done: pop ret
69
85
 
70
- ; dynamic slide ; [n]
71
- nslide:
72
- swap push -1 swap store
73
- :npop push -1 load ret
74
-
86
+ ; slides N elements off the stack, as if the `slide` operator took an argument
87
+ ; [N]
88
+ ;
89
+ ; [1 2 3 4 5 2] => [1 2 5]
90
+ ; [1 2 3 4 1] => [1 2 4]
91
+ nslide: swap push -1 swap store :npop push -1 load ret
92
+
93
+ ; copies the Nth element to the top of the stack; this does exactly what
94
+ ; a `copy N` instruction would do, but we don't always know N in advance
95
+ ; [N]
96
+ ;
97
+ ; [1 2 3 4 0] => [1 2 3 4 4] ; `copy 0` is just dup
98
+ ; [1 2 3 4 3] => [1 2 3 4 1]
75
99
  ncopy: push -10 dup store
76
100
  _ncopy_loop:
77
101
  dup jz _ncopy_save swap
@@ -1,104 +1,134 @@
1
- import math
2
1
  import case
2
+ import math ; divmod, ilog, max, pow
3
+ import stack ; to_a
4
+ import util ; gt, lt, neq, range
3
5
 
4
6
  ;;; String packing and unpacking
5
7
 
6
8
  ; convert a 0-terminated string on the stack to a single base-128 integer
7
- strpack: push 0 ; tally
9
+ ; [0 ... c b a] => ["abc..."]
10
+ ;
11
+ ; [0 99 98 97] => ["abc"]
12
+ ; [0 99 98 97] => [1634657]
13
+ ; [0] => [0]
14
+ strpack: push 0 ; accumulator
8
15
  _strpack_loop:
9
16
  swap dup jz _strpack_done
10
- copy 1 push 128 mul add
11
- slide 1
17
+ copy 1 push 128 mul add slide 1
12
18
  jump _strpack_loop
13
-
14
19
  _strpack_done: pop :strrev ret
15
20
 
16
21
  ; convert a single base-128 integer to a 0-terminated string on the stack
17
- strunpack:
18
- :strrev push 0 swap ; terminator
19
-
22
+ ; ["abc..."] => [0 ... c b a]
23
+ ;
24
+ ; ["abc"] => [0 99 98 97]
25
+ ; [1634657] => [0 99 98 97]
26
+ ; [0] => [0]
27
+ strunpack: :strrev push 0 swap ; terminator
20
28
  _strunpack_loop:
21
29
  dup jz _strunpack_done
22
30
  dup push 128 mod swap push 128 div
23
31
  jump _strunpack_loop
24
-
25
32
  _strunpack_done: pop ret
26
33
 
27
- ;;;
28
-
29
- ; takes a packed string and just returns it log128 to give the length
30
- strlen: push 128 :ilog ret
31
-
32
- ;;;
34
+ ; returns the length of a packed string, which is just the
35
+ ; value itself log-128, +1 if the integer logarithm isn't exact.
36
+ ; [S] => [len(S)]
37
+ ;
38
+ ; [""] => [0]
39
+ ; ["abc"] => [3]
40
+ ; ["foobar"] => [6]
41
+ strlen: dup push 128 :ilog swap push 128 mod push 0 :neq add ret
33
42
 
34
43
  ; takes two packed strings and returns their concatenation (as a packed string)
35
- strcat:
36
- push 128 copy 2 :strlen :pow
37
- mul add ret
38
-
39
- ;;;
40
-
41
- ; reverses a packed string in-place (heapless) [s] => [s.reverse]
42
- strrev: push 0 swap ; [tally string]
43
-
44
- _strrev_loop: ; [t s]
44
+ ; [S T] => [S+T]
45
+ ;
46
+ ; ["foo" ""] => ["foo"]
47
+ ; ["" "foo"] => ["foo"]
48
+ ; ["foo" "bar"] => ["foobar"]
49
+ strcat: push 128 copy 2 :strlen :pow mul add ret
50
+
51
+ ; reverses a packed string "in-place"
52
+ ; [S] => [reverse(S)]
53
+ ;
54
+ ; ["foo"] => ["oof"]
55
+ ; ["bark"] => ["krab"]
56
+ ; ["ab"] => ["ba"] ['a'] => ['a'] [""] => [""]
57
+ strrev: push 0 swap
58
+ _strrev_loop:
45
59
  dup jz _strrev_done
46
- swap push 128 mul ; [s t]
60
+ swap push 128 mul
47
61
  copy 1 push 128 mod add
48
62
  swap push 128 div
49
63
  jump _strrev_loop
50
-
51
64
  _strrev_done: pop ret
52
65
 
53
- ;;;
54
-
55
- ; takes a packed string, a start index, and a length and returns the
66
+ ; takes a packed string S, a start index I, and a length L and returns the
56
67
  ; corresponding substring (simply by doing division with powers of 128; neat)
68
+ ; [S I L] => [substring]
69
+ ;
70
+ ; ["foobar" 0 6] => ["foobar"]
71
+ ; ["foobar" 1 4] => ["ooba"]
72
+ ; ["foobar" 1 10] => ["oobar"]
73
+ ; ["foobar" 5 1] => ['r']
74
+ ; ["foobar" 6 0] => [""]
57
75
  strslice:
58
76
  swap push 128 swap :pow
59
77
  copy 2 swap div
60
78
  swap push 128 swap :pow
61
79
  mod slide 1 ret
62
80
 
63
- ;;;
64
-
65
- ; index of substring t in string s ; [s t]
81
+ ; returns the index of substring T in string S (or -1 if not found)
82
+ ; [S T] => [index]
83
+ ;
84
+ ; ["foobar" 'o'] => [1]
85
+ ; ["foobar" "ob"] => [2]
86
+ ; ["foobar" ""] => [0]
87
+ ; ["foobar" "bar"] => [3]
88
+ ; ["foobar" "bark"] => [-1]
66
89
  strindex: swap push 0
67
-
68
90
  _strindex_loop: ; [t s i]
69
91
  copy 1 copy 3 :strlen push 0 swap :strslice
70
92
  copy 3 sub jz _strindex_found
71
- push 1 add ; increment index
72
- swap push 128 div dup jz _strindex_no
93
+ push 1 add swap push 128 div dup jz _strindex_no
73
94
  swap jump _strindex_loop
74
-
75
95
  _strindex_no: push -1 slide 3 ret
76
-
77
96
  _strindex_found: slide 2 ret
78
97
 
79
- ;;;
80
-
98
+ ; returns the character at index I in string S
99
+ ; [S I] => [char]
100
+ ;
101
+ ; ["foobar" 1] => ['o']
102
+ ; ["foobar" 3] => ['b']
103
+ ; ["foobar" 5] => ['r']
104
+ ; ["foobar" 6] => [""]
81
105
  charat: push 1 :strslice ret
82
106
 
83
- ;;;
84
-
85
- ; 1 if the character at the top of the stack is alphabetical, 0 otherwise
107
+ ; returns 1 if the character at the top of the stack is
108
+ ; alphabetical (ASCII 65-90 or 97-122), 0 otherwise
109
+ ; [C] => [0/1]
110
+ ;
111
+ ; ['@'] => [0] ['a'] => [1]
112
+ ; ['z'] => [1] ['['] => [0]
113
+ ; ['`'] => [0] ['A'] => [1]
114
+ ; ['Z'] => [1] ['{'] => [0]
86
115
  isalpha:
87
116
  dup push 123 :lt jz _isalpha_no
88
117
  dup push 64 :gt jz _isalpha_no
89
118
  push 32 mod $-- push 32 mod push 26 :lt ret
90
119
  _isalpha_no: dup sub ret
91
120
 
92
- ;;;
93
-
94
- ; repeat a string s n times [s n]
121
+ ; returns string S replicated N times
122
+ ; [S N] => [S+S+...]
123
+ ;
124
+ ; ["abc" 1] => ["abc"]
125
+ ; ["abc" 2] => ["abcabc"]
126
+ ; ["abc" 0] => [""]
95
127
  strrep: push 0 swap
96
- _strrep_loop: ; [s t n]
128
+ _strrep_loop:
97
129
  dup jz _strrep_done
98
130
  swap copy 2 :strcat
99
- swap push 1 sub
100
- jump _strrep_loop
101
-
131
+ swap push 1 sub jump _strrep_loop
102
132
  _strrep_done: swap slide 2 ret
103
133
 
104
134
  ;;; String alignment
@@ -107,19 +137,48 @@ _strrep_done: swap slide 2 ret
107
137
  ; we swap before calling strcat.
108
138
  _justc: swap copy 2 :strlen sub push 0 :max :strrep ret
109
139
 
110
- ; Left-justify string s to width w with character c. [s w c]
140
+ ; left-justifies string S to width W with character C
141
+ ; [S W C] => [S, left-justified]
142
+ ;
143
+ ; ["foo" 5 'x'] => ["fooxx"]
144
+ ; ["foobar" 4 'x'] => ["foobar"]
145
+ ; ["" 3 'x'] => ["xxx"]
111
146
  ljustc: :_justc :strcat ret
112
147
 
113
- ; Left-justify string s to width w with spaces. [s w]
148
+ ; left-justifies string S to width W with spaces
149
+ ; [S W] => [S, left-justified]
150
+ ;
151
+ ; ["foo" 5] => ["foo "]
152
+ ; ["foobar" 4] => ["foobar"]
153
+ ; ["" 3] => [528416]
114
154
  ljust: push ' ' :ljustc ret
115
155
 
116
- ; Right-justify string s to width w with character c. [s w c]
156
+ ; right-justifies string S to width W with character C
157
+ ; [S W C] => [S, right-justified]
158
+ ;
159
+ ; ["foo" 5 'x'] => ["xxfoo"]
160
+ ; ["foobar" 4 'x'] => ["foobar"]
161
+ ; ["" 3 'x'] => ["xxx"]
117
162
  rjustc: :_justc swap :strcat ret
118
163
 
119
- ; Right-justify string s to width w with spaces. [s w]
164
+ ; right-justifies string S to width W with spaces
165
+ ; [S W C] => [S, right-justified]
166
+ ;
167
+ ; ["foo" 5] => [" foo"]
168
+ ; ["foobar" 4] => ["foobar"]
169
+ ; ["" 3] => [528416]
120
170
  rjust: push ' ' :rjustc ret
121
171
 
122
- ; Center string s to width w with character c. [s w c]
172
+ ; centers string S to width W with character C, favoring left alignment when
173
+ ; there's a parity mismatch (even-length string to odd width or vice versa)
174
+ ; ! TODO: This seems unnecessarily intricate, but perhaps just its nature.
175
+ ; [S W C] => [S, centered]
176
+ ;
177
+ ; ["abc" 7 'x'] => ["xxabcxx"]
178
+ ; ["abc" 6 'x'] => ["xabcxx"]
179
+ ; ["abcd" 6 'o'] => ["oabcdo"]
180
+ ; ["abcd" 7 'o'] => ["oabcdoo"]
181
+ ; ["abcd" 3 '!'] => ["abcd"]
123
182
  centerc:
124
183
  swap dup copy 3 :strlen sub
125
184
  push 0 :max push 2 div
@@ -130,22 +189,42 @@ centerc:
130
189
  copy 2 swap :strrep :strcat
131
190
  slide 2 ret
132
191
 
133
- ; Center string s to width w with spaces. [s w]
192
+ ; centers string S to width W with spaces
193
+ ; [S W] => [S, centered]
194
+ ;
195
+ ; ["abc" 7] => [" abc "]
196
+ ; ["abc" 6] => [" abc "]
197
+ ; ["abcd" 6] => [" abcd "]
198
+ ; ["abcd" 7] => [" abcd "]
199
+ ; ["abcd" 3] => ["abcd"]
134
200
  center: push ' ' :centerc ret
135
201
 
136
202
  ;;;
137
203
 
138
- ; remove the last character of a string
139
- strchop:
140
- dup :strlen push 1 sub
141
- push 0 swap :strslice ret
142
-
143
- ; Split string s on delimiting character d.
144
- ; ! clobbers heap addresses -1 and -2
145
- strsplit: ; [s d]
204
+ ; removes the last character of a string
205
+ ; [S] => [S.chop]
206
+ ;
207
+ ; ["foobar"] => ["fooba"]
208
+ ; ["abc"] => ["ab"]
209
+ ; ["a"] => [""]
210
+ ; ! [""] => ERROR TODO: Should just be a no-op.
211
+ strchop: dup :strlen push 1 sub push 0 swap :strslice ret
212
+
213
+ ; splits string S on delimiting character C, leaving the resultant substrings
214
+ ; on the stack as a pseudo-array (length at top of stack)
215
+ ; ! TODO: permit string delimiter
216
+ ; ! clobbers heap addresses -1 (strlen), -2, and -3
217
+ ; [S C] => [A]
218
+ ;
219
+ ; ["fooxbar" 'x'] => ["foo" "bar" 2]
220
+ ; ["foobar" 'x'] => ["foobar" 1]
221
+ ; ["foo|bar|baz" '|'] => ["foo" "bar" "baz" 3]
222
+ ; ["foo,,bar" ','] => ["foo" "" "bar" 3]
223
+ ; ["/foo/bar/" '/'] => ["" "foo" "bar" "" 4]
224
+ strsplit:
146
225
  push -3 push 1 store ; number of found substrings
147
226
  push -2 swap store ; stash delimiter to allow some stack juggling
148
- _strsplit_loop: ; [s]
227
+ _strsplit_loop:
149
228
  dup dup push -2 load
150
229
  :strindex dup jn _strsplit_done ; done when index of delimiter is -1
151
230
  push 0 swap :strslice
@@ -154,80 +233,137 @@ _strsplit_loop: ; [s]
154
233
  swap push -3 swap push 1 add store ; update number of found
155
234
  push 1 add push 128 swap :pow div ; shrink haystack
156
235
  jump _strsplit_loop
157
-
158
236
  _strsplit_done: push 2 sub slide 1 load ret
159
237
 
160
- ;;;
238
+ ; splits the string S on newlines
239
+ lines: push 10 :strsplit ret
161
240
 
162
- ; [...strs n delim]
163
- ; ! clobbers heap address -2 (strlen uses -1)
241
+ ; joins the pseudo-array of strings A into string S with delimiter string D
242
+ ; ! clobbers heap address -2 (and strlen uses -1)
243
+ ; [A D] => [S]
244
+ ;
245
+ ; ["foo" "bar" 2 'x'] => ["fooxbar"]
246
+ ; ["foo" "bar" "baz" 3 '--'] => ["foo--bar--baz"]
247
+ ; ["foo" 1 "?!"] => ["foo"]
164
248
  strjoinc:
165
249
  dup :strlen pop ; get delimiter length into -1
166
250
  push -2 swap store
167
251
  map (push -2 load :strcat) ; add delimiter to all elements
168
252
  swap push 128 copy 1 :strlen
169
253
  push -2 load :strlen
170
- sub :pow mod swap ; remove delimiter from last and flow
254
+ sub :pow mod swap ; remove delimiter from last and flow into strjoin
255
+
256
+ ; concatenates the pseudo-array of strings A into string S
257
+ ; [A] => [S]
258
+ ;
259
+ ; ["foo" 1] => ["foo"]
260
+ ; ["foo" "bar" 2] => ["foobar"]
261
+ ; ["foo" 'x' "bar" 'x' "baz" 5] => ["fooxbarxbaz"]
171
262
  strjoin: reduce (:strcat) ret
172
263
 
173
- ;;;
174
-
175
- ; return the number of ocurrences of character c in string s [s c]
264
+ ; returns the number of ocurrences of character C in string S
265
+ ; [S C] => [N]
266
+ ;
267
+ ; ["foobar" 'a'] => [1]
268
+ ; ["foobar" 'o'] => [2]
269
+ ; ["foobar" 'c'] => [0]
176
270
  strcountc: swap push 0 swap
177
-
178
- _strcountc_loop: ; c t s
271
+ _strcountc_loop:
179
272
  dup jz _strcountc_done
180
273
  dup push 128 mod copy 3 sub jz _strcountc_yes
181
274
  push 128 div jump _strcountc_loop
182
-
183
275
  _strcountc_yes:
184
276
  swap push 1 add swap push 128 div
185
277
  jump _strcountc_loop
186
-
187
278
  _strcountc_done: swap slide 2 ret
188
279
 
189
- ; number of ocurrences of all characters in string c [s c]
280
+ ; returns the total number of ocurrences of all characters in string T in string S
281
+ ; [S T] => [N]
190
282
  ; ! clobbers heap address -2
283
+ ;
284
+ ; ["foobar" 'o'] => [2]
285
+ ; ["foobar" "ob"] => [3]
286
+ ; ["foxboar" "box"] => [4]
287
+ ; ["eunoia" "aeiou"] => [5]
288
+ ; ["why" "aeiou"] => [0]
191
289
  strcount:
192
290
  swap push -2 swap store
193
291
  :strunpack push 0 :to_a
194
292
  map (push -2 load swap :strcountc)
195
293
  reduce (add) ret
196
294
 
197
- ;;;
198
-
199
- ; Convert all characters in src to the corresponding characters in dest.
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
297
+ ; of the same length. TODO: make this smarter (ranges, length mismatch)
200
298
  ; ! clobbers heap addresses -1, -2, and -3
201
- strtrans: ; [string src dest]
299
+ ; [S A B] => [S]
300
+ ;
301
+ ; ["abcd" "abc" "xyz"] => ["xyzd"]
302
+ ; ["foobar" "oba" "ele"] => ["feeler"]
303
+ ; ["abcdcba" "abcd" "xyz|"] => ["xyz|zyx"]
304
+ strtrans:
202
305
  push -3 swap store
203
306
  push -2 swap store
204
307
  dup :strlen push -1 swap store
205
308
  :strunpack push -1 load
206
- map (:_strtrans) pop
207
- :strpack ret
208
-
309
+ map (:_strtrans) pop :strpack ret
209
310
  _strtrans:
210
311
  dup push -2 load swap :strindex
211
312
  dup jn _strtrans_no
212
313
  push -3 load swap :charat
213
314
  slide 1 ret
214
-
215
315
  _strtrans_no: pop ret
216
316
 
317
+ ; expands the length-2 string S to contain the intervening ASCII characters
318
+ ; ! TODO: make this smarter; multiple ranges in one string
319
+ ; [S] => [S, expanded]
320
+ ;
321
+ ; ["CJ"] => ["CDEFGHIJ"]
322
+ ; ["DA"] => ["DE"] TODO: bug
323
+ ; ["af"] => ["abcdef"]
324
+ ; ["09"] => ["0123456789"]
325
+ ; ["(1"] => ["()*+,-./01"]
217
326
  strexpand:
218
327
  push 0 swap push 128 :divmod
219
328
  swap :range :strpack :strrev ret
220
329
 
221
- strsqueeze: push 0 swap ; [sentinel s]
330
+ ; "squeezes" runs of the same character in string S to just one occurrence
331
+ ; [S] => [S, squeezed]
332
+ ;
333
+ ; ["abc"] => ["abc"]
334
+ ; ["foobar"] => ["fobar"]
335
+ ; ["bookkeeper"] => ["bokeper"]
336
+ ; ["xxxxxxx"] => ["x"]
337
+ strsqueeze: push 0 swap
222
338
  _strsqueeze_loop: ; [s]
223
339
  dup jz _strsqueeze_done
224
340
  push 128 :divmod dup copy 3 sub jz _strsqueeze_skip
225
341
  swap jump _strsqueeze_loop
226
-
227
342
  _strsqueeze_skip: pop jump _strsqueeze_loop
228
-
229
343
  _strsqueeze_done: pop :strpack :strrev ret
230
344
 
231
- strsum: :strunpack push 0 :to_a :arysum ret
232
-
233
- lines: push 10 :strsplit ret
345
+ ; returns the sum of the ordinal values of the characters in string S
346
+ ; [S] => [N]
347
+ ;
348
+ ; ["ABC"] => [198]
349
+ ; ["012"] => [147]
350
+ ; ["a"] => [97]
351
+ ; [""] => [] TODO: bug, should be 0
352
+ strsum: :strunpack push 0 :to_a reduce (add) ret
353
+
354
+ ; rotates the string S to the left N times, wrapping
355
+ ; [S N]
356
+ ;
357
+ ; ["abc" 0] => ["abc"]
358
+ ; ["abcd" 1] => ["bcda"]
359
+ ; ["abcd" 5] => ["bcda"]
360
+ ; ["foodbar" 4] => ["barfood"]
361
+ strrotl: push 128 swap copy 2 :strlen mod :pow :divmod :strcat ret
362
+
363
+ ; rotates the string S to the right N times, wrapping
364
+ ; [S N]
365
+ ;
366
+ ; ["abcd" 1] => ["dabc"]
367
+ ; ["abcd" 5] => ["dabc"]
368
+ ; ["foodbar" 3] => ["barfood"]
369
+ strrotr: push 0 swap sub :strrotl ret