linguify 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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