spitewaste 0.1.004 → 0.1.009

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e6953a0755edbb33708f3cdc1ebeb33cdb6a47c1f3d6335716d25f693dec56c
4
- data.tar.gz: 130d57daadcee3de1a465e4be9b28bba16d70c4dafa87c59195696be06e49a7c
3
+ metadata.gz: 660c0022e893f3656cb3851b0bb3bddebe1df750555138a58dcd81339f11effd
4
+ data.tar.gz: d5d539641ca545525a26a6d357831baa1dcb95189a4023955d15b45142bf2dad
5
5
  SHA512:
6
- metadata.gz: b1f2075fb27a9ecf5599365cf029bfeebacfda5208a5df357ea94de00465111f61d42cc58a5220e16de710dfe4c778acf911e65ab3d86459c7039cee37f011a6
7
- data.tar.gz: ea2003b53fe38c7fa092b2c43c383706980009fd1efa54f5733e40937b180fd76ea08e973dd5e16835fab1854f075add855af1957357aeab391df0dab6945e92
6
+ metadata.gz: 28be3283bde45056b240eaba41f4b783a4b4e7bdacf8cfce02e052bf11f002c4e19beb9b101cf2c42638a1406de56f0b466503289a2596e457a4817442fa59f1
7
+ data.tar.gz: 57591a541274bef66a01828a2b719b4ec5b8091cb83e1cb488adf61041590cb84344db411cddbdd9d9f0dc16395b30cb59f6be397633c996a448262162d2c8c2
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
@@ -64,8 +65,8 @@ end
64
65
  StackRx = /(?<=\[)[^\]]*(?=\])/ # match [.*], but don't capture the brackets
65
66
 
66
67
  def parse_doc doc
67
- # strip comment character and any implementation details
68
- doc.gsub!(/; */, '').gsub!(/^!.+\n/, '')
68
+ # strip leading comment to margin and any implementation details (!)
69
+ doc.gsub!(/^; */, '').gsub!(/^!.+\n/, '')
69
70
 
70
71
  head, specs = doc.split "\n\n"
71
72
  *desc, effect = head.split ?\n
data/bin/spw CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'spitewaste/cli'
4
4
  require 'spitewaste/cli/asm'
5
+ require 'spitewaste/cli/docs'
5
6
  require 'spitewaste/cli/exec'
6
7
  require 'spitewaste/cli/compile'
7
8
  require 'spitewaste/cli/convert'
@@ -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
@@ -11,29 +11,31 @@ class SpitewasteCLI < Thor
11
11
  exit 1
12
12
  end
13
13
 
14
- # TODO: Figure out how to invoke a command from this class method.
15
- def self.handle_no_command_error *args
16
- exec $0, 'exec', *args
17
- end
18
-
19
- class_option :format,
20
- desc: 'input format (in case auto-detection misguesses)',
21
- aliases: '-f'
22
-
23
- class_option :aliases,
24
- desc: 'augment or override one or more of the default instruction mnemonics [WIP]',
25
- banner: 'pop:drop...',
26
- type: :array,
27
- aliases: '-a'
28
-
29
- class_option :coexist,
30
- desc: <<DESC,
14
+ def self.shared_options
15
+ option :format,
16
+ desc: 'input format (in case auto-detection misguesses)',
17
+ aliases: '-f'
18
+
19
+ option :aliases,
20
+ desc: 'augment or override one or more of the default instruction mnemonics [WIP]',
21
+ banner: 'pop:drop...',
22
+ type: :array,
23
+ aliases: '-a'
24
+
25
+ option :coexist,
26
+ desc: <<DESC,
31
27
  allow multiple mnemonics to refer to the same instruction where possible [WIP]
32
28
 
33
29
  \e[1mNOTE: \e[0mIf --no-coexist (the default), aliases take precedence and render the original mnemonics invalid.
34
30
  DESC
35
- type: :boolean,
36
- aliases: '-x'
31
+ type: :boolean,
32
+ aliases: '-x'
33
+ end
34
+
35
+ # TODO: Figure out how to invoke a command from this class method.
36
+ def self.handle_no_command_error *args
37
+ exec $0, 'exec', *args
38
+ end
37
39
 
38
40
  def self.validate_format options
39
41
  if fmt = options[:format]&.to_sym and !VALID_FORMATS.include?(fmt)
@@ -3,6 +3,8 @@ class SpitewasteCLI
3
3
  'Generate plain Whitespace assembly from INPUT and write it to OUTPUT.'
4
4
  long_desc 'Mostly just a convenience wrapper around `spw convert in -o asm out`'
5
5
 
6
+ shared_options
7
+
6
8
  def asm input, output = '/dev/stdout'
7
9
  as = Spitewaste::Assembler.new File.read input
8
10
  File.open(output, ?w) { |of| as.assemble! format: :assembly, io: of }
@@ -39,6 +39,8 @@ DESC
39
39
  type: :boolean,
40
40
  aliases: '-k'
41
41
 
42
+ shared_options
43
+
42
44
  def compile file = nil
43
45
  fmt = SpitewasteCLI.validate_format options
44
46
 
@@ -21,6 +21,8 @@ DESC
21
21
  - use % to write to $HOME/.cache/spitewaste directory, where Spiceweight knows to look',
22
22
  aliases: '-s'
23
23
 
24
+ shared_options
25
+
24
26
  def convert input = '/dev/stdin', output = '/dev/stdout'
25
27
  ext = File.extname output
26
28
  Kernel.exec 'spw', 'image', input, output if ext == '.png'
@@ -0,0 +1,51 @@
1
+ def bold s; "\e[1m#{s}\e[0m" end
2
+ def under s; "\e[4m#{s}\e[0m" end
3
+ def hi s; "\e[7m#{s}\e[0m" end
4
+
5
+ DOCS = File.expand_path '../libspw/docs.json', __dir__
6
+
7
+ class SpitewasteCLI
8
+ desc 'docs [OPTIONS] [TERMS...]',
9
+ 'Search for matching terms in the documentation of the standard library.'
10
+
11
+ option :show_specs,
12
+ desc: 'display the in-situ specs showing correct outputs for given inputs',
13
+ type: :boolean,
14
+ default: false,
15
+ aliases: '-s'
16
+
17
+ option :match_all,
18
+ desc: 'require that all provided search terms match instead of 1+',
19
+ type: :boolean,
20
+ default: false,
21
+ aliases: '-m'
22
+
23
+ def docs *terms
24
+ abort "need at least one search term" if terms.empty?
25
+
26
+ docs = JSON.load File.read DOCS
27
+ found = []
28
+ min = options[:match_all] ? terms.size : 1
29
+
30
+ docs.each do |lib, fns|
31
+ fns.each do |fn, data|
32
+ full = "#{lib}/#{bold under fn} #{data['full']}"
33
+ ms = terms.count { |t| full[/#{t}/i] }
34
+ found << [lib, fn, full, ms] if ms >= min
35
+ end
36
+ end
37
+
38
+ abort "no matches for terms: #{terms}" if found.empty?
39
+
40
+ found.sort_by! { |v| -v[-1] }
41
+
42
+ IO.popen('less -R', 'w') do |io|
43
+ found.each do |lib, fn, full|
44
+ full.gsub! /#{terms * ?|}/i, &method(:hi)
45
+ desc, specs = full.split "\n\n"
46
+ io.puts "#{?- * 10}\n#{desc}\n\n"
47
+ io.puts specs if options[:show_specs]
48
+ end
49
+ end
50
+ end
51
+ end
@@ -16,6 +16,8 @@ class SpitewasteCLI
16
16
  - use % to write to $HOME/.cache/spitewaste directory, where Spiceweight knows to look',
17
17
  aliases: '-s'
18
18
 
19
+ shared_options
20
+
19
21
  def exec input = '/dev/stdin'
20
22
  fmt = SpitewasteCLI.validate_format options
21
23
 
@@ -41,6 +41,8 @@ DESC
41
41
  type: :numeric,
42
42
  aliases: '-l'
43
43
 
44
+ shared_options
45
+
44
46
  def image input, output = nil
45
47
  fmt = SpitewasteCLI.validate_format options
46
48
  output ||= Pathname.new(input).sub_ext '.png'
@@ -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