spitewaste 0.1.004 → 0.1.009

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: 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