spitewaste 0.1.002 → 0.1.007
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -6
- data/Rakefile +30 -11
- data/bin/spw +1 -0
- data/lib/spitewaste.rb +1 -1
- data/lib/spitewaste/assembler.rb +4 -4
- data/lib/spitewaste/cli.rb +21 -19
- data/lib/spitewaste/cli/asm.rb +2 -0
- data/lib/spitewaste/cli/compile.rb +2 -0
- data/lib/spitewaste/cli/convert.rb +2 -0
- data/lib/spitewaste/cli/docs.rb +51 -0
- data/lib/spitewaste/cli/exec.rb +12 -13
- data/lib/spitewaste/cli/image.rb +2 -0
- data/lib/spitewaste/libspw/array.spw +75 -20
- data/lib/spitewaste/libspw/bits.spw +70 -68
- data/lib/spitewaste/libspw/case.spw +34 -26
- data/lib/spitewaste/libspw/docs.json +1 -0
- data/lib/spitewaste/libspw/fun.spw +40 -24
- data/lib/spitewaste/libspw/io.spw +2 -0
- data/lib/spitewaste/libspw/math.spw +83 -45
- data/lib/spitewaste/libspw/prime.spw +36 -35
- data/lib/spitewaste/libspw/random.spw +2 -0
- data/lib/spitewaste/libspw/rational.spw +153 -0
- data/lib/spitewaste/libspw/stack.spw +54 -30
- data/lib/spitewaste/libspw/string.spw +228 -92
- data/lib/spitewaste/libspw/test.spw +3 -0
- data/lib/spitewaste/libspw/util.spw +97 -41
- data/lib/spitewaste/parsers/spitewaste.rb +23 -17
- data/lib/spitewaste/version.rb +1 -1
- data/spitewaste.gemspec +17 -12
- metadata +63 -8
- data/demo/factorial-nicespace.png +0 -0
- data/demo/factorial.asm +0 -47
- data/demo/factorial.png +0 -0
- data/demo/factorial.wsa +0 -5
@@ -1,46 +1,47 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import math ; divmod
|
2
|
+
import util ; inc
|
3
|
+
|
4
|
+
; returns 1 if the number N is prime, 0 otherwise
|
5
|
+
; [N] => [0 | 1]
|
6
|
+
;
|
7
|
+
; [0] => [0] [1] => [0]
|
8
|
+
; [2] => [1] [4] => [0]
|
9
|
+
; [3] => [1] [9] => [0]
|
10
|
+
prime?:
|
3
11
|
dup push 3 sub jn _prime_special ; special-case < 3
|
4
12
|
dup push 2 mod jz _prime_even ; otherwise, even -> false
|
5
|
-
push 3
|
6
|
-
|
7
|
-
|
8
|
-
copy 1 copy 1 dup mul sub jn _prime_yes ; divisor > isqrt(n), n is prime
|
13
|
+
push 3 ; initial divisor to check
|
14
|
+
_prime_loop:
|
15
|
+
copy 1 copy 1 dup mul sub jn _prime_yes ; divisor > isqrt(n), so n is prime
|
9
16
|
copy 1 copy 1 mod jz _prime_no
|
10
17
|
push 2 add jump _prime_loop
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
_prime_two:
|
25
|
-
push 1 ret
|
26
|
-
|
27
|
-
;;;
|
28
|
-
|
18
|
+
_prime_even: dup sub ret
|
19
|
+
_prime_no: pop dup sub ret ; return 0
|
20
|
+
_prime_yes: pop dup div ret ; return 1
|
21
|
+
_prime_special: push 2 :eq ret
|
22
|
+
|
23
|
+
; returns the first prime number P greater than N
|
24
|
+
; [N] => [P]
|
25
|
+
;
|
26
|
+
; [0] => [2] [1] => [2]
|
27
|
+
; [2] => [3] [3] => [5]
|
28
|
+
; [13] => [17]
|
29
|
+
; [100] => [101]
|
29
30
|
next_prime: push 1 add dup :prime? jz next_prime ret
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
;
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
32
|
+
; returns the prime factorization of N as a pseudo-array
|
33
|
+
; ! N must be greater than 1
|
34
|
+
; [N] => [A]
|
35
|
+
;
|
36
|
+
; [2] => [2 1]
|
37
|
+
; [8] => [2 2 2 3]
|
38
|
+
; [15] => [3 5 2]
|
39
|
+
; [17] => [17 1]
|
40
|
+
; [100] => [2 2 5 5 4]
|
41
|
+
factor: push 2 swap push -2,0 store
|
42
|
+
_factor_loop: dup push 2 sub jn _factor_done jump _divisor_loop
|
40
43
|
_divisor_loop:
|
41
44
|
dup copy 2 :divmod jz _divisor_keep
|
42
45
|
pop swap :next_prime swap jump _factor_loop
|
43
|
-
|
44
46
|
_divisor_keep: push -2 :inc slide 1 copy 1 swap jump _factor_loop
|
45
|
-
|
46
47
|
_factor_done: slide 1 push 3 sub load ret
|
@@ -0,0 +1,153 @@
|
|
1
|
+
;;; Rational numbers
|
2
|
+
|
3
|
+
import math ; abs, divmod, gcd, pow, sign
|
4
|
+
import util ; die!, neq
|
5
|
+
|
6
|
+
; maximum value of either component of a rational number before behavior is
|
7
|
+
; undefined; 2^31 by default to give interpreters without bignums a chance,
|
8
|
+
; but customizable from userland.
|
9
|
+
$RAT = push 2,31 :pow
|
10
|
+
|
11
|
+
; encodes a numerator N and a denominator D as a rational number R with the
|
12
|
+
; following structure: ((N × $RAT + D) << 1) + sign (0 for negative, else 1).
|
13
|
+
; This representation is nice because, with care, it makes it so that we never
|
14
|
+
; have to involve the heap (besides divmod) just to do fairly basic arithmetic.
|
15
|
+
; [N D] => [R]
|
16
|
+
;
|
17
|
+
; [22 7] => [R(22,7)]
|
18
|
+
; [4 8] => [R(4,8)] ; no implicit simplification TODO: make it configurable?
|
19
|
+
; [5 1] => [R(5,1)] ; no conversion to integer (duh)
|
20
|
+
; [-3 4] => [R(-3,4)]
|
21
|
+
; [3 -4] => [R(-3,4)] ; sign always held in the numerator
|
22
|
+
; [-1 -3] => [R(1,3)]
|
23
|
+
to_r: dup jz _to_r_dbz
|
24
|
+
dup :sign copy 2 :sign mul push -1 :neq
|
25
|
+
copy 2 :abs copy 2 :abs
|
26
|
+
$RAT copy 2 mul add push 2 mul
|
27
|
+
copy 2 add slide 4 ret
|
28
|
+
_to_r_dbz: push "0 in denominator!" :die!
|
29
|
+
|
30
|
+
; returns the numerator N of the rational number R, which may be negative
|
31
|
+
; [R] => [N]
|
32
|
+
;
|
33
|
+
; [R(22,7)] => [22]
|
34
|
+
; [R(-3,4)] => [-3]
|
35
|
+
; [R(3,-4)] => [-3]
|
36
|
+
ratnum: push 2 :divmod push 2 mul push 1 sub swap $RAT div mul ret
|
37
|
+
|
38
|
+
; returns the denominator D of the rational number R; always positive
|
39
|
+
; [R] => [D]
|
40
|
+
;
|
41
|
+
; [R(22,7)] => [7]
|
42
|
+
; [R(-3,4)] => [4]
|
43
|
+
; [R(3,-4)] => [4]
|
44
|
+
ratden: push 2 div $RAT mod ret
|
45
|
+
|
46
|
+
; decomposes the rational number R into its numerator N and denominator D
|
47
|
+
; [R] => [N D]
|
48
|
+
;
|
49
|
+
; [R(22,7)] => [22 7]
|
50
|
+
; [R(-3,4)] => [-3 4]
|
51
|
+
; [R(3,-4)] => [-3 4]
|
52
|
+
; [R(4,8)] => [4 8] ; no implicit simplification
|
53
|
+
from_r: dup :ratnum swap :ratden ret
|
54
|
+
|
55
|
+
; fully simplifies the rational number R by dividing both of its components
|
56
|
+
; by their greatest common divisor
|
57
|
+
; [R] => [R, simplified]
|
58
|
+
;
|
59
|
+
; [R(4,8)] => [R(1,2)]
|
60
|
+
; [R(-3,12)] => [R(-1,4)]
|
61
|
+
; [R(17,-51)] => [R(-1,3)]
|
62
|
+
ratsimp:
|
63
|
+
push 2 :divmod swap $RAT :divmod dup copy 2 :gcd
|
64
|
+
swap copy 1 div copy 2 copy 2 div swap :to_r
|
65
|
+
slide 2 push 2 div push 2 mul add ret
|
66
|
+
|
67
|
+
; helper prologue shared by all but ratmul
|
68
|
+
_rathead:
|
69
|
+
copy 1 :ratnum copy 1 :ratden mul
|
70
|
+
copy 2 :ratden copy 2 :ratnum mul ret
|
71
|
+
|
72
|
+
; helper epilogue shared by all but ratdiv
|
73
|
+
_rattail:
|
74
|
+
copy 2 :ratden copy 2 :ratden mul
|
75
|
+
:to_r :ratsimp slide 2 ret
|
76
|
+
|
77
|
+
; returns the simplified sum of the rational numbers Ra and Rb
|
78
|
+
; [Ra Rb] => [Ra + Rb]
|
79
|
+
;
|
80
|
+
; [R(-9,-14) R(-3,19)] => [R(129,266)]
|
81
|
+
; [R(17,30) R(18,10)] => [R(71,30)]
|
82
|
+
; [R(-27,14) R(15,-23)] => [R(-831,322)]
|
83
|
+
; [R(3,-9) R(8,3)] => [R(7,3)]
|
84
|
+
; [R(-5,27) R(-2,-27)] => [R(-1,9)]
|
85
|
+
; [R(-27,-8) R(-15,22)] => [R(237,88)]
|
86
|
+
; [R(-9,-29) R(-27,3)] => [R(-252,29)]
|
87
|
+
; [R(2,-21) R(4,6)] => [R(4,7)]
|
88
|
+
ratadd: :_rathead add :_rattail ret
|
89
|
+
|
90
|
+
; returns the simplified difference of the rational numbers Ra and Rb
|
91
|
+
; [Ra Rb] => [Ra - Rb]
|
92
|
+
;
|
93
|
+
; [R(21,25) R(-28,27)] => [R(1267,675)]
|
94
|
+
; [R(14,7) R(13,6)] => [R(-1,6)]
|
95
|
+
; [R(-24,-9) R(-5,-21)] => [R(17,7)]
|
96
|
+
; [R(-27,-2) R(-2,26)] => [R(353,26)]
|
97
|
+
; [R(-27,3) R(2,-22)] => [R(-98,11)]
|
98
|
+
; [R(-4,23) R(-9,13)] => [R(155,299)]
|
99
|
+
; [R(-14,19) R(-18,-11)] => [R(-496,209)]
|
100
|
+
; [R(-29,21) R(-15,-16)] => [R(-779,336)]
|
101
|
+
ratsub: :_rathead sub :_rattail ret
|
102
|
+
|
103
|
+
; returns the simplified product of the rational numbers Ra and Rb
|
104
|
+
; [Ra Rb] => [Ra × Rb]
|
105
|
+
;
|
106
|
+
; [R(-24,26) R(-1,-30)] => [R(-2,65)]
|
107
|
+
; [R(19,4) R(27,2)] => [R(513,8)]
|
108
|
+
; [R(25,27) R(4,-11)] => [R(-100,297)]
|
109
|
+
; [R(1,18) R(4,8)] => [R(1,36)]
|
110
|
+
; [R(1,27) R(-8,29)] => [R(-8,783)]
|
111
|
+
; [R(25,-13) R(-6,24)] => [R(25,52)]
|
112
|
+
; [R(6,-13) R(9,23)] => [R(-54,299)]
|
113
|
+
; [R(11,8) R(-19,-19)] => [R(11,8)]
|
114
|
+
ratmul: copy 1 :ratnum copy 1 :ratnum mul :_rattail ret
|
115
|
+
|
116
|
+
; returns the simplified quotient of the rational numbers Ra and Rb
|
117
|
+
; [Ra Rb] => [Ra / Rb]
|
118
|
+
;
|
119
|
+
; [R(-30,-22) R(15,12)] => [R(12,11)]
|
120
|
+
; [R(13,28) R(-15,29)] => [R(-377,420)]
|
121
|
+
; [R(7,-30) R(-22,-12)] => [R(-7,55)]
|
122
|
+
; [R(15,4) R(8,-8)] => [R(-15,4)]
|
123
|
+
; [R(-23,28) R(-16,-15)] => [R(-345,448)]
|
124
|
+
; [R(-18,12) R(6,18)] => [R(-9,2)]
|
125
|
+
; [R(29,-2) R(11,-21)] => [R(609,22)]
|
126
|
+
; [R(-23,25) R(25,-3)] => [R(69,625)]
|
127
|
+
ratdiv: :_rathead :to_r :ratsimp slide 2 ret
|
128
|
+
|
129
|
+
; returns the simplified modulus of the rational numbers Ra and Rb
|
130
|
+
; [Ra Rb] => [Ra % Rb]
|
131
|
+
;
|
132
|
+
; [R(-15,-3) R(-16,-10)] => [R(1,5)]
|
133
|
+
; [R(4,2) R(21,21)] => [R(0,1)]
|
134
|
+
; [R(24,10) R(-18,-3)] => [R(12,5)]
|
135
|
+
; [R(3,-7) R(-2,16)] => [R(-3,56)]
|
136
|
+
; [R(4,28) R(-29,7)] => [R(-4,1)]
|
137
|
+
; [R(7,-27) R(10,23)] => [R(109,621)]
|
138
|
+
; [R(28,-3) R(30,-12)] => [R(-11,6)]
|
139
|
+
; [R(-29,21) R(19,-23)] => [R(-268,483)]
|
140
|
+
ratmod: :_rathead mod :_rattail ret
|
141
|
+
|
142
|
+
; returns the sign of the rational number R
|
143
|
+
; [R] => [-1 | 0 | 1]
|
144
|
+
;
|
145
|
+
; [R(4,3)] => [1]
|
146
|
+
; [R(4,-3)] => [-1]
|
147
|
+
; [R(0,10)] => [0]
|
148
|
+
; [R(-3,4)] => [-1]
|
149
|
+
; [R(-3,-4)] => [1]
|
150
|
+
ratsign:
|
151
|
+
dup $RAT push 2 mul push 2 add sub jn _ratsign_zero
|
152
|
+
push 2 mod push 2 mul push 1 sub ret
|
153
|
+
_ratsign_zero: pop push 0 ret
|
@@ -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
|
-
|
44
|
-
|
45
|
-
|
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
|
-
;
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
;
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
-
|
18
|
-
|
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
|
-
;
|
30
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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] => [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
|
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] => [S']
|
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
|
-
;
|
81
|
+
; returns the index I of substring T in string S (or -1 if not found)
|
82
|
+
; [S T] => [I]
|
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
|
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 C at index I in string S
|
99
|
+
; [S I] => [C]
|
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
|
-
;
|
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
|
-
;
|
121
|
+
; returns string S replicated N times
|
122
|
+
; [S N] => [S']
|
123
|
+
;
|
124
|
+
; ["abc" 1] => ["abc"]
|
125
|
+
; ["abc" 2] => ["abcabc"]
|
126
|
+
; ["abc" 0] => [""]
|
95
127
|
strrep: push 0 swap
|
96
|
-
_strrep_loop:
|
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
|
-
;
|
140
|
+
; left-justifies string S to width W with character C
|
141
|
+
; [S W C] => [S']
|
142
|
+
;
|
143
|
+
; ["foo" 5 'x'] => ["fooxx"]
|
144
|
+
; ["foobar" 4 'x'] => ["foobar"]
|
145
|
+
; ["" 3 'x'] => ["xxx"]
|
111
146
|
ljustc: :_justc :strcat ret
|
112
147
|
|
113
|
-
;
|
148
|
+
; left-justifies string S to width W with spaces
|
149
|
+
; [S W] => [S']
|
150
|
+
;
|
151
|
+
; ["foo" 5] => ["foo "]
|
152
|
+
; ["foobar" 4] => ["foobar"]
|
153
|
+
; ["" 3] => [528416]
|
114
154
|
ljust: push ' ' :ljustc ret
|
115
155
|
|
116
|
-
;
|
156
|
+
; right-justifies string S to width W with character C
|
157
|
+
; [S W C] => [S']
|
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
|
-
;
|
164
|
+
; right-justifies string S to width W with spaces
|
165
|
+
; [S W C] => [S']
|
166
|
+
;
|
167
|
+
; ["foo" 5] => [" foo"]
|
168
|
+
; ["foobar" 4] => ["foobar"]
|
169
|
+
; ["" 3] => [528416]
|
120
170
|
rjust: push ' ' :rjustc ret
|
121
171
|
|
122
|
-
;
|
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']
|
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
|
-
;
|
192
|
+
; centers string S to width W with spaces
|
193
|
+
; [S W] => [S']
|
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
|
-
;
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
;
|
144
|
-
; !
|
145
|
-
|
204
|
+
; removes the last character of a string
|
205
|
+
; [S] => [S']
|
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:
|
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
|
-
;
|
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
|
-
;
|
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
|
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
|
-
;
|
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
|
-
|
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']
|
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
|
-
|
330
|
+
; "squeezes" runs of the same character in string S to just one occurrence
|
331
|
+
; [S] => [S']
|
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
|
-
|
232
|
-
|
233
|
-
|
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
|