jop 0.0.2
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 +7 -0
- data/lib/jop.rb +308 -0
- data/lib/tokenizer.rb +61 -0
- metadata +46 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3297e329236a8838c0036f086ba8d4a871140ded
|
4
|
+
data.tar.gz: ff32cc6d6f4d0360814b3e16f4a7fdd1563286df
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 83334657122ade4b7cf4e8ccc0a101a10efee6595ffb2d633f3f115a861a7018b2bca42cabfbceeb76a2b1b4132063c52f4a3b163e7d118ab932052ea1e07992
|
7
|
+
data.tar.gz: aca459965e3c6fb141e5171ddccab6ee02eb39d3f4eb73491cdc10adad81202941cf04129f28673948db2505a35e774ee5a874d86c7df152ba225a7c4abf6b94
|
data/lib/jop.rb
ADDED
@@ -0,0 +1,308 @@
|
|
1
|
+
|
2
|
+
require 'tokenizer'
|
3
|
+
|
4
|
+
class Op
|
5
|
+
def numeric_literal? text
|
6
|
+
text =~ /^_?\d+/
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_numeric text
|
10
|
+
return text.to_i if text =~ /^\d+/
|
11
|
+
-(text[1...text.length]).to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
def integer_args interpreter
|
15
|
+
interpreter.tokens
|
16
|
+
.take_while {|n| numeric_literal?(n) }
|
17
|
+
.reverse
|
18
|
+
.map(&:to_i)
|
19
|
+
end
|
20
|
+
|
21
|
+
def apply_monad_deep element, &block
|
22
|
+
return yield element unless element.kind_of? Array
|
23
|
+
element.map {|e| apply_monad_deep(e, &block) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
class Ceil < Op; REP = '>.'
|
29
|
+
def run ary, interpreter
|
30
|
+
apply_monad_deep(ary) {|e| e.ceil }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
class Complement < Op; REP = '-.'
|
36
|
+
def run ary, interpreter
|
37
|
+
apply_monad_deep(ary) {|e| 1 - e }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
class Curtail < Op; REP = '}:'
|
43
|
+
def run ary, interpreter
|
44
|
+
ary.take(ary.count-1)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
class Decrement < Op; REP = '<:'
|
50
|
+
def run ary, interpreter
|
51
|
+
apply_monad_deep(ary) {|e| e - 1 }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
class Double < Op; REP = '+:'
|
57
|
+
def run ary, interpreter
|
58
|
+
apply_monad_deep(ary) {|e| e * 2 }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
class Drop < Op; REP = '}.'
|
64
|
+
def run ary, interpreter
|
65
|
+
if interpreter.tokens.size > 0 && numeric_literal?(interpreter.tokens[0])
|
66
|
+
number = to_numeric(interpreter.tokens[0])
|
67
|
+
interpreter.advance(1)
|
68
|
+
if number >= 0
|
69
|
+
ary.drop(number)
|
70
|
+
else
|
71
|
+
ary.reverse.drop(-number).reverse
|
72
|
+
end
|
73
|
+
else
|
74
|
+
ary.drop(1)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
class Exp < Op; REP = '^'
|
81
|
+
def run ary, interpreter
|
82
|
+
apply_monad_deep(ary) {|n| Math::exp(n) }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
class Floor < Op; REP = '<.'
|
88
|
+
def run ary, interpreter
|
89
|
+
apply_monad_deep(ary) {|e| e.floor }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
class GradeDown < Op; REP = '\:'
|
95
|
+
def run ary, interpreter
|
96
|
+
GradeUp.new.run(ary, interpreter).reverse
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
class GradeUp < Op; REP = '/:'
|
102
|
+
def run ary, interpreter
|
103
|
+
ary.zip(0...ary.length).sort_by {|e| e[0] }.map {|e| e[1] }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
class Halve < Op; REP = '-:'
|
109
|
+
def run ary, interpreter
|
110
|
+
apply_monad_deep(ary) {|e| e / 2.0 }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
class Increment < Op; REP = '>:'
|
116
|
+
def run ary, interpreter
|
117
|
+
apply_monad_deep(ary) {|e| e + 1 }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
class Insert < Op; REP = '/'
|
123
|
+
def run ary, interpreter
|
124
|
+
if interpreter.tokens[0] == Plus::REP
|
125
|
+
interpreter.advance(1)
|
126
|
+
box ary.reduce(&method(:add))
|
127
|
+
elsif interpreter.tokens[0] == Sign::REP
|
128
|
+
interpreter.advance(1)
|
129
|
+
box ary.reduce(:*)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def add x, y
|
136
|
+
return x + y unless arrays? [x,y]
|
137
|
+
x.zip(y).map {|x,y| add(x,y) }
|
138
|
+
end
|
139
|
+
|
140
|
+
def box e
|
141
|
+
e.kind_of?(Array) ? e : [e]
|
142
|
+
end
|
143
|
+
|
144
|
+
def arrays? ary
|
145
|
+
ary.all? {|e| e.kind_of? Array }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
class NoOp < Op; REP = ''
|
151
|
+
def run ary, interpreter
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
class Plus < Op; REP = '+'
|
157
|
+
def run ary, interpreter
|
158
|
+
args = integer_args(interpreter)
|
159
|
+
interpreter.advance(args.length)
|
160
|
+
args.empty? ? ary : ary.zip(args).map {|x,y| x + y }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
class Reciprocal < Op; REP = '%'
|
166
|
+
def run ary, interpreter
|
167
|
+
apply_monad_deep(ary) {|e| 1 / e.to_f }
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
class ReverseRotate < Op; REP = '|.'
|
173
|
+
def run ary, interpreter
|
174
|
+
if interpreter.tokens.size > 0 && numeric_literal?(interpreter.tokens[0])
|
175
|
+
number = to_numeric(interpreter.tokens[0])
|
176
|
+
interpreter.advance(1)
|
177
|
+
segment_length = number % ary.length
|
178
|
+
segment = ary.take(segment_length)
|
179
|
+
ary.drop(segment_length) + segment
|
180
|
+
else
|
181
|
+
ary.reverse
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
class Shape < Op; REP = '$'
|
188
|
+
def run ary, interpreter
|
189
|
+
ranges = integer_args(interpreter)
|
190
|
+
interpreter.advance(ranges.length)
|
191
|
+
fill_matrix(ranges, ary.cycle.each)
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
def fill_matrix ranges, elements
|
197
|
+
return elements.next if ranges.size <= 0
|
198
|
+
(0...ranges.first).map { fill_matrix(ranges.drop(1), elements) }
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
class Sign < Op; REP = '*'
|
204
|
+
def run ary, interpreter
|
205
|
+
apply_monad_deep(ary) {|e| e <=> 0 }
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
class Square < Op; REP = '*:'
|
211
|
+
def run ary, interpreter
|
212
|
+
apply_monad_deep(ary) {|e| e ** 2 }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
class Tail < Op; REP = '{:'
|
218
|
+
def run ary, interpreter
|
219
|
+
ary.drop(ary.count-1)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
class Take < Op; REP = '{.'
|
225
|
+
def run ary, interpreter
|
226
|
+
if interpreter.tokens.size > 0 && numeric_literal?(interpreter.tokens[0])
|
227
|
+
number = to_numeric(interpreter.tokens[0])
|
228
|
+
interpreter.advance(1)
|
229
|
+
if number >= 0
|
230
|
+
ary.take(number)
|
231
|
+
else
|
232
|
+
ary.reverse.take(-number).reverse
|
233
|
+
end
|
234
|
+
else
|
235
|
+
ary.take(1)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
class Tally < Op; REP = '#'
|
242
|
+
def run ary, interpreter
|
243
|
+
[ary.count]
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
class Tilde < Op; REP = '~'
|
249
|
+
def run ary, interpreter
|
250
|
+
if interpreter.tokens[0] == GradeUp::REP
|
251
|
+
interpreter.advance(1)
|
252
|
+
ary.sort
|
253
|
+
elsif interpreter.tokens[0] == GradeDown::REP
|
254
|
+
interpreter.advance(1)
|
255
|
+
ary.sort.reverse
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
class Jop
|
262
|
+
attr_reader :tokens
|
263
|
+
|
264
|
+
def initialize command_text
|
265
|
+
@command_text = command_text
|
266
|
+
@tokens = Tokenizer.new(command_text).tokens.reverse
|
267
|
+
end
|
268
|
+
|
269
|
+
def advance amount
|
270
|
+
@tokens = @tokens[amount...@tokens.length]
|
271
|
+
end
|
272
|
+
|
273
|
+
def eval_on ary
|
274
|
+
result = ary
|
275
|
+
while not @tokens.empty?
|
276
|
+
result = eval_op(result)
|
277
|
+
end
|
278
|
+
result
|
279
|
+
end
|
280
|
+
|
281
|
+
private
|
282
|
+
|
283
|
+
def operators
|
284
|
+
if not @operators
|
285
|
+
@operators = []
|
286
|
+
ObjectSpace.each_object(::Class) {|klass| @operators << klass.new if klass < Op }
|
287
|
+
end
|
288
|
+
@operators
|
289
|
+
end
|
290
|
+
|
291
|
+
def eval_op ary
|
292
|
+
token = @tokens[0]
|
293
|
+
advance(1)
|
294
|
+
operators.detect(NoOp.new) {|op | op.class::REP == token }
|
295
|
+
.run(ary, self)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
class Array
|
300
|
+
def j command_text
|
301
|
+
Jop.new(command_text).eval_on(self)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
|
307
|
+
|
308
|
+
|
data/lib/tokenizer.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
class Tokenizer
|
5
|
+
attr_reader :tokens, :stream
|
6
|
+
|
7
|
+
def operator? char
|
8
|
+
'~^-%*|<>\\{}+/#$'.include?(char)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize text
|
12
|
+
@stream = text
|
13
|
+
@tokens = []
|
14
|
+
|
15
|
+
skip_whitespace
|
16
|
+
while tokenizing?
|
17
|
+
if operator?(current_char)
|
18
|
+
if '.:'.include?(next_char)
|
19
|
+
@tokens << current_char + next_char
|
20
|
+
advance(2)
|
21
|
+
else
|
22
|
+
@tokens << current_char
|
23
|
+
advance(1)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
if stream =~ /^_?\d+/
|
27
|
+
match_data = /^_?\d+/.match(stream)
|
28
|
+
@tokens << match_data[0]
|
29
|
+
advance(match_data[0].length)
|
30
|
+
else
|
31
|
+
break
|
32
|
+
end
|
33
|
+
end
|
34
|
+
skip_whitespace
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def tokenizing?
|
41
|
+
@stream.length > 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def current_char
|
45
|
+
@stream.length > 0 ? @stream[0] : ' '
|
46
|
+
end
|
47
|
+
|
48
|
+
def next_char
|
49
|
+
@stream.length > 1 ? @stream[1] : ' '
|
50
|
+
end
|
51
|
+
|
52
|
+
def advance amount
|
53
|
+
@stream = @stream[amount..-1]
|
54
|
+
end
|
55
|
+
|
56
|
+
def skip_whitespace
|
57
|
+
@stream.lstrip!
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
end
|
metadata
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jop
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Feathers
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-19 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Jop is a gem that adds operations of the J programming language to Ruby
|
14
|
+
arrays
|
15
|
+
email: michael.feathers@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/jop.rb
|
21
|
+
- lib/tokenizer.rb
|
22
|
+
homepage: http://rubygems.org/gems/jop
|
23
|
+
licenses:
|
24
|
+
- MIT
|
25
|
+
metadata: {}
|
26
|
+
post_install_message:
|
27
|
+
rdoc_options: []
|
28
|
+
require_paths:
|
29
|
+
- lib
|
30
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
requirements: []
|
41
|
+
rubyforge_project:
|
42
|
+
rubygems_version: 2.0.3
|
43
|
+
signing_key:
|
44
|
+
specification_version: 4
|
45
|
+
summary: jop
|
46
|
+
test_files: []
|