zabcon 0.0.6 → 0.0.327

Sign up to get free protection for your applications and to get access to all the features.
data/libs/lexer.rb ADDED
@@ -0,0 +1,846 @@
1
+ #GPL 2.0 http://www.gnu.org/licenses/gpl-2.0.html
2
+ #Zabbix CLI Tool and associated files
3
+ #Copyright (C) 2009,2010 Andrew Nelson nelsonab(at)red-tux(dot)net
4
+ #
5
+ #This program is free software; you can redistribute it and/or
6
+ #modify it under the terms of the GNU General Public License
7
+ #as published by the Free Software Foundation; either version 2
8
+ #of the License, or (at your option) any later version.
9
+ #
10
+ #This program is distributed in the hope that it will be useful,
11
+ #but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ #GNU General Public License for more details.
14
+ #
15
+ #You should have received a copy of the GNU General Public License
16
+ #along with this program; if not, write to the Free Software
17
+ #Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
+
19
+ #--
20
+ ##########################################
21
+ # Subversion information
22
+ # $Id: lexer.rb 325 2011-09-26 08:57:00Z nelsonab $
23
+ # $Revision: 325 $
24
+ ##########################################
25
+ #++
26
+
27
+ #The Lexr class is a fork from Michael Baldry's gem, Lexr
28
+ #The origional source for his work can be found here:
29
+ # https://github.com/michaelbaldry/lexr
30
+
31
+ require 'zbxapi/exceptions'
32
+ require "zbxapi/zdebug"
33
+
34
+ #This is a wrapper class for creating a generalized lexer.
35
+ class Lexr
36
+
37
+ class NoLexerError < RuntimeError
38
+ end
39
+
40
+ def self.setup(&block)
41
+ dsl = Lexr::Dsl.new
42
+ block.arity == 1 ? block[dsl] : dsl.instance_eval(&block)
43
+ dsl
44
+ end
45
+
46
+ def initialize(text, rules, default_rule, counter)
47
+ @text, @rules, @default_rule = text, rules, default_rule
48
+ @current = nil
49
+ @position = 0
50
+ @counter=counter
51
+ end
52
+
53
+ def parse
54
+ retval=[]
55
+ until self.end?
56
+ retval << self.next
57
+ end
58
+ retval
59
+ end
60
+
61
+ def next
62
+ return @current = Lexr::Token.end if @position >= @text.length
63
+ @res=""
64
+ @rules.each do |rule|
65
+ @res = rule.match(unprocessed_text)
66
+ next unless @res
67
+
68
+ raise Lexr::UnmatchableTextError.new(rule.raises, @position) if @res and rule.raises?
69
+
70
+ @position += @res.characters_matched
71
+ return self.next if rule.ignore?
72
+ return @current = @res.token
73
+ end
74
+ if !@default_rule.nil?
75
+ @res=@default_rule.match(unprocessed_text)
76
+ @position += @res.characters_matched
77
+ return @current = @res.token
78
+ end
79
+ raise Lexr::UnmatchableTextError.new(unprocessed_text[0..0], @position)
80
+ end
81
+
82
+ def end?
83
+ @current == Lexr::Token.end
84
+ end
85
+
86
+ def counter(symbol)
87
+ @counter[symbol]
88
+ end
89
+
90
+ private
91
+
92
+ def unprocessed_text
93
+ @text[@position..-1]
94
+ end
95
+
96
+ class Token
97
+ attr_reader :value, :kind
98
+
99
+ def initialize(value, kind = nil)
100
+ @value, @kind = value, kind
101
+ end
102
+
103
+ def self.method_missing(sym, *args)
104
+ self.new(args.first, sym)
105
+ end
106
+
107
+ def to_s
108
+ "#{kind}(#{value})"
109
+ end
110
+
111
+ def ==(other)
112
+ @kind == other.kind && @value == other.value
113
+ end
114
+ end
115
+
116
+ class Rule
117
+ attr_reader :pattern, :symbol, :raises
118
+
119
+ def converter ; @opts[:convert_with] ; end
120
+ def ignore? ; @opts[:ignore] ; end
121
+ def raises? ; @opts[:raises] ; end
122
+
123
+ def initialize(pattern, symbol, opts = {})
124
+ @pattern, @symbol, @opts = pattern, symbol, opts
125
+ @raises=opts[:raises]
126
+ @counter={}
127
+ end
128
+
129
+ def set_counter(counter)
130
+ @counter=counter
131
+ end
132
+
133
+ def match(text)
134
+ text_matched = self.send :"#{pattern.class.name.downcase}_matcher", text
135
+ return nil unless text_matched
136
+ increment(@opts[:increment])
137
+ decrement(@opts[:decrement])
138
+ value = converter ? converter[text_matched] : text_matched
139
+ Lexr::MatchData.new(text_matched.length, Lexr::Token.new(value, symbol))
140
+ end
141
+
142
+ def ==(other)
143
+ @pattern == other.pattern &&
144
+ @symbol == other.symbol &&
145
+ @opts[:convert_with] == other.converter &&
146
+ @opts[:ignore] == other.ignore?
147
+ end
148
+
149
+ def counter(symbol)
150
+ @counter[symbol]
151
+ end
152
+
153
+ def scan(text)
154
+ pat = pattern
155
+ if pattern.is_a?(String)
156
+ pat=/#{Regexp.escape(pattern)}/
157
+ end
158
+ res=text.scan(/#{pat}/)
159
+ end
160
+
161
+ private
162
+
163
+ def increment(symbol)
164
+ if !symbol.nil?
165
+ @counter[symbol]=0 if @counter[symbol].nil?
166
+ @counter[symbol]+=1
167
+ end
168
+ end
169
+
170
+ def decrement(symbol)
171
+ if !symbol.nil?
172
+ @counter[symbol]=0 if @counter[symbol].nil?
173
+ @counter[symbol]-=1
174
+ end
175
+ end
176
+
177
+ def string_matcher(text)
178
+ return nil unless text[0..pattern.length-1] == pattern
179
+ pattern
180
+ end
181
+
182
+ def regexp_matcher(text)
183
+ return nil unless m = text.match(/\A#{pattern}/)
184
+ m[0]
185
+ end
186
+ end
187
+
188
+ class Dsl
189
+
190
+ attr_reader :available_tokens
191
+
192
+ def initialize
193
+ @rules = []
194
+ @default=nil
195
+ @available_tokens=[]
196
+ end
197
+
198
+ def matches(rule_hash)
199
+ pattern = rule_hash.keys.reject { |k| k.class == Symbol }.first
200
+ symbol = rule_hash[pattern]
201
+ opts = rule_hash.delete_if { |k, v| k.class != Symbol }
202
+ @rules << Rule.new(pattern, symbol, opts)
203
+ @available_tokens = @available_tokens | [symbol]
204
+ end
205
+
206
+ def default(rule_hash)
207
+ pattern = rule_hash.keys.reject { |k| k.class == Symbol }.first
208
+ symbol = rule_hash[pattern]
209
+ opts = rule_hash.delete_if { |k, v| k.class != Symbol }
210
+ @default = Rule.new(pattern, symbol, opts)
211
+ end
212
+
213
+ def ignores(rule_hash)
214
+ matches rule_hash.merge(:ignore => true)
215
+ end
216
+
217
+ def new(str)
218
+ @counter={}
219
+ @rules.each { |r| r.set_counter(@counter) }
220
+ Lexr.new(str, @rules, @default, @counter)
221
+ end
222
+
223
+ end
224
+
225
+ class UnmatchableTextError < StandardError
226
+ attr_reader :character, :position
227
+
228
+ def initialize(character, position)
229
+ @character, @position = character, position
230
+ end
231
+
232
+ def message
233
+ "Unexpected character '#{character}' at position #{position + 1}"
234
+ end
235
+
236
+ def inspect
237
+ message
238
+ end
239
+ end
240
+
241
+ class MatchData
242
+ attr_reader :characters_matched, :token
243
+
244
+ def initialize(characters_matched, token)
245
+ @characters_matched = characters_matched
246
+ @token = token
247
+ end
248
+ end
249
+ end
250
+
251
+ class String
252
+ #lexer_parse will parse the string using the lexer object passed
253
+ def lexer_parse(lexer)
254
+ @lex=lexer.new(self)
255
+ @lex.parse
256
+ end
257
+
258
+ def lexer_counter(symbol)
259
+ raise Lexr::NoLexerError.new("no lexer defined") if @lex.nil?
260
+ @lex.counter(symbol)
261
+ end
262
+ end
263
+
264
+
265
+ ExpressionLexer = Lexr.setup {
266
+ matches /\\/ => :escape
267
+ matches /\$[\w]+/ => :variable
268
+ matches /"([^"\\]*(\\.[^"\\]*)*)"/ => :quote
269
+ matches "(" => :l_paren, :increment=>:paren
270
+ matches ")" => :r_paren, :decrement=>:paren
271
+ matches "{" => :l_curly, :increment=>:curly
272
+ matches "}" => :r_curly, :decrement=>:curly
273
+ matches "[" => :l_square, :increment=>:square
274
+ matches "]" => :r_square, :decrement=>:square
275
+ matches "," => :comma
276
+ matches /\s+/ => :whitespace
277
+ matches /[-+]?\d*\.\d+/ => :number, :convert_with => lambda { |v| Float(v) }
278
+ matches /[-+]?\d+/ => :number, :convert_with => lambda { |v| Integer(v) }
279
+ matches "=" => :equals
280
+ matches "\"" => :umatched_quote, :raises=> "Unmatched quote"
281
+ default /[^\s^\\^"^\(^\)^\{^\}^\[^\]^,^=]+/ => :word
282
+ }
283
+
284
+ class Tokenizer < Array
285
+ include ZDebug
286
+
287
+ class InvalidCharacter <ZError
288
+ attr_accessor :invalid_char, :invalid_str, :position
289
+
290
+ def initialize(message=nil, params={})
291
+ super(message,params)
292
+ @message=message || "Invalid Character"
293
+ @position=params[:position] || nil
294
+ @invalid_char=params[:invalid_char] || nil
295
+ @invalid_str=params[:invalid_str] || raise(RuntimeError.new(":invalid_str required",:retry=>false))
296
+ end
297
+
298
+ def show_message
299
+ preamble="#{@message} \"#{@invalid_char}\" : "
300
+ pointer="^".rjust(@position+preamble.length+1)
301
+ puts preamble+@invalid_str
302
+ puts pointer
303
+ end
304
+
305
+ end
306
+
307
+ class UnexpectedClose < InvalidCharacter
308
+ def initialize(message=nil, params={})
309
+ super(message,params)
310
+ @message=message || "Invalid Character"
311
+ end
312
+ end
313
+
314
+ class DelimiterExpected < InvalidCharacter
315
+ def initialize(message=nil, params={})
316
+ super(message,params)
317
+ @message=message || "Delimiter expected"
318
+ end
319
+ end
320
+
321
+ class ItemExpected < InvalidCharacter
322
+ def initialize(message=nil, params={})
323
+ super(message,params)
324
+ @message=message || "Item expected"
325
+ end
326
+ end
327
+
328
+ class WhitespaceExpected < InvalidCharacter
329
+ def initialize(message=nil, params={})
330
+ super(message,params)
331
+ @message=message || "Whitespace expected"
332
+ end
333
+ end
334
+
335
+ def initialize(str)
336
+ super()
337
+ debug(8,:msg=>"Initial String",:var=>str.inspect)
338
+ replace(str.lexer_parse(ExpressionLexer))
339
+ debug(8,:msg=>"Tokens",:var=>self)
340
+ debug(8,:msg=>"Tokens(Length)",:var=>length)
341
+ @available_tokens=ExpressionLexer.available_tokens
342
+ end
343
+
344
+ def parse(args={})
345
+ pos=args[:pos] || 0
346
+ pos,tmp=unravel(pos)
347
+ if tmp.length==1 && tmp[0].class==Array
348
+ tmp[0]
349
+ else
350
+ tmp
351
+ end
352
+ end
353
+
354
+ #drop
355
+ #drops the first num elements from the tokens array
356
+ def drop(num)
357
+ start=num-1
358
+ self.slice!(start..length)
359
+ end
360
+
361
+ def join(str=nil)
362
+ self.map {|i| i.value}.join(str)
363
+ end
364
+
365
+ def what_is?(pos,args={})
366
+ return :end if end?(pos)
367
+ return :whitespace if of_type?(pos,:whitespace)
368
+ return :comma if of_type?(pos,:comma)
369
+ return :escape if of_type?(pos,:escape)
370
+ return :paren if of_type?(pos,:paren)
371
+ return :close if close?(pos)
372
+ return :hash if hash?(pos)
373
+ return :array if array?(pos)
374
+ # return :simple_array if simple_array?(pos)
375
+ return :assignment if assignment?(pos)
376
+ :other
377
+ end
378
+
379
+ def of_type?(pos,types,args={})
380
+ raise "Types must be symbol or array" if !(types.class==Symbol || types.class==Array)
381
+ return false if pos>length-1
382
+ if types.class!=Array
383
+ if ([:element, :open, :close, :hash, :array, :paren] & [types]).empty?
384
+ return self[pos].kind==types
385
+ else
386
+ types=[types]
387
+ end
388
+ end
389
+ valid_types=[]
390
+ valid_types<<[:word,:number,:quote,:variable] if types.delete(:element)
391
+ valid_types<<[:l_curly, :l_paren, :l_square] if types.delete(:open)
392
+ valid_types<<[:r_paren, :r_curly, :r_square] if types.delete(:close)
393
+ valid_types<<[:l_paren] if types.delete(:paren)
394
+ valid_types<<[:l_curly] if types.delete(:hash)
395
+ valid_types<<[:l_square] if types.delete(:array)
396
+ valid_types<<types
397
+ valid_types.flatten!
398
+ !(valid_types & [self[pos].kind]).empty?
399
+ end
400
+
401
+ #Walk
402
+ #Parameters:
403
+ # pos, :look_for, :walk_over
404
+ #Will walk over tokens denoted in :walk_over but stop on token symbols denoted in :look_for
405
+ #:walk_over defaults to the whitespace token, returning the position walked to
406
+ #Will start at pos
407
+ #If :look_for is assigned a value walk will walk over :walk_over tokens
408
+ #and stop when the passed token is found.
409
+ #:walk_over and :look_for can be either a single symbol or an array of symbols
410
+ #if :walk_over is nil walk will walk over all tokens until :look_for is found
411
+ #returns the position walked to or nil if :look_for was not found or the end was found
412
+ #If the end was found @pos will never be updated
413
+ def walk(pos,args={})
414
+ look_for = args[:look_for] || []
415
+ look_for=[look_for] if look_for.class!=Array
416
+ look_for.compact!
417
+ walk_over = args[:walk_over] || [:whitespace]
418
+ walk_over=[walk_over] if walk_over.class!=Array
419
+ walk_over.compact!
420
+
421
+ start_pos=pos
422
+ raise ":walk_over and :look_for cannot both be empty" if look_for.empty? && walk_over.empty?
423
+
424
+ return start_pos if end?(pos)
425
+
426
+ if walk_over.empty?
427
+ while !end?(pos) && !(look_for & [self[pos].kind]).empty?
428
+ pos+=1
429
+ end
430
+ else
431
+ while !end?(pos) && !(walk_over & [self[pos].kind]).empty?
432
+ pos+=1
433
+ end
434
+ end
435
+ if !look_for.empty?
436
+ return start_pos if (look_for & [self[pos].kind]).empty?
437
+ end
438
+ pos
439
+ end
440
+
441
+ #returns true if the token at pos is a closing token
442
+ #returns true/false if the token at pos is of type :close
443
+ def close?(pos, args={})
444
+ close=args[:close] || nil #redundancy is for readability
445
+ if close!=nil
446
+ return self[pos].kind==close
447
+ end
448
+ of_type?(pos,:close)
449
+ end
450
+
451
+ # Performs a set intersection operator to see if we have a close token as pos
452
+ def open?(pos, open_type=nil)
453
+ return of_type?(pos,:open) if open_type.nil?
454
+ of_type?(pos,open_type)
455
+ end
456
+
457
+ def end?(pos, args={})
458
+ close=args[:close] || :nil
459
+ !(pos<length && !close?(pos,:close=>close) && self[pos].kind!=:end)
460
+ end
461
+
462
+ def invalid?(pos, invalid_tokens)
463
+ !(invalid_tokens & [self[pos].kind]).empty?
464
+ end
465
+
466
+ #assignment?
467
+ #Checks to see if the current position denotes an assignment
468
+ #if :pos is passed it, that wil be used as the starting reference point
469
+ #if :return_pos is passed it will return the associated positions for each
470
+ #element if an assignment is found, otherwise will return nil
471
+ def assignment?(pos, args={})
472
+ return_pos=args[:return_pos] || false
473
+ p1_pos=pos
474
+ (p2_pos = walk(pos+1)).nil? && (return false)
475
+ (p3_pos = walk(p2_pos+1)).nil? && (return false)
476
+
477
+ p1=of_type?(p1_pos,:element)
478
+ p2=of_type?(p2_pos,:equals)
479
+ p3=of_type?(p3_pos,[:element, :open])
480
+ is_assignment=p1 && p2 && p3
481
+ if return_pos #return the positions if it is an assignment, otherwise the result of the test
482
+ is_assignment ? [p1_pos,p2_pos,p3_pos] : nil
483
+ else
484
+ is_assignment
485
+ end
486
+ end
487
+
488
+ #Do we have an array? [1,2,3]
489
+ def array?(pos, args={})
490
+ return false if self[pos].kind==:whitespace
491
+ open?(pos,:array)
492
+ end
493
+
494
+ #Do we have a simple array? "1 2,3,4" -> 2,3,4
495
+ def simple_array?(pos,args={})
496
+ return false if array?(pos)
497
+ p1=pos # "bla , bla" -> (p1=bla) (p2=,) (p3=bla)
498
+
499
+ #Find the remaining positions. Return false if walk returns nil
500
+ (p2 = walk(pos+1)).nil? && (return false)
501
+ (p3 = walk(p2+1)).nil? && (return false)
502
+
503
+ p1=of_type?(p1,:element)
504
+ p2=of_type?(p2,:comma)
505
+ p3=of_type?(p3,[:element, :open])
506
+ p1 && p2 && p3
507
+ end
508
+
509
+ def hash?(pos,args={})
510
+ open?(pos,:hash)
511
+ end
512
+
513
+ def invalid_character(pos, args={})
514
+ msg=args[:msg] || nil
515
+ end_pos=args[:end_pos] || pos
516
+ error_class=args[:error] || InvalidCharacter
517
+ if !error_class.class_of?(ZError)
518
+ raise ZError.new("\"#{error_class.inspect}\" is not a valid class. :error must be of class ZError or a descendant.", :retry=>false)
519
+ end
520
+ retry_var=args[:retry] || true
521
+
522
+ debug(5,:msg=>"Invalid_Character (function/line num is caller)",:stack_pos=>1,:trace_depth=>4)
523
+
524
+ invalid_str=self[0..pos-1].join || ""
525
+ position=invalid_str.length
526
+ invalid_str+=self[pos..self.length-1].join if !invalid_str.empty?
527
+ invalid_char=self[pos].value
528
+ raise error_class.new(msg, :invalid_str=>invalid_str,:position=>position,:invalid_char=>invalid_char, :retry=>retry_var)
529
+ end
530
+
531
+ private
532
+
533
+ def get_close(pos)
534
+ case self[pos].kind
535
+ when :l_curly
536
+ :r_curly
537
+ when :l_square
538
+ :r_square
539
+ when :l_paren
540
+ :r_paren
541
+ when :word, :quote, :number
542
+ :whitespace
543
+ else
544
+ nil
545
+ end
546
+ end
547
+
548
+ def get_assignment(pos,args={})
549
+ positions=assignment?(pos,:return_pos=>true)
550
+ invalid_character(pos,:msg=>"Invalid assignment") if positions.nil?
551
+ lside=self[positions[0]].value
552
+ if of_type?(positions[2],:element)
553
+ rside=self[positions[2]].value
554
+ pos=positions[2]+1
555
+ elsif of_type?(positions[2],:l_curly)
556
+ pos,rside=get_hash(positions[2])
557
+ else
558
+ pos,rside=unravel(positions[2]+1,:close=>get_close(positions[2]))
559
+ end
560
+
561
+ return pos,{lside=>rside}
562
+ end
563
+
564
+ def get_hash(pos, args={})
565
+ invalid_character(pos) if self[pos].kind!=:l_curly
566
+ pos+=1
567
+ retval={}
568
+ havecomma=true #preload the havecomma statement
569
+ while !end?(pos,:close=>:r_curly)
570
+ pos=walk(pos) #walk over excess whitespace
571
+ if assignment?(pos) && havecomma
572
+ pos, hashval=get_assignment(pos)
573
+ retval.merge!(hashval)
574
+ havecomma=false
575
+ elsif of_type?(pos,:comma)
576
+ pos+=1
577
+ havecomma=true
578
+ else
579
+ invalid_character(pos, :msg=>"Invalid character found while building hash")
580
+ end
581
+ pos=walk(pos) #walk over excess whitespace
582
+ end
583
+ pos+=1 #we should be over the closing curly brace, increment position
584
+ invlaid_character if havecomma
585
+ return pos, retval
586
+ end
587
+
588
+ def get_escape(pos,args={})
589
+ keep_initial=args[:keep_initial] || false
590
+
591
+ invalid_character(pos,:msg=>"Escape characters cannot be last") if end?(pos+1)
592
+ pos+=1 if !keep_initial #gobble the first escape char
593
+ retval=[]
594
+ while !end?(pos) && self[pos].kind==:escape
595
+ retval<<self[pos].value
596
+ pos+=1
597
+ end
598
+ invalid_character "Unexpected End of String during escape" if end?(pos)
599
+ retval<<self[pos].value
600
+ pos+=1
601
+ return pos,retval.flatten.join
602
+ end
603
+
604
+ class Status
605
+ attr_accessor :close, :nothing_seen, :delim
606
+ attr_accessor :have_delim, :have_item, :item_seen, :delim_seen
607
+
608
+ def initialize(pos,tokenizer,args={})
609
+ super()
610
+ @tokenizer=tokenizer
611
+ @start_pos=pos
612
+ @close=args[:close] || nil
613
+ @item_seen=args[:preload].nil?
614
+
615
+ @have_item = @have_delim = @delim_seen = false
616
+
617
+ #If we expect to find a closing element, the delimiter will be a comma
618
+ #Otherwise we'll discover it later
619
+ if args[:delim].nil?
620
+ @delim=close.nil? ? nil : :comma
621
+ else
622
+ @delim=args[:delim]
623
+ end
624
+
625
+ #self[:have_item]=false
626
+ #self[:have_delim]=false
627
+ #self[:nothing_seen]=true
628
+ @stat_hash={}
629
+ @stat_hash[:skip_until_close]=args[:skip_until_close]==true || false
630
+ end
631
+
632
+ def inspect
633
+ str="#<#{self.class}:0x#{self.__id__.to_s(16)} "
634
+ arr=[]
635
+ vars=instance_variables
636
+ vars.delete("@tokenizer")
637
+ vars.delete("@stat_hash")
638
+ vars.each{|i| arr<<"#{i}=#{instance_variable_get(i).inspect}" }
639
+ @stat_hash.each_pair{ |k,v| arr<<"@#{k.to_s}=#{v.inspect}" }
640
+ str+=arr.join(", ")+">"
641
+ str
642
+ end
643
+
644
+ def []=(key,value)
645
+ @stat_hash[key]=value
646
+ end
647
+
648
+ def [](key)
649
+ @stat_hash[key]
650
+ end
651
+
652
+ def method_missing(sym,*args)
653
+ have_key=@stat_hash.has_key?(sym)
654
+ if have_key && args.empty?
655
+ return @stat_hash[sym]
656
+ elsif have_key && !args.empty?
657
+ str=sym.to_s
658
+ if str[str.length-1..str.length]=="="
659
+ str.chop!
660
+ @stat_hash[str.intern]=args
661
+ return args
662
+ end
663
+ end
664
+
665
+ super(sym,args)
666
+ end
667
+
668
+ def item(pos)
669
+ #set the delimiter to whitespace if we've never seen a delimiter but have an item
670
+ if @delim.nil? && @have_item && !@delim_seen
671
+ @delim=:whitespace
672
+ @delim_seen=true
673
+ end
674
+
675
+ @tokenizer.invalid_character(pos, :error=>DelimiterExpected) if
676
+ @have_item && @delim!=:whitespace && !@tokenizer.of_type?(pos,[:open,:close])
677
+ @item_seen=true
678
+ @have_item=true
679
+ @have_delim=false
680
+ end
681
+
682
+ def delimiter(pos)
683
+ if @tokenizer.of_type?(pos,:comma)
684
+ @tokenizer.invalid_character(pos,:error=>WhitespaceExpected) if @delim==:whitespace
685
+ @tokenizer.invalid_character(pos,:error=>ItemExpected) if @delim==:comma and @have_delim
686
+ elsif @tokenizer.of_type?(pos,:whitespace)
687
+ @delim=:whitespace if @delim.nil? && @seen_item
688
+ else
689
+ @tokenizer.invalid_character(pos)
690
+ end
691
+
692
+ @delim_seen=true
693
+ @have_item=false
694
+ @have_delim=true
695
+ end
696
+
697
+ end
698
+
699
+ def unravel(pos,args={})
700
+ status=Status.new(pos,self,args)
701
+ status[:start_pos]=pos
702
+
703
+ if args[:preload]
704
+ retval = []
705
+ retval<<args[:preload]
706
+ else
707
+ retval=[]
708
+ end
709
+
710
+ raise "Close cannot be nil if skip_until_close" if status.skip_until_close && status.close.nil?
711
+
712
+ debug(8,:msg=>"Unravel",:var=>[status,pos])
713
+
714
+ invalid_tokens=[]
715
+ invalid_tokens<<:whitespace if !status.close.nil? && !([:r_curly,:r_paren,:r_square] & [status.close]).empty?
716
+
717
+ pos=walk(pos) #skip whitespace
718
+ invalid_character(pos) if invalid?(pos,[:comma]) || close?(pos) #String cannot start with a comma or bracket close
719
+
720
+ while !end?(pos,:close=>status.close)
721
+ begin
722
+ debug(8,:msg=>"Unravel-while",:var=>[pos,self[pos]])
723
+ debug(8,:msg=>"Unravel-while",:var=>[status,status.have_item,status.close])
724
+ debug(8,:msg=>"Unravel-while",:var=>retval)
725
+
726
+ invalid_character(pos,:error=>UnexpectedClose) if close?(pos) && status.close.nil?
727
+
728
+ if of_type?(pos,:escape)
729
+ debug(8,:msg=>"escape",:var=>[pos,self[pos]])
730
+ pos,result=get_escape(pos)
731
+ retval<<result
732
+ next
733
+ end
734
+
735
+ if status.skip_until_close
736
+ debug(8,:msg=>"skip_until_close",:var=>[pos,self[pos]])
737
+ retval<<self[pos].value
738
+ pos+=1
739
+ pos=walk(pos)
740
+ next
741
+ end
742
+
743
+ case what_is?(pos)
744
+ when :escape
745
+ status.item(pos)
746
+ pos,result=get_escape(pos)
747
+ retval<<result
748
+ when :paren
749
+ status.item(pos)
750
+ pos,result=unravel(pos+1,:close=>get_close(pos),:skip_until_close=>true)
751
+ retval<<"("
752
+ result.each {|i| retval<<i }
753
+ retval<<")"
754
+ when :hash
755
+ debug(8,:msg=>"hash",:var=>[pos,self[pos]])
756
+ status.item(pos)
757
+ pos,result=get_hash(pos)
758
+ debug(8,:msg=>"hash-return",:var=>[pos,self[pos]])
759
+ retval<<result
760
+ when :array
761
+ status.item(pos)
762
+ pos,result=unravel(pos+1,:close=>get_close(pos))
763
+ retval<<result
764
+ #when :simple_array
765
+ # #if our delimiter is a comma then we've already detected the simple array
766
+ # if delim==:comma
767
+ # retval<<self[pos].value
768
+ # pos+=1
769
+ # have_item=true
770
+ # else
771
+ # pos,result=unravel(pos,:close=>:whitespace)
772
+ # retval<<result
773
+ # have_item=false
774
+ # end
775
+ when :assignment
776
+ status.item(pos)
777
+ debug(8,:msg=>"assignment",:var=>[pos,self[pos]])
778
+ pos,result=get_assignment(pos)
779
+ debug(8,:msg=>"assignment-return",:var=>[pos,self[pos]])
780
+ retval<<result
781
+ have_item=true
782
+ when :comma, :whitespace
783
+ begin
784
+ status.delimiter(pos)
785
+ rescue WhitespaceExpected
786
+ last=retval.pop
787
+ pos+=1
788
+ pos,result=unravel(pos,:close=>:whitespace, :preload=>last)
789
+ retval<<result
790
+ end
791
+ return pos, retval if status.have_item && status.close==:whitespace
792
+ pos+=1
793
+ when :close
794
+ invalid_character(pos,:error=>UnexpectedClose) if self[pos].kind!=status.close
795
+ pos+=1
796
+ return pos,retval
797
+ when :other
798
+ debug(8,:msg=>"Unravel-:other",:var=>[self[pos]])
799
+ status.item(pos)
800
+ #if status.have_item && status.close==:whitespace
801
+ # return pos,retval
802
+ #end
803
+ retval<<self[pos].value
804
+ pos+=1
805
+ else #case what_is?(pos)
806
+ invalid_character(pos)
807
+ end #case what_is?(pos)
808
+ debug(8,:msg=>"walk",:var=>[pos,self[pos]])
809
+ pos=walk(pos) #walk whitespace ready for next round
810
+ debug(8,:msg=>"walk-after",:var=>[pos,self[pos]])
811
+ rescue DelimiterExpected=>e
812
+ debug(8,:var=>caller.length)
813
+ debug(8,:var=>status)
814
+ debug(8,:var=>[pos,self[pos]])
815
+ if status.delim==:comma && status.have_item
816
+ debug(8)
817
+ return pos,retval
818
+ else
819
+ debug(8)
820
+ raise e
821
+ end
822
+ debug(8)
823
+ end
824
+ end
825
+ invalid_character(pos) if status.have_delim && status.delim==:comma
826
+ pos+=1
827
+ debug(8,:msg=>"Unravel-While-end",:var=>[have_item, status.delim])
828
+
829
+ return pos, retval
830
+ end
831
+ end
832
+
833
+
834
+
835
+ #p test_str="\"test\"=test1,2.0,3, 4 \"quote test\" value = { a = { b = [ c = { d = [1,a,g=f,3,4] }, e=5,6,7,8] } }"
836
+ #p test_str="value = { a = { b = [ c = { d = [1,a,g=f,3,4] }, e=5,6,7,8] } }"
837
+ #p test_str="test=4, 5, 6, 7 {a={b=4,c=5}} test2=[1,2,[3,[4]],5] value=9, 9"
838
+ #p test_str="a=[1,2] b={g=2} c 1,two,[1.1,1.2,1.3,[A]],three,4 d e[1,2] "
839
+ #p test_str="word1 word2,word3 , d , e,"
840
+ #p test_str=" test a=1, bla {b={c=2}}"
841
+ #p test_str="a=b \\(a=c\\)"
842
+ #p test_str="\\)"
843
+
844
+ #p tokens=Tokenizer.new(test_str)
845
+ #p result=tokens.parse
846
+