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 +4 -4
- data/README.md +2 -2
- data/Rakefile +4 -3
- data/bin/spw +1 -0
- data/lib/spitewaste.rb +2 -0
- data/lib/spitewaste/assembler.rb +51 -6
- 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 +2 -0
- data/lib/spitewaste/cli/image.rb +2 -0
- data/lib/spitewaste/libspw/array.spw +69 -18
- data/lib/spitewaste/libspw/bits.spw +70 -70
- data/lib/spitewaste/libspw/case.spw +34 -28
- data/lib/spitewaste/libspw/docs.json +1 -0
- data/lib/spitewaste/libspw/fun.spw +34 -22
- data/lib/spitewaste/libspw/math.spw +7 -3
- data/lib/spitewaste/libspw/prime.spw +33 -33
- data/lib/spitewaste/libspw/rational.spw +23 -0
- data/lib/spitewaste/libspw/string.spw +20 -19
- data/lib/spitewaste/libspw/test.spw +2 -3
- data/lib/spitewaste/libspw/util.spw +66 -20
- data/lib/spitewaste/parsers/spitewaste.rb +17 -3
- data/lib/spitewaste/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 660c0022e893f3656cb3851b0bb3bddebe1df750555138a58dcd81339f11effd
|
|
4
|
+
data.tar.gz: d5d539641ca545525a26a6d357831baa1dcb95189a4023955d15b45142bf2dad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
- [
|
|
39
|
-
- - [
|
|
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
|
-
|
|
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
|
|
68
|
-
doc.gsub!(
|
|
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
data/lib/spitewaste.rb
CHANGED
data/lib/spitewaste/assembler.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Spitewaste
|
|
2
2
|
class Assembler
|
|
3
|
-
attr_reader :
|
|
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
|
-
|
|
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 =
|
|
23
|
-
@
|
|
24
|
-
@
|
|
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
|
-
|
|
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
|
data/lib/spitewaste/cli.rb
CHANGED
|
@@ -11,29 +11,31 @@ class SpitewasteCLI < Thor
|
|
|
11
11
|
exit 1
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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)
|
data/lib/spitewaste/cli/asm.rb
CHANGED
|
@@ -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 }
|
|
@@ -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
|
data/lib/spitewaste/cli/exec.rb
CHANGED
data/lib/spitewaste/cli/image.rb
CHANGED
|
@@ -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:
|
|
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
|
-
;
|
|
48
|
-
|
|
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
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
;
|
|
6
|
-
|
|
7
|
-
;
|
|
8
|
-
;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
copy
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
push
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
;
|
|
45
|
-
;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
;
|
|
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
|