spitewaste 0.1.003 → 0.1.008

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: 0b4f49809a688762a3b50dab64b93163e493aa75a33d2565b9892ec5ad8c9bc0
4
- data.tar.gz: 6a06768b5a4edcf18ca0948900effd3c3fb01fbdea2e2da8d29080036870614f
3
+ metadata.gz: 6477ee6d326bbb3fb166983df576812e8cb55b7ca4f484c69003491da676b5c9
4
+ data.tar.gz: db9ffe920b78a3439c2e9c20336f0f336f5bf24b6e6e6ba43b0d2de6f7cac44b
5
5
  SHA512:
6
- metadata.gz: 99cc02efffe8e2ddecf76b1b8578c4eb5e3a6c96b0cf1d46d063b9268507120a718835822f906739962a0b36262ae08328c610db3b37b0a777b3ff84d5e2df02
7
- data.tar.gz: fa3425ae1c074a9a34f78bf110a7d8bb645b15205bb78f68787060e61c018cf63925c2c32d67bbf5ab0d4552c37727555dfcdd384824af2b6087c665bf969233
6
+ metadata.gz: 95697bc3b0579a3e286c446bb1c6ada9a529ca239c18d285be290b063233a80ab90e01846e54469ecfc550669c46330d118fbd4e42381233a825effeb39554f2
7
+ data.tar.gz: 9bf01bb21a013d50721d2247d998d34e1660d9d4247224c54063ef11137059c734e40cdb6e3a412c619fe5df368e252272604a5229d7dcf01401c95cd5ed8b14
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
@@ -7,15 +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
16
- end
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
17
22
 
18
- File.open('docs.json', ?w) { |f| JSON.dump all_docs, f }
23
+ File.open('docs.json', ?w) { |f| JSON.dump docs, f }
24
+ end
19
25
  end
20
26
 
21
27
  def extract_docs path
@@ -41,14 +47,26 @@ def strpack s
41
47
  end
42
48
 
43
49
  def parse_stack s
44
- 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
+ }
45
63
  end
46
64
 
47
- def parse_doc doc
48
- StackRx = /(?<=\[)[^\]]+(?=\])/ # match [.+], but don't capture the brackets
65
+ StackRx = /(?<=\[)[^\]]*(?=\])/ # match [.*], but don't capture the brackets
49
66
 
50
- # strip comment character and any implementation details
51
- doc.gsub!(/; */, '').gsub!(/^!.+\n/, '')
67
+ def parse_doc doc
68
+ # strip leading comment to margin and any implementation details (!)
69
+ doc.gsub!(/^; */, '').gsub!(/^!.+\n/, '')
52
70
 
53
71
  head, specs = doc.split "\n\n"
54
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