cursor 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/cursor.rb +1138 -0
  2. metadata +37 -0
data/cursor.rb ADDED
@@ -0,0 +1,1138 @@
1
+ #!/bin/env ruby
2
+ # cursor.rb
3
+
4
+ # An object in this Cursor class can be best thought of as a cursor in a text
5
+ # editor. Many of the same operations apply - insert, delete, replace, copy,
6
+ # paste, move, goto begin/end, mark position, goto mark, etc. Unlike a
7
+ # text editor, this class can operate on variety of data, not just characters
8
+ # and strings. It is up to the derived classes to deal with what type of data
9
+ # is stored (i.e. characters, arbitrary array objects) and how it is stored (in
10
+ # an Array, String, IO, mapping to another Cursor, etc).
11
+ class Cursor
12
+
13
+ include Comparable
14
+ include Enumerable
15
+
16
+ # Operate on data before the cursor rather than after
17
+ Reverse = 1
18
+ # Hold position - do the operation and come back
19
+ Hold = 2
20
+ # Just count the elements from get/delete instead of returning their value
21
+ Ignore = 4
22
+ # Read the data being replaced using put and return it (instead of the count)
23
+ Read = 4
24
+ # Delete instead of retrieve elements (used with get)
25
+ Delete = 8
26
+ # Insert instead of replace elements (used with put)
27
+ Insert = 8
28
+ # Put a single element instead of a sequence (needed when it looks like a sequence)
29
+ Single = 16
30
+ # Operate on what follows the cursor and then move foward
31
+ Next = 0
32
+ # Operate on what precedes the cursor and then move backward
33
+ Prev = Reverse
34
+ # Operate on what follows the cursor and hold position
35
+ After = Hold
36
+ # Operate on what precedes the cursor and hold position
37
+ Before = Reverse|Hold
38
+
39
+ protected
40
+
41
+ # Delete one element at the cursor and return it. The observed bits in +flags+
42
+ # are +Reverse+ (delete before the cursor instead of after) and +Ignore+
43
+ # (return true instead of the deleted element). When the cursor is at the end (or beginning),
44
+ # either nil or the code block result is returned (see #get).
45
+ # <b>This method must be overriden
46
+ # in a derived class</b>.
47
+ def _delete1(flags,&more) # :yield: len=1
48
+ raise(NotImplementedError)
49
+ end
50
+ # Insert one element at the cursor. Only the +Reverse+ bit
51
+ # is observed in +flags+. nil should be returned. <b>This method must be overriden
52
+ # in a derived class</b>.
53
+ def _insert1(value,flags)
54
+ raise(NotImplementedError)
55
+ end
56
+ # Get an element at the cursor. The observed bits in +flags+ are +Reverse+
57
+ # (read before instead of after the cursor), +Hold+ (don't move the
58
+ # cursor), and +Ignore+ (just return +true+ instead of the value). When the
59
+ # cursor is at the end (or beginning), either nil or the code block result
60
+ # is returned (see #get).
61
+ def _get1(flags,&more) # :yield: len=1
62
+ value = _delete1(flags&~Ignore) { |len| more&&more[len] || return }
63
+ if flags&Hold != 0
64
+ flags ^= Hold
65
+ flags ^= Reverse
66
+ end
67
+ _insert1(value,flags)
68
+ (flags&Ignore != 0) ? true : value
69
+ end
70
+ # Put an element at the cursor. The observed bits in +flags+ are +Reverse+
71
+ # (put before instead of after the cursor), +Hold+ (don't move the
72
+ # cursor), and +Read+ (read and return what is being replaced instead of
73
+ # just returning +true+). When the cursor is at the end (or beginning),
74
+ # either nil or the code block result is returned (see #get).
75
+ # The value is inserted at that point when that happens.
76
+ def _put1(value,flags,&more) # :yield: len=1
77
+ value0 = _delete1(flags^Read,&more)
78
+ if flags&Hold != 0
79
+ flags ^= Hold
80
+ flags ^= Reverse
81
+ end
82
+ _insert1(value,flags)
83
+ value0
84
+ end
85
+
86
+ public
87
+
88
+ # Create a cursor.
89
+ def initialize
90
+ @positions = []
91
+ end
92
+ # Return the class used for passing/returning a sequence of elements. This
93
+ # class must behave as a String or an Array ([], []=, slice, slice!, length).
94
+ # The default in the base class is Array.
95
+ def data_class
96
+ Array
97
+ end
98
+ # Get a single element or a sequence of them.
99
+ #
100
+ # +len+ can be +nil+ (get one element), a positive number (get a
101
+ # sequence of that many elements), 0 (get all remaining),
102
+ # a negative number (get all but that many), or what +data_class+ says
103
+ # (get a sequence up until it finds a match to +len+).
104
+ #
105
+ # The observed bits in +flags+ are +Reverse+
106
+ # (read before instead of after the cursor), +Hold+ (don't move the
107
+ # cursor), +Ignore+ (just return +true+ or a count instead of the value),
108
+ # and +Delete+ (delete the element or sequence while retrieving it).
109
+ #
110
+ # If there is left, either nil (no code block) or the result from code block is
111
+ # returned. This code block could set some variable(s) to say signify it
112
+ # is done or return data from some other source to make it look as though
113
+ # it is not done. When returning more data, it should return it in a
114
+ # String or Array of up to +len+ elements.
115
+ def get(len=nil,flags=Next,&more) # :yield: len
116
+ g = (flags&Delete != 0) ? :_delete1 : :_get1
117
+ return(__send__(g,flags,&more)) if not len
118
+ if flags&Hold != 0 && flags&Delete == 0
119
+ flags ^= Hold
120
+ position { get(len,flags,&more) }
121
+ elsif len.class==data_class
122
+ if flags&Ignore != 0
123
+ value = get(len,flags^Ignore,&more)
124
+ return(value&&value.size)
125
+ end
126
+ value = data_class.new
127
+ if flags&Reverse != 0
128
+ i = len.size-1
129
+ step = -1
130
+ finish = -1
131
+ else
132
+ i = 0
133
+ step = +1
134
+ finish = len.size
135
+ end
136
+ loop do
137
+ v = __send__(g,flags) { |len|
138
+ more&&more[len] || begin
139
+ value = value.reverse if flags&Reverse != 0
140
+ value = nil if value.size==0
141
+ return
142
+ end
143
+ }
144
+ value << v
145
+ if v==len[i]
146
+ i += step
147
+ if i==finish
148
+ value = value.reverse if flags&Reverse != 0
149
+ return(value)
150
+ end
151
+ else
152
+ i = 0
153
+ end
154
+ end
155
+ else
156
+ if flags&Ignore != 0
157
+ len1 = 0
158
+ if len<=0
159
+ if len<0 and flags&Delete != 0
160
+ value = get(len,flags^Ignore,&more)
161
+ return(value&&value.size)
162
+ end
163
+ continue = true
164
+ loop do
165
+ __send__(g,flags) { |len| continue = more&&more[len] }
166
+ break unless continue
167
+ len1 += 1
168
+ end
169
+ (len..-1).each do
170
+ _get1(flags^Reverse) { |len|
171
+ more&&more[len] || (return(len1))
172
+ }
173
+ len1 -= 1
174
+ end
175
+ else
176
+ len -= 1
177
+ while len1<=len
178
+ __send__(g,flags) { |len|
179
+ more&&more[len] ||
180
+ (return(len1==0 ? nil : len1))
181
+ }
182
+ len1 += 1
183
+ end
184
+ end
185
+ len1
186
+ else
187
+ value = data_class.new
188
+ if len<=0
189
+ continue = true
190
+ loop do
191
+ v = __send__(g,flags) { |len| continue = more&&more[len] }
192
+ break unless continue
193
+ value << v
194
+ end
195
+ (len..-1).each do
196
+ return(nil) if value.size==0
197
+ v = value.slice!(-1)
198
+ if flags&Delete != 0
199
+ _insert1(v,flags&~Read^Reverse)
200
+ else
201
+ _get1(flags^Reverse|Ignore) { raise(IndexError,"couldn't get back to where we were") }
202
+ end
203
+ end
204
+ else
205
+ len -= 1
206
+ while value.size<=len
207
+ v = __send__(g,flags&~Ignore) { |len|
208
+ more&&more[len] || begin
209
+ if value.size==0
210
+ value = nil
211
+ elsif flags&Reverse != 0
212
+ value = value.reverse
213
+ end
214
+ return(value)
215
+ end
216
+ }
217
+ value << v
218
+ end
219
+ end
220
+ flags&Reverse != 0 ? value.reverse : value
221
+ end
222
+ end
223
+ end
224
+ # Put a single element or a sequence of them at the cursor.
225
+ #
226
+ # The observed bits in +flags+ are +Reverse+
227
+ # (read before instead of after the cursor), +Hold+ (don't move the
228
+ # cursor), +Read+ (return the what is overwritten instead of +true+ or a count),
229
+ # +Insert+ (insert rather than replace +value+), and +Single+ (force +value+
230
+ # to be treated as a single element rather than a sequence).
231
+ #
232
+ # When the cursor is at the end (or beginning),
233
+ # either nil or the code block result is returned (see #get).
234
+ # The value is inserted at that point when that happens.
235
+ def put(value,flags=Next,&more) # :yield: len
236
+ if flags&Single != 0 or value.class!=data_class
237
+ if flags&Insert != 0
238
+ if flags&Hold != 0
239
+ flags ^= Hold
240
+ flags ^= Reverse
241
+ end
242
+ return(_insert1(value,flags,&more))
243
+ else
244
+ return(_put1(value,flags,&more))
245
+ end
246
+ end
247
+ if flags&Insert != 0
248
+ flags |= Single
249
+ if flags&Hold != 0
250
+ flags ^= Hold
251
+ flags ^= Reverse
252
+ end
253
+ if flags&Reverse != 0
254
+ start = value.size-1
255
+ finish = 0
256
+ step = -1
257
+ else
258
+ start = 0
259
+ finish = value.size-1
260
+ step = +1
261
+ end
262
+ start.step(finish,step) do |i|
263
+ _insert1(value[i],flags)
264
+ end
265
+ nil
266
+ elsif flags&Hold != 0
267
+ flags ^= Hold
268
+ position { put(value,flags,&more) }
269
+ else
270
+ flags |= Single
271
+ if flags&Reverse != 0
272
+ start = value.size-1
273
+ finish = 0
274
+ step = -1
275
+ else
276
+ start = 0
277
+ finish = value.size-1
278
+ step = +1
279
+ end
280
+ if flags&Read != 0
281
+ value0 = data_class.new
282
+ replacing = true
283
+ start.step(finish,step) do |i|
284
+ v = _put1(value[i],flags) { |len|
285
+ replacing = more&&more[len]
286
+ }
287
+ value0 << v if replacing
288
+ end
289
+ if value0.size==0
290
+ nil
291
+ elsif step<0
292
+ value0.reverse
293
+ else
294
+ value0
295
+ end
296
+ else
297
+ len0 = 0
298
+ replacing = true
299
+ start.step(finish,step) do |i|
300
+ _put1(value[i],flags) { |len|
301
+ replacing = more&&more[len]
302
+ }
303
+ len0 += 1 if replacing
304
+ end
305
+ if len0==0
306
+ nil
307
+ else
308
+ len0
309
+ end
310
+ end
311
+ end
312
+ end
313
+ # With no +p+ or code block, this will return an numeric position. This
314
+ # base class uses 0 to mean the beginning and the total number of elements to
315
+ # represent the end. Derived classes can shift these relative positions
316
+ # if desired (i.e. Cursor::Reversed uses negative numbers).
317
+ #
318
+ # With a +p+ or code block specified, optionally set #pos= +p+,
319
+ # execute the code block or find the #pos (without a block), and finally return to where it
320
+ # started (using #pos/#pos=). The return value will be the
321
+ # result of code block or the inner #pos.
322
+ def pos(p=nil,&code) # :yield:
323
+ if code or p
324
+ start = pos
325
+ self.pos = p if p
326
+ ret = code ? code[] : pos
327
+ self.pos = start
328
+ ret
329
+ else
330
+ p = get(0,Prev|Ignore)
331
+ return(0) if not p or p==0
332
+ get(p,Next|Ignore)==p or raise(IndexError,"couldn't get back to where we were: #{p}")
333
+ p
334
+ end
335
+ end
336
+ # Returns the #pos
337
+ def to_i
338
+ pos
339
+ end
340
+ # Returns the string "pos=#{#pos}". Derived classes may override this
341
+ # to provide more information (i.e. line and column).
342
+ def to_s
343
+ "pos=#{pos}"
344
+ end
345
+ # Set #pos to be +p+. This base class also allows +p+ to be negative
346
+ # values measured from the end just like the indices for String/Array.
347
+ def pos=(p)
348
+ if p<0
349
+ get(0,Next|Ignore)
350
+ return(-0.1) if p>-1
351
+ p = -(get(-p,Prev|Ignore)||0.1)
352
+ else
353
+ get(0,Prev|Ignore)
354
+ return(0) if p<1
355
+ get(p,Next|Ignore)||0
356
+ end
357
+ end
358
+ def _finalizer(id) # :nodoc:
359
+ i = @positions.index(id)
360
+ @positions.slice!(i) if i
361
+ end
362
+ protected :_finalizer
363
+ # Similar to #pos except Cursor::Position objects (which are Cursors) are
364
+ # used to save and retrieve the position.
365
+ def position(p=nil,&code) # :yield:
366
+ if code or p
367
+ start = position
368
+ self.position = p if p
369
+ ret = code ? code[] : position
370
+ self.position = start
371
+ start.close
372
+ ret
373
+ else
374
+ p = Position.new(self,pos)
375
+ @positions << (p.object_id >> 1)
376
+ ObjectSpace.define_finalizer(p,method(:_finalizer))
377
+ p
378
+ end
379
+ end
380
+ # Set the position to +p+. +p+ can be numeric (from #pos) or from #position.
381
+ def position=(p)
382
+ if p.respond_to?(:pos)
383
+ position?(p) or raise(TypeError,"invalid position #{p}")
384
+ self.pos = p.pos
385
+ else
386
+ self.pos = p
387
+ end
388
+ end
389
+ # Without a code block, this queries whether a particular #position +p+ is
390
+ # valid (is a child) or determines if there is any outstanding #position
391
+ # (when +p+=+nil+).
392
+ #
393
+ # With a code block, it sets optionally sets the #position= +p+ and
394
+ # and executes the code. If the result of the code is false/nil, it will
395
+ # return to the original #position (intially saved). Otherwise it will stay where the code
396
+ # left it. The result of the code block is returned. This is useful when
397
+ # you want the cursor to stay for a pass/match, and return to try something
398
+ # else for a fail/mismatch.
399
+ def position?(p=nil,&code) # :yield:
400
+ if code
401
+ start = position
402
+ self.position = p if p
403
+ ret = code[]
404
+ self.position = start if not ret
405
+ start.close
406
+ ret
407
+ elsif p
408
+ i = @positions.rindex(p.object_id >> 1)
409
+ if not i
410
+ return(true) if p.object_id==self.object_id
411
+ return(nil)
412
+ end
413
+ if p.closed?
414
+ @positions.slice!(i)
415
+ nil
416
+ else
417
+ true
418
+ end
419
+ else
420
+ while @positions.size>0
421
+ return(true) if not ObjectSpace._id2ref(@positions[0] << 1).closed?
422
+ @positions.shift
423
+ end
424
+ end
425
+ end
426
+ # Either discard/close the given #position +p+ or discard/close every child #position (+p+=+nil+).
427
+ def position!(p=nil)
428
+ if p
429
+ p.close
430
+ position?(p) && raise(RuntimeError,"#{p} didn't seem to close")
431
+ else
432
+ @positions.each { |p| ObjectSpace._id2ref(p << 1).close }
433
+ @positions = []
434
+ end
435
+ self
436
+ end
437
+ # Close the cursor. This will also close every child #position.
438
+ def close
439
+ position!
440
+ # this should make just about any operation fail
441
+ instance_variables.each { |v| remove_instance_variable(v) }
442
+ nil
443
+ end
444
+ # Is the cursor closed?
445
+ def closed?
446
+ instance_variables.size==0
447
+ end
448
+ # Are we at the end? Or beginning if +reverse+.
449
+ def eof?(reverse=false)
450
+ flags = After|Ignore
451
+ flags |= Reverse if reverse
452
+ not get(nil,flags)
453
+ end
454
+ alias_method :eof, :eof?
455
+ # Is +other+ (either #pos or #position) at the same location as the cursor?
456
+ def ==(other)
457
+ if other.respond_to?:pos
458
+ position?(other) or return(false)
459
+ other = other.pos
460
+ else
461
+ other = pos(other)
462
+ end
463
+ pos==other
464
+ end
465
+ alias_method :===, :==
466
+ # Compare +other+ (from #pos or #position) to the current position. +1
467
+ # for the self is after, -1 for self being before, and 0 for it being at
468
+ # same location.
469
+ def <=>(other)
470
+ if other.respond_to?:pos
471
+ position?(other) or raise(TypeError,"invalid position #{p}")
472
+ other = other.pos
473
+ else
474
+ other = pos(other)
475
+ end
476
+ pos<=>other
477
+
478
+ end
479
+ # If +other+ is from #position, this will return the distance (number
480
+ # or elements) from +other+ to +self+. This can be +, -, or 0.
481
+ # If +other+ is numeric, this returns a new position that is decreased by
482
+ # this amount (positive: before, negative: after). Otherwise, it is taken to
483
+ # be a +len+ for #get (going backward).
484
+ def -(other)
485
+ if other.respond_to?:pos
486
+ position?(other) or raise(TypeError,"invalid position #{p}")
487
+ pos.to_i-other.pos.to_i
488
+ elsif other.kind_of?Numeric
489
+ if other>0
490
+ position { get(other,Prev|Ignore);position }
491
+ elsif other<0
492
+ position { get(other,Next|Ignore);position }
493
+ else
494
+ position
495
+ end
496
+ else
497
+ position { get(other,Prev|Ignore);position }
498
+ end
499
+ end
500
+ # If +other+ is numeric, this returns a new position that is increased by
501
+ # this amount (positive: before, negative: after). Otherwise, it is taken to
502
+ # be a +len+ for #get (going forward).
503
+ def +(other)
504
+ if other.kind_of?Numeric
505
+ if other>0
506
+ position { get(other,Next|Ignore);position }
507
+ elsif other<0
508
+ position { get(other,Prev|Ignore);position }
509
+ else
510
+ position
511
+ end
512
+ else
513
+ position { get(other,Next|Ignore);position }
514
+ end
515
+ end
516
+ # Returns a Reversed cursor.
517
+ def -@
518
+ Cursor::Reversed.new(self)
519
+ end
520
+ # Increments the cursor by +len+ (same +len+ of #get - defaults to one element)
521
+ # Returns +nil+ at the end and self otherwise.
522
+ def succ!(len=nil); get(len,Next|Ignore) && self; end
523
+ # Decrements the cursor by +len+ (same +len+ of #get - defaults to one element)
524
+ # Returns +nil+ at the beginning and self otherwise.
525
+ def pred!(len=nil); get(len,Prev|Ignore) && self; end
526
+ # Similar to #succ! except a new #position is returned instead of
527
+ # modifying the current.
528
+ def succ(len=nil); position { get(len,Next|Ignore) && position }; end
529
+ # Similar to #pred! except a new #position is returned instead of
530
+ # modifying the current.
531
+ def pred(len=nil); position { get(len,Prev|Ignore) && position }; end
532
+ # Go to the beginning
533
+ def first!; get(0,Prev|Ignore); self; end
534
+ # Go to the end
535
+ def last!; get(0,Next|Ignore); self; end
536
+ # Similar to #first! except a new #position is returned instead of
537
+ # modifying the current.
538
+ def first; position { get(0,Prev|Ignore);position }; end
539
+ # Similar to #last! except a new #position is returned instead of
540
+ # modifying the current.
541
+ def last; position { get(0,Next|Ignore);position }; end
542
+ alias_method :begin, :first
543
+ alias_method :begin!, :first!
544
+ alias_method :end, :last
545
+ alias_method :end!, :last!
546
+ # Returns the number of elements.
547
+ def size
548
+ l1 = position { get(0,Next|Ignore) } || 0
549
+ l0 = position { get(0,Prev|Ignore) } || 0
550
+ l0+l1
551
+ end
552
+ alias_method :length, :size
553
+ # Determines whether there is anything before or after the cursor.
554
+ def empty?
555
+ get(nil,After |Ignore)&&(return(false)) ||
556
+ get(nil,Before|Ignore)&&(return(false)) ||
557
+ true
558
+ end
559
+ # Removes all elements and returns the number removed
560
+ def clear
561
+ (get(0,Next|Delete|Ignore)||0)+(get(0,Prev|Delete|Ignore)||0)
562
+ end
563
+ # Appends a single element at the end
564
+ def << (value)
565
+ get(0,Next|Ignore)
566
+ put(value,Next|Single)
567
+ end
568
+ # Prepends a single element at the beginning
569
+ def >> (value)
570
+ get(0,Prev|Ignore)
571
+ put(value,Prev|Single)
572
+ end
573
+ protected
574
+ def _index(index,len,flags) # :nodoc:
575
+ if index.respond_to?:exclude_end?
576
+ len = (index.last-index.first).to_i
577
+ if index.exclude_end?
578
+ len = len.ceil
579
+ else
580
+ len = len.to_i+1
581
+ end
582
+ index = index.first
583
+ index += len if flags&Reverse != 0
584
+ len = 0 if len<0
585
+ elsif len.kind_of?Numeric
586
+ if len<0
587
+ flags ^= Reverse
588
+ len = -len
589
+ end
590
+ len = len.to_i
591
+ end
592
+ self.position = index if index
593
+ [len,flags]
594
+ end
595
+ public
596
+ # Provides random access for the cursor like what is in Array/String.
597
+ # +index+ can be +nil+ (start at the current location), a numeric (possibly range)
598
+ # (just like Array/String), and even a range from start to end cursors.
599
+ # +len+ can be positive/0 (just like in Array/String), negative (goes
600
+ # in reverse), or even a +data_class+ (passed to #get).
601
+ # +flags+ can take on the same things they can in get. A code block can
602
+ # be given just like in #get.
603
+ def [](index=nil,len=nil,flags=After,&more) # :yield: len
604
+ len,flags = _index(index,len,flags)
605
+ return(data_class.new) if len==0
606
+ get(len,flags,&more)
607
+ end
608
+ # Random access store like in Array/String. Accepts the same enhancements
609
+ # for index, len, and flags as #[] does.
610
+ def []=(*args) # :args: (index=nil,len=nil,flags=After,value)
611
+ value = args.slice!(-1)
612
+ index,len,flags = *args
613
+ flags ||= After
614
+ len,flags = _index(index,len,flags)
615
+ if not len
616
+ ret = put(value,flags|Single)
617
+ else
618
+ flags &= ~Single
619
+ if len==0
620
+ flags &= ~Read
621
+ ret = put(value,flags|Insert)
622
+ elsif value.size==len
623
+ ret = put(value,flags&~Insert)
624
+ else
625
+ ret = get(len,flags^Ignore|Delete)
626
+ put(value,flags|Insert)
627
+ end
628
+ end
629
+ if flags&Read != 0
630
+ ret
631
+ else
632
+ value
633
+ end
634
+ end
635
+ alias_method :slice, :[]
636
+ # Random access slice! like in Array/String. Accepts the same enhancements
637
+ # for index, len, flags, and code block as #[] does (same except +Delete+
638
+ # bit is set).
639
+ def slice!(index=nil,len=nil,flags=After,&more) # :yield: len
640
+ len,flags = _index(index,len,flags)
641
+ return(data_class.new) if len==0
642
+ get(len,flags|Delete,&more)
643
+ end
644
+ # Performs each just to make this class Enumerable. Accepts the same enhancements
645
+ # for index, len, and flags as #[] does. +index+ defaults to nil which
646
+ # starts where the cursor (like IO) as opposed to the beginning (like Array).
647
+ # The cursor will be left at the end (or beginning if the +Reverse+ flags bit is use).
648
+ # nil is returned (or the break value if the code does a break).
649
+ def each(index=nil,len=nil,flags=Next,&code) # :yield: value
650
+ len,flags = _index(index,len,flags)
651
+ raise if len==0 or flags&Hold != 0
652
+ continue = true
653
+ loop do
654
+ value = get(len,flags) { continue = nil }
655
+ if not continue
656
+ code[value] if len and value
657
+ break
658
+ end
659
+ code[value]
660
+ end
661
+ end
662
+ # copies data from +index+ to the current location. Uses #[] and #[]=
663
+ # to accomplish the copy so that all of those options are available.
664
+ # +pflags are used for the put and +gflags+ for the get.
665
+ def copy_from(index,len=nil,pflags=After,gflags=After)
666
+ gflags &= ~Ignore
667
+ if len
668
+ pflags |= Single
669
+ else
670
+ pflags &= ~Single
671
+ end
672
+ self[nil,nil,pflags] = self[index,len,gflags]
673
+ end
674
+ # copies data from the current location to +index+. Uses #[] and #[]=
675
+ # to accomplish the copy so that all of those options are available.
676
+ # +pflags are used for the put and +gflags+ for the get.
677
+ def copy_to(index,len=nil,pflags=After,gflags=After)
678
+ gflags &= ~Ignore
679
+ if len
680
+ pflags |= Single
681
+ else
682
+ pflags &= ~Single
683
+ end
684
+ self[index,nil,pflags] = self[nil,len,gflags]
685
+ end
686
+
687
+ # Objects in this class are mainly used to simply mark/remember the location
688
+ # of a parent cursor. But, this class also has the fully functionality of the
689
+ # parent. When this child want to do an operation, it uses the parent to
690
+ # do it and returns the parent to where it was. Derived classes where the
691
+ # underlying data is random access may be able to implement this class to
692
+ # directly access the data rather than go through the parent.
693
+ class Position < Cursor
694
+ def initialize(parent,p)
695
+ @parent = parent
696
+ @pos = p
697
+ end
698
+ def data_class
699
+ @parent.data_class
700
+ end
701
+ def get(*args,&more) # :args: (len=nil, flags=Next) { |len| ... }
702
+ @parent.pos(@pos) {
703
+ ret = @parent.get(*args,&more)
704
+ @pos = @parent.pos
705
+ ret
706
+ }
707
+ end
708
+ def put(*args,&more) # :args: (value, flags=Next) { |len| ... }
709
+ @parent.pos(@pos) {
710
+ ret = @parent.put(*args,&more)
711
+ @pos = @parent.pos
712
+ ret
713
+ }
714
+ end
715
+ def position(p=nil,&code) # :yield:
716
+ if p or code
717
+ super(p,&code)
718
+ else
719
+ @parent.pos(@pos) { @parent.position }
720
+ end
721
+ end
722
+ def position?(p=nil,&code) # :yield:
723
+ if code
724
+ super(p,&code)
725
+ else
726
+ @parent.position?(p)
727
+ end
728
+ end
729
+ def position!(p=nil)
730
+ if p
731
+ @parent.position!(p)
732
+ else
733
+ nil
734
+ end
735
+ end
736
+ def close
737
+ parent = @parent
738
+ super
739
+ parent.position?(self)
740
+ end
741
+ end
742
+
743
+ # This class can be used to reverse the direction of operations on a given
744
+ # cursor. It operates on the given cursor directly moving it around.
745
+ class Reversed < Cursor
746
+ def initialize(cursor)
747
+ super()
748
+ @cursor = cursor
749
+ end
750
+ def data_class
751
+ @cursor.data_class
752
+ end
753
+ def get(len=nil,flags=Next,&more) # :yield: len
754
+ @cursor.get(len,flags^Reverse)
755
+ end
756
+ def put(value,flags=Next,&more) # :yield: len
757
+ @cursor.put(value,flags^Reverse,&more)
758
+ end
759
+ # Every +pos+ returned here is negative (-0.1: end, -N: beginning)
760
+ # unlike the base class.
761
+ def pos(p=nil,&code) # :yield:
762
+ if p or code
763
+ super(*args,&code)
764
+ else
765
+ p = @cursor.pos
766
+ p = -p
767
+ p = -0.1 if p==0
768
+ p
769
+ end
770
+ end
771
+ def pos=(p)
772
+ p = -p
773
+ p = -0.1 if p==0
774
+ p = (@cursor.pos = p)
775
+ p = -p
776
+ p = -0.1 if p==0
777
+ p
778
+ end
779
+ end
780
+
781
+ # This class is used to test the base class by overriding as few methods
782
+ # as possible.
783
+ class Test < Cursor
784
+ def initialize(data=[],index=0)
785
+ super()
786
+ @data = data
787
+ @pos = index
788
+ @data_class = (@data.respond_to?:data_class) ?
789
+ @data.data_class : @data.class
790
+ end
791
+ def data_class
792
+ @data_class
793
+ end
794
+ def _delete1(flags,&more) # :yield: len=1
795
+ if flags&Reverse != 0
796
+ @pos>0 && (@pos -= 1)
797
+ else
798
+ @pos<@data.size
799
+ end or return(
800
+ if ret = more&&more[1]
801
+ if flags&Ignore != 0
802
+ ret = true
803
+ else
804
+ ret,=ret
805
+ ret
806
+ end
807
+ end
808
+ )
809
+ if flags&Ignore != 0
810
+ @data[@pos,1] = @data_class.new
811
+ 1
812
+ else
813
+ @data.slice!(@pos)
814
+ end
815
+ end
816
+ def _insert1(value,flags)
817
+ @data[@pos,0] = (@data_class.new << value)
818
+ @pos += 1 if flags&Reverse == 0
819
+ end
820
+ end
821
+
822
+ # This class puts a cursor on an Array or String.
823
+ class Indexed < Cursor
824
+ def initialize(data=[],index=0)
825
+ super()
826
+ @data = data
827
+ @pos = index
828
+ @data_class = (@data.respond_to?:data_class) ?
829
+ @data.data_class : @data.class
830
+ end
831
+ def data_class
832
+ @data_class
833
+ end
834
+ def _delete1(flags,&more) # :yield: len=1
835
+ if flags&Reverse != 0
836
+ @pos>0 && @pos -= 1
837
+ else
838
+ @pos<@data.size
839
+ end or return(
840
+ if ret = more&&more[1]
841
+ if flags&Ignore != 0
842
+ ret = true
843
+ else
844
+ ret,=ret
845
+ ret
846
+ end
847
+ end
848
+ )
849
+ if flags&Ignore != 0
850
+ @data[@pos,1] = @data_class.new
851
+ true
852
+ else
853
+ @data.slice!(@pos)
854
+ end
855
+ end
856
+ def _insert1(value,flags)
857
+ @data[@pos,0] = (@data_class.new << value)
858
+ @pos += 1 if flags&Reverse == 0
859
+ end
860
+ end
861
+
862
+ # This class treats an IO (or StringIO) as a Cursor. An IO is already
863
+ # like a Cursor, but doesn't have as robust an interface. Deleting and
864
+ # inserting is a slow/painful process.
865
+ class IO < Cursor
866
+ def initialize(io=StringIO.new)
867
+ super()
868
+ @io = io
869
+ end
870
+ def data_class
871
+ String
872
+ end
873
+ def _delete1(flags,&more) # :yield: len=1
874
+ if flags&Reverse != 0
875
+ begin
876
+ @io.seek(-1,::IO::SEEK_CUR)
877
+ ret = @io.getc
878
+ rescue
879
+ ret = nil
880
+ end
881
+ else
882
+ ret = @io.getc
883
+ end or return(
884
+ if ret = more&&more[1]
885
+ if flags&Ignore != 0
886
+ ret = true
887
+ else
888
+ ret,=ret
889
+ ret
890
+ end
891
+ end
892
+ )
893
+ ret = true if flags&Ignore != 0
894
+ after = 0
895
+ while c = @io.getc
896
+ after += 1
897
+ @io.seek(-2,::IO::SEEK_CUR)
898
+ @io.putc(c)
899
+ @io.seek(+1,::IO::SEEK_CUR)
900
+ end
901
+ @io.seek(-1,::IO::SEEK_CUR)
902
+ @io.truncate(@io.pos)
903
+ @io.seek(-after,::IO::SEEK_CUR) if after>0
904
+ ret
905
+ end
906
+ def _insert1(value,flags)
907
+ after = 0
908
+ while c = @io.getc
909
+ after += 1
910
+ @io.seek(-1,::IO::SEEK_CUR)
911
+ @io.putc(value)
912
+ value = c
913
+ end
914
+ @io.putc(value)
915
+ after += 1 if flags&Reverse != 0
916
+ @io.seek(-after,::IO::SEEK_CUR) if after>0
917
+ nil
918
+ end
919
+ def _get1(flags,&more) # :yield: len=1
920
+ if flags&Reverse != 0
921
+ begin
922
+ @io.seek(-1,::IO::SEEK_CUR)
923
+ rescue
924
+ if ret = more&&more[1]
925
+ ret,=ret
926
+ if flags&Hold != 0
927
+ flags ^= Hold
928
+ flags ^= Reverse
929
+ end
930
+ _insert(flags,ret)
931
+ ret = true if flags&Ignore != 0
932
+ end
933
+ return(ret)
934
+ end
935
+ if flags&Ignore != 0
936
+ @io.seek(+1,::IO::SEEK_CUR) if flags&Hold != 0
937
+ ret = true
938
+ else
939
+ ret = @io.getc
940
+ @io.seek(-1,::IO::SEEK_CUR) if flags&Hold == 0
941
+ end
942
+ else
943
+ if not ret = @io.getc
944
+ if ret = more&&more[1]
945
+ ret,=ret
946
+ @io.putc(ret)
947
+ @io.seek(-1,::IO::SEEK_CUR) if flags&Hold != 0
948
+ ret = true if flags&Ignore != 0
949
+ end
950
+ return(ret)
951
+ end
952
+ @io.ungetc(ret) if flags&Hold != 0
953
+ ret = true if flags&Ignore != 0
954
+ end
955
+ ret
956
+ end
957
+ def _put1(value,flags,&more) # :yield: len=1
958
+ if flags&Reverse != 0
959
+ begin
960
+ @io.seek(-1,::IO::SEEK_CUR)
961
+ rescue
962
+ if ret = more&&more[1]
963
+ if flags&Read == 0
964
+ ret = true
965
+ else
966
+ ret,=ret
967
+ end
968
+ end
969
+ if flags&Hold != 0
970
+ flags ^= Hold
971
+ flags ^= Reverse
972
+ end
973
+ _insert1(value,flags)
974
+ return(ret)
975
+ end
976
+ if flags&Read != 0
977
+ ret = @io.getc
978
+ @io.ungetc(ret)
979
+ else
980
+ ret = true
981
+ end
982
+ @io.putc(value)
983
+ if flags&Hold == 0
984
+ @io.seek(-1,::IO::SEEK_CUR)
985
+ end
986
+ else
987
+ if flags&Read != 0
988
+ if not ret = @io.getc
989
+ if ret = more&&more[1]
990
+ ret, = ret
991
+ end
992
+ else
993
+ @io.ungetc(ret)
994
+ end
995
+ elsif @io.eof?
996
+ ret = more&&more[1]&&true
997
+ else
998
+ ret = true
999
+ end
1000
+ @io.putc(value)
1001
+ @io.seek(-1,::IO::SEEK_CUR) if flags&Hold != 0
1002
+ end
1003
+ ret
1004
+ end
1005
+ def close
1006
+ @io.close
1007
+ super
1008
+ end
1009
+ end
1010
+
1011
+ # This Cursor class uses an Array/String for data before the cursor
1012
+ # and another one for data after the cursor. The result of this is that
1013
+ # data is only deleted/inserted at the end of these 2. Because of that
1014
+ # most operations have similar expense and are linear with respect to the
1015
+ # number of elements being moved, inserted, deleted, replaced, etc.
1016
+ class Buffer < Cursor
1017
+ def initialize(before=[],after=before.class.new)
1018
+ super()
1019
+ @before = before
1020
+ @after = after
1021
+ @data_class = (@before.respond_to?:data_class) ?
1022
+ @before.data_class : @before.class
1023
+ end
1024
+ def data_class
1025
+ @data_class
1026
+ end
1027
+ def _delete1(flags,&more) # :yield: len=1
1028
+ data = (flags&Reverse != 0) ? @before : @after
1029
+ data.size>0 or return(
1030
+ if ret = more&&more[1]
1031
+ if flags&Ignore != 0
1032
+ ret = true
1033
+ else
1034
+ ret,=ret
1035
+ ret
1036
+ end
1037
+ end
1038
+ )
1039
+ if flags&Ignore != 0
1040
+ data[-1,1] = @data_class.new
1041
+ true
1042
+ else
1043
+ data.slice!(-1)
1044
+ end
1045
+ end
1046
+ def _insert1(value,flags)
1047
+ data = (flags&Reverse != 0) ? @after : @before
1048
+ data << value
1049
+ end
1050
+ end
1051
+
1052
+ # This class gives unidirectional cursors (i.e. IO pipes) some
1053
+ # bidirectional capabilities. An input cursor and/or an output cursor
1054
+ # can be specified. The #position, #position?, and #position! methods are
1055
+ # used to control buffering. Full cursor capability (limited by the buffer
1056
+ # cursor) is accessible starting from the first #position. When the end of
1057
+ # the buffer is reached more data is read from the input cursor (if not nil) using #get . When no
1058
+ # #position is outstanding, everything before the buffer cursor is written
1059
+ # to the output cursor (if not nil) using #put. If the cursor is attempted
1060
+ # to be moved before the buffer, the output cursor is read in reverse.
1061
+ class Buffered < Cursor
1062
+ def initialize(input,output=nil,buffer=Buffer.new((input||output).data_class.new))
1063
+ @input = input
1064
+ @output = output
1065
+ @buffer = buffer
1066
+ @offset = 0
1067
+ super()
1068
+ end
1069
+ def data_class
1070
+ @buffer.data_class
1071
+ end
1072
+ def get(len=nil,flags=Next,&more) # :yield: len
1073
+ value = @buffer.get(len,flags) { |len|
1074
+ if flags&Reverse != 0
1075
+ if @offset==0
1076
+ more&&more[len]
1077
+ else
1078
+ v = @output.get(len,Prev,&more)
1079
+ @offset -= v.size if v
1080
+ v
1081
+ end
1082
+ elsif @input
1083
+ @input.get(len,Next,&more)
1084
+ else
1085
+ more&&more[len]
1086
+ end
1087
+ }
1088
+ if not position?
1089
+ if output_value = @buffer.get(0,Prev|Delete)
1090
+ @offset += output_value.size
1091
+ @output.put(output_value,Next) if @output
1092
+ end
1093
+ end
1094
+ value
1095
+ end
1096
+ def put(value,flags=Next,&more) # :yield: len
1097
+ value0 = @buffer.put(value,flags) { |len|
1098
+ if flags&Reverse != 0
1099
+ if @offset==0
1100
+ more&&more[len]
1101
+ else
1102
+ v = @output.get(len,Prev,&more)
1103
+ @offset -= v.size if v
1104
+ v
1105
+ end
1106
+ elsif @input
1107
+ @input.get(len,Next,&more)
1108
+ else
1109
+ more&&more[len]
1110
+ end
1111
+ }
1112
+ if not position?
1113
+ if output_value = @buffer.get(0,Prev|Delete)
1114
+ @offset += output_value.size
1115
+ @output.put(output_value,Next) if @output
1116
+ end
1117
+ end
1118
+ value0
1119
+ end
1120
+ def pos(p=nil,&code) # :yield:
1121
+ if p or code
1122
+ super(*args,&code)
1123
+ else
1124
+ @buffer.pos+@offset
1125
+ end
1126
+ end
1127
+ def pos=(p)
1128
+ if p<0
1129
+ @buffer.pos = p
1130
+ else
1131
+ @buffer.pos = p-@offset
1132
+ end
1133
+ end
1134
+ end
1135
+
1136
+ end
1137
+
1138
+
metadata ADDED
@@ -0,0 +1,37 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.10
3
+ specification_version: 1
4
+ name: cursor
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.5"
7
+ date: 2005-05-19
8
+ summary: external iterator API
9
+ require_paths:
10
+ - "."
11
+ email:
12
+ homepage:
13
+ rubyforge_project: cursor
14
+ description:
15
+ autorequire: cursor
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ authors:
28
+ - Eric Mahurin
29
+ files:
30
+ - cursor.rb
31
+ test_files: []
32
+ rdoc_options: []
33
+ extra_rdoc_files: []
34
+ executables: []
35
+ extensions: []
36
+ requirements: []
37
+ dependencies: []