spitewaste 0.1.002 → 0.1.007
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/Gemfile +1 -6
- data/Rakefile +30 -11
- data/bin/spw +1 -0
- data/lib/spitewaste.rb +1 -1
- data/lib/spitewaste/assembler.rb +4 -4
- 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 +12 -13
- data/lib/spitewaste/cli/image.rb +2 -0
- data/lib/spitewaste/libspw/array.spw +75 -20
- data/lib/spitewaste/libspw/bits.spw +70 -68
- data/lib/spitewaste/libspw/case.spw +34 -26
- data/lib/spitewaste/libspw/docs.json +1 -0
- data/lib/spitewaste/libspw/fun.spw +40 -24
- data/lib/spitewaste/libspw/io.spw +2 -0
- data/lib/spitewaste/libspw/math.spw +83 -45
- data/lib/spitewaste/libspw/prime.spw +36 -35
- data/lib/spitewaste/libspw/random.spw +2 -0
- data/lib/spitewaste/libspw/rational.spw +153 -0
- data/lib/spitewaste/libspw/stack.spw +54 -30
- data/lib/spitewaste/libspw/string.spw +228 -92
- data/lib/spitewaste/libspw/test.spw +3 -0
- data/lib/spitewaste/libspw/util.spw +97 -41
- data/lib/spitewaste/parsers/spitewaste.rb +23 -17
- data/lib/spitewaste/version.rb +1 -1
- data/spitewaste.gemspec +17 -12
- metadata +63 -8
- data/demo/factorial-nicespace.png +0 -0
- data/demo/factorial.asm +0 -47
- data/demo/factorial.png +0 -0
- data/demo/factorial.wsa +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 266085412e216eb7705988b1832d0d409db6fccbd67f598dcfe2de5138bad9b3
|
|
4
|
+
data.tar.gz: 2155b872a192198984f4d8476b70845f6ca8cb040d65c5324edd42bf7b75fc3f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9c9b8f395fafda61356d2ce1dc8dce7637fb11e4e92851bf670355fc27b90e0c2f83fb48a1241c4100a6d61af29361677ae4605fb02e336b8bc5e123a97d0ad
|
|
7
|
+
data.tar.gz: 973ee87e4e313d8e7aa3eda6b87740d104112db2f06ae216d6d005ea2c31752bfe5dfadb764bcda88b8f2753026811d22da82dacea5e715bd7ee6bc097f96064
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
|
@@ -7,14 +7,21 @@ Rake::TestTask.new(:test) do |t|
|
|
|
7
7
|
t.test_files = FileList['test/*_test.rb']
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
task test: :docs
|
|
11
|
+
|
|
12
|
+
desc 'Generate standard library documentation.'
|
|
11
13
|
task :docs do |t|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
docs = {}
|
|
15
|
+
|
|
16
|
+
Dir.chdir('lib/spitewaste/libspw') do |d|
|
|
17
|
+
Dir['*.spw'].each do |path|
|
|
18
|
+
next if path['random']
|
|
19
|
+
lib = File.basename path, '.spw'
|
|
20
|
+
docs[lib] = extract_docs path
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
File.open('docs.json', ?w) { |f| JSON.dump docs, f }
|
|
16
24
|
end
|
|
17
|
-
File.open('docs.json', ?w) { |f| JSON.dump all_docs, f }
|
|
18
25
|
end
|
|
19
26
|
|
|
20
27
|
def extract_docs path
|
|
@@ -35,19 +42,31 @@ def extract_docs path
|
|
|
35
42
|
docs
|
|
36
43
|
end
|
|
37
44
|
|
|
38
|
-
StackRx = /(?<=\[)[^\]]+(?=\])/
|
|
39
|
-
|
|
40
45
|
def strpack s
|
|
41
46
|
s.bytes.zip(0..).sum { |b, e| b * 128 ** e }
|
|
42
47
|
end
|
|
43
48
|
|
|
44
49
|
def parse_stack s
|
|
45
|
-
s.
|
|
50
|
+
s.scan(/"[^"]*"|\S+/).map { |v|
|
|
51
|
+
begin
|
|
52
|
+
Integer v
|
|
53
|
+
rescue
|
|
54
|
+
if v[/R\((-?\d+),(-?\d+)\)/]
|
|
55
|
+
n, d = $1.to_i, $2.to_i
|
|
56
|
+
s = (n<=>0) * (d<=>0)
|
|
57
|
+
(n.abs * 2**31 + d.abs) * 2 + (s == -1 ? 0 : 1)
|
|
58
|
+
else
|
|
59
|
+
strpack v.delete %('")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
}
|
|
46
63
|
end
|
|
47
64
|
|
|
65
|
+
StackRx = /(?<=\[)[^\]]*(?=\])/ # match [.*], but don't capture the brackets
|
|
66
|
+
|
|
48
67
|
def parse_doc doc
|
|
49
|
-
# strip comment
|
|
50
|
-
doc.gsub!(
|
|
68
|
+
# strip leading comment to margin and any implementation details (!)
|
|
69
|
+
doc.gsub!(/^; */, '').gsub!(/^!.+\n/, '')
|
|
51
70
|
|
|
52
71
|
head, specs = doc.split "\n\n"
|
|
53
72
|
*desc, effect = head.split ?\n
|
data/bin/spw
CHANGED
data/lib/spitewaste.rb
CHANGED
data/lib/spitewaste/assembler.rb
CHANGED
|
@@ -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
|
-
@instructions = parser.instructions
|
|
24
|
-
@src = parser.src if format == :spitewaste
|
|
22
|
+
@parser = parser.new(program, **options).tap &:parse
|
|
23
|
+
@instructions = @parser.instructions
|
|
24
|
+
@src = @parser.src if format == :spitewaste
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def assemble! format:, io: STDOUT, **options
|
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 = 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#{lib}/#{bold under fn} #{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
|
@@ -16,28 +16,27 @@ class SpitewasteCLI
|
|
|
16
16
|
- use % to write to $HOME/.cache/spitewaste directory, where Spiceweight knows to look',
|
|
17
17
|
aliases: '-s'
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
shared_options
|
|
20
|
+
|
|
21
|
+
def exec input = '/dev/stdin'
|
|
20
22
|
fmt = SpitewasteCLI.validate_format options
|
|
21
23
|
|
|
22
|
-
raise LoadError, "No such file '#{
|
|
24
|
+
raise LoadError, "No such file '#{input}'", [] unless File.exists? input
|
|
23
25
|
|
|
24
26
|
opts = options.dup # options is frozen
|
|
25
27
|
if opts[:symbol_file] == '%'
|
|
26
|
-
opts[:symbol_file] = SpitewasteCLI.make_cache_path
|
|
28
|
+
opts[:symbol_file] = SpitewasteCLI.make_cache_path input
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
else
|
|
36
|
-
file
|
|
37
|
-
end
|
|
31
|
+
if File.extname(input) != '.ws'
|
|
32
|
+
io = Tempfile.new
|
|
33
|
+
as = Spitewaste::Assembler.new File.read(input), format: fmt, **opts
|
|
34
|
+
as.assemble! format: :whitespace, io: io
|
|
35
|
+
input = io.tap(&:close).path
|
|
36
|
+
end
|
|
38
37
|
|
|
39
38
|
cmd = options[:interpreter].split
|
|
40
|
-
cmd.map! { |c| c == '%' ?
|
|
39
|
+
cmd.map! { |c| c == '%' ? input : c }
|
|
41
40
|
Kernel.exec *cmd
|
|
42
41
|
end
|
|
43
42
|
end
|
data/lib/spitewaste/cli/image.rb
CHANGED
|
@@ -1,17 +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
|
+
|
|
7
|
+
import math ; divmod, max, min, pos?
|
|
8
|
+
import stack ; bury, dig, ncopy, nslide, roll
|
|
9
|
+
import util ; dec, die!, eq
|
|
10
|
+
|
|
1
11
|
$amax = 1000
|
|
2
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]
|
|
3
19
|
arysum: reduce (add) ret
|
|
4
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]
|
|
5
26
|
arydup:
|
|
6
27
|
dup $++ push -2 copy 1 store
|
|
7
28
|
times (push -2 load $-- :ncopy) ret
|
|
8
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]
|
|
9
36
|
arypop: slide 1 $-- ret
|
|
10
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]
|
|
11
44
|
aryshift: dup :dig $-- ret
|
|
12
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]
|
|
13
51
|
arycat: dup $++ :roll add ret
|
|
14
52
|
|
|
53
|
+
;;;
|
|
54
|
+
|
|
15
55
|
arypack:
|
|
16
56
|
:arydup reduce (:max) $++ push -1 swap store
|
|
17
57
|
$-- times (push -1 load mul add)
|
|
@@ -26,36 +66,43 @@ aryunpack:
|
|
|
26
66
|
|
|
27
67
|
arylen: push $amax mod ret
|
|
28
68
|
|
|
29
|
-
|
|
30
|
-
|
|
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]
|
|
31
75
|
aryindex: copy 1 ; two copies of length, one gets decremented, index is diff
|
|
32
|
-
_aryindex_loop:
|
|
33
|
-
dup jn _aryindex_notfound
|
|
76
|
+
_aryindex_loop: dup jn _aryindex_notfound
|
|
34
77
|
dup push 2 add :roll copy 2 :eq jz _aryindex_no
|
|
35
78
|
copy 2 copy 1 sub swap $++ :nslide ret
|
|
36
|
-
|
|
37
79
|
_aryindex_no: $-- jump _aryindex_loop
|
|
38
|
-
|
|
39
80
|
_aryindex_notfound: slide 1 ret
|
|
40
81
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
;
|
|
44
|
-
|
|
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:
|
|
45
89
|
dup jn _aryat_neg
|
|
46
90
|
copy 1 swap sub dup :pos? jz _aryat_oob
|
|
47
91
|
:roll swap $-- :nslide ret
|
|
48
|
-
|
|
49
92
|
_aryat_neg: copy 1 add dup jn _aryat_oob jump aryat
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
93
|
+
_aryat_oob: push "(aryat) index out of bounds!" :die!
|
|
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]
|
|
53
101
|
minmax:
|
|
54
|
-
:arydup reduce (:max)
|
|
55
|
-
push -1 swap store
|
|
102
|
+
:arydup reduce (:max) push -1 swap store
|
|
56
103
|
reduce (:min) push -1 load ret
|
|
57
104
|
|
|
58
|
-
;;;
|
|
105
|
+
;;; TODO: make sort not atrociously inefficient
|
|
59
106
|
|
|
60
107
|
sort: push -3 copy 1 store ; preserve length
|
|
61
108
|
_sort_loop:
|
|
@@ -66,17 +113,25 @@ _sort_loop:
|
|
|
66
113
|
push 0 copy 1 sub jn _sort_loop
|
|
67
114
|
push 3 sub load ret
|
|
68
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]
|
|
69
122
|
aryrev: push -3 copy 1 store
|
|
70
123
|
_aryrev_loop:
|
|
71
124
|
swap copy 1 :bury $--
|
|
72
125
|
push 0 copy 1 sub jn _aryrev_loop
|
|
73
126
|
push 3 sub load ret
|
|
74
127
|
|
|
75
|
-
|
|
76
|
-
|
|
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]
|
|
77
133
|
aryrep: push -1 swap push -3 copy 1 store store
|
|
78
134
|
_aryrep_loop:
|
|
79
135
|
push -1 dup :dec load jz _aryrep_done
|
|
80
136
|
:arydup jump _aryrep_loop
|
|
81
|
-
|
|
82
137
|
_aryrep_done: push -3 load $-- times (:arycat) ret
|