spitewaste 0.1.008 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -56,3 +56,24 @@ _collatz_done: pop ret
56
56
 
57
57
  ; ruby:
58
58
  ; push "'" :strcat push "ruby -e 'p " swap :strcat shell ret
59
+
60
+ $_to_roman(r, v) {
61
+ push `v` :divmod swap push `r` swap :strrep
62
+ push -2 load swap :strcat push -2 swap store
63
+ }
64
+
65
+ ; converts the number N to a string of roman numerals R
66
+ ; [N] => [R]
67
+ ;
68
+ ; [2020] => ["MMXX"]
69
+ ; [1666] => ["MDCLXVI"]
70
+ ; [1337] => ["MCCCXXXVII"]
71
+ ; [94] => ["XCIV"]
72
+ to_roman: push -2,0 store
73
+ $_to_roman("M", 1000) $_to_roman("CM", 900)
74
+ $_to_roman("D", 500) $_to_roman("CD", 400)
75
+ $_to_roman("C", 100) $_to_roman("XC", 90)
76
+ $_to_roman("L", 50) $_to_roman("XL", 40)
77
+ $_to_roman("X", 10) $_to_roman("IX", 9)
78
+ $_to_roman("V", 5) $_to_roman("IV", 4)
79
+ $_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]
@@ -20,10 +19,10 @@ _pow_done: swap slide 2 ret
20
19
  ;
21
20
  ; [0] => [1], [1] => [1], [2] => [2]
22
21
  ; [3] => [6], [5] => [120], [10] => [3628800]
23
- factorial: push 0 swap :range
22
+ factorial: dup jz _fac_zero push 0 swap :range
24
23
  _fac_loop: swap dup jz _fac_done mul jump _fac_loop
25
-
26
24
  _fac_done: pop ret
25
+ _fac_zero: $++ ret
27
26
 
28
27
  ; returns the integer square root of N
29
28
  ; [N] => [floor(sqrt(N))]
@@ -155,6 +154,8 @@ _divisors_square: slide 1 $-- ret ; de-duplicate when N is a perfect square
155
154
  ; [16 4] => [1820]
156
155
  ; [50 3] => [19600]
157
156
  nCk:
157
+ copy 1 copy 1 sub jn _nCk_empty
158
158
  copy 1 copy 1 sub :factorial
159
159
  swap :factorial mul
160
160
  swap :factorial swap div ret
161
+ _nCk_empty: push 0 slide 2 ret
@@ -10,3 +10,7 @@ rand:
10
10
 
11
11
  rand_range: ; [a b]
12
12
  copy 1 sub :rand swap mod add ret
13
+
14
+ dice:
15
+ push -2 swap store push 1 :aryfill
16
+ map (push -2 load :rand_range) 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: push -2 swap store :from_r :divmod push 0 swap
191
+ _to_f_loop:
192
+ push -1 load :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
@@ -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)
@@ -293,7 +295,7 @@ strcount:
293
295
  reduce (add) ret
294
296
 
295
297
  ; 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
298
+ ; in string S, ; similar to the `tr` utility in Unix. A and B must be
297
299
  ; of the same length. TODO: make this smarter (ranges, length mismatch)
298
300
  ; ! clobbers heap addresses -1, -2, and -3
299
301
  ; [S A B] => [S']
@@ -319,9 +321,10 @@ _strtrans_no: pop ret
319
321
  ; [S] => [S']
320
322
  ;
321
323
  ; ["CJ"] => ["CDEFGHIJ"]
322
- ; ["DA"] => ["DE"] TODO: bug
324
+ ; ["DA"] => ["DCBA"]
323
325
  ; ["af"] => ["abcdef"]
324
326
  ; ["09"] => ["0123456789"]
327
+ ; ["90"] => ["9876543210"]
325
328
  ; ["(1"] => ["()*+,-./01"]
326
329
  strexpand:
327
330
  push 0 swap push 128 :divmod
@@ -342,17 +345,38 @@ _strsqueeze_loop: ; [s]
342
345
  _strsqueeze_skip: pop jump _strsqueeze_loop
343
346
  _strsqueeze_done: pop :strpack :strrev ret
344
347
 
348
+ $_strdel(cmp) {
349
+ :strunpack push -1 load :strlen
350
+ select (push -2 load swap :strindex `cmp`)
351
+ pop :strpack ret
352
+ }
353
+
354
+ ; returns the string S with all characters in string T removed, like `tr -d`.
355
+ ; If the first character of T is '^', instead only those characters are kept.
356
+ ; [S T] => [S']
357
+ ;
358
+ ; ["abc123" "abc"] => ["123"]
359
+ ; ["abc123" "123"] => ["abc"]
360
+ ; ["abcba12321" "abc"] => ["12321"]
361
+ ; ["abc12321cba" "^2ac"] => ["ac22ca"]
362
+ ; ["facetious" "^aeiou"] => ["aeiou"]
363
+ strdel: push -1 copy 2 store push -2 copy 1 store
364
+ push 0 :charat push '^' :neq jz _strdel_comp $_strdel(:neg?)
365
+ _strdel_comp: $_strdel(:pos?)
366
+
345
367
  ; returns the sum of the ordinal values of the characters in string S
346
368
  ; [S] => [N]
347
369
  ;
348
370
  ; ["ABC"] => [198]
349
371
  ; ["012"] => [147]
350
372
  ; ["a"] => [97]
351
- ; [""] => [] TODO: bug, should be 0
352
- strsum: :strunpack push 0 :to_a reduce (add) ret
373
+ ; [""] => [0]
374
+ strsum: dup jz _strsum_empty
375
+ :strunpack push 0 :to_a reduce (add) ret
376
+ _strsum_empty: ret
353
377
 
354
378
  ; rotates the string S to the left N times, wrapping
355
- ; [S N]
379
+ ; [S N] => [S']
356
380
  ;
357
381
  ; ["abc" 0] => ["abc"]
358
382
  ; ["abcd" 1] => ["bcda"]
@@ -361,9 +385,18 @@ strsum: :strunpack push 0 :to_a reduce (add) ret
361
385
  strrotl: push 128 swap copy 2 :strlen mod :pow :divmod :strcat ret
362
386
 
363
387
  ; rotates the string S to the right N times, wrapping
364
- ; [S N]
388
+ ; [S N] => [S']
365
389
  ;
366
390
  ; ["abcd" 1] => ["dabc"]
367
391
  ; ["abcd" 5] => ["dabc"]
368
392
  ; ["foodbar" 3] => ["barfood"]
369
393
  strrotr: push 0 swap sub :strrotl ret
394
+
395
+ ; frobnicates the string S by XORing all its bytes with 42
396
+ ; [S] => [S']
397
+ ;
398
+ ; ["foobar"] => ["LEEHKX"]
399
+ ; ["LEEHKX"] => ["foobar"]
400
+ memfrob:
401
+ dup :strlen pop :strunpack push -1 load $++
402
+ 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
@@ -1,11 +1,10 @@
1
1
  import io ; print, println
2
2
  import string ; strcat
3
+ import util ; die!
3
4
 
4
5
  assert_eq:
5
6
  dup copy 2 sub jz _assert_eq_yes
6
7
  push "expected " :print onum
7
8
  push ", got " :print onum
8
- push " for test " :strcat :print :println
9
- exit
10
-
9
+ push " for test " swap :strcat :die!
11
10
  _assert_eq_yes: pop pop pop ret
@@ -4,18 +4,51 @@ import stack ; to_a
4
4
  import string ; charat, strcat, strindex, strlen
5
5
 
6
6
  ; inserts between the top two stack values the intervening consecutive elements
7
- ; ! TODO: Support step, and negative as well.
8
- ; [I J] => [I I+1 ... J]
7
+ ; supports counting up or down
8
+ ; [I J] => [I I±1 ... J]
9
9
  ;
10
10
  ; [2 5] => [2 3 4 5]
11
+ ; [5 2] => [5 4 3 2]
11
12
  ; [0 4] => [0 1 2 3 4]
13
+ ; [4 0] => [4 3 2 1 0]
12
14
  ; [-2 0] => [-2 -1 0]
15
+ ; [0 -2] => [0 -1 -2]
13
16
  ; [-3 3] => [-3 -2 -1 0 1 2 3]
14
- ; [4 4] => [4 5] TODO: bug
15
- range:
17
+ ; [3 -3] => [3 2 1 0 -1 -2 -3]
18
+ ; [4 4] => [4]
19
+ range: dup copy 2 sub jz _range_one dup copy 2 sub jn _range_down
16
20
  copy 1 push 1 add swap
17
- copy 1 copy 1 sub jn range
18
- pop ret
21
+ copy 1 copy 1 sub jn range pop ret
22
+ _range_one: pop ret
23
+ _range_down:
24
+ copy 1 push 1 sub swap
25
+ dup copy 2 sub jn _range_down pop ret
26
+
27
+ $range_loop(fn, cmp) {
28
+ `fn`:
29
+ dup copy 2 add push -1 load `cmp` jz _`fn`_done
30
+ copy 1 copy 1 add swap jump `fn`
31
+ _`fn`_done: pop ret
32
+ }
33
+
34
+ ; inserts between I and J the intervening consecutive elements, counting by
35
+ ; step S up/down to (but never beyond) J
36
+ ; [I J S] => [I I±S ... ~J]
37
+ ;
38
+ ; [1 4 1] => [1 2 3 4]
39
+ ; [4 1 -1] => [4 3 2 1]
40
+ ; [2 10 3] => [2 5 8]
41
+ ; [10 2 -3] => [10 7 4]
42
+ ; [-5 25 10] => [-5 5 15 25]
43
+ ; [25 -5 -10] => [25 15 5 -5]
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)
19
52
 
20
53
  ; prints the string at the top of the stack and halts execution
21
54
  die!: :println exit
@@ -54,6 +87,7 @@ _stoi_invalid: pop pop slide 1 swap div push -2 load mul ret
54
87
  _stoi_done: swap slide 2 push -2 load mul ret
55
88
 
56
89
  ; creature comforts
90
+
57
91
  bin: push 2 :stoi ret
58
92
  oct: push 8 :stoi ret
59
93
  to_i: push 10 :stoi ret
@@ -63,11 +97,13 @@ hex: push 16 :stoi ret
63
97
  ; [N B]
64
98
  ;
65
99
  ; [42 2] => ["101010"]
100
+ ; [-42 2] => ["-101010"]
66
101
  ; [511 8] => ["777"]
67
102
  ; [12345 10] => ["12345"]
103
+ ; [-54321 10] => ["-54321"]
68
104
  ; [57005 16] => ["dead"]
69
105
  ; [81699 17] => ["gabe"]
70
- itos: swap push 0 ; accumulator
106
+ itos: swap push -2 copy 1 :neg? store :abs push 0
71
107
  _itos_loop:
72
108
  swap dup jz _itos_done
73
109
  swap copy 1 copy 3 mod
@@ -75,9 +111,10 @@ _itos_loop:
75
111
  swap :strcat
76
112
  swap copy 2 div
77
113
  swap jump _itos_loop
78
- _itos_done: swap slide 2 ret
114
+ _itos_done: swap slide 2 push 45,-2 load mul swap :strcat ret
79
115
 
80
116
  ; creature comforts
117
+
81
118
  to_bin: push 2 :itos ret
82
119
  to_oct: push 8 :itos ret
83
120
  to_s: push 10 :itos ret
@@ -134,22 +171,45 @@ _neq_no: push 0 ret
134
171
  ; pops A and B and pushes 1 if A is greater than B, 0 otherwise
135
172
  ; [A B] => [A > B]
136
173
  ;
137
- ; [4 3] => [1]
138
- ; [3 4] => [0]
139
- ; [2 2] => [0]
140
- ; [2 1] => [1]
174
+ ; [4 3] => [1] [3 4] => [0]
175
+ ; [2 2] => [0] [2 1] => [1]
141
176
  gt: swap ; intentionally flow into lt
142
177
 
143
178
  ; pops A and B and pushes 1 if A is less than than B, 0 otherwise
144
179
  ; [A B] => [A < B]
145
180
  ;
146
- ; [3 4] => [1]
147
- ; [4 3] => [0]
148
- ; [2 2] => [0]
149
- ; [1 2] => [1]
181
+ ; [3 4] => [1] [4 3] => [0]
182
+ ; [2 2] => [0] [1 2] => [1]
150
183
  lt: sub jn _lt_yes push 0 ret
151
184
  _lt_yes: push 1 ret
152
185
 
186
+ ; pops A and B and pushes 1 if A is less than or equal to B, 0 otherwise
187
+ ; [A B] => [A > B]
188
+ ;
189
+ ; [2 2] => [1] [2 1] => [0]
190
+ ; [4 3] => [0] [3 4] => [1]
191
+ lte: swap ; intentionally flow into gte
192
+
193
+ ; pops A and B and pushes 1 if A is greater than or equal to B, 0 otherwise
194
+ ; [A B] => [A > B]
195
+ ;
196
+ ; [2 2] => [1] [1 2] => [0]
197
+ ; [3 4] => [0] [4 3] => [1]
198
+ gte: sub jn _gte_no push 1 ret
199
+ _gte_no: push 0 ret
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
+
153
213
  ; Though extremely rare, it's possible that we know a particular value is
154
214
  ; stored in the heap at some key, just not which one. This subroutine takes
155
215
  ; a value V to search for and a starting index I, and either returns the first
@@ -158,3 +218,31 @@ _lt_yes: push 1 ret
158
218
  heap_seeking_missile:
159
219
  $++ dup load copy 2 :eq jz heap_search
160
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!
@@ -64,9 +64,13 @@ maxby_lesser_%1$s: swap jump maxby_resume_%1$s
64
64
  maxby_done_%1$s:
65
65
  SPW
66
66
 
67
- 'minby' => 'maxby (%2$s push -1 mul)',
68
- 'each' => 'dup times (dup call roll %2$s push 1 sub) pop',
69
- 'count' => 'select (%2$s) dup call nslide',
67
+ 'minby' => 'maxby (%2$s push -1 mul)',
68
+ 'each' => 'dup times (dup call roll %2$s push 1 sub) pop',
69
+ 'all' => 'map (%2$s) reduce (add) push -11 load call eq',
70
+ # TODO: optimize any to stop early if possible
71
+ 'any' => 'map (%2$s) reduce (add) push 0 call gt',
72
+ 'none' => 'map (%2$s) reduce (add) push 0 call eq',
73
+ 'count' => 'select (%2$s) dup call nslide',
70
74
  'select' => generate_filter_spw('select', 0, 1),
71
75
  'reject' => generate_filter_spw('reject', 1, 0),
72
76
  }
@@ -11,7 +11,7 @@ module Spitewaste
11
11
  INSTRUCTIONS = /(\S+):|(\b(#{OPERATORS_M2T.keys * ?|})\s+(-?\d\S*)?)/
12
12
  SPECIAL_INSN = /(call|jump|jz|jn)\s+(\S+)/
13
13
 
14
- attr_reader :src, :instructions, :error
14
+ attr_reader :src, :instructions, :error, :symbol_table
15
15
 
16
16
  def initialize program, **options
17
17
  @src = program.dup
@@ -57,14 +57,14 @@ module Spitewaste
57
57
  private
58
58
 
59
59
  def preprocess!
60
- @src.prepend "import syntax\n"
60
+ @src << "\nimport syntax"
61
61
  resolve_imports
62
62
  seed_prng if @seen.include? 'random'
63
63
  resolve_strings
64
- remove_comments
65
64
  add_sugar
66
- fucktionalize
65
+ remove_comments
67
66
  propagate_macros
67
+ fucktionalize
68
68
  end
69
69
 
70
70
  def resolve_imports
@@ -78,7 +78,13 @@ module Spitewaste
78
78
  while @src['import']
79
79
  imports = []
80
80
  @src.gsub!(/import\s+(\S+).*/) {
81
- imports << resolve($1) if @seen.add? $1
81
+ if $1 == ?*
82
+ imports = Dir[LIBSPW + '/*.spw'].map {
83
+ File.read(_1) if @seen.add? File.basename(_1, '.spw')
84
+ }
85
+ else
86
+ imports << resolve($1) if @seen.add? $1
87
+ end
82
88
  '' # remove import statement
83
89
  }
84
90
  @src << imports.join(?\n)
@@ -91,7 +97,7 @@ module Spitewaste
91
97
  end
92
98
 
93
99
  def seed_prng
94
- @src.prepend "push $seed,#{rand 2**31} store $seed = -9001"
100
+ @src.prepend "push $seed,#{rand 2**31} store $seed = -9001\n"
95
101
  end
96
102
 
97
103
  def resolve_strings
@@ -111,6 +117,10 @@ module Spitewaste
111
117
  @src.gsub!(/'(.)'/) { $1.ord }
112
118
  # quick push (`push 1,2,3` desugars to individual pushes)
113
119
  @src.gsub!(/push \S+/) { |m| m.split(?,) * ' push ' }
120
+ # quick store (`^2` = `push 2 swap store`)
121
+ @src.gsub!(/\^(-?\d+)/, 'push \1 swap store')
122
+ # quick load (`@2` = `push 2 load`)
123
+ @src.gsub!(/@(-?\d+)/, 'push \1 load')
114
124
  end
115
125
 
116
126
  def gensym
@@ -160,7 +170,7 @@ module Spitewaste
160
170
  subroutines = {}
161
171
  while label = tokens.shift
162
172
  sublen = tokens.index { |t| t[/:$/] } || tokens.size
163
- subroutines[label.chop] = tokens.shift sublen
173
+ subroutines[label.chop] ||= tokens.shift sublen
164
174
  end
165
175
 
166
176
  # A subroutine may indirectly depend on the one immediately after by