spitewaste 0.1.005 → 0.1.010

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e31b372173f7c653a34f8c084bc2398c5152c5180bb8ab070ea5f56e74f0943
4
- data.tar.gz: 49ca4f1cd8972c0d946350f7c84b5b36daf7158797022ed225cb1623b53b6e25
3
+ metadata.gz: e90a64e56fcb4e3f9d7727b36fbd71c327693bb8b4a4c7f1ff21332e01317215
4
+ data.tar.gz: 4b0abbf0dd1e86a939014a598d844ba56ee85c67df12938fecb5a0273192e2b7
5
5
  SHA512:
6
- metadata.gz: 47ef47beccb9ccb021686a900af3c0db455eeb4d3b770e09bbc49acf5c479b2c24ee8e69c549fce7793b566e2b6fcafb1321f109f6edabb050944a8828e26ced
7
- data.tar.gz: 61b8867940d3783b660fafeb0a27ae18baf426586168a84e1d36a9680c7399e2e79c69d76517c660f400120e76b21fa511e46fcb0ec6b218cf3d77f0342376cc
6
+ metadata.gz: 752150cdcefbcf39a836d3551ece95f0abbcd11a078c61b1283710fa8a6c24c391cd02a5ffba7241eaf839ab1d068bb66e716123b4f1f923b434872aea42a824
7
+ data.tar.gz: e44b79f9bfb77daa2165de8d3a2d2757636af2f6607fd218b12138853cc4c63898105f6ae5d1e6ae7c382a0503058ddbf3eb883ea1a5ea4d4a1d6c6b5c6afd3d
data/README.md CHANGED
@@ -35,8 +35,8 @@ See `spw help` for guidance on invoking the command-line interface, and [this tu
35
35
  * {ws, wsa, asm} to {ws, wsa, asm}
36
36
  * all supported formats to C++ and PNG
37
37
  - [x] Execute all supported formats by converting to Whitespace and passing the results to a user-specified interpreter
38
- - [ ] Thoroughly document the standard library and make it searchable (eventually `spw docs`)
39
- - - [ ] using a doctest-like approach for free specs
38
+ - [x] Thoroughly document the standard library and make it searchable (via `spw docs`)
39
+ - - [x] using a doctest-like approach for free specs
40
40
  - [ ] Write a proper Spitewaste parser for better error reporting (and performance?)
41
41
  - [ ] Support user-specified aliases of the builtin mnemonics in case you wanna say `discard` instead of `pop`, etc.
42
42
  - [ ] Resolve missing identifiers by auto-importing the necessary standard library?
data/Rakefile CHANGED
@@ -14,7 +14,8 @@ task :docs do |t|
14
14
  docs = {}
15
15
 
16
16
  Dir.chdir('lib/spitewaste/libspw') do |d|
17
- %w[rational.spw array.spw stack.spw util.spw string.spw math.spw].each do |path|
17
+ Dir['*.spw'].each do |path|
18
+ next if path['random']
18
19
  lib = File.basename path, '.spw'
19
20
  docs[lib] = extract_docs path
20
21
  end
@@ -1,3 +1,5 @@
1
+ Warning[:experimental] = false
2
+
1
3
  module Spitewaste
2
4
  OPERATORS_M2T = { # mnemonic to tokens
3
5
  push: " ", copy: " \t ", slide: " \t\n", label: "\n ",
@@ -1,6 +1,6 @@
1
1
  module Spitewaste
2
2
  class Assembler
3
- attr_reader :src, :parser, :instructions
3
+ attr_reader :parser
4
4
 
5
5
  def initialize program, **options
6
6
  format = options[:format]
@@ -9,7 +9,7 @@ module Spitewaste
9
9
  raise ArgumentError, "unknown format for parse: #{format}"
10
10
  end
11
11
 
12
- @parser =
12
+ parser =
13
13
  case format
14
14
  when :whitespace
15
15
  WhitespaceParser
@@ -19,9 +19,9 @@ module Spitewaste
19
19
  SpitewasteParser
20
20
  end
21
21
 
22
- parser = @parser.new(program, **options).tap &:parse
23
- @instructions = parser.instructions
24
- @src = parser.src if format == :spitewaste
22
+ @parser = parser.new(program, **options).tap &:parse
23
+ @src = @parser.src if format == :spitewaste
24
+ @instructions = @parser.instructions
25
25
  end
26
26
 
27
27
  def assemble! format:, io: STDOUT, **options
@@ -42,7 +42,52 @@ module Spitewaste
42
42
  else
43
43
  AssemblyEmitter
44
44
  end
45
- emitter.new(format == :wsassembly ? @src : @instructions, **options).emit io: io
45
+
46
+ # Worthwhile optimizations can only be done if we have a symbol table.
47
+ optimize! if parser.respond_to? :symbol_table
48
+
49
+ src = format == :wsassembly ? @src : @instructions
50
+ emitter.new(src, **options).emit io: io
51
+ end
52
+
53
+ def optimize_constant_pow
54
+ pow = parser.symbol_table['pow']
55
+
56
+ case @instructions[@ip - 2, 3]
57
+ in [[:push, base], [:push, exp], [:call, ^pow]]
58
+ @instructions[@ip - 2, 3] = [[:push, base ** exp]]
59
+
60
+ in [[:push, base], [:dup, _], [:call, ^pow]]
61
+ @instructions[@ip - 2, 3] = [[:push, base ** base]]
62
+
63
+ else nil
64
+ end
65
+ end
66
+
67
+ def optimize_constant_strpack
68
+ strpack = parser.symbol_table['strpack']
69
+ return if @instructions[@ip] != [:call, strpack]
70
+
71
+ # grab all the instructions between `push 0` and this `call strpack`
72
+ start = @instructions[0, @ip].rindex [:push, 0]
73
+ between = @instructions[start + 1...@ip]
74
+
75
+ bytes = []
76
+ # optimization only applies if all of the intervening ops are pushes
77
+ return unless between.all? { |op, arg| op == :push && bytes << arg }
78
+
79
+ packed = bytes.reverse.zip(0..).sum { |b, e| b * 128 ** e }
80
+ @instructions[start..@ip] = [[:push, packed]]
81
+ @ip = start + 1
82
+ end
83
+
84
+ def optimize!
85
+ @ip = 0
86
+ while @instructions[@ip]
87
+ next @ip -= 1 if optimize_constant_pow
88
+ next if optimize_constant_strpack
89
+ @ip += 1
90
+ end
46
91
  end
47
92
 
48
93
  def try_elide_main
@@ -29,7 +29,7 @@ class SpitewasteCLI
29
29
 
30
30
  docs.each do |lib, fns|
31
31
  fns.each do |fn, data|
32
- full = data['full']
32
+ full = "#{lib}/#{bold under fn} #{data['full']}"
33
33
  ms = terms.count { |t| full[/#{t}/i] }
34
34
  found << [lib, fn, full, ms] if ms >= min
35
35
  end
@@ -41,9 +41,9 @@ class SpitewasteCLI
41
41
 
42
42
  IO.popen('less -R', 'w') do |io|
43
43
  found.each do |lib, fn, full|
44
- full.gsub! /#{terms * ?|}/, &method(:hi)
44
+ full.gsub! /#{terms * ?|}/i, &method(:hi)
45
45
  desc, specs = full.split "\n\n"
46
- io.puts "#{?- * 10}\n#{lib}/#{bold under fn} #{desc}\n\n"
46
+ io.puts "#{?- * 10}\n#{desc}\n\n"
47
47
  io.puts specs if options[:show_specs]
48
48
  end
49
49
  end
@@ -1,21 +1,57 @@
1
+ ;;; Pseudo-arrays
2
+
3
+ ; An "array" is really just a sequence of elements on the stack followed by
4
+ ; the number of elements, so care must be taken to ensure one knows what kind
5
+ ; of data is currently being operated on.
6
+
1
7
  import math ; divmod, max, min, pos?
2
8
  import stack ; bury, dig, ncopy, nslide, roll
3
9
  import util ; dec, die!, eq
4
10
 
5
11
  $amax = 1000
6
12
 
13
+ ; returns the sum of the array A
14
+ ; [A] => [sum]
15
+ ;
16
+ ; [3 1] => [3]
17
+ ; [4 3 2 1 4] => [10]
18
+ ; [-10 -5 7 5 10 5] => [7]
7
19
  arysum: reduce (add) ret
8
20
 
21
+ ; duplicates the array at the top of the stack
22
+ ; [A] => [A A]
23
+ ;
24
+ ; [1 2 3 3] => [1 2 3 3 1 2 3 3]
25
+ ; [7 50 10 2] => [7 50 10 2 50 10 2]
9
26
  arydup:
10
27
  dup $++ push -2 copy 1 store
11
28
  times (push -2 load $-- :ncopy) ret
12
29
 
30
+ ; removes the element at the end of the array A
31
+ ; [A] => [A']
32
+ ;
33
+ ; [10 1] => [0]
34
+ ; [1 2 3 4 4] => [1 2 3 3]
35
+ ; [7 10 20 30 3] => [7 10 20 2]
13
36
  arypop: slide 1 $-- ret
14
37
 
38
+ ; removes the element at the beginning of the array A
39
+ ; [A] => [A']
40
+ ;
41
+ ; [10 1] => [0]
42
+ ; [1 2 3 4 4] => [2 3 4 3]
43
+ ; [7 10 20 30 3] => [7 20 30 2]
15
44
  aryshift: dup :dig $-- ret
16
45
 
46
+ ; returns the concatenation of arrays A and B
47
+ ; [A B] => [A+B]
48
+ ;
49
+ ; [1 2 3 3 4 5 6 3] => [1 2 3 4 5 6 6]
50
+ ; [7 10 2 5 4 3 3] => [7 10 5 4 3 5]
17
51
  arycat: dup $++ :roll add ret
18
52
 
53
+ ;;;
54
+
19
55
  arypack:
20
56
  :arydup reduce (:max) $++ push -1 swap store
21
57
  $-- times (push -1 load mul add)
@@ -30,36 +66,43 @@ aryunpack:
30
66
 
31
67
  arylen: push $amax mod ret
32
68
 
33
- ;;;
34
-
69
+ ; returns the index I of element E in array A (or -1 if not found)
70
+ ; [A E] => [I]
71
+ ;
72
+ ; [1 2 3 4 4 2] => [1]
73
+ ; [1 2 3 4 4 4] => [3]
74
+ ; [1 2 3 4 4 5] => [-1]
35
75
  aryindex: copy 1 ; two copies of length, one gets decremented, index is diff
36
- _aryindex_loop: ; [h n l]
37
- dup jn _aryindex_notfound
76
+ _aryindex_loop: dup jn _aryindex_notfound
38
77
  dup push 2 add :roll copy 2 :eq jz _aryindex_no
39
78
  copy 2 copy 1 sub swap $++ :nslide ret
40
-
41
79
  _aryindex_no: $-- jump _aryindex_loop
42
-
43
80
  _aryindex_notfound: slide 1 ret
44
81
 
45
- ;;;
46
-
47
- ; element at index i
48
- aryat: ; [a i]
82
+ ; returns the element E at index I in array A (or dies on out of bounds)
83
+ ; [A I] => [E]
84
+ ;
85
+ ; [1 2 3 4 4 0] => [1]
86
+ ; [1 2 3 4 4 2] => [3]
87
+ ; [1 2 3 4 4 -1] => [4] negative index counts from the end
88
+ aryat:
49
89
  dup jn _aryat_neg
50
90
  copy 1 swap sub dup :pos? jz _aryat_oob
51
91
  :roll swap $-- :nslide ret
52
-
53
92
  _aryat_neg: copy 1 add dup jn _aryat_oob jump aryat
54
-
55
93
  _aryat_oob: push "(aryat) index out of bounds!" :die!
56
94
 
95
+ ; returns the minimum and maximum elements of array A
96
+ ; [A] => [min max]
97
+ ;
98
+ ; [4 3 2 1 4] => [1 4]
99
+ ; [6 8 -3 4 0 5] => [-3 8]
100
+ ; [7 1] => [7 7]
57
101
  minmax:
58
- :arydup reduce (:max)
59
- push -1 swap store
102
+ :arydup reduce (:max) push -1 swap store
60
103
  reduce (:min) push -1 load ret
61
104
 
62
- ;;;
105
+ ;;; TODO: make sort not atrociously inefficient
63
106
 
64
107
  sort: push -3 copy 1 store ; preserve length
65
108
  _sort_loop:
@@ -70,17 +113,25 @@ _sort_loop:
70
113
  push 0 copy 1 sub jn _sort_loop
71
114
  push 3 sub load ret
72
115
 
116
+ ; reverses the array A
117
+ ; [A] => [A']
118
+ ;
119
+ ; [1 2 3 3] => [3 2 1 3]
120
+ ; [7 1] => [7 1]
121
+ ; [5 4 3 2 1 5] => [1 2 3 4 5 5]
73
122
  aryrev: push -3 copy 1 store
74
123
  _aryrev_loop:
75
124
  swap copy 1 :bury $--
76
125
  push 0 copy 1 sub jn _aryrev_loop
77
126
  push 3 sub load ret
78
127
 
79
- ;;;
80
-
128
+ ; returns the array A replicated N times
129
+ ; ! N must be greater than 1
130
+ ; [A N] => [A']
131
+ ;
132
+ ; [3 2 1 3 4] => [3 2 1 3 2 1 3 2 1 3 2 1 12]
81
133
  aryrep: push -1 swap push -3 copy 1 store store
82
134
  _aryrep_loop:
83
135
  push -1 dup :dec load jz _aryrep_done
84
136
  :arydup jump _aryrep_loop
85
-
86
137
  _aryrep_done: push -3 load $-- times (:arycat) ret
@@ -1,74 +1,74 @@
1
- import math ; ilog, pow
2
- import string ; strcat, strlen, strrev
3
- import util ; bin, digits, itos
4
-
5
- ; TODO: don't use strings for bitwise operations!
6
-
7
- ; bitwise bitwise_and
8
- ; ! clobbers heap address -1
9
- bitwise_and: ; [a b] => [a & b]
10
- push -1 push 0 store ; empty string tally
11
-
12
- _bitwise_and_loop:
13
- copy 1 copy 1 add jz _bitwise_done
14
- copy 1 push 2 mod
15
- copy 1 push 2 mod
16
- add push 2 sub jz _bitwise_and_yes
17
- push '0' jump _bitwise_and_update
18
-
19
- _bitwise_and_yes: push '1'
20
- _bitwise_and_update: :_bitwise_update jump _bitwise_and_loop
21
-
22
- _bitwise_update:
23
- push -1 load swap :strcat
24
- push -1 swap store
25
- push 2 div swap push 2 div ret
26
-
27
- _bitwise_done: push -1 load :strrev :bin slide 2 ret
28
-
29
- ; bitwise bitwise_or
30
- ; ! clobbers heap address -1
31
- bitwise_or: ; [a b] => [a | b]
32
- push -1 push 0 store ; empty string tally
33
-
34
- _bitwise_or_loop:
35
- copy 1 copy 1 add jz _bitwise_done
36
- copy 1 push 2 mod
37
- copy 1 push 2 mod
38
- add jz _bitwise_or_no
39
- push '1' jump _bitwise_or_update
40
-
41
- _bitwise_or_no: push '0'
42
- _bitwise_or_update: :_bitwise_update jump _bitwise_or_loop
43
-
44
- ; bitwise bitwise_xor
45
- ; ! clobbers heap address -1
46
- bitwise_xor: ; [a b] => [a ^ b]
47
- push -1 push 0 store ; empty string tally
48
-
49
- _bitwise_xor_loop:
50
- copy 1 copy 1 add jz _bitwise_done
51
- copy 1 push 2 mod
52
- copy 1 push 2 mod
53
- add push 1 sub jz _bitwise_xor_yes
54
- push '0' jump _bitwise_xor_update
55
-
56
- _bitwise_xor_yes: push '1'
57
- _bitwise_xor_update: :_bitwise_update jump _bitwise_xor_loop
58
-
59
- ; flip all bits in n ; [n]
60
- bitwise_not:
1
+ import math ; ilog, pow
2
+ import util ; digits
3
+
4
+ ; The bitwise operators all work very similarly, differing only in how they
5
+ ; map pairs of bits to 0 or 1. For AND, we simply multiply them. For OR, we
6
+ ; sum them, add 1, then divide by 2. For XOR, we only want 1 if they're neq.
7
+ ; We repeatedly halve the inputs while consuming bits and stop when at least
8
+ ; one of them becomes zero. For AND, the answer is in the accumulator and we
9
+ ; are done. For OR and XOR, it's likely the nonzero input still has bits to
10
+ ; contribute, so we also accumulate its product with the current power of 2.
11
+ $bitfun(op, bit, done) {
12
+ push -2,1,-1,0 store store
13
+ _`op`_loop: push -1
14
+ copy 2 push 2 mod copy 2 push 2 mod `bit`
15
+ push -1 load swap push -2 load dup push 2 mul
16
+ push -2 swap store mul add store
17
+ push 2 div swap push 2 div
18
+ dup copy 2 mul jz _`op`_done swap jump _`op`_loop
19
+ _`op`_done: `done` ret
20
+ }
21
+
22
+ ; returns the bitwise AND of A and B
23
+ ; [A B] => [A & B]
24
+ ;
25
+ ; [65 47] => [1] [83 25] => [17]
26
+ ; [40 64] => [0] [74 59] => [10]
27
+ ; [31 18] => [18] [86 32] => [0]
28
+ ; [93 79] => [77] [11 79] => [11]
29
+ band: $bitfun(band, mul, mul $-- load)
30
+
31
+ ; returns the bitwise OR of A and B
32
+ ; [A B] => [A | B]
33
+ ;
34
+ ; [66 51] => [115] [64 4] => [68]
35
+ ; [61 77] => [125] [93 65] => [93]
36
+ ; [14 87] => [95] [71 37] => [103]
37
+ ; [7 19] => [23] [38 92] => [126]
38
+ bor: $bitfun(bor, add $++ push 2 div, add push -2 load mul push -1 load add)
39
+
40
+ ; returns the bitwise XOR of A and B
41
+ ; [A B] => [A ^ B]
42
+ ;
43
+ ; [4 14] => [10] [0 80] => [80]
44
+ ; [51 5] => [54] [97 77] => [44]
45
+ ; [39 65] => [102] [12 26] => [22]
46
+ ; [44 36] => [8] [6 21] => [19]
47
+ bxor: $bitfun(bxor, :neq, add push -2 load mul push -1 load add)
48
+
49
+ ; returns the infinite-precision bitwise NOT of N by inverting all of its bits
50
+ ; [N] => [~N]
51
+ ;
52
+ ; [58] => [5] [46] => [17]
53
+ ; [48] => [15] [87] => [40]
54
+ ; [98] => [29] [51] => [12]
55
+ ; [3] => [0] [42] => [21]
56
+ bnot:
61
57
  dup push 0 swap push 1 add sub
62
58
  swap push 2 :ilog push 2 swap :pow mod ret
63
59
 
64
- ;;;
65
-
66
- ; number of set bits in n ; [n]
60
+ ; returns the number of bits in the binary representation of N
61
+ ; [N] => [# of bits]
62
+ ;
63
+ ; [0] => [0] [1] => [1]
64
+ ; [7] => [3] [8] => [4]
65
+ ; [255] => [8]
66
+ blength: dup jz _blength_zero push 2 :ilog $++ ret
67
+ _blength_zero: ret
68
+
69
+ ; returns the number of set bits in N
70
+ ; [N] => [popcount]
71
+ ;
72
+ ; [0] => [0] [1] => [1]
73
+ ; [7] => [3] [8] => [1]
67
74
  popcount: push 2 :digits reduce (add) ret
68
-
69
- ;;;
70
-
71
- ; most significant set bit in n, as a power of 2 ; [n]
72
- msb: dup jz _msb_zero
73
- push 2 :itos :strlen $-- push 2 swap :pow ret
74
- _msb_zero: ret
@@ -1,34 +1,40 @@
1
- import bits ; bitwise_and, bitwise_or, bitwise_xor
2
- import string ; isalpha, strcat, strunpack
3
-
4
1
  ;;; Case conversion
5
2
 
6
- upcase: :strunpack push 0 ; tally
7
- _upcase_loop:
8
- swap dup jz _upcase_done
9
- dup :isalpha jz _upcase_cat
10
- push 95 :bitwise_and
11
- _upcase_cat:
12
- :strcat jump _upcase_loop
13
-
14
- _upcase_done: pop ret
3
+ import bits ; band, bor, bxor
4
+ import string ; isalpha, strcat, strunpack
15
5
 
16
- downcase: :strunpack push 0 ; tally
17
- _downcase_loop:
18
- swap dup jz _downcase_done
19
- dup :isalpha jz _downcase_cat
20
- push 32 :bitwise_or
21
- _downcase_cat:
22
- :strcat jump _downcase_loop
6
+ ; The case conversion functions all behave very similarly, differing only in
7
+ ; how they modify the ordinal value V of a character to be kept.
8
+ ; upcase is V & 95, downcase is V | 32, and swapcase is V ^ 32.
9
+ $casefun(case, op) {
10
+ :strunpack push 0 ; get characters on stack and initialize accumulator
11
+ _`case`_loop: swap dup jz _`case`_done dup :isalpha jz _`case`_cat `op`
12
+ _`case`_cat: :strcat jump _`case`_loop
13
+ _`case`_done: pop ret
14
+ }
23
15
 
24
- _downcase_done: pop ret
16
+ ; returns string S with all alphabetical characters capitalized
17
+ ; [S] => [S']
18
+ ;
19
+ ; [""] => [""]
20
+ ; ["abc"] => ["ABC"]
21
+ ; ["123"] => ["123"]
22
+ ; ["Abc123Def"] => ["ABC123DEF"]
23
+ upcase: $casefun(upcase, push 0x5F :band)
25
24
 
26
- swapcase: :strunpack push 0 ; tally
27
- _swapcase_loop:
28
- swap dup jz _swapcase_done
29
- dup :isalpha jz _swapcase_cat
30
- push 32 :bitwise_xor
31
- _swapcase_cat:
32
- :strcat jump _swapcase_loop
25
+ ; returns string S with all alphabetical characters lower-cased
26
+ ; [S] => [S']
27
+ ;
28
+ ; [""] => [""]
29
+ ; ["ABC"] => ["abc"]
30
+ ; ["123"] => ["123"]
31
+ ; ["aBC123dEF"] => ["abc123def"]
32
+ downcase: $casefun(downcase, push 0x20 :bor)
33
33
 
34
- _swapcase_done: pop ret
34
+ ; returns string S with the case of all alphabetical characters swapped
35
+ ; [S] => [S']
36
+ ;
37
+ ; [""] => [""]
38
+ ; ["FooBar"] => ["fOObAR"]
39
+ ; ["Abc123deF"] => ["aBC123DEf"]
40
+ swapcase: $casefun(swapcase, push 0x20 :bxor)