jejune 1.1.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,710 @@
1
+ #!/usr/bin/ruby
2
+ # encoding: utf-8
3
+ #--
4
+ # Copyright (c) 2010-2011 Kyle C. Yetter
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ #++
25
+
26
+ module Jejune
27
+ class Translator < TreeWalker
28
+ #require 'jejune/rewrite-debug'
29
+ #include DebugRewrite
30
+ include Data
31
+ include NodeTest
32
+
33
+ skip( BREAK, FALSE, TRUE, NULL, THIS, NUMBER, UNDEFINED )
34
+
35
+ def initialize( manager )
36
+ @manager = manager
37
+ super()
38
+ end
39
+
40
+
41
+ def on_macro( node )
42
+ @manager.define_macro( node )
43
+ node.replace!( '' )
44
+ end
45
+
46
+ def on_pound( node )
47
+ name, arguments = node
48
+ replacement = @manager.expand_macro( name.text, *arguments )
49
+ node.replace!( replacement )
50
+ end
51
+
52
+ def on_class_def( node )
53
+ descend( node )
54
+
55
+ name_token, *member_nodes = node
56
+ name = name_token.text
57
+ members = []
58
+ constructor = nil
59
+
60
+ for member_node in member_nodes
61
+ case member_node.type
62
+ when DEF
63
+ def_name = member_node.first.text
64
+ if member_node.length == 3
65
+ args = member_node[ 1 ]
66
+ body = member_node[ 2 ]
67
+ else
68
+ args = nil
69
+ body = member_node[ 1 ]
70
+ end
71
+
72
+ if def_name == 'initialize'
73
+ constructor = build_function( args, body, name );
74
+ else
75
+ members.push( [ @manager.jstring( def_name ), build_function( args, body ) ].join( ': ' ) )
76
+ end
77
+ when ASGN
78
+ var_name = @manager.jstring( member_node[ 0 ].text )
79
+ var_val = member_node[ 1 ].source
80
+
81
+ members.push( [ var_name, var_val ].join( ': ' ) )
82
+ when VAR
83
+ for assign in member_node
84
+ members.push( [ @manager.jstring( assign[ 0 ].text ), assign[ 1 ].source ].join( ': ' ) )
85
+ end
86
+ when GET, SET
87
+ members.push( member_node.source )
88
+ end
89
+ end
90
+
91
+ constructor ||= "function #{ name }() { return this; }"
92
+
93
+ node.replace!( "#{ constructor };\n#{ name }.prototype = {\n#{ members.join( ",\n" ) }\n};" )
94
+ end
95
+
96
+ def on_object_def( node )
97
+ descend( node )
98
+
99
+ var_token, *member_nodes = node
100
+ if is_var = ( var_token.type == VAR )
101
+ name_token, *member_nodes = member_nodes
102
+ else
103
+ name_token = var_token
104
+ end
105
+
106
+ name = name_token.text
107
+ members = []
108
+
109
+ for member_node in member_nodes
110
+ case member_node.type
111
+ when DEF
112
+ def_name = member_node.first.text
113
+ if member_node.length == 3
114
+ args = member_node[ 1 ]
115
+ body = member_node[ 2 ]
116
+ else
117
+ args = nil
118
+ body = member_node[ 1 ]
119
+ end
120
+
121
+ members.push( [ @manager.jstring( def_name ), build_function( args, body ) ].join( ': ' ) )
122
+ when ASGN
123
+ var_name = @manager.jstring( member_node[ 0 ].text )
124
+ var_val = member_node[ 1 ].source
125
+
126
+ members.push( [ var_name, var_val ].join( ': ' ) )
127
+ when VAR
128
+ for assign in member_node
129
+ members.push( [ @manager.jstring( assign[ 0 ].text ), assign[ 1 ].source ].join( ': ' ) )
130
+ end
131
+ when GET, SET
132
+ members.push( member_node.source )
133
+ end
134
+ end
135
+ prefix = is_var ? "var #{ name }" : name
136
+ node.replace!( "#{ prefix } = {\n#{ members.join( ",\n" ) }\n};" )
137
+ end
138
+
139
+ def on_try( node )
140
+ descend( node )
141
+ if default_catch = @manager.macros[ 'defaultCatch' ]
142
+ error_token = TokenData::Token.new( ID, :default, 'jjse' )
143
+ catch_source = ' catch ( jjse ) {' << default_catch.apply( error_token ) << '}'
144
+ else
145
+ catch_source = ' catch ( jjse ) { /* do nothing */ }'
146
+ end
147
+ stream = node.token_stream
148
+ i = node.stop_index
149
+ i -= 1 while tk = stream[ i ] and tk.type != RBRACE
150
+ if i >= 0 then node.insert_after!( i, catch_source )
151
+ else node.insert_after!( catch_source )
152
+ end
153
+ end
154
+
155
+
156
+ #
157
+ # ^( ITER ^(CALL $name ^(ARGUMENTS $args ...)) ^(PARAMS $block_parmas ...) ^(BLOCK ...) )
158
+ #
159
+ def on_iter( node )
160
+ descend( node )
161
+ target, args, block = node
162
+
163
+ func = build_function( args, block )
164
+ block.delete!
165
+
166
+ case target.type
167
+ when CALL, NEW
168
+ arg_node = target.last
169
+ if placeholder = arg_node.find { | child | child.type == FUNC_ARG }
170
+ placeholder.replace!( func )
171
+ else
172
+ append_argument( arg_node, func )
173
+ end
174
+ else
175
+ if target.type == ID and target.text == 'eval'
176
+ node.replace!( "(#{ func })()" )
177
+ else
178
+ target.append!( '( ' << func << ' )' )
179
+ end
180
+ end
181
+ end
182
+
183
+ # confirm that the `&' placeholder is occurring under a block call
184
+ def on_func_arg( node )
185
+ arguments_node = node.parent
186
+ arguments_type = arguments_node && arguments_node.type
187
+ unless arguments_type == ARGUMENTS
188
+ raise TranslationError, "BUG: function placeholder argument in an arguments list"
189
+ end
190
+
191
+ call_node = arguments_node.parent
192
+ call_type = call_node && call_node.type
193
+ unless call_type == CALL or call_type == NEW
194
+ raise TranslationError, "BUG: function placeholder argument isn't under a function/new call"
195
+ end
196
+
197
+ iter_node = call_node.parent
198
+ iter_type = iter_node && iter_node.type
199
+ unless iter_type == ITER
200
+ raise TranslationError, "no iteration block corresponding to function placeholder argument"
201
+ end
202
+ end
203
+
204
+ def on_ivar( node )
205
+ node.replace!( node.token, "this." + node.text[ 1..-1 ] )
206
+ end
207
+
208
+ def on_function( node )
209
+ descend( node )
210
+ add_param_parsing( *node.last( 2 ) )
211
+ end
212
+
213
+ def on_set( node )
214
+ descend( node )
215
+
216
+ param_node, body_node = node.last( 2 )
217
+ body_node ||= param_node
218
+ #add_return( body_node )
219
+
220
+ if param_node and param_node.type == PARAMS
221
+ add_param_parsing( param_node, body_node )
222
+ else
223
+ param_node.append!( '()' )
224
+ end
225
+ end
226
+
227
+ def on_get( node )
228
+ descend( node )
229
+
230
+ param_node, body_node = node.last( 2 )
231
+ body_node ||= param_node
232
+ add_return( body_node )
233
+
234
+ # this is a hack to fix double returns that show up in some situations until
235
+ # I figure out exactly why
236
+ node.source
237
+
238
+ if param_node and param_node.type == PARAMS
239
+ add_param_parsing( param_node, body_node )
240
+ else
241
+ param_node.append!( '()' )
242
+ end
243
+ end
244
+
245
+ def on_post_unless( node )
246
+ descend( node )
247
+ error_name, condition = node[ 0 ], node[ 1 ]
248
+ condition.surround!( '!( ', ')' )
249
+ node.replace!( node.token, 'if' )
250
+ end
251
+
252
+ def on_finally( node )
253
+ bottom = top = node
254
+ descent = []
255
+ while n = bottom[ 0 ] and CATCH_TYPES.include?( n.type )
256
+ descent.unshift( *bottom.drop( 1 ) )
257
+ bottom = n
258
+ end
259
+ descent.unshift( *bottom )
260
+
261
+ for child in descent
262
+ enter( child )
263
+ end
264
+
265
+ if FUNCTION_TYPES.include?( descent.first.type )
266
+ function_block = descent.first.last
267
+
268
+ catch_span = bottom.token.index .. top.token_stop_index
269
+ code = node.token_stream.render( catch_span )
270
+ node.delete!( catch_span )
271
+
272
+ surround_block( function_block, "try {", "} " << code )
273
+ end
274
+ end
275
+
276
+ def on_catch( node )
277
+ descend( node )
278
+ ##if node.first.type == FUNCTION
279
+ ## func_node = node.first
280
+ ## catch_range = ( node.first.token_stop_index + 1 .. node.token_stop_index )
281
+ ## catch_source = node.source( catch_range )
282
+ ## node.delete!( catch_range )
283
+ ## require 'pp'
284
+ ## puts func_node.last.source( func_node.last.inner_range )
285
+ ##end
286
+ end
287
+
288
+ def on_arrow( node )
289
+ descend( node )
290
+ name_node = param_node = body_node = nil
291
+ i = 0
292
+
293
+ if node[ i ].type == ID
294
+ name_node, i = node[ i ], i + 1
295
+ end
296
+
297
+ if node[ i ].type == PARAMS
298
+ param_node, i = node[ i ], i + 1
299
+ end
300
+
301
+ body_node = node[ i ]
302
+ add_return( body_node )
303
+ add_param_parsing( param_node, body_node ) unless param_node.nil?
304
+
305
+ if name_node
306
+ if node.token.index > name_node.token.index
307
+ # the name is a property definition ala { a: 3, b ->() { ... } }
308
+ name_node.append!( ':' )
309
+ node.replace!( node.token, param_node ? 'function' : 'function()' )
310
+ else
311
+ # the name is a function name ala ->b() { ... }
312
+ node.replace!( node.token, 'function ' )
313
+ name_node.append!( '()' ) if param_node.nil?
314
+ end
315
+ else
316
+ node.replace!( node.token, param_node ? 'function' : 'function()' )
317
+ end
318
+ end
319
+
320
+ def on_method( node )
321
+ descend( node )
322
+ on_arrow( node )
323
+ node.first.append!( ':' )
324
+ end
325
+
326
+ def on_arguments( node )
327
+ node.empty? and return
328
+ descend( node )
329
+ convert_property_lists( node )
330
+ stream = node.token_stream
331
+ # delete trailing comma
332
+ node.last.token_stop_index.upto( node.token_stop_index ) { | i |
333
+ if stream[ i ].type == COMMA
334
+ stream.delete( i )
335
+ break
336
+ end
337
+ }
338
+ end
339
+
340
+ def on_array( node )
341
+ node.empty? and return
342
+ descend( node )
343
+ convert_property_lists( node )
344
+ end
345
+
346
+
347
+ def on_if( node )
348
+ special_condition( node ) or descend( node )
349
+ end
350
+
351
+ def on_unless( node )
352
+ special_condition( node, true ) and return
353
+ descend( node )
354
+ invert_clause( node.first )
355
+ node.replace!( node.token, 'if' )
356
+ end
357
+
358
+ def on_until( node )
359
+ descend( node )
360
+ invert_clause( node.first )
361
+ node.replace!( node.token, 'while' )
362
+ end
363
+
364
+ def on_colon( node )
365
+ descend( node )
366
+ if key = node[ 0 ] and key.type == ID
367
+ key.replace!( key.text.to_json )
368
+ end
369
+ end
370
+
371
+ def on_string( node )
372
+ literal = node.text or return
373
+ quote = literal[ 0, 1 ]
374
+ body = literal[ 1, literal.length - 2 ]
375
+ node.replace!( @manager.jstring( body, quote == "'" ) )
376
+ end
377
+
378
+ def on_doc( node )
379
+ literal = node.text or return
380
+ body = @manager.outdent( literal[ 1, literal.length - 2 ] )
381
+ value = @manager.string_value( body )
382
+ node.replace!( value.to_json )
383
+ end
384
+
385
+ def on_dstring( node )
386
+ data = Blob.extract( node.token, DSTRING )
387
+ node.replace!( @manager.interpolate( data, false ) )
388
+ end
389
+
390
+ def on_ddoc( node )
391
+ data = Blob.extract( node.token, DDOC )
392
+ node.replace!( @manager.interpolate( data, true ) )
393
+ end
394
+
395
+ def on_general( node )
396
+ token = node.token
397
+ data = Blob.extract( token )
398
+ node.replace!( convert_blob( data ) )
399
+ end
400
+
401
+ def on_or_asgn( node )
402
+ descend( node )
403
+ left, right = node
404
+ target, value = left.source, right.source
405
+
406
+ right.type == QMARK and value = "( #{ value } )"
407
+ node.replace!( "#{ target } = #{ target } || #{ value }" )
408
+ end
409
+
410
+ def on_ejs( node )
411
+ ejs = extract_ejs( node.token )
412
+ node.replace!( ejs.as_function )
413
+ end
414
+
415
+ def on_is_undefined( node )
416
+ descend( node )
417
+ expr = %[('undefined' == typeof( #{ node.last.source } ))]
418
+ node.replace!( expr )
419
+ end
420
+
421
+ def on_is_defined( node )
422
+ descend( node )
423
+ expr = %[('undefined' != typeof( #{ node.last.source } ))]
424
+ node.replace!( expr )
425
+ end
426
+
427
+ def on_directive( node )
428
+ token = node.token
429
+ file = token.source_name
430
+
431
+ current_line = token.line
432
+ after_line = current_line + token.text.count( "\n" )
433
+
434
+ src = scanner( node.token, 2 )
435
+ src.space!
436
+
437
+ command = src.id! or raise( "malformed directive `#{ node.token.text }'" )
438
+
439
+ case command
440
+ when 'hide'
441
+ node.replace!( '' )
442
+ when 'require', 'include'
443
+ targets = []
444
+ src.space!
445
+
446
+ case c = src.peek( 1 )
447
+ when '<', '[', '(', '{'
448
+ content = src.nested!( c, DELIMS[ c ] )
449
+ targets.concat( @manager.split_words( content[ 1 ... -1 ] ) )
450
+ when '"', '`', "'"
451
+ content = src.string!
452
+ targets << @manager.string_value( content[ 1 ... -1 ], content[ 0, 1 ] == "'" )
453
+ else
454
+ content = src.chunk!
455
+ targets << content.gsub( /\\(.)/m, '\1' )
456
+ end
457
+
458
+ repl = ''
459
+ method = @manager.method( "#{ command }!" )
460
+ targets.inject( repl ) do | repl, lib |
461
+ @manager.location_markers? and repl << "// #{ lib }:1\n"
462
+ repl << method.call( lib ).to_s << "\n"
463
+ @manager.location_markers? ? repl << "// #{ file }:#{ after_line }\n" : repl
464
+ end
465
+
466
+ node.replace!( repl )
467
+ end
468
+ end
469
+
470
+
471
+ def translate( node )
472
+ node.infer_boundaries
473
+ enter( node )
474
+ #debug_rewrite( node.token_stream )
475
+ #puts '============'
476
+ #node.rewrite.reduce
477
+ #debug_rewrite( node.token_stream )
478
+ return node.rewrite.execute
479
+ #rescue ANTLR3::Bug
480
+ # debug_rewrite( node.token_stream )
481
+ # raise
482
+ ensure
483
+ node.token_stream.delete_program
484
+ end
485
+
486
+ private
487
+
488
+ def special_condition( node, invert = false )
489
+ clause = node.first
490
+ if clause.type == ID and BROWSERS.include?( clause.text )
491
+ if @manager.browser == clause.text
492
+ include_node = node[ invert ? 2 : 1 ]
493
+ else
494
+ include_node = node[ invert ? 1 : 2 ]
495
+ end
496
+
497
+ if include_node
498
+ descend( include_node )
499
+ replacement = include_node.source
500
+ replacement.gsub!( /\A\{\s*|\s*\}\z/, '' )
501
+ else
502
+ replacement = ''
503
+ end
504
+ node.replace!( replacement )
505
+ #program = node.token_stream.program
506
+ #range = node.token_range
507
+
508
+ #program.instance_variable_get( :@operations ).delete_if do | i |
509
+ # i.covered_by?( range )
510
+ #end
511
+ return true
512
+ end
513
+
514
+ return nil
515
+ end
516
+
517
+ def add_function_arg( arg_node, func )
518
+ if placeholder = arg_node.find { | child | child.type == FUNC_ARG }
519
+ placeholder.replace!( func )
520
+ else
521
+ append_argument( arg_node, func )
522
+ end
523
+ end
524
+
525
+ def scanner( token, pos = nil )
526
+ SourceScanner.new( token.text,
527
+ :line => token.line,
528
+ :column => token.column,
529
+ :file => token.source_name,
530
+ :position => pos
531
+ )
532
+ end
533
+
534
+ def convert_blob( blob )
535
+ handler_for( blob ).process( @manager, blob )
536
+ end
537
+
538
+ def extract_ejs( ejs_token )
539
+ name = nil
540
+ parameters = nil
541
+ src = scanner( ejs_token, 3 ) # skip leading `ejs'
542
+ src.space!( true )
543
+ name = src.id!
544
+ src.space!( true )
545
+
546
+ if src.see?( '(' )
547
+ arg_tokens = []
548
+ src.lexer do | lex |
549
+ arg_tokens << lex.next_token
550
+ depth = 1
551
+ lex.each do | token |
552
+ arg_tokens << token
553
+ case token.type
554
+ when LPAREN
555
+ depth += 1
556
+ when RPAREN
557
+ depth -= 1
558
+ depth.zero? and break
559
+ end
560
+ end
561
+ end
562
+
563
+ arg_tokens = RewriteStream.new( arg_tokens )
564
+ adaptor = RewriteAdaptor.new( arg_tokens )
565
+ tree = Jejune::Parser.new( arg_tokens, :adaptor => adaptor ).function_parameters.tree
566
+ parameters = ParameterSet.study( tree )
567
+ end
568
+
569
+ src.space!( true )
570
+ src.scan( /%/ )
571
+ body = @manager.outdent( src.rest[ 1 ... -1 ] )
572
+
573
+ EJJS.new( body,
574
+ :name => name, :parameters => parameters,
575
+ :file => ejs_token.source_name,
576
+ :line => src.line, :column => src.column
577
+ )
578
+ end
579
+
580
+
581
+ def convert_property_lists( node )
582
+ property_definition?( node.first ) and node.first.prepend!( '{ ' )
583
+
584
+ node.each_cons( 2 ) do | left, right |
585
+ t1, t2 = property_definition?( left ), property_definition?( right )
586
+
587
+ if !t1 and t2
588
+ right.prepend!( '{ ' )
589
+ elsif t1 and !t2
590
+ left.append!( ' }' )
591
+ end
592
+ end
593
+
594
+ property_definition?( node.last ) and node.last.replace!( node.last.source << ' }' )
595
+ end
596
+
597
+ def build_function( args, block, name = nil )
598
+ block.text == '{' and add_return( block )
599
+
600
+ block.strip! # get rid of surrounding { } / do end
601
+
602
+ if args
603
+ params = ParameterSet.study( args )
604
+ unless args.stop_index < args.start_index
605
+ # trash the | x, ... | declaration unless there isn't one
606
+ # note: if there's only an imaginary PARAMs, for some unknown reason,
607
+ # calling args.delete! will insert everything between the open brace
608
+ # and the first token of the first statement
609
+ args.delete!
610
+ end
611
+ else
612
+ params = ParameterSet.new();
613
+ end
614
+
615
+ body = params.parsing_source << block.source
616
+ body.strip!
617
+ if body =~ /\n/
618
+ body = "\n" << body << "\n"
619
+ else
620
+ body = " " << body << " "
621
+ end
622
+
623
+ prefix = name ? "function #{ name }" : "function"
624
+
625
+ "#{ prefix }#{ params.declaration } {#{ body }}"
626
+ end
627
+
628
+ def append_argument( node, source )
629
+ if node.empty?
630
+ tk = node.start
631
+ source = " #{ source } "
632
+ else
633
+ tk = node.last.stop
634
+ source = ", #{ source } "
635
+ end
636
+ node.insert_after!( tk, source )
637
+ end
638
+
639
+ def add_return( block )
640
+ if last_statement = block.last and expression?( last_statement )
641
+ term = last_statement.stop
642
+ term.type == SEMI and last_statement.delete!( term )
643
+ last_statement.surround!( 'return( ', ' );' )
644
+ end
645
+ end
646
+
647
+ def invert_clause( clause )
648
+ clause.surround!( '( !', ' )' )
649
+ end
650
+
651
+ def add_param_parsing( param_node, body_node )
652
+ if params = ParameterSet.study( param_node )
653
+ param_node.replace!( params.declaration )
654
+ surround_block( body_node, params.parsing_source )
655
+ end
656
+ end
657
+
658
+ def surround_block( block_node, before = nil, after = nil )
659
+ tokens = block_node.tokens
660
+ if before
661
+ if n = block_node.first
662
+ n.prepend!( before )
663
+ elsif t = tokens.find { | t | t.type == LBRACE }
664
+ block_node.append!( t, before )
665
+ end
666
+ end
667
+ if after
668
+ if t = tokens.reverse_each.find { | t | t.type == RBRACE }
669
+ block_node.prepend!( t, after )
670
+ elsif n = block_node.last
671
+ n.append!( after )
672
+ end
673
+ end
674
+ return block_node
675
+ end
676
+ end
677
+
678
+
679
+
680
+ ClassBuilder = Struct.new( :name, :constructor, :members );
681
+ class ClassBuilder
682
+ include Constants
683
+
684
+ def initialize( node )
685
+ super( nil, nil, [] );
686
+ study( *node );
687
+ end
688
+
689
+ def study( name, *members );
690
+ self.name = name.text;
691
+ members.each do | member |
692
+ case member.type
693
+ when DEF
694
+ if member.first.text == 'initialize'
695
+ self.constructor = member
696
+ else
697
+ self.members.push( member );
698
+ end
699
+ else
700
+ self.members.push( member );
701
+ end
702
+ end
703
+ return self
704
+ end
705
+
706
+
707
+ end
708
+ end
709
+
710
+