zlocalize 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,756 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ require 'rdoc'
4
+ require 'rdoc/options'
5
+ require 'rdoc/ruby_lex'
6
+ require File.join(File.dirname(__FILE__),'translation_file')
7
+ require File.join(File.dirname(__FILE__),'harvester')
8
+
9
+ module ZLocalize
10
+
11
+ # base class for Expressions we're interested in
12
+ class Expression #:nodoc: all
13
+ attr_accessor :line_no
14
+ attr_accessor :char_no
15
+ attr_accessor :text
16
+ attr_accessor :sub_method
17
+
18
+ def initialize(line_no,char_no,text = '')
19
+ @line_no, @char_no, @text = line_no, char_no, text
20
+ end
21
+
22
+ def set_text(t)
23
+ @text = t
24
+ self
25
+ end
26
+
27
+ # puts spaces before the output generated by +display+. Used for debugging purposes.
28
+ def prefix(indent)
29
+ ' ' * (indent*INDENT_STEP)
30
+ end
31
+
32
+ # display this Expression as a string. Used for debugging purposes
33
+ def display(indent = 0)
34
+ i = prefix(indent)
35
+ i << "#{self.class.name} (#{line_no},#{char_no}): #{@text}\n"
36
+ end
37
+
38
+ # Since Expression is an abstract class, no output is generated
39
+ # (descendants redefine this method)
40
+ def to_translation_entry(filename)
41
+ nil
42
+ end
43
+
44
+ def to_entry_string
45
+ @text.force_encoding("UTF-8")
46
+ end
47
+
48
+ def is_translate_call
49
+ false
50
+ end
51
+ end # class Expression
52
+
53
+ # Basic identifier expression
54
+ class IdentifierExpression < Expression #:nodoc: all
55
+ attr_accessor :name
56
+ attr_accessor :parameters
57
+
58
+ def initialize(line_no,char_no,name)
59
+ @name = name
60
+ @parameters = []
61
+ @sub_method = nil
62
+ super(line_no,char_no)
63
+ end
64
+
65
+ # Used for debugging
66
+ def display(indent = 0)
67
+ i = prefix(indent)
68
+ i << "#{self.class.name} (#{line_no},#{char_no}): #{@name} (#{parameters.size} parameters)\n"
69
+ @parameters.each { |p| i << p.display(indent+2) }
70
+ unless @sub_method.nil?
71
+ i << prefix(indent+1) + "sub-method:\n"
72
+ i << @sub_method.display(indent+2)
73
+ end
74
+ i
75
+ end
76
+
77
+ # Convert ourselves to a Hash or an Array of Hashes which each represent
78
+ # a call to a translate method.
79
+ # Will only output something if this expression is an actual call to <tt>_</tt> or <tt>n_</tt>
80
+ def to_translation_entry(filename)
81
+ if ['_','n_'].include?(@name)
82
+ params = @parameters.dup
83
+ elsif @name == 'ZLocalize' && @sub_method.is_a?(IdentifierExpression) && ['pluralize','translate'].include?(@sub_method.name)
84
+ params = @sub_method.parameters.dup
85
+ else
86
+ return nil
87
+ end
88
+ if params.size > 0
89
+ p1 = params.shift
90
+ # we only collect litteral strings and Arrays
91
+ entry = []
92
+ if [StringExpression,ArrayExpression].include?(p1.class)
93
+ entry << TranslationEntry.new( 'plural' => @name == 'n_',
94
+ 'source' => p1.to_entry_string,
95
+ 'references' => [ "#{filename}:#{p1.line_no}" ])
96
+ end
97
+ # collect all other parameters to this call, in case they are themselves calls to nested _("")
98
+ params.each { |p| entry << p.to_translation_entry(filename) }
99
+ return entry.flatten.compact
100
+ else
101
+ nil
102
+ end
103
+ end
104
+
105
+ def to_entry_string
106
+ "(identifier '#{name}')".force_encoding("UTF-8")
107
+ end
108
+
109
+ end # class IdentifierExpression
110
+
111
+ # a String Expression (litteral occurence of a string)
112
+ class StringExpression < IdentifierExpression #:nodoc: all
113
+
114
+ def display(indent = 0)
115
+ i = prefix(indent)
116
+ i << "#{self.class.name} (#{line_no},#{char_no}): #{@name}\n"
117
+ unless @sub_method.nil?
118
+ i << prefix(indent+1) + "sub-method:\n"
119
+ i << @sub_method.display(indent+2)
120
+ end
121
+ i
122
+ end
123
+
124
+ def to_entry_string
125
+ ZLocalize.clean_ruby_string(@name).force_encoding("UTF-8")
126
+ # @name
127
+ end
128
+
129
+ end #class StringExpression
130
+
131
+ # a Number Expression
132
+ class NumberExpression < IdentifierExpression #:nodoc: all
133
+
134
+ def display(indent = 0)
135
+ i = prefix(indent)
136
+ i << "#{self.class.name} (#{line_no},#{char_no}): #{@name}\n"
137
+ unless @sub_method.nil?
138
+ i << prefix(indent+1) + "sub-method:\n"
139
+ i << @sub_method.display(indent+2)
140
+ end
141
+ i
142
+ end
143
+
144
+ def to_entry_string
145
+ "\"(number '#{@name}')\"".force_encoding("UTF-8")
146
+ end
147
+
148
+ end # class NumberExpression
149
+
150
+ # a Symbol Expression
151
+ class SymbolExpression < IdentifierExpression #:nodoc: all
152
+
153
+ def display(indent = 0)
154
+ i = prefix(indent)
155
+ i << "#{self.class.name} (#{line_no},#{char_no}): #{@name}\n"
156
+ unless @sub_method.nil?
157
+ i << prefix(indent+1) + "sub-method:\n"
158
+ i << @sub_method.display(indent+2)
159
+ end
160
+ i
161
+ end
162
+
163
+ def to_entry_string
164
+ "\"(symbol #{@name})\"".force_encoding("UTF-8")
165
+ end
166
+
167
+ end # class SymbolExpression
168
+
169
+ class RangeExpression < IdentifierExpression #:nodoc: all
170
+ attr_accessor :low
171
+ attr_accessor :high
172
+
173
+ def display(indent = 0)
174
+ p = prefix(indent)
175
+ i = p + "#{self.class.name} (#{line_no},#{char_no})\n"
176
+ i << p + "low:\n" + low.display(indent+1)
177
+ i << p + "high:\n" + high.display(indent+1)
178
+ i << "\n"
179
+ end
180
+
181
+ def to_entry_string
182
+ return "\"(range)\"".force_encoding("UTF-8")
183
+ end
184
+
185
+ end # class RangeExpression
186
+
187
+ # an Array declaration. It will hold its elements as a list of Expressions
188
+ class ArrayExpression < IdentifierExpression #:nodoc: all
189
+
190
+ attr_accessor :elements
191
+
192
+ def initialize(line_no,char_no)
193
+ super(line_no,char_no,'array')
194
+ @elements = []
195
+ end
196
+
197
+ def display(indent = 0)
198
+ i = super(indent)
199
+ @elements.each { |e| i << e.display(indent+1) }
200
+ i
201
+ end
202
+
203
+ def to_translation_entry(filename)
204
+ @elements.collect { |elem| elem.to_translation_entry(filename) }.flatten.compact
205
+ end
206
+
207
+ def to_entry_string
208
+ @elements.collect { |e| e.to_entry_string }
209
+ # '[' + @elements.collect { |e| e.to_entry_string }.join(', ') + ']'
210
+ end
211
+
212
+ end # class ArrayExpression
213
+
214
+ # a Hash declaration. Elements will be a Hash in which each element
215
+ # will itself be a HashElementExpression like this:
216
+ # { :key => Expression, :value => Expression }
217
+ class HashExpression < IdentifierExpression #:nodoc: all
218
+
219
+ attr_accessor :elements
220
+
221
+ def initialize(line_no,char_no)
222
+ super(line_no,char_no,'hash')
223
+ @elements = []
224
+ end
225
+
226
+ def display(indent = 0)
227
+ i = super(indent)
228
+ @elements.each { |e| i << e.display(indent) }
229
+ i
230
+ end
231
+
232
+ def to_translation_entry(filename)
233
+ @elements.collect { |elem| elem.to_translation_entry(filename) }.flatten.compact
234
+ end
235
+
236
+ def to_entry_string
237
+ '{ ' + @elements.collect { |e| e.to_entry_string }.compact.join(',') + ' }'.force_encoding("UTF-8")
238
+ end
239
+
240
+ end # class HashExpression
241
+
242
+ # a Hash Element. It holds the key and the value of the element as Expressions
243
+ class HashElementExpression < IdentifierExpression #:nodoc: all
244
+ attr_accessor :key
245
+ attr_accessor :value
246
+
247
+ def initialize(line_no,char_no)
248
+ super(line_no,char_no,'hashElement')
249
+ end
250
+
251
+ def display(indent = 0)
252
+ i = super(indent)
253
+ i << prefix(indent) + "key:\n" + @key.display(indent+1)
254
+ i << prefix(indent) + "value:\n" + @value.display(indent+1)
255
+ end
256
+
257
+ def to_translation_entry(filename)
258
+ r = [key.to_translation_entry(filename),value.to_translation_entry(filename)].flatten.compact
259
+ end
260
+
261
+ def to_entry_string
262
+ "#{key.to_entry_string} => #{value.to_entry_string}".force_encoding("UTF-8")
263
+ end
264
+
265
+ end # class HashElementExpression
266
+
267
+ # an Operator expression holds the operator itself, and the operands
268
+ class OperatorExpression < Expression #:nodoc: all
269
+
270
+ attr_accessor :operator
271
+ attr_accessor :operands
272
+
273
+ def initialize(line_no,char_no,op)
274
+ super(line_no,char_no,op)
275
+ @operands = []
276
+ @operator = op
277
+ end
278
+
279
+ def display(indent = 0)
280
+ p = prefix(indent)
281
+ i = p + "#{self.class.name} (#{line_no},#{char_no})\n"
282
+ @operands.each { |o| i << o.display(indent+1) }
283
+ i
284
+ end
285
+
286
+ def to_translation_entry(filename)
287
+ @operands.collect { |o| o.to_translation_entry(filename) }.flatten.compact
288
+ end
289
+
290
+ def to_entry_string
291
+ @operands.collect { |o| o.to_entry_string }.join(" #{@operator} ").force_encoding("UTF-8")
292
+ end
293
+
294
+ end # class OperatorExpression
295
+
296
+ # represents an 'inline-if' expression, as in:
297
+ # something ? when_true : when_false
298
+ class ConditionalExpression < Expression #:nodoc: all
299
+ attr_accessor :condition
300
+ attr_accessor :true_expr
301
+ attr_accessor :false_expr
302
+
303
+ def initialize(line_to,char_no)
304
+ super(line_to,char_no,'?')
305
+ end
306
+
307
+ def display(indent = 0)
308
+ i = prefix(indent) + "#{self.class.name} (#{line_no},#{char_no})\n"
309
+ i << prefix(indent+1) + "condition:\n"
310
+ i << @condition.display(indent+2)
311
+ i << prefix(indent+1) + "when true:\n"
312
+ i << @true_expr.display(indent+2)
313
+ i << prefix(indent+1) + "when false:\n"
314
+ i << @false_expr.display(indent+2)
315
+ i
316
+ end
317
+
318
+ def to_translation_entry(filename)
319
+ [condition.to_translation_entry(filename),
320
+ true_expr.to_translation_entry(filename),
321
+ false_expr.to_translation_entry(filename)].flatten.compact
322
+ end
323
+
324
+ def to_entry_string
325
+ "#{conditions.as_entry_string} ? #{true_expr.as_entry_string} : #{false_expr.as_entry_string}".force_encoding("UTF-8")
326
+ end
327
+
328
+ end # class ConditionalExpression
329
+
330
+ # any other Expression that we don't really care about
331
+ class DontCareExpression < Expression #:nodoc: all
332
+ end
333
+
334
+
335
+ OPERATOR_TOKENS = [RDoc::RubyToken::TkGT, RDoc::RubyToken::TkLT, RDoc::RubyToken::TkPLUS,
336
+ RDoc::RubyToken::TkMINUS, RDoc::RubyToken::TkMULT, RDoc::RubyToken::TkSTAR,
337
+ RDoc::RubyToken::TkDIV,
338
+ RDoc::RubyToken::TkMOD, RDoc::RubyToken::TkBITOR, RDoc::RubyToken::TkBITXOR,
339
+ RDoc::RubyToken::TkBITAND, RDoc::RubyToken::TkBITNOT, RDoc::RubyToken::TkNOTOP,
340
+ RDoc::RubyToken::TkPOW, RDoc::RubyToken::TkCMP, RDoc::RubyToken::TkEQ,
341
+ RDoc::RubyToken::TkEQQ, RDoc::RubyToken::TkNEQ, RDoc::RubyToken::TkGEQ,
342
+ RDoc::RubyToken::TkLEQ, RDoc::RubyToken::TkANDOP, RDoc::RubyToken::TkOROP,
343
+ RDoc::RubyToken::TkMATCH, RDoc::RubyToken::TkNMATCH, RDoc::RubyToken::TkLSHFT,
344
+ RDoc::RubyToken::TkRSHFT]
345
+
346
+ UNARY_OP_TOKENS = [RDoc::RubyToken::TkPLUS,RDoc::RubyToken::TkMINUS,RDoc::RubyToken::TkBITNOT,RDoc::RubyToken::TkNOTOP]
347
+
348
+
349
+ # Parses a Ruby (or ERB) file and generates a list of language tokens
350
+ class SourceParser
351
+
352
+ attr_accessor :translate_calls
353
+ attr_accessor :in_hash
354
+ attr_accessor :filename
355
+ attr_accessor :root
356
+
357
+ def initialize(filename, root, is_erb = false)
358
+ @in_hash = 0
359
+ @translate_calls = []
360
+ @root = File.expand_path(root).downcase
361
+ @filename = File.expand_path(filename).downcase
362
+ @relative_filename = @filename.gsub(@root,'')
363
+ content = File.open(filename, "r") { |f| f.read }
364
+ if is_erb
365
+ content = ERB.new(content).src
366
+ end
367
+
368
+ @lex = RDoc::RubyLex.new(content,RDoc::Options.new)
369
+ @token_list = []
370
+ while tk = @lex.token
371
+ @token_list << tk
372
+ end
373
+ @token_index = -1
374
+ @last_token_index = @token_list.size
375
+ end
376
+
377
+ def get_tk
378
+ @token_index += 1
379
+ if @token_index < @last_token_index
380
+ @tk = @token_list[@token_index]
381
+ else
382
+ @tk = nil
383
+ end
384
+ @tk
385
+ end
386
+
387
+ def peek_tk
388
+ peek_index = @token_index + 1
389
+ return peek_index < @last_token_index ? @token_list[peek_index] : nil
390
+ end
391
+
392
+ def rewind
393
+ @token_index = -1
394
+ end
395
+
396
+ def skip_white_space
397
+ while [RDoc::RubyToken::TkNL, RDoc::RubyToken::TkSPACE].include?(@tk.class)
398
+ get_tk
399
+ end
400
+ end
401
+
402
+ # simply list the tokens in the source file
403
+ def list_tokens
404
+ while get_tk
405
+ puts @tk.inspect
406
+ end
407
+ end
408
+
409
+ def parse_error(tk,msg)
410
+ raise(ParseError,"\n\nIn file #{@filename}, on line #{tk.line_no} at position #{tk.char_no}: " + msg + "\n\n")
411
+ end
412
+
413
+ # we simply detect calls to the ZLocalize methods and parse their parameter list
414
+ def parse
415
+ rewind
416
+ while get_tk
417
+ if @tk.is_a?(RDoc::RubyToken::TkCONSTANT) && @tk.text = 'ZLocalize'
418
+ get_tk # should be a dot
419
+ get_tk # either 'translate' or 'pluralize'
420
+ parse_translate_call if ['translate','pluralize'].include?(@tk.text)
421
+ elsif @tk.is_a?(RDoc::RubyToken::TkIDENTIFIER)
422
+ parse_translate_call if ['_','n_'].include?(@tk.text)
423
+ end
424
+ end
425
+ end
426
+
427
+ # parse a call to one of the translation methods <tt>_(...)</tt>, <tt>n_(...)</tt>
428
+ def parse_translate_call
429
+ m = IdentifierExpression.new(@tk.line_no,@tk.char_no,@tk.text)
430
+ get_tk
431
+ skip_white_space
432
+ expect_r_paren = @tk.is_a?(RDoc::RubyToken::TkLPAREN)
433
+ get_tk if expect_r_paren
434
+ m.parameters = parse_parameters
435
+ if expect_r_paren && !@tk.is_a?(RDoc::RubyToken::TkRPAREN)
436
+ parse_error(@tk,"')' expected but '#{@tk.text}' (#{@tk.class.name}) found")
437
+ end
438
+ @translate_calls << m
439
+ m
440
+ end
441
+
442
+
443
+ # parse the list of actual parameters to a method
444
+ def parse_parameters
445
+ parameters = []
446
+ skip_white_space
447
+ return if @tk.is_a?(RDoc::RubyToken::TkRPAREN)
448
+
449
+ parameters << parse_expression
450
+ skip_white_space
451
+ while @tk.is_a?(RDoc::RubyToken::TkCOMMA) do
452
+ get_tk
453
+ parameters << parse_expression
454
+ skip_white_space
455
+ end
456
+ parameters
457
+ end
458
+
459
+ # parse identifier(...), identifier[...] or identifier{...}
460
+ def parse_identifier
461
+ case @tk
462
+ when RDoc::RubyToken::TkLPAREN
463
+ ident_text = "(Method From Stack)"
464
+ when RDoc::RubyToken::TkfLBRACK
465
+ ident_text = "(Array Access)"
466
+ else
467
+ ident_text = @tk.text
468
+ line, char = @tk.line_no, @tk.char_no
469
+ get_tk
470
+ # check for a Ruby 2.0 Hash key symbol (identifier directly followed by a COLON)
471
+ if @tk.is_a?(RDoc::RubyToken::TkCOLON) || @tk.is_a?(RDoc::RubyToken::TkSYMBEG)
472
+ expr = SymbolExpression.new(line,char,'')
473
+ expr.set_text(ident_text + ':')
474
+ expr.name = ident_text + ':'
475
+ get_tk
476
+ return expr
477
+ end
478
+ end
479
+ ident = IdentifierExpression.new(@tk.line_no,@tk.char_no,ident_text)
480
+ while(@tk.is_a?(RDoc::RubyToken::TkCOLON2))
481
+ get_tk
482
+ ident.name << '::' + @tk.text
483
+ get_tk
484
+ end
485
+
486
+ case @tk
487
+ when RDoc::RubyToken::TkLPAREN
488
+ get_tk
489
+ ident.parameters = parse_parameters
490
+ unless @tk.is_a?(RDoc::RubyToken::TkRPAREN)
491
+ parse_error(@tk,"')' expected but '#{@tk.text}' found")
492
+ end
493
+ get_tk
494
+ when RDoc::RubyToken::TkfLBRACK
495
+ get_tk
496
+ ident.parameters = parse_parameters
497
+ unless @tk.is_a?(RDoc::RubyToken::TkRBRACK)
498
+ parse_error(@tk,"']' expected but '#{@tk.text}' found")
499
+ end
500
+ get_tk
501
+ end # case @tk
502
+ parse_identifier_method_call(ident)
503
+ end # parse_identifier
504
+
505
+ # parse the additional methods in an expression
506
+ # for example:
507
+ # self.method.sub_method(1).another_sub_method(2)
508
+ def parse_identifier_method_call(ident)
509
+ case @tk
510
+ when RDoc::RubyToken::TkDOT
511
+ get_tk
512
+ ident.sub_method = parse_identifier
513
+ when RDoc::RubyToken::TkLPAREN, RDoc::RubyToken::TkfLBRACK
514
+ ident.sub_method = parse_identifier
515
+ end
516
+ ident
517
+ end
518
+
519
+ # check if the expression currently being parsed as a range specifier
520
+ def check_for_range_expression(expr)
521
+ skip_white_space
522
+ if [RDoc::RubyToken::TkDOT2,RDoc::RubyToken::TkDOT3].include?(@tk.class)
523
+ range_expr = RangeExpression.new(expr.line_no,expr.char_no,'')
524
+ range_expr.low = expr
525
+ get_tk
526
+ range_expr.high = parse_expression
527
+ return range_expr
528
+ else
529
+ return expr
530
+ end
531
+ end
532
+
533
+ # check if the expression is a hash element declaration (i.e. is it followed by <tt>=></tt>)
534
+ def check_for_hash_element_expression(expr)
535
+ return expr if @in_hash > 0
536
+ if expr.is_a?(SymbolExpression) && expr.text =~ /:$/
537
+ he_expr = HashElementExpression.new(expr.line_no,expr.char_no)
538
+ he_expr.key = expr
539
+ skip_white_space
540
+ he_expr.value = parse_expression
541
+ # get_tk
542
+ return he_expr
543
+ else
544
+ skip_white_space
545
+ if @tk.is_a?(RDoc::RubyToken::TkASSIGN)
546
+ get_tk
547
+ if @tk.is_a?(RDoc::RubyToken::TkGT)
548
+ he_expr = HashElementExpression.new(expr.line_no,expr.char_no)
549
+ he_expr.key = expr
550
+ get_tk
551
+ he_expr.value = parse_expression
552
+ return he_expr
553
+ end
554
+ end
555
+ end
556
+ return expr
557
+ end
558
+
559
+ # parse an Expression, which can be either a simple identifier,
560
+ # a method call with parameters, an operator expression (as in 1 + 1)
561
+ # or an 'inline-if' expression
562
+ def parse_expression
563
+ skip_white_space
564
+ expr1 = parse_operand
565
+ skip_white_space
566
+ if OPERATOR_TOKENS.include?(@tk.class)
567
+ expr = OperatorExpression.new(expr1.line_no,expr1.char_no,'')
568
+ expr.operands << expr1
569
+ while OPERATOR_TOKENS.include?(@tk.class)
570
+ get_tk
571
+ expr.operands << parse_operand
572
+ end
573
+ else
574
+ expr = expr1
575
+ end
576
+ skip_white_space
577
+ if @tk.is_a?(RDoc::RubyToken::TkQUESTION)
578
+ get_tk
579
+ expr2 = ConditionalExpression.new(expr.line_no,expr.char_no)
580
+ expr2.condition = expr1
581
+ expr2.true_expr = parse_expression
582
+ skip_white_space
583
+ unless @tk.is_a?(RDoc::RubyToken::TkCOLON)
584
+ parse_error(@tk,"':' expected but #{@tk.text} found")
585
+ end
586
+ get_tk
587
+ expr2.false_expr = parse_expression
588
+ expr = expr2
589
+ end
590
+ expr
591
+ end
592
+
593
+ # parse and return an operand to an OperatorExpression
594
+ def parse_operand
595
+ skip_white_space
596
+ while UNARY_OP_TOKENS.include?(@tk.class)
597
+ get_tk
598
+ skip_white_space
599
+ end
600
+ case @tk
601
+ when RDoc::RubyToken::TkLPAREN, RDoc::RubyToken::TkfLPAREN
602
+ get_tk
603
+ expr = parse_expression
604
+ unless @tk.is_a?(RDoc::RubyToken::TkRPAREN)
605
+ parse_error(@tk,"')' expected but #{@tk.text} found (unmatched parenthesis)")
606
+ end
607
+ get_tk
608
+ expr = parse_identifier_method_call(expr) # ident.something
609
+ expr = check_for_range_expression(expr)
610
+ expr = check_for_hash_element_expression(expr)
611
+ when RDoc::RubyToken::TkSTRING, RDoc::RubyToken::TkDSTRING, RDoc::RubyToken::TkXSTRING, RDoc::RubyToken::TkREGEXP
612
+ expr = StringExpression.new(@tk.line_no,@tk.char_no, @tk.text)
613
+ get_tk
614
+ # check for Ruby 2.2 Hash symbol (string directly followed by a COLON)
615
+ if @tk.is_a?(RDoc::RubyToken::TkCOLON)
616
+ expr = SymbolExpression.new(expr.line_no,expr.char_no,expr.name + ':')
617
+ expr.text = expr.name
618
+ get_tk
619
+ end
620
+ expr = parse_identifier_method_call(expr)
621
+ expr = check_for_range_expression(expr)
622
+ expr = check_for_hash_element_expression(expr)
623
+ when RDoc::RubyToken::TkINTEGER, RDoc::RubyToken::TkFLOAT, RDoc::RubyToken::TkTRUE, RDoc::RubyToken::TkFALSE
624
+ expr = NumberExpression.new(@tk.line_no,@tk.char_no,@tk.text)
625
+ get_tk
626
+ expr = parse_identifier_method_call(expr)
627
+ expr = check_for_range_expression(expr)
628
+ expr = check_for_hash_element_expression(expr)
629
+ when RDoc::RubyToken::TkSYMBEG
630
+ expr = SymbolExpression.new(@tk.line_no,@tk.char_no,'')
631
+ get_tk
632
+ if @tk.text =~ /^[a-z]{1,}[a-z0-9\_]*?$/ #is_a?(RDoc::RubyToken::TkIDENTIFIER)
633
+ expr.set_text(':' + @tk.text)
634
+ expr.name = ':' + @tk.text
635
+ get_tk
636
+ expr = parse_identifier_method_call(expr)
637
+ expr = check_for_hash_element_expression(expr)
638
+ else
639
+ parse_error(@tk,"':' not followed by identifier or operator")
640
+ end
641
+ when RDoc::RubyToken::TkGVAR, RDoc::RubyToken::TkIVAR, RDoc::RubyToken::TkSELF, RDoc::RubyToken::TkNIL
642
+ expr = parse_identifier
643
+ when RDoc::RubyToken::TkCONSTANT, RDoc::RubyToken::TkIDENTIFIER
644
+ expr = parse_identifier
645
+ expr = check_for_hash_element_expression(expr)
646
+ when RDoc::RubyToken::TkLBRACK
647
+ expr = parse_array
648
+ expr = parse_identifier_method_call(expr)
649
+ when RDoc::RubyToken::TkLBRACE
650
+ expr = parse_hash
651
+ expr = parse_identifier_method_call(expr)
652
+ when RDoc::RubyToken::TkKW
653
+ # check for a Ruby 2.0 syntax hash key symbol (such as end: begin: class: case: etc...)
654
+ if peek_tk.is_a?(RDoc::RubyToken::TkCOLON)
655
+ ident_text, line, char = @tk.text, @tk.line_no, @tk.char_no
656
+ get_tk
657
+ expr = SymbolExpression.new(line,char,'')
658
+ expr.set_text(ident_text + ':')
659
+ expr.name = ident_text + ':'
660
+ get_tk
661
+ expr = check_for_hash_element_expression(expr)
662
+ end
663
+ end # case @tk
664
+ expr
665
+ end
666
+
667
+ # parse an Array declaration <tt>[...]</tt>
668
+ def parse_array
669
+ get_tk
670
+ array_expr = ArrayExpression.new(@tk.line_no,@tk.char_no)
671
+ skip_white_space
672
+ array_expr.elements << parse_expression
673
+ skip_white_space
674
+ while @tk.is_a?(RDoc::RubyToken::TkCOMMA) do
675
+ get_tk
676
+ array_expr.elements << parse_expression
677
+ skip_white_space
678
+ end
679
+ unless @tk.is_a?(RDoc::RubyToken::TkRBRACK)
680
+ parse_error(@tk,"']' expected but '#{@tk.text}' found")
681
+ end
682
+ get_tk
683
+ array_expr
684
+ end
685
+
686
+ # Parse a +Hash+ declaration <tt>{ ... }</tt>
687
+ def parse_hash
688
+ @in_hash += 1
689
+ get_tk
690
+ hash_expr = HashExpression.new(@tk.line_no,@tk.char_no)
691
+ skip_white_space
692
+ hash_expr.elements << parse_hash_element
693
+ skip_white_space
694
+ while @tk.is_a?(RDoc::RubyToken::TkCOMMA) do
695
+ get_tk
696
+ hash_expr.elements << parse_hash_element
697
+ skip_white_space
698
+ end
699
+ unless @tk.is_a?(RDoc::RubyToken::TkRBRACE)
700
+ parse_error(@tk,"'}' expected but '#{@tk.text}' found")
701
+ end
702
+ get_tk
703
+ @in_hash -= 1
704
+ hash_expr
705
+ end
706
+
707
+ # parse a Hash element within a +Hash+ declaration
708
+ def parse_hash_element
709
+ el = HashElementExpression.new(@tk.line_no,@tk.char_no)
710
+ el.key = parse_expression
711
+ if el.key.is_a?(SymbolExpression) && el.key.text =~ /:$/
712
+ skip_white_space
713
+ el.value = parse_expression
714
+ # get_tk
715
+ return el
716
+ else
717
+ skip_white_space
718
+ if @tk.is_a?(RDoc::RubyToken::TkASSIGN)
719
+ get_tk
720
+ if @tk.is_a?(RDoc::RubyToken::TkGT)
721
+ get_tk
722
+ skip_white_space
723
+ el.value = parse_expression
724
+ skip_white_space
725
+ return el
726
+ end
727
+ end
728
+ end
729
+ parse_error(@tk,"'=>' expected but '#{@tk.text}' found")
730
+ end
731
+
732
+ # return a Hash of all translation entries we collected
733
+ def translation_entries
734
+ entries = {}
735
+ self.translate_calls.each do |c|
736
+ e = c.to_translation_entry(@relative_filename)
737
+ if e.is_a?(Array)
738
+ e.each do |te|
739
+ if entries[te.source]
740
+ entries[te.source].references += te.references
741
+ else
742
+ entries[te.source] = te
743
+ end
744
+ end
745
+ elsif e.is_a?(TranslationEntry)
746
+ if entries[te.source]
747
+ entries[e.source].references += te.references
748
+ else
749
+ entries[e.source] = e
750
+ end
751
+ end
752
+ end
753
+ entries
754
+ end
755
+ end # class SourceParser
756
+ end # module ZLocalize