zabcon 0.0.6 → 0.0.327

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.
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
+