spitewaste 0.1.001 → 0.1.006

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: c2124fde880a571c835efe1e74e397a0dea698786c69a62852c4ad8bd7e3ffe0
4
- data.tar.gz: 0d40e036ca0d7f51ea617be600274088160a8159dc7e04f87af8429d29154fc4
3
+ metadata.gz: 17fcfab8a738ff0666343f1456c4608f0b721a7e8a1a1dc89887c18b258d538c
4
+ data.tar.gz: 7d923d804a932d6a055804a616ae6b518db37ca87006bf049ecd2a590477c10a
5
5
  SHA512:
6
- metadata.gz: 8ca420c6896dba0743f70f27a6558178bfe6201fd821afd45594e11796a975ebd042eb6d65fda6b20221f40831d183e417aca2e7dd7c19edff16c4e99ea15135
7
- data.tar.gz: 1699a938795047284088697c7c3d7f444e626b033f888080618d2d7350635a69dab37b63455cd6544740e747b2665f30df5c15d14c64bdb1a2f2755387cd4ff5
6
+ metadata.gz: c2b108463c434fed7f09d5a6b922c0363cf979ed9ba89a3bd731dba0436e7ed39da71d9ffc76ad1f8f3c9b5ba3ad58405bf9777443e8d3f116a4eb7001d4ba44
7
+ data.tar.gz: 07e9108b800fa22b36cf033b57eb6d8fdc5df3a73e3d19e2385093ea9a5dc4fc91bfddb23620ab4df6cbe28dc17f24a3ce86d3bb5c88b156b5ffc223184376c9
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/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2
+ Version 2, December 2004
3
+
4
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
5
+
6
+ Everyone is permitted to copy and distribute verbatim or modified
7
+ copies of this license document, and changing it is allowed as long
8
+ as the name is changed.
9
+
10
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12
+
13
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
data/README.md CHANGED
@@ -1,7 +1,4 @@
1
- <p align="center">
2
- <img src="abc" /><br >
3
- "Code should be invisible." — someone, surely
4
- </p>
1
+ <p align="center">"Code should be invisible." — someone, surely</p>
5
2
 
6
3
  # Spitewaste
7
4
 
@@ -21,7 +18,7 @@ Spitewaste is the pseudo-assembly language referred to throughout this document,
21
18
 
22
19
  **spw** is a command-line utility whose primary purpose is to "lower" Spitewaste programs to Whitespace for execution by some external interpreter, but it's capable of several (perhaps too many) other transformations; see `spw help convert` for further details. In addition to losslessly converting between various representations, spw can generate pretty pictures of Whitespace code syntax-highlighted in your favorite color scheme via `spw image`.
23
20
 
24
- Spitewaste is not [a Whitespace interpreter](collidedscope/spiceweight), but `spw exec` will convert its input to something your interpreter of choice should understand and ferry it along. Finally, `spw compile` will blindly translate Whitespace instructions to functionally equivalent C++ code and feed that into a compiler; the results aren't spectacular (compilers loathe `goto`), but it's fun to pretend Whitespace is a compiled language.
21
+ Spitewaste is not [a Whitespace interpreter](../../../spiceweight), but `spw exec` will convert its input to something your interpreter of choice should understand and ferry it along. Finally, `spw compile` will blindly translate Whitespace instructions to functionally equivalent C++ code and feed that into a compiler; the results aren't spectacular (compilers loathe `goto`), but it's fun to pretend Whitespace is a compiled language.
25
22
 
26
23
  ## Where <sub>can I get it?</sub>
27
24
 
data/Rakefile CHANGED
@@ -1,10 +1,83 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
+ require 'json'
3
4
 
4
5
  Rake::TestTask.new(:test) do |t|
5
6
  t.libs << 'test'
6
- t.libs << 'lib'
7
7
  t.test_files = FileList['test/*_test.rb']
8
8
  end
9
9
 
10
- task :default => :test
10
+ task test: :docs
11
+
12
+ desc 'Generate standard library documentation.'
13
+ task :docs do |t|
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 }
24
+ end
25
+ end
26
+
27
+ def extract_docs path
28
+ docs = {}
29
+ buffer = ''
30
+
31
+ File.open(path).each_line do |line|
32
+ if line.strip.empty?
33
+ buffer.clear
34
+ elsif line[/^([^_]\S+):/]
35
+ docs[$1] = parse_doc buffer.dup unless buffer.empty?
36
+ buffer.clear
37
+ elsif line[/^;/]
38
+ buffer << line
39
+ end
40
+ end
41
+
42
+ docs
43
+ end
44
+
45
+ def strpack s
46
+ s.bytes.zip(0..).sum { |b, e| b * 128 ** e }
47
+ end
48
+
49
+ def parse_stack 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
+ }
63
+ end
64
+
65
+ StackRx = /(?<=\[)[^\]]*(?=\])/ # match [.*], but don't capture the brackets
66
+
67
+ def parse_doc doc
68
+ # strip leading comment to margin and any implementation details (!)
69
+ doc.gsub!(/^; */, '').gsub!(/^!.+\n/, '')
70
+
71
+ head, specs = doc.split "\n\n"
72
+ *desc, effect = head.split ?\n
73
+ desc *= ?\n
74
+
75
+ cases = []
76
+ specs.scan(StackRx).each_slice 2 do |spec|
77
+ cases << spec.map { parse_stack _1 }
78
+ end if specs
79
+
80
+ {full: doc, desc: desc, effect: effect, cases: cases}
81
+ end
82
+
83
+ task default: :test
@@ -120,6 +120,6 @@ A standard library! Writing a Whitespace program from scratch can be a daunting
120
120
 
121
121
  ### Strings
122
122
 
123
- Pretty much every program needs strings, and Spitewaste has 'em! But how? Well, a string is ultimately just a sequence of bytes, and we can use the power of exponents to smush multiple numbers into a single value. If we had the numbers 3, 9, and 4, we could easily encode them as a single base-10 number: 3 × 10<sup>2</sup> + 9 × 10<sup>1</sup> + 4 × 10<sup>0</sup>. Strings in Spitewaste are similarly encoded, except backwards (as an implementation detail) and in base-128 rather than decimal.
123
+ Pretty much every program needs strings, and Spitewaste has 'em! But how? Well, a string is ultimately just a sequence of bytes, and we can use the power of exponents to smush multiple numbers into a single value. If we had the numbers 3, 9, and 4, we could easily encode them as a single base-10 number: 3 × 10<sup>2</sup> + 9 × 10<sup>1</sup> + 4 × 10<sup>0</sup> = 394. Strings in Spitewaste are similarly encoded, except backwards (as an implementation detail) and in base-128 rather than decimal.
124
124
 
125
125
  When you say <code>push "ABC"</code>, the assembler transforms it into <code>push 1106241</code>, the value of: 67 ('C') × 128<sup>2</sup> + 66 × 128<sup>1</sup> + 65 × 128<sup>0</sup>. Whitespace interpreters SHOULD (and many do) support arbitrarily large integers, so we don't have to worry about these representations not fitting into 64 bits. Once we're able to pass strings around as individual values, a plethora of possibilities opens up, and the standard library contains many string functions to reflect this.
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,7 +1,7 @@
1
1
  module Spitewaste
2
2
  class AssemblyEmitter < Emitter
3
3
  def emit io:
4
- io.puts instructions.map { (_1 * ' ').rstrip }
4
+ io.puts instructions.map { |i| (i * ' ').rstrip }
5
5
  end
6
6
  end
7
7
  end
@@ -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