gap_buffer 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,38 @@
1
+ Implementation of a Gap Buffer for Ruby MRI.
2
+ (c) 2010 Lourens Naudé (methodmissing), based off work from Hsin Tsao <stsao@lazyhacker.com>
3
+
4
+ http://github.com/methodmissing/gap_buffer
5
+
6
+ How Gap Buffers work :
7
+
8
+ http://en.wikipedia.org/wiki/Gap_buffer
9
+ http://www.lazyhacker.com/gapbuffer/gapbuffer.htm
10
+
11
+ This library works with Ruby 1.8 and 1.9 and exposes the following API :
12
+
13
+ gb = GapBuffer.new
14
+ gb.size #=> 20
15
+ gb.offset #=> 9
16
+ gb << "test"
17
+ gb.print #=> test_______________
18
+ gb.insert('b')
19
+ gb.offset #=> 5
20
+ gb.print #=> testb______________
21
+ gb.insert_at(6, 'u')
22
+ gb.print #=> testbu_____________
23
+ gb.previous #=> b
24
+ gb.next #=> u
25
+ gb.offset = 7
26
+ gb.put('f')
27
+ gb.print #=> testbuf____________
28
+
29
+ To run the test suite:
30
+
31
+ rake
32
+
33
+ Todo:
34
+
35
+ Better 1.9 compat
36
+ Look into moving off the C++ implementation
37
+
38
+ Work in progress, thanks for watching!
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env rake
2
+ require 'rake/testtask'
3
+ require 'rake/clean'
4
+ $:.unshift(File.expand_path('lib'))
5
+ GAP_BUFFER_ROOT = 'ext/gap_buffer'
6
+
7
+ desc 'Default: test'
8
+ task :default => :test
9
+
10
+ desc 'Run gap_buffer tests.'
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs = [GAP_BUFFER_ROOT]
13
+ t.pattern = 'test/test_*.rb'
14
+ t.ruby_opts << '-rtest'
15
+ t.libs << 'test'
16
+ t.warning = true
17
+ t.verbose = true
18
+ end
19
+ task :test => :build
20
+
21
+ namespace :build do
22
+ file "#{GAP_BUFFER_ROOT}/rubymain.cpp"
23
+ file "#{GAP_BUFFER_ROOT}/extconf.rb"
24
+ file "#{GAP_BUFFER_ROOT}/Makefile" => %W(#{GAP_BUFFER_ROOT}/rubymain.cpp #{GAP_BUFFER_ROOT}/extconf.rb) do
25
+ Dir.chdir(GAP_BUFFER_ROOT) do
26
+ ruby 'extconf.rb'
27
+ end
28
+ end
29
+
30
+ desc "generate makefile"
31
+ task :makefile => %W(#{GAP_BUFFER_ROOT}/Makefile #{GAP_BUFFER_ROOT}/rubymain.cpp)
32
+
33
+ dlext = Config::CONFIG['DLEXT']
34
+ file "#{GAP_BUFFER_ROOT}/gap_buffer.#{dlext}" => %W(#{GAP_BUFFER_ROOT}/Makefile #{GAP_BUFFER_ROOT}/rubymain.cpp) do
35
+ Dir.chdir(GAP_BUFFER_ROOT) do
36
+ sh 'make' # TODO - is there a config for which make somewhere?
37
+ end
38
+ end
39
+
40
+ desc "compile gap buffer extension"
41
+ task :compile => "#{GAP_BUFFER_ROOT}/gap_buffer.#{dlext}"
42
+
43
+ task :clean do
44
+ Dir.chdir(GAP_BUFFER_ROOT) do
45
+ sh 'make clean'
46
+ end if File.exists?("#{GAP_BUFFER_ROOT}/Makefile")
47
+ end
48
+
49
+ CLEAN.include("#{GAP_BUFFER_ROOT}/Makefile")
50
+ CLEAN.include("#{GAP_BUFFER_ROOT}/gap_buffer.#{dlext}")
51
+ end
52
+
53
+ task :clean => %w(build:clean)
54
+
55
+ desc "compile"
56
+ task :build => %w(build:compile)
57
+
58
+ task :install do |t|
59
+ Dir.chdir(GAP_BUFFER_ROOT) do
60
+ sh 'sudo make install'
61
+ end
62
+ end
63
+
64
+ desc "clean build install"
65
+ task :setup => %w(clean build install)
66
+
67
+ desc 'Run benchmarks'
68
+ task :bench do
69
+ ruby "bench/gap_buffer.rb"
70
+ end
71
+ task :bench => :build
@@ -0,0 +1,40 @@
1
+ $:.unshift "."
2
+ require 'benchmark'
3
+ require File.dirname(__FILE__) + '/../ext/gap_buffer/gap_buffer'
4
+
5
+ TESTS = 1000
6
+ ITEMS = (0..TESTS).to_a
7
+
8
+ def prepare
9
+ string = ITEMS.join
10
+ gb = GapBuffer.new(TESTS)
11
+ gb << ITEMS.join
12
+ [string, gb]
13
+ end
14
+
15
+ Benchmark.bmbm do |results|
16
+ results.report("[String] random insert") do
17
+ string, gb = prepare
18
+ TESTS.times{ r = rand(TESTS); string[r] = r.to_s }
19
+ end
20
+ results.report("[GapBuffer] random insert") do
21
+ string, gb = prepare
22
+ TESTS.times{ r = rand(TESTS); gb.insert_at(r, r.to_s); }
23
+ end
24
+ results.report("[String] insert") do
25
+ string, gb = prepare
26
+ TESTS.times{|i| string[i] = i.to_s }
27
+ end
28
+ results.report("[GapBuffer] insert") do
29
+ string, gb = prepare
30
+ TESTS.times{|i| gb.insert_at(i, i.to_s); }
31
+ end
32
+ results.report("[String] random remove") do
33
+ string, gb = prepare
34
+ TESTS.times{ r = rand(TESTS); string.delete!(r.to_s) }
35
+ end
36
+ results.report("[GapBuffer] random remove") do
37
+ string, gb = prepare
38
+ TESTS.times{ r = rand(TESTS); gb.delete(r) }
39
+ end
40
+ end
@@ -0,0 +1,62 @@
1
+ require 'mkmf'
2
+
3
+ dir_config('gap_buffer')
4
+ have_func('rb_thread_blocking_region')
5
+
6
+ # Shamelessly leached from Eventmachine (https://github.com/eventmachine/eventmachine)
7
+
8
+ case RUBY_PLATFORM
9
+ when /mswin32/, /mingw32/, /bccwin32/
10
+ check_heads(%w[windows.h winsock.h], true)
11
+ check_libs(%w[kernel32 rpcrt4 gdi32], true)
12
+
13
+ if GNU_CHAIN
14
+ CONFIG['LDSHARED'] = "$(CXX) -shared -lstdc++"
15
+ else
16
+ $defs.push "-EHs"
17
+ $defs.push "-GR"
18
+ end
19
+
20
+ when /solaris/
21
+ add_define 'OS_SOLARIS8'
22
+ check_libs(%w[nsl socket], true)
23
+
24
+ if CONFIG['CC'] == 'cc' and `cc -flags 2>&1` =~ /Sun/ # detect SUNWspro compiler
25
+ # SUN CHAIN
26
+ add_define 'CC_SUNWspro'
27
+ $preload = ["\nCXX = CC"] # hack a CXX= line into the makefile
28
+ $CFLAGS = CONFIG['CFLAGS'] = "-KPIC"
29
+ CONFIG['CCDLFLAGS'] = "-KPIC"
30
+ CONFIG['LDSHARED'] = "$(CXX) -G -KPIC -lCstd"
31
+ else
32
+ # GNU CHAIN
33
+ # on Unix we need a g++ link, not gcc.
34
+ CONFIG['LDSHARED'] = "$(CXX) -shared"
35
+ end
36
+
37
+ when /openbsd/
38
+ # OpenBSD branch contributed by Guillaume Sellier.
39
+
40
+ # on Unix we need a g++ link, not gcc. On OpenBSD, linking against libstdc++ have to be explicitly done for shared libs
41
+ CONFIG['LDSHARED'] = "$(CXX) -shared -lstdc++ -fPIC"
42
+ CONFIG['LDSHAREDXX'] = "$(CXX) -shared -lstdc++ -fPIC"
43
+
44
+ when /darwin/
45
+ # on Unix we need a g++ link, not gcc.
46
+ # Ff line contributed by Daniel Harple.
47
+ CONFIG['LDSHARED'] = "$(CXX) " + CONFIG['LDSHARED'].split[1..-1].join(' ')
48
+
49
+ when /linux/
50
+ # on Unix we need a g++ link, not gcc.
51
+ CONFIG['LDSHARED'] = "$(CXX) -shared"
52
+
53
+ when /aix/
54
+ # on Unix we need a g++ link, not gcc.
55
+ CONFIG['LDSHARED'] = "$(CXX) -shared -Wl,-G"
56
+
57
+ else
58
+ # on Unix we need a g++ link, not gcc.
59
+ CONFIG['LDSHARED'] = "$(CXX) -shared"
60
+ end
61
+
62
+ create_makefile('gap_buffer')
@@ -0,0 +1,480 @@
1
+ /*
2
+ * gap_buffer.cpp
3
+ *
4
+ * Author: Hsin Tsao (stsao@lazyhacker.com)
5
+ * Version: 1.0 (June 12, 2003)
6
+ *
7
+ * This provides the implementation to the GapBuffer class defined
8
+ * in text_buffer.h.
9
+ *
10
+ * Portions of this work derived from Joseph Allen's usenet
11
+ * postings on comp.editor that was released to the public
12
+ * domain.
13
+ *
14
+ *
15
+ * There are no restrictions on the use of this code other
16
+ * than to include my name in any derived work. There are
17
+ * no warranty for this obviously, but you are welcomed
18
+ * to modify, correct, or adapt the code to your needs. The
19
+ * author appreciates if you are willing to submit corrections
20
+ * or suggestions for improvement.
21
+ *
22
+ *
23
+ * http://www.lazyhacker.com
24
+ */
25
+
26
+ #include <iostream>
27
+ #include <cstdlib>
28
+ #include <stdio.h>
29
+ #include <string.h>
30
+ #include <sys/stat.h>
31
+ #include "gap_buffer.h"
32
+
33
+ using namespace std;
34
+
35
+ GapBuffer::GapBuffer(int gsize) : GAP_SIZE(gsize)
36
+ {
37
+ InitBuffer(GAP_SIZE);
38
+
39
+ }
40
+
41
+
42
+ GapBuffer::GapBuffer(FILE *file, int gsize) : GAP_SIZE(gsize)
43
+ {
44
+
45
+ // determine the size of the file then create
46
+ // a buffer of size + GAP_SIZE
47
+ struct stat buf;
48
+
49
+ fstat(fileno(file), &buf);
50
+ long file_size = buf.st_size;
51
+ InitBuffer(file_size + GAP_SIZE);
52
+ MoveGapToPoint();
53
+ ExpandGap( (int)file_size );
54
+ unsigned int amount = fread(gapstart, 1, file_size, file);
55
+
56
+ gapstart += amount;
57
+
58
+
59
+
60
+ }
61
+
62
+ /* Copy Constructor - Since we have pointers
63
+ * as member data, we need to provide our own
64
+ * copy constructor because the default will
65
+ * only cause copies to all point to the same
66
+ * location.
67
+ */
68
+ GapBuffer::GapBuffer(const GapBuffer& tb)
69
+ {
70
+ GAP_SIZE = tb.GAP_SIZE;
71
+
72
+ buffer = (char *) malloc(tb.bufend - tb.buffer);
73
+
74
+ strcpy(buffer, tb.buffer);
75
+
76
+ bufend = buffer + (tb.bufend - tb.buffer);
77
+ gapstart = buffer + (tb.gapstart - tb.buffer);
78
+ gapend = gapstart + (tb.gapend - tb.gapstart);
79
+ point = buffer + (tb.point - tb.buffer);
80
+
81
+ }
82
+
83
+
84
+ GapBuffer::~GapBuffer()
85
+ {
86
+
87
+ if (buffer) {
88
+ free(buffer);
89
+ }
90
+
91
+ }
92
+
93
+ /*
94
+ * Copy the characters from one location to another. We have
95
+ * to write our own instead of using memcopy because we are
96
+ * working within a single linear buffer and thus can have
97
+ * overlap between the source and destination.
98
+ */
99
+ int GapBuffer::CopyBytes(char *destination, char *source, unsigned int length)
100
+ {
101
+
102
+ if ( (destination == source) || (length == 0) ) {
103
+ return 1;
104
+ }
105
+
106
+ // if we're moving the character toward the front of the buffer
107
+ if (source > destination) {
108
+
109
+ // check to make sure that we don't go beyond the buffer
110
+ if ( (source + length) >= bufend ) {
111
+ return 0;
112
+ }
113
+
114
+ for (; length > 0; length--) {
115
+ *(destination++) = *(source++);
116
+ }
117
+
118
+ } else {
119
+
120
+ // To prevent overwriting characters we still
121
+ // need to move, go to the back and copy forward.
122
+ source += length;
123
+ destination += length;
124
+
125
+ for (; length > 0; length--) {
126
+ // decrement first 'cause we start one byte beyond where we want
127
+ *(--destination) = *(--source);
128
+ }
129
+ }
130
+
131
+ return 1;
132
+
133
+ }
134
+
135
+ /*
136
+ * Expand the buffer to new size + GAP_SIZE.
137
+ *
138
+ */
139
+ void GapBuffer::ExpandBuffer(unsigned int size)
140
+ {
141
+
142
+ // Check to see that we actually need to increase the buffer
143
+ // since BufferSize doesn't include the gap.
144
+ if ( ( (bufend - buffer) + size) > BufferSize() ) {
145
+
146
+ char *origbuffer = buffer;
147
+
148
+ int NewBufferSize = (bufend - buffer) + size + GAP_SIZE;
149
+
150
+ buffer = (char *) realloc(buffer, NewBufferSize);
151
+
152
+ point += buffer - origbuffer;
153
+ bufend += buffer - origbuffer;
154
+ gapstart += buffer - origbuffer;
155
+ gapend += buffer - origbuffer;
156
+ }
157
+
158
+ }
159
+
160
+ /*
161
+ * Move the gap to the current position of the point.
162
+ * The point should end in same location as gapstart.
163
+ */
164
+ void GapBuffer::MoveGapToPoint()
165
+ {
166
+
167
+
168
+ if (point == gapstart) {
169
+ return;
170
+ }
171
+
172
+ if (point == gapend) {
173
+ point = gapstart;
174
+ return;
175
+ }
176
+
177
+ // Move gap towards the left
178
+ if (point < gapstart) {
179
+ // Move the point over by gapsize.
180
+ CopyBytes(point + (gapend-gapstart), point, gapstart - point);
181
+ gapend -= (gapstart - point);
182
+ gapstart = point;
183
+ } else {
184
+ // Since point is after the gap, find distance
185
+ // between gapend and point and that's how
186
+ // much we move from gapend to gapstart.
187
+ CopyBytes(gapstart, gapend, point - gapend);
188
+ gapstart += point - gapend;
189
+ gapend = point;
190
+ point = gapstart;
191
+ }
192
+ }
193
+
194
+ /*
195
+ * Expand the size of the gap. If the required
196
+ * size is less then the current gap size, do
197
+ * nothing. If the size is greater than the
198
+ * current size, increase the gap to the default
199
+ * gap size + size.
200
+ */
201
+ void GapBuffer::ExpandGap(unsigned int size)
202
+ {
203
+
204
+
205
+ if (size > SizeOfGap()) {
206
+ size += GAP_SIZE;
207
+ ExpandBuffer(size);
208
+ CopyBytes(gapend+size, gapend, bufend - gapend);
209
+
210
+ gapend += size;
211
+ bufend += size;
212
+ }
213
+
214
+ }
215
+
216
+ /*
217
+ * Set point to offset from start of buffer.
218
+ */
219
+ void GapBuffer::SetPoint(unsigned int offset)
220
+ {
221
+
222
+ point = buffer + offset;
223
+
224
+ if (point > gapstart) {
225
+ point += gapend - gapstart;
226
+ }
227
+
228
+ }
229
+
230
+ /*
231
+ * Returns the current size of the gap.
232
+ */
233
+ int GapBuffer::SizeOfGap()
234
+ {
235
+ return gapend - gapstart;
236
+
237
+ }
238
+
239
+ /*
240
+ * Returns offset from point to start of buffer.
241
+ */
242
+ unsigned int GapBuffer::PointOffset()
243
+ {
244
+
245
+ if (point > gapend) {
246
+ return ((point - buffer) - (gapend - gapstart));
247
+ } else {
248
+ return (point - buffer);
249
+ }
250
+ }
251
+
252
+ /*
253
+ * Return character that point is pointing to.
254
+ * If point is inside the gap, then return the
255
+ * the first character outside the gap.
256
+ */
257
+ char GapBuffer::GetChar()
258
+ {
259
+
260
+ // If the point is anywhere in the gap, then
261
+ // it should always be at the start of the gap.
262
+ if (point == gapstart) {
263
+ point = gapend;
264
+ }
265
+
266
+ return *point;
267
+ }
268
+
269
+ /*
270
+ * Return the previous character and
271
+ * move point back one position.
272
+ */
273
+ char GapBuffer::PreviousChar()
274
+ {
275
+
276
+ if (point == gapend) {
277
+ point = gapstart;
278
+ }
279
+
280
+ return *(--point);
281
+ }
282
+
283
+ /*
284
+ * Replace the character of point.
285
+ */
286
+ void GapBuffer::ReplaceChar(char ch)
287
+ {
288
+
289
+ // Since we're just replacing the current character,
290
+ // we don't need to move or modify the gap.
291
+ if (point == gapstart) {
292
+ point = gapend;
293
+ }
294
+
295
+ if (point == bufend) {
296
+ ExpandBuffer(1);
297
+ bufend++;
298
+ }
299
+
300
+ *point = ch;
301
+ }
302
+
303
+ /*
304
+ * Increment pointer. Returns next character.
305
+ */
306
+ char GapBuffer::NextChar()
307
+ {
308
+ // point should not be in the gap.
309
+ if (point == gapstart) {
310
+ point = gapend;
311
+ return *point;
312
+ }
313
+
314
+ return *(++point);
315
+
316
+ }
317
+
318
+ void GapBuffer::PutChar(char ch)
319
+ {
320
+ InsertChar(ch);
321
+ *point++;
322
+
323
+ }
324
+
325
+ /*
326
+ * Insert character at point position. Note
327
+ * that repeatedly calling this function will
328
+ * keep calling MoveGapToPoint since this function
329
+ * doesn't advance the point. The result is the
330
+ * text appears reverse of key strokes.
331
+ */
332
+ void GapBuffer::InsertChar(char ch)
333
+ {
334
+ // Here we do need to move the gap if the point
335
+ // is not already at the start of the gap.
336
+
337
+ if (point != gapstart) {
338
+ MoveGapToPoint();
339
+ }
340
+
341
+ // check to make sure that the gap has room
342
+ if (gapstart == gapend) {
343
+ ExpandGap(1);
344
+ }
345
+
346
+ *(gapstart++) = ch;
347
+
348
+ }
349
+
350
+ /*
351
+ * Delete "size" number of characters.
352
+ */
353
+ void GapBuffer::DeleteChars(unsigned int size)
354
+ {
355
+
356
+ if (point != gapstart) {
357
+ MoveGapToPoint();
358
+ }
359
+
360
+ // We shifted the gap so that gapend points to the location
361
+ // where we want to start deleting so extend it
362
+ // to cover all the characters.
363
+ gapend += size;
364
+ }
365
+
366
+ void GapBuffer::InsertString(char *string, unsigned int length)
367
+ {
368
+
369
+ MoveGapToPoint();
370
+
371
+ if (length > SizeOfGap()) {
372
+ ExpandGap(length);
373
+ }
374
+
375
+ do {
376
+ PutChar(*(string++));
377
+ } while ( length-- );
378
+ }
379
+
380
+ /*
381
+ * Here we initilize the buffer and set
382
+ * the pointers to the correct position.
383
+ */
384
+ int GapBuffer::InitBuffer(unsigned int size)
385
+ {
386
+
387
+ /*
388
+ if (buffer) {
389
+ free(buffer);
390
+ }
391
+ */
392
+ buffer = NULL;
393
+ buffer = (char *) malloc(size);
394
+
395
+ if (!buffer) {
396
+ return 0;
397
+ }
398
+
399
+ point = buffer;
400
+ gapstart = buffer;
401
+
402
+ // initially gapend is outside of buffer
403
+ gapend = buffer + size;
404
+ bufend = gapend;
405
+
406
+ return 1;
407
+
408
+ }
409
+
410
+ int GapBuffer::BufferSize()
411
+ {
412
+ return (bufend - buffer) - (gapend - gapstart);
413
+ }
414
+
415
+ char* GapBuffer::GetBuffer()
416
+ {
417
+ return buffer;
418
+ }
419
+
420
+ void GapBuffer::PrintBuffer()
421
+ {
422
+ /*
423
+ char ch;
424
+
425
+ cout << "Printing the buffer: " << endl;
426
+ SetPoint(0);
427
+ while (point < bufend) {
428
+ cout << GetCharMovePoint();
429
+ }
430
+
431
+ cout << "Printing the buffer in reverse: " << endl;
432
+
433
+ while (point >= buffer) {
434
+ cout << GetPrevCharMovePoint();
435
+ }
436
+ */
437
+
438
+ char *temp = buffer;
439
+
440
+
441
+ while (temp < bufend) {
442
+
443
+ if ( (temp >= gapstart) && (temp < gapend) ) {
444
+ cout << "_";
445
+ temp++;
446
+ } else {
447
+ cout << *(temp++);
448
+ }
449
+
450
+ }
451
+ cout << endl;
452
+ }
453
+
454
+ int GapBuffer::SaveBufferToFile(FILE *file, unsigned int bytes)
455
+ {
456
+
457
+ if (!bytes) {
458
+ return 1;
459
+ }
460
+
461
+ if (point == gapstart) {
462
+ point = gapend;
463
+ }
464
+
465
+ if ( (gapstart > point) && (gapstart < (point + bytes)) && (gapstart != gapend) ) {
466
+ if ( gapstart - point != fwrite(point, 1, gapstart-point, file) ) {
467
+ return 0;
468
+ }
469
+
470
+ if ( (bytes - (gapstart - point)) != fwrite(gapend, 1, bytes-(gapstart - point), file) ) {
471
+ return 1;
472
+ }
473
+
474
+ return 1;
475
+ } else {
476
+ return bytes == fwrite(point, 1, bytes, file);
477
+ }
478
+
479
+
480
+ }
@@ -0,0 +1,159 @@
1
+ /*
2
+ * gap_buffer.h
3
+ *
4
+ * Author: Hsin Tsao (stsao@lazyhacker.com)
5
+ * Version: 1.0 (June 12, 2003)
6
+ *
7
+ * A text buffer class using the buffer-gap technique for managing
8
+ * the text stored in the buffer.
9
+ *
10
+ * Portions of this work derived from Joseph Allen's usenet
11
+ * postings on comp.editor that was released to the public
12
+ * domain.
13
+ *
14
+ *
15
+ * There are no restrictions on the use of this code other
16
+ * than to include my name in any derived work. There are
17
+ * no warranty for this obviously, but you are welcomed
18
+ * to modify, correct, or adapt the code to your needs. The
19
+ * author appreciates if you are willing to submit corrections
20
+ * or suggestions for improvement.
21
+ *
22
+ *
23
+ * http://www.lazyhacker.com
24
+ */
25
+
26
+ #include <stdio.h>
27
+
28
+ class GapBuffer {
29
+
30
+ char *point; // location pointer into buffer
31
+ char *buffer; // start of text buffer
32
+ char *bufend; // first location outside buffer
33
+ char *gapstart; // start of gap
34
+ char *gapend; // first location after end of gap
35
+
36
+ unsigned int GAP_SIZE; // expand GAP by this value
37
+
38
+ int InitBuffer(unsigned int size);
39
+
40
+ /*
41
+ * Copy the characters from one location to another. We have
42
+ * to write our own instead of using memcopy because we are
43
+ * working within a single linear buffer and thus can have
44
+ * overlap between the source and destination.
45
+ */
46
+ int CopyBytes(char *destination, char *source, unsigned int length);
47
+
48
+ /*
49
+ * Expand the size of the buffer.
50
+ */
51
+ void ExpandBuffer(unsigned int size);
52
+
53
+ /*
54
+ * Expand the size of the gap.
55
+ */
56
+ void ExpandGap(unsigned int size);
57
+
58
+ public:
59
+
60
+ static const int DEFAULT_GAP_SIZE=20;
61
+
62
+ /* Constructor with default gap size. */
63
+ GapBuffer(int gsize=DEFAULT_GAP_SIZE);
64
+
65
+ /* Constructor with instantiating with an existing file. */
66
+ GapBuffer(FILE *file, int gsize=DEFAULT_GAP_SIZE);
67
+
68
+ /* Copy constructor to deal with our pointer members. */
69
+ GapBuffer(const GapBuffer& tb);
70
+
71
+ ~GapBuffer();
72
+
73
+ /*
74
+ * Returns the size of the buffer minus the gap.
75
+ */
76
+ int BufferSize();
77
+
78
+ /*
79
+ * Move the gap to the current position of the point.
80
+ */
81
+ void MoveGapToPoint();
82
+
83
+ /*
84
+ * Set point to offset from start of buffer.
85
+ */
86
+ void SetPoint(unsigned int offset);
87
+
88
+ /*
89
+ * Returns the current size of the gap.
90
+ */
91
+ int SizeOfGap();
92
+
93
+ /*
94
+ * Returns offset from point to start of buffer.
95
+ */
96
+ unsigned int PointOffset();
97
+
98
+ /*
99
+ * Return character that point is pointing to.
100
+ * If point is inside the gap, then return the
101
+ * the first character outside the gap.
102
+ */
103
+ char GetChar();
104
+
105
+ /*
106
+ * Return the previous character and
107
+ * move point back one position.
108
+ */
109
+ char PreviousChar();
110
+
111
+ /*
112
+ * Replace the character of point. Does
113
+ * not move the gap.
114
+ */
115
+ void ReplaceChar(char ch);
116
+
117
+ /*
118
+ * Get the next character and increment point.
119
+ */
120
+ char NextChar();
121
+
122
+ /*
123
+ * Inserts a character at point location
124
+ * and advance the point.
125
+ */
126
+ void PutChar(char ch);
127
+
128
+ /*
129
+ * Insert character at point position, but
130
+ * does NOT advance the point.
131
+ */
132
+ void InsertChar(char ch);
133
+
134
+ /*
135
+ * Delete "size" number of characters.
136
+ */
137
+ void DeleteChars(unsigned int size);
138
+
139
+ /*
140
+ * Inserts a length size string
141
+ * at point.
142
+ */
143
+ void InsertString(char *string, unsigned int length);
144
+
145
+ /*
146
+ * Prints out the current buffer from start
147
+ * to end.
148
+ */
149
+ void PrintBuffer();
150
+
151
+ /*
152
+ * Saves to file the number of bytes starting from
153
+ * the point.
154
+ */
155
+ int SaveBufferToFile(FILE *file, unsigned int bytes);
156
+
157
+ char *GetBuffer();
158
+
159
+ };
@@ -0,0 +1,191 @@
1
+ #include "gap_buffer.h"
2
+ #include <ruby.h>
3
+
4
+ #ifndef RSTRING_PTR
5
+ #define RSTRING_PTR(obj) RSTRING(obj)->ptr
6
+ #endif
7
+
8
+ #ifndef RSTRING_LEN
9
+ #define RSTRING_LEN(obj) RSTRING(obj)->len
10
+ #endif
11
+
12
+ #define GetGapBuffer(obj) \
13
+ GapBuffer *gb = NULL; \
14
+ Data_Get_Struct((obj), GapBuffer, gb); \
15
+ if (!gb) \
16
+ rb_raise(rb_eException, "No gap buffer!");
17
+
18
+ #define GapBufferChar(chr) \
19
+ char c = (chr); \
20
+ if (c == 0) return rb_str_tmp_new(0); \
21
+ return rb_str_new(&c, 1);
22
+
23
+ VALUE rb_cGapBuffer;
24
+
25
+ static void rb_gap_buffer_free(void *gb)
26
+ {
27
+ if(gb) delete((GapBuffer*) gb);
28
+ }
29
+
30
+ static VALUE rb_gap_buffer_new(int argc, VALUE *argv, VALUE obj)
31
+ {
32
+ VALUE size;
33
+ rb_scan_args(argc, argv, "01", &size);
34
+ if (NIL_P(size)) size = rb_const_get(rb_cGapBuffer, rb_intern("GAP_SIZE"));
35
+ GapBuffer *gb = NULL;
36
+ gb = new GapBuffer(NUM2INT(size));
37
+ if (!gb)
38
+ rb_raise(rb_eException, "No gap buffer!");
39
+ return Data_Wrap_Struct(rb_cGapBuffer, 0, rb_gap_buffer_free, (void*)gb);
40
+ }
41
+
42
+ static VALUE rb_gap_buffer_size(VALUE obj)
43
+ {
44
+ GetGapBuffer(obj);
45
+ return INT2FIX(gb->BufferSize());
46
+ }
47
+
48
+ static VALUE rb_gap_buffer_gap_size(VALUE obj)
49
+ {
50
+ GetGapBuffer(obj);
51
+ return INT2FIX(gb->SizeOfGap());
52
+ }
53
+
54
+ static VALUE rb_gap_buffer_print(VALUE obj)
55
+ {
56
+ GetGapBuffer(obj);
57
+ gb->PrintBuffer();
58
+ return Qnil;
59
+ }
60
+
61
+ static VALUE rb_gap_buffer_append(VALUE obj, VALUE str)
62
+ {
63
+ GetGapBuffer(obj);
64
+ Check_Type(str, T_STRING);
65
+ gb->InsertString(RSTRING_PTR(str), (unsigned int)RSTRING_LEN(str));
66
+ return obj;
67
+ }
68
+
69
+ static VALUE rb_gap_buffer_offset(VALUE obj)
70
+ {
71
+ GetGapBuffer(obj);
72
+ return INT2FIX(gb->PointOffset());
73
+ }
74
+
75
+ static VALUE rb_gap_buffer_offset_equals(VALUE obj, VALUE offset)
76
+ {
77
+ GetGapBuffer(obj);
78
+ gb->SetPoint(FIX2INT(offset));
79
+ return offset;
80
+ }
81
+
82
+ static VALUE rb_gap_buffer_move(VALUE obj)
83
+ {
84
+ GetGapBuffer(obj);
85
+ gb->MoveGapToPoint();
86
+ return rb_gap_buffer_offset(obj);
87
+ }
88
+
89
+ static VALUE rb_gap_buffer_get(VALUE obj)
90
+ {
91
+ GetGapBuffer(obj);
92
+ GapBufferChar(gb->GetChar());
93
+ }
94
+
95
+ static VALUE rb_gap_buffer_previous(VALUE obj)
96
+ {
97
+ GetGapBuffer(obj);
98
+ GapBufferChar(gb->PreviousChar());
99
+ }
100
+
101
+ static VALUE rb_gap_buffer_next(VALUE obj)
102
+ {
103
+ GetGapBuffer(obj);
104
+ GapBufferChar(gb->NextChar());
105
+ }
106
+
107
+ static VALUE rb_gap_buffer_replace(VALUE obj, VALUE str)
108
+ {
109
+ GetGapBuffer(obj);
110
+ Check_Type(str, T_STRING);
111
+ char c = *RSTRING_PTR(str);
112
+ gb->ReplaceChar(c);
113
+ return Qnil;
114
+ }
115
+
116
+ static VALUE rb_gap_buffer_put(VALUE obj, VALUE str)
117
+ {
118
+ GetGapBuffer(obj);
119
+ Check_Type(str, T_STRING);
120
+ char c = *RSTRING_PTR(str);
121
+ gb->PutChar(c);
122
+ return Qnil;
123
+ }
124
+
125
+ static VALUE rb_gap_buffer_insert(VALUE obj, VALUE str)
126
+ {
127
+ GetGapBuffer(obj);
128
+ Check_Type(str, T_STRING);
129
+ char c = *RSTRING_PTR(str);
130
+ gb->InsertChar(c);
131
+ return Qnil;
132
+ }
133
+
134
+ static VALUE rb_gap_buffer_delete(VALUE obj, VALUE size)
135
+ {
136
+ GetGapBuffer(obj);
137
+ gb->DeleteChars(FIX2INT(size));
138
+ return Qnil;
139
+ }
140
+
141
+ static VALUE rb_gap_buffer_to_s(VALUE obj, VALUE size)
142
+ {
143
+ GetGapBuffer(obj);
144
+ return(rb_str_new2(gb->GetBuffer()));
145
+ }
146
+
147
+ static VALUE rb_gap_buffer_insert_at(VALUE obj, VALUE offset, VALUE str)
148
+ {
149
+ GetGapBuffer(obj);
150
+ Check_Type(str, T_STRING);
151
+ char c = *RSTRING_PTR(str);
152
+ gb->SetPoint(FIX2INT(offset));
153
+ gb->InsertChar(c);
154
+ return Qnil;
155
+ }
156
+
157
+ static VALUE rb_gap_buffer_delete_at(VALUE obj, VALUE offset)
158
+ {
159
+ GetGapBuffer(obj);
160
+ gb->SetPoint(FIX2INT(offset));
161
+ gb->DeleteChars(1);
162
+ return Qnil;
163
+ }
164
+
165
+ extern "C" void Init_gap_buffer()
166
+ {
167
+ rb_cGapBuffer = rb_define_class("GapBuffer", rb_cObject);
168
+ rb_define_const(rb_cGapBuffer, "GAP_SIZE", INT2NUM(20));
169
+
170
+ rb_define_module_function(rb_cGapBuffer, "new", (VALUE(*)(...))rb_gap_buffer_new, -1);
171
+ rb_define_method(rb_cGapBuffer, "size", (VALUE(*)(...))rb_gap_buffer_size, 0);
172
+ rb_define_method(rb_cGapBuffer, "gap_size", (VALUE(*)(...))rb_gap_buffer_gap_size, 0);
173
+ rb_define_method(rb_cGapBuffer, "print", (VALUE(*)(...))rb_gap_buffer_print, 0);
174
+ rb_define_method(rb_cGapBuffer, "<<", (VALUE(*)(...))rb_gap_buffer_append, 1);
175
+ rb_define_method(rb_cGapBuffer, "offset", (VALUE(*)(...))rb_gap_buffer_offset, 0);
176
+ rb_define_method(rb_cGapBuffer, "position", (VALUE(*)(...))rb_gap_buffer_offset, 0);
177
+ rb_define_method(rb_cGapBuffer, "offset=", (VALUE(*)(...))rb_gap_buffer_offset_equals, 1);
178
+ rb_define_method(rb_cGapBuffer, "position=", (VALUE(*)(...))rb_gap_buffer_offset_equals, 1);
179
+ rb_define_method(rb_cGapBuffer, "move", (VALUE(*)(...))rb_gap_buffer_move, 0);
180
+ rb_define_method(rb_cGapBuffer, "get", (VALUE(*)(...))rb_gap_buffer_get, 0);
181
+ rb_define_method(rb_cGapBuffer, "previous", (VALUE(*)(...))rb_gap_buffer_previous, 0);
182
+ rb_define_method(rb_cGapBuffer, "next", (VALUE(*)(...))rb_gap_buffer_next, 0);
183
+ rb_define_method(rb_cGapBuffer, "replace", (VALUE(*)(...))rb_gap_buffer_replace, 1);
184
+ rb_define_method(rb_cGapBuffer, "put", (VALUE(*)(...))rb_gap_buffer_put, 1);
185
+ rb_define_method(rb_cGapBuffer, "insert", (VALUE(*)(...))rb_gap_buffer_insert, 1);
186
+ rb_define_method(rb_cGapBuffer, "delete", (VALUE(*)(...))rb_gap_buffer_delete, 1);
187
+ rb_define_method(rb_cGapBuffer, "to_s", (VALUE(*)(...))rb_gap_buffer_to_s, 0);
188
+ rb_define_method(rb_cGapBuffer, "to_str", (VALUE(*)(...))rb_gap_buffer_to_s, 0);
189
+ rb_define_method(rb_cGapBuffer, "insert_at", (VALUE(*)(...))rb_gap_buffer_insert_at, 2);
190
+ rb_define_method(rb_cGapBuffer, "delete_at", (VALUE(*)(...))rb_gap_buffer_delete_at, 1);
191
+ }
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'gap_buffer'
3
+ s.version = '0.1'
4
+ s.date = '2010-10-25'
5
+ s.authors = ['Lourens Naudé', 'Hsin Tsao']
6
+ s.email = ['lourens@methodmissing.com', 'stsao@lazyhacker.com']
7
+ s.description = 'WIP implementation of a Gap Buffer for Ruby MRI.'
8
+ s.homepage = 'http://github.com/methodmissing/gap_buffer'
9
+ s.summary = 'WIP implementation of a Gap Buffer for Ruby MRI.'
10
+ s.extensions = 'ext/gap_buffer/extconf.rb'
11
+ s.files = Dir.glob("{ext,test,bench}/**/*") + %w[README Rakefile gap_buffer.gemspec]
12
+ s.has_rdoc = true
13
+ s.extra_rdoc_files = Dir['ext/gap_buffer/*.c']
14
+ end
@@ -0,0 +1,3 @@
1
+ $:.unshift "."
2
+ require 'ext/gap_buffer/gap_buffer'
3
+ require 'test/unit'
@@ -0,0 +1,140 @@
1
+ class TestGapBuffer < Test::Unit::TestCase
2
+ def test_initialize
3
+ assert_equal 20, GapBuffer::GAP_SIZE
4
+ assert_instance_of GapBuffer, GapBuffer.new(10)
5
+ assert_equal 10, GapBuffer.new(10).gap_size
6
+ assert_equal 20, GapBuffer.new.gap_size
7
+ end
8
+
9
+ def test_size
10
+ gb = GapBuffer.new
11
+ assert_equal 0, gb.size
12
+ end
13
+
14
+ def test_gap_size
15
+ gb = GapBuffer.new
16
+ assert_equal 20, gb.gap_size
17
+ end
18
+
19
+ def test_print
20
+ gb = GapBuffer.new
21
+ gb.print
22
+ end
23
+
24
+ def test_append
25
+ gb = GapBuffer.new
26
+ gb << 'test'
27
+ assert_equal 'test', gb.to_s
28
+ assert_equal 5, gb.offset
29
+ end
30
+
31
+ def test_offset
32
+ gb = GapBuffer.new
33
+ assert_equal 0, gb.offset
34
+ gb << 'test'
35
+ assert_equal 5, gb.offset
36
+ gb << 'buffer'
37
+ assert_equal 12, gb.offset
38
+ end
39
+
40
+ def test_offset_equals
41
+ gb = GapBuffer.new
42
+ assert_equal 0, gb.offset
43
+ gb.offset = 5
44
+ assert_equal 5, gb.offset
45
+ end
46
+
47
+ def test_move
48
+ gb = GapBuffer.new
49
+ assert_equal 0, gb.offset
50
+ gb.offset = 5
51
+ gb.move
52
+ end
53
+
54
+ def test_get
55
+ gb = GapBuffer.new
56
+ gb << 'test'
57
+ gb.offset = 2
58
+ assert_equal 's', gb.get
59
+ assert_equal 2, gb.offset
60
+ end
61
+
62
+ def test_previous
63
+ gb = GapBuffer.new
64
+ gb << 'test'
65
+ assert_equal '', gb.previous
66
+ gb.offset = 2
67
+ assert_equal 'e', gb.previous
68
+ assert_equal 1, gb.offset
69
+ end
70
+
71
+ def test_next
72
+ gb = GapBuffer.new
73
+ gb << 'test'
74
+ assert_equal '', gb.previous
75
+ gb.offset = 2
76
+ assert_equal 't', gb.next
77
+ assert_equal 3, gb.offset
78
+ end
79
+
80
+ def test_delete
81
+ gb = GapBuffer.new
82
+ gb << 'test'
83
+ assert_equal '', gb.previous
84
+ gb.offset = 2
85
+ gb.delete(2)
86
+ assert_equal 'test', gb.to_s
87
+ end
88
+
89
+ def test_to_s
90
+ gb = GapBuffer.new
91
+ gb << 'test'
92
+ assert_equal 'test', gb.to_s
93
+ end
94
+
95
+ def test_replace
96
+ gb = GapBuffer.new
97
+ gb << 'test'
98
+ gb.offset = 2
99
+ gb.replace('x')
100
+ assert_equal 'text', gb.to_s
101
+ end
102
+
103
+ def test_insert
104
+ gb = GapBuffer.new
105
+ gb << 'test'
106
+ gb.offset = 2
107
+ gb.insert('x')
108
+ assert_equal 'text', gb.to_s
109
+ gb.insert('zz')
110
+ assert_equal 'tezt', gb.to_s
111
+ end
112
+
113
+ def test_insert_at
114
+ gb = GapBuffer.new
115
+ gb << 'test'
116
+ gb.insert_at(2, 'x')
117
+ assert_equal 'text', gb.to_s
118
+ gb.insert_at(3, 'z')
119
+ assert_equal 'texz', gb.to_s
120
+ end
121
+
122
+ def test_delete_at
123
+ gb = GapBuffer.new
124
+ gb << 'test'
125
+ gb.insert_at(2, 'x')
126
+ assert_equal 'text', gb.to_s
127
+ gb.delete_at(2)
128
+ assert_equal 'text', gb.to_s
129
+ end
130
+
131
+ def test_put
132
+ gb = GapBuffer.new
133
+ gb << 'test'
134
+ gb.offset = 2
135
+ gb.put('x')
136
+ assert_equal 'text', gb.to_s
137
+ gb.put('zzzzz')
138
+ assert_equal 'texz', gb.to_s
139
+ end
140
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gap_buffer
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ version: "0.1"
9
+ platform: ruby
10
+ authors:
11
+ - "Lourens Naud\xC3\xA9"
12
+ - Hsin Tsao
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-10-25 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: WIP implementation of a Gap Buffer for Ruby MRI.
22
+ email:
23
+ - lourens@methodmissing.com
24
+ - stsao@lazyhacker.com
25
+ executables: []
26
+
27
+ extensions:
28
+ - ext/gap_buffer/extconf.rb
29
+ extra_rdoc_files: []
30
+
31
+ files:
32
+ - ext/gap_buffer/extconf.rb
33
+ - ext/gap_buffer/gap_buffer.cpp
34
+ - ext/gap_buffer/gap_buffer.h
35
+ - ext/gap_buffer/rubymain.cpp
36
+ - test/test.rb
37
+ - test/test_gap_buffer.rb
38
+ - bench/gap_buffer.rb
39
+ - README
40
+ - Rakefile
41
+ - gap_buffer.gemspec
42
+ has_rdoc: true
43
+ homepage: http://github.com/methodmissing/gap_buffer
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options: []
48
+
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.3.7
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: WIP implementation of a Gap Buffer for Ruby MRI.
74
+ test_files: []
75
+