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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b185ba760bbddf427962b292046de239f29b6a13964c7182eea2da1a2bcc0d10
4
- data.tar.gz: 36991071a4e6bc98134d235a340dbaf90f082020ffc2e2bd8a67b83970b86e68
3
+ metadata.gz: 266085412e216eb7705988b1832d0d409db6fccbd67f598dcfe2de5138bad9b3
4
+ data.tar.gz: 2155b872a192198984f4d8476b70845f6ca8cb040d65c5324edd42bf7b75fc3f
5
5
  SHA512:
6
- metadata.gz: add4543eead4effe21e7eab4ff3bd18fc6f544889368c14b2b178953adddbb1f53913dc737ebcb95ce5f49ee144c81e1916072e2c9e72752c5cb9aab5d0313d5
7
- data.tar.gz: 1249b0e70507839d31c15f7fb29d084eb7b572dea51f5b398416e871b57e5d3eb621e2ed756cd5871a28a8a7ebd72bc59f1b65a8b884a441dc9de77a3e254271
6
+ metadata.gz: b9c9b8f395fafda61356d2ce1dc8dce7637fb11e4e92851bf670355fc27b90e0c2f83fb48a1241c4100a6d61af29361677ae4605fb02e336b8bc5e123a97d0ad
7
+ data.tar.gz: 973ee87e4e313d8e7aa3eda6b87740d104112db2f06ae216d6d005ea2c31752bfe5dfadb764bcda88b8f2753026811d22da82dacea5e715bd7ee6bc097f96064
data/Gemfile CHANGED
@@ -1,8 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in spitewaste.gemspec
4
-
5
- gem 'rake', '~> 12.0'
6
- gem 'minitest', '~> 5.0'
7
- gem 'thor', '~> 1.0'
8
- gem 'oily_png' # TODO: optionalize?
3
+ gemspec
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
- desc "Generate standard library documentation."
10
+ task test: :docs
11
+
12
+ desc 'Generate standard library documentation.'
11
13
  task :docs do |t|
12
- all_docs = {}
13
- Dir['lib/spitewaste/libspw/math*'].each do |path|
14
- lib = File.basename path, '.spw'
15
- all_docs[lib] = extract_docs path
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.split.map { Integer(_1) rescue strpack _1.delete %('") }
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 character and any implementation details
50
- doc.gsub!(/; */, '').gsub!(/^!.+\n/, '')
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
@@ -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'
@@ -17,7 +17,7 @@ module Spitewaste
17
17
  black = program.size - white
18
18
 
19
19
  return :whitespace if white > black
20
- program[/[^-\w\s]/] ? :spitewaste : :assembly # TODO: something more robust?
20
+ program[/import|[^-\w\s]/] ? :spitewaste : :assembly
21
21
  end
22
22
  end
23
23
 
@@ -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
+ @instructions = @parser.instructions
24
+ @src = @parser.src if format == :spitewaste
25
25
  end
26
26
 
27
27
  def assemble! format:, io: STDOUT, **options
@@ -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 = 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
@@ -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
- def exec file = '/dev/stdin'
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 '#{file}'", [] unless File.exists? 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 file
28
+ opts[:symbol_file] = SpitewasteCLI.make_cache_path input
27
29
  end
28
30
 
29
- path =
30
- if File.extname(file) != '.ws'
31
- io = Tempfile.new
32
- as = Spitewaste::Assembler.new File.read(file), format: fmt, **opts
33
- as.assemble! format: :whitespace, io: io
34
- io.tap(&:close).path
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 == '%' ? path : c }
39
+ cmd.map! { |c| c == '%' ? input : c }
41
40
  Kernel.exec *cmd
42
41
  end
43
42
  end
@@ -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,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: ; [h n l]
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
- ; element at index i
44
- 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:
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
- _aryat_oob: push "(aryat) index out of bounds!" :die
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