linguify 0.3.0

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.
@@ -0,0 +1,513 @@
1
+ # encoding: utf-8
2
+ #
3
+ # A rewrite of Ruby2Ruby's sexp processor
4
+ #
5
+
6
+ require 'sexp_processor'
7
+
8
+ class Ruby2Js < SexpProcessor
9
+ VERSION = '1.3.1'
10
+ LINE_LENGTH = 78
11
+
12
+ BINARY = [:<=>, :==, :<, :>, :<=, :>=, :-, :+, :*, :/, :%, :<<, :>>, :**]
13
+
14
+ ##
15
+ # Nodes that represent assignment and probably need () around them.
16
+ #
17
+ # TODO: this should be replaced with full precedence support :/
18
+
19
+ ASSIGN_NODES = [
20
+ :dasgn,
21
+ :flip2,
22
+ :flip3,
23
+ :lasgn,
24
+ :masgn,
25
+ :attrasgn,
26
+ :op_asgn1,
27
+ :op_asgn2,
28
+ :op_asgn_and,
29
+ :op_asgn_or,
30
+ :return,
31
+ :if, # HACK
32
+ ]
33
+
34
+ def initialize
35
+ super
36
+ @indent = " "
37
+ self.auto_shift_type = true
38
+ self.strict = true
39
+ self.expected = String
40
+
41
+ @calls = []
42
+
43
+ # self.debug[:defn] = /zsuper/
44
+ end
45
+
46
+ def parenthesize exp
47
+ case self.context[1]
48
+ when nil, :scope, :if, :iter, :resbody, :when, :while, :until then
49
+ exp
50
+ else
51
+ "(#{exp})"
52
+ end
53
+ end
54
+
55
+ def indent(s)
56
+ s.to_s.split(/\n/).map{|line| @indent + line}.join("\n")
57
+ end
58
+
59
+ def cond_loop(exp, name)
60
+ cond = process(exp.shift)
61
+ body = process(exp.shift)
62
+ head_controlled = exp.shift
63
+
64
+ body = indent(body).chomp if body
65
+
66
+ code = []
67
+ if head_controlled then
68
+ if name == 'until'
69
+ code << "while(!#{cond}){"
70
+ else
71
+ code << "#{name}(#{cond}){"
72
+ end
73
+ code << body if body
74
+ code << "}"
75
+ else
76
+ code << "begin"
77
+ code << body if body
78
+ code << "end #{name} #{cond}"
79
+ end
80
+ code.join("\n")
81
+ end
82
+
83
+ def process_and(exp)
84
+ parenthesize "#{process exp.shift} && #{process exp.shift}"
85
+ end
86
+
87
+ def process_arglist(exp) # custom made node
88
+ code = []
89
+ until exp.empty? do
90
+ code << process(exp.shift)
91
+ end
92
+ code.join ', '
93
+ end
94
+
95
+ def process_args(exp)
96
+ args = []
97
+
98
+ until exp.empty? do
99
+ arg = exp.shift
100
+ case arg
101
+ when Symbol then
102
+ args << arg
103
+ when Array then
104
+ case arg.first
105
+ when :block then
106
+ asgns = {}
107
+ arg[1..-1].each do |lasgn|
108
+ asgns[lasgn[1]] = process(lasgn)
109
+ end
110
+
111
+ args.each_with_index do |name, index|
112
+ args[index] = asgns[name] if asgns.has_key? name
113
+ end
114
+ else
115
+ raise "unknown arg type #{arg.first.inspect}"
116
+ end
117
+ else
118
+ raise "unknown arg type #{arg.inspect}"
119
+ end
120
+ end
121
+
122
+ return "(#{args.join ', '})"
123
+ end
124
+
125
+ def process_array(exp)
126
+ "[#{process_arglist(exp)}]"
127
+ end
128
+
129
+ def process_attrasgn(exp)
130
+ receiver = process exp.shift
131
+ name = exp.shift
132
+ args = exp.empty? ? nil : exp.shift
133
+
134
+ case name
135
+ when :[]= then
136
+ rhs = process args.pop
137
+ "#{receiver}[#{process(args)}] = #{rhs}"
138
+ else
139
+ name = name.to_s.sub(/=$/, '')
140
+ if args && args != s(:arglist) then
141
+ "#{receiver}.#{name} = #{process(args)}"
142
+ end
143
+ end
144
+ end
145
+
146
+ def process_block(exp)
147
+ result = []
148
+
149
+ exp << nil if exp.empty?
150
+ until exp.empty? do
151
+ code = exp.shift
152
+ if code.nil? or code.first == :nil then
153
+ result << "# do nothing\n"
154
+ else
155
+ result << process(code)
156
+ end
157
+ end
158
+
159
+ result = parenthesize result.join ";\n"
160
+ result += ";\n" unless result.start_with? "("
161
+
162
+ return result
163
+ end
164
+
165
+ def process_break(exp)
166
+ val = exp.empty? ? nil : process(exp.shift)
167
+ # HACK "break" + (val ? " #{val}" : "")
168
+ if val then
169
+ "break #{val}"
170
+ else
171
+ "break"
172
+ end
173
+ end
174
+
175
+ def process_call(exp)
176
+ receiver_node_type = exp.first.nil? ? nil : exp.first.first
177
+ receiver = process exp.shift
178
+ receiver = "(#{receiver})" if ASSIGN_NODES.include? receiver_node_type
179
+
180
+ name = exp.shift
181
+ args = []
182
+ raw = []
183
+
184
+ # this allows us to do both old and new sexp forms:
185
+ exp.push(*exp.pop[1..-1]) if exp.size == 1 && exp.first.first == :arglist
186
+
187
+ @calls.push name
188
+
189
+ in_context :arglist do
190
+ until exp.empty? do
191
+ arg_type = exp.first.sexp_type
192
+ e = exp.shift
193
+ raw << e.dup
194
+ arg = process e
195
+
196
+ next if arg.empty?
197
+
198
+ strip_hash = (arg_type == :hash and
199
+ not BINARY.include? name and
200
+ (exp.empty? or exp.first.sexp_type == :splat))
201
+ wrap_arg = Ruby2Ruby::ASSIGN_NODES.include? arg_type
202
+
203
+ arg = arg[2..-3] if strip_hash
204
+ arg = "(#{arg})" if wrap_arg
205
+
206
+ args << arg
207
+ end
208
+ end
209
+
210
+ case name
211
+ when *BINARY then
212
+ "(#{receiver} #{name} #{args.join(', ')})"
213
+ when :[] then
214
+ receiver ||= "self"
215
+ if raw.size == 1 && raw.first.sexp_type == :lit && raw.first.to_a[1].kind_of?(Symbol)
216
+ "#{receiver}.#{args.first[1..-1]}"
217
+ else
218
+ "#{receiver}[#{args.join(', ')}]"
219
+ end
220
+ when :[]= then
221
+ receiver ||= "self"
222
+ rhs = args.pop
223
+ "#{receiver}[#{args.join(', ')}] = #{rhs}"
224
+ when :"-@" then
225
+ "-#{receiver}"
226
+ when :"+@" then
227
+ "+#{receiver}"
228
+ when :new
229
+ args = nil if args.empty?
230
+ args = "(#{args.join(',')})" if args
231
+ receiver = "#{receiver}" if receiver
232
+
233
+ "#{name} #{receiver}#{args}"
234
+ when :lambda
235
+ receiver = "#{receiver}." if receiver
236
+
237
+ "#{receiver}function"
238
+ when :this # this.something
239
+ receiver = "#{receiver}." if receiver
240
+ "#{receiver}#{name}"
241
+ else
242
+ args = nil if args.empty?
243
+ args = "#{args.join(',')}" if args
244
+ receiver = "#{receiver}." if receiver
245
+
246
+ "#{receiver}#{name}(#{args})"
247
+ end
248
+ ensure
249
+ @calls.pop
250
+ end
251
+
252
+ def process_const(exp)
253
+ exp.shift.to_s
254
+ end
255
+
256
+ def process_defn(exp)
257
+ type1 = exp[1].first
258
+ type2 = exp[2].first rescue nil
259
+
260
+ if type1 == :args and [:ivar, :attrset].include? type2 then
261
+ name = exp.shift
262
+ case type2
263
+ when :ivar then
264
+ exp.clear
265
+ return "attr_reader #{name.inspect}"
266
+ when :attrset then
267
+ exp.clear
268
+ return "attr_writer :#{name.to_s[0..-2]}"
269
+ else
270
+ raise "Unknown defn type: #{exp.inspect}"
271
+ end
272
+ end
273
+
274
+ case type1
275
+ when :scope, :args then
276
+ name = exp.shift
277
+ args = process(exp.shift)
278
+ args = "" if args == "()"
279
+ body = []
280
+ until exp.empty? do
281
+ body << indent(process(exp.shift))
282
+ end
283
+ body = body.join("\n")
284
+ return "#{exp.comments}#{name}#{args}{\n#{body}\n}".gsub(/\n\s*\n+/, "\n")
285
+ else
286
+ raise "Unknown defn type: #{type1} for #{exp.inspect}"
287
+ end
288
+ end
289
+
290
+ def process_hash(exp)
291
+ result = []
292
+
293
+ until exp.empty?
294
+ e = exp.shift
295
+ if e.sexp_type == :lit
296
+ lhs = process(e)
297
+ rhs = exp.shift
298
+ t = rhs.first
299
+ rhs = process rhs
300
+ rhs = "(#{rhs})" unless [:lit, :str, :array, :iter].include? t # TODO: verify better!
301
+
302
+ result << "\n#{lhs[1..-1]}: #{rhs}"
303
+ else
304
+ lhs = process(e)
305
+ rhs = exp.shift
306
+ t = rhs.first
307
+ rhs = process rhs
308
+ rhs = "(#{rhs})" unless [:lit, :str].include? t # TODO: verify better!
309
+
310
+ result << "\n#{lhs}: #{rhs}"
311
+ end
312
+ end
313
+
314
+ return "{ #{indent(result.join(', '))} }"
315
+ end
316
+
317
+ def process_iasgn(exp)
318
+ lhs = exp.shift
319
+ if exp.empty? then # part of an masgn
320
+ lhs.to_s
321
+ else
322
+ if lhs.to_s[0] == '@'
323
+ "this.#{lhs.to_s[1..-1]} = #{process exp.shift}"
324
+ else
325
+ "#{lhs} = #{process exp.shift}"
326
+ end
327
+ end
328
+ end
329
+
330
+ def process_if(exp)
331
+ expand = Ruby2Ruby::ASSIGN_NODES.include? exp.first.first
332
+ c = process exp.shift
333
+ t_type = exp.first.sexp_type if exp.first
334
+ t = process exp.shift
335
+ f_type = exp.first.sexp_type if exp.first
336
+ f = process exp.shift
337
+
338
+ c = "(#{c.chomp})" #if c =~ /\n/
339
+
340
+ if t then
341
+ #unless expand then
342
+ # if f then
343
+ # r = "#{c} ? (#{t}) : (#{f})"
344
+ # r = nil if r =~ /return/ # HACK - need contextual awareness or something
345
+ # else
346
+ # r = "#{t} if #{c}"
347
+ # end
348
+ # return r if r and (@indent+r).size < LINE_LENGTH and r !~ /\n/
349
+ #end
350
+
351
+ r = "if#{c}{\n#{indent(t)}#{[:block, :while, :if].include?(t_type) ? '':';'}\n"
352
+ r << "}else{\n#{indent(f)}#{[:block, :while, :if].include?(f_type) ? '':';'}\n" if f
353
+ r << "}"
354
+
355
+ r
356
+ elsif f
357
+ unless expand then
358
+ r = "#{f} unless #{c}"
359
+ return r if (@indent+r).size < LINE_LENGTH and r !~ /\n/
360
+ end
361
+ "if(!#{c}){\n#{indent(f)}\n}"
362
+ else
363
+ # empty if statement, just do it in case of side effects from condition
364
+ "if #{c}{\n#{indent '// do nothing'}\n}"
365
+ end
366
+ end
367
+
368
+ def process_iter(exp)
369
+ iter = process exp.shift
370
+ args = exp.shift
371
+ args = (args == 0) ? '' : process(args)
372
+ body = exp.empty? ? nil : process(exp.shift)
373
+
374
+ b, e = #if iter == "END" then
375
+ [ "{", "}" ]
376
+ #else
377
+ # [ "do", "end" ]
378
+ # end
379
+
380
+ iter.sub!(/\(\)$/, '')
381
+
382
+ result = []
383
+ result << "#{iter}(#{args})"
384
+ result << "#{b}"
385
+ result << "\n"
386
+ if body then
387
+ result << indent(body.strip)
388
+ result << "\n"
389
+ end
390
+ result << e
391
+ result.join
392
+ end
393
+
394
+ def process_ivar(exp)
395
+ "this.#{exp.shift.to_s[1..-1]}"
396
+ end
397
+
398
+ def process_lasgn(exp)
399
+ s = "#{exp.shift}"
400
+ s += " = #{process exp.shift}" unless exp.empty?
401
+ s
402
+ end
403
+
404
+ def process_lit(exp)
405
+ obj = exp.shift
406
+ case obj
407
+ when Range then
408
+ "(#{obj.inspect})"
409
+ else
410
+ obj.inspect
411
+ end
412
+ end
413
+
414
+ def process_lvar(exp)
415
+ exp.shift.to_s
416
+ end
417
+
418
+ def process_masgn(exp)
419
+ lhs = exp.shift
420
+ rhs = exp.empty? ? nil : exp.shift
421
+
422
+ case lhs.first
423
+ when :array then
424
+ lhs.shift
425
+ lhs = lhs.map do |l|
426
+ case l.first
427
+ when :masgn then
428
+ "(#{process(l)})"
429
+ else
430
+ process(l)
431
+ end
432
+ end
433
+ when :lasgn then
434
+ lhs = [ splat(lhs.last) ]
435
+ when :splat then
436
+ lhs = [ :"*" ]
437
+ else
438
+ raise "no clue: #{lhs.inspect}"
439
+ end
440
+
441
+ if context[1] == :iter and rhs then
442
+ lhs << splat(rhs[1])
443
+ rhs = nil
444
+ end
445
+
446
+ unless rhs.nil? then
447
+ t = rhs.first
448
+ rhs = process rhs
449
+ rhs = rhs[1..-2] if t == :array # FIX: bad? I dunno
450
+ return "#{lhs.join(", ")} = #{rhs}"
451
+ else
452
+ return lhs.join(", ")
453
+ end
454
+ end
455
+
456
+ def process_nil(exp)
457
+ "null"
458
+ end
459
+
460
+ def process_return(exp)
461
+ if exp.empty? then
462
+ return "return"
463
+ else
464
+ return "return #{process exp.shift}"
465
+ end
466
+ end
467
+
468
+ def process_scope(exp)
469
+ exp.empty? ? "" : process(exp.shift)
470
+ end
471
+
472
+ def process_true(exp)
473
+ "true"
474
+ end
475
+
476
+ def process_until(exp)
477
+ cond_loop(exp, 'until')
478
+ end
479
+
480
+ def process_while(exp)
481
+ cond_loop(exp, 'while')
482
+ end
483
+
484
+ end
485
+
486
+ module Linguify
487
+ class Linguified
488
+
489
+ def indent
490
+ @indenture ||= ''
491
+ @indenture += ' '
492
+ end
493
+ def indenture
494
+ @indenture ||= ''
495
+ @indenture
496
+ end
497
+ def indenture= str
498
+ @indenture = str
499
+ end
500
+ def new_line
501
+ "\n" + indenture
502
+ end
503
+ def dent
504
+ @indenture ||= ''
505
+ @indenture = @indenture[2..-1]
506
+ end
507
+
508
+ def to_js sexy = @sexy
509
+ Ruby2Js.new.process(sexy)
510
+ end
511
+
512
+ end
513
+ end
data/lib/linguify.rb ADDED
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require 'linguify/linguified'
4
+ require 'linguify/reduction'
5
+ require 'linguify/sexp'
6
+ require 'linguify/string'
7
+ require 'linguify/proc'
8
+
9
+ def reduce(regexp,&code)
10
+ rule = regexp.values[0].kind_of?(Hash) ? {
11
+ :match => regexp.keys[0],
12
+ :result => regexp.values[0][:to] || '',
13
+ :lang => regexp.values[0][:lang] || :ruby,
14
+ :inline => regexp.values[0][:inline] || false,
15
+ :proc => code
16
+ } : {
17
+ :match => regexp.keys[0],
18
+ :result => regexp.values[0],
19
+ :lang => :ruby,
20
+ :inline => false,
21
+ :proc => code
22
+ }
23
+ Linguify::rules << rule
24
+ end
25
+
26
+ module Linguify
27
+
28
+ def self.rules
29
+ @@rules ||= []
30
+ end
31
+
32
+ end
33
+
34
+
@@ -0,0 +1,77 @@
1
+ # encoding: utf-8
2
+
3
+ require 'linguify'
4
+
5
+ describe Linguify::Linguified, "#linguify" do
6
+
7
+ it "should reduce multiple rules into ruby code" do
8
+
9
+ reduce /all directories/ => 'directories' do
10
+ Dir.entries('.').select{ |f| f[0] != '.' && File.directory?(f) }
11
+ end
12
+
13
+ reduce /({directories:[^}]*}) recursively/ => 'directories' do |dirs|
14
+ all_dirs = dirs
15
+ Find.find(dirs) do |path|
16
+ if FileTest.directory?(path)
17
+ if File.basename(path)[0] == '.'
18
+ Find.prune # Don't look any further into this directory.
19
+ else
20
+ all_dirs << path
21
+ next
22
+ end
23
+ end
24
+ end
25
+ all_dirs
26
+ end
27
+
28
+ reduce /all files inside ({directories:[^}]*})/ => 'files' do |dirs|
29
+ dirs.map{ |f| File.new(f, "r") }
30
+ end
31
+
32
+ reduce /view ({files:[^}]*})/ => '' do |files|
33
+ files.each do |file|
34
+ pp file
35
+ end
36
+ end
37
+
38
+ "view all files inside all directories recursively".linguify.to_ruby.should == "code = lambda do\n directories_0 = Dir.entries(\".\").select { |f| (not (f[0] == \".\")) and File.directory?(f) }\n directories_1 = (all_dirs = directories_0\n Find.find(directories_0) do |path|\n if FileTest.directory?(path) then\n if (File.basename(path)[0] == \".\") then\n Find.prune\n else\n (all_dirs << path)\n next\n end\n end\n end\n all_dirs)\n files_2 = directories_1.map { |f| File.new(f, \"r\") }\n files_2.each { |file| pp(file) }\nend\n"
39
+ end
40
+
41
+ it "should mix javascript and ruby" do
42
+ reduce /a possible javascript NOSQL query/ => {:to => 'query', :lang => :js} do
43
+ @db.forEach(lambda{ |record|
44
+ emit(record);
45
+ }
46
+ )
47
+ end
48
+
49
+ reduce /execute ({query:[^}]*})/ => '' do |query|
50
+ db.map query
51
+ end
52
+
53
+ "execute a possible javascript NOSQL query".linguify.to_ruby.should == "code = lambda do\n query = \"function(){\\n this.db.forEach(function(record){\\n emit(record)\\n });\\n}\"\n db.map(query)\nend\n"
54
+ end
55
+
56
+ it "should inline sub-expressions" do
57
+ reduce /sub expression/ => {:to => 'sub_expression', :lang => :ruby, :inline => true} do
58
+ pp "this is the sub expression code"
59
+ end
60
+
61
+ reduce /({sub_expression:[^}]*}) of inlined code/ => {:to => 'code', :lang => :ruby, :inline => true} do |sub|
62
+ something.each do |foobar|
63
+ pp foobar
64
+ end
65
+ end
66
+
67
+ reduce /execute ({code:[^}]*})/ => '' do |code|
68
+ pp "hey mum"
69
+ code
70
+ code[:sub]
71
+ pp "you will never know what I just did"
72
+ end
73
+
74
+ "execute sub expression of inlined code".linguify.to_ruby.should == "code = lambda do\n (pp(\"hey mum\")\n (something.each { |foobar| pp(foobar) })\n pp(\"this is the sub expression code\")\n pp(\"you will never know what I just did\"))\nend\n"
75
+ end
76
+
77
+ end
data/tasks/spec.rake ADDED
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rake'
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc "Run specs"
7
+ RSpec::Core::RakeTask.new do |t|
8
+ end