cursor 0.5

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.
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: []