Class: AutoC::String

Inherits:
Type show all
Includes:
Redirecting
Defined in:
lib/autoc/string.rb

Overview

String is wrapper around the standard null-terminated C string which has the capabilities of both a plain string and a string builder optimized for appending and incremental building.

It is ought to be on par with the raw C strings performance-wise (or even exceed it since the string size is tracked).

Unlike the plain C string, this String type has value type semantics but it can be turned into the reference type with Reference.

The String’s default character type, CharType, is char although this can be changed.

String generally obeys the Vector interface with respect to working with its characters and resembles the List interface when using its building capabilities.

Generated C interface

String management

void typeCopy(Type * dst, Type * src)

Create a new string dst filled with a copy the contents of src.

NOTE: Previous contents of dst is overwritten.

void typeCopyRange(Type * dst, Type * src, size_t first, size_t last)

Create a new string dst filled with a part the contents of src lying in the range [first, last], that is including the character at position first and including the character at position last.

NOTE: Previous contents of dst is overwritten.

WARNING: first must not exceed last (that is, first ⇐ last) and both indices must be valid otherwise behavior is undefined. See typeWithin().

void typeCtor(Type * self, const CharType * chars)

Create a new string self with a copy of the null-terminated C string chars.

NULL value of chars is permitted; this case corresponds to an empty string "".

NOTE: Previous contents of self is overwritten.

void typeDtor(Type * self)

Destroy string self.

int typeEqual(Type * lt, Type * rt)

Return non-zero value if strings lt and rt are considered equal by contents and zero value otherwise.

size_t typeIdentify(Type * self)

Return hash code for string self.

Basic operations

const CharType * typeChars(Type * self)

Return a read-only view of the string in a form of the standard C null-terminated string.

NOTE: the returned value need not to be freed.

WARNING: the returned value should be considered volatile and thus may be altered or invalidated by a subsequent call to any String method!

int typeEmpty(Type * self)

Return non-zero value if string self has zero length and zero value otherwise.

CharType typeGet(Type * self, size_t index)

Return a copy of the character stored in self at position index.

WARNING: index must be a valid index otherwise the behavior is undefined. See typeWithin().

void typeSet(Type * self, size_t index, CharType value)

Store a copy of the character value in string self at position index.

WARNING: index must be a valid index otherwise the behavior is undefined. See typeWithin().

size_t typeSize(Type * self)

Return number of characters stored in string self.

Note that this does not include the null terminator.

int typeWithin(Type * self, size_t index)

Return non-zero value if index is a valid character index and zero value otherwise. A valid index lies between 0 and typeSize()-1 inclusively.

String buffer operations

Functions which provide the string buffer functionality. This allows the incremental building of strings without excessive storage copying/reallocation.

void typePushChars(Type * self, const CharType* chars)

Append a copy of the null-terminated C string chars to string self.

int typePushChar(Type * self, CharType value)

Append a copy of the character value to string self.

Return non-zero value on success and zero value on conversion error.

NOTE: this convenience function applies generic formatting rules and is currently implemented as a macro; for more precise control over the formatting process use the typePushFormat() function.

int typePushInt(Type * self, int value)

Append string representation of the integer value to string self.

Return non-zero value on success and zero value on conversion error.

NOTE: this convenience function applies generic formatting rules and is currently implemented as a macro; for more precise control over the formatting process use the typePushFormat() function.

int typePushFloat(Type * self, double value)

Append string representation of the floating-point value to string self.

Return non-zero value on success and zero value on conversion error.

NOTE: this convenience function applies generic formatting rules and is currently implemented as a macro; for more precise control over the formatting process use the typePushFormat() function.

int typePushPtr(Type * self, void* value)

Append string representation of the pointer value to string self.

Return non-zero value on success and zero value on conversion error.

NOTE: this convenience function applies generic formatting rules and is currently implemented as a macro; for more precise control over the formatting process use the typePushFormat() function.

void typePushString(Type * self, Type * from)

Append a copy of the contents of string from to string self.

int typePushFormat(Type * self, const char* format, …​);

Append the ?sprintf()- formatted string to string self.

Return non-zero value on successful formatting and zero value if the call to ?sprintf() failed. The latter usually happens due to the encoding error.

This function tries to use the vsnprintf() standard C function if possible and falls back to unsafe vsprintf() function which is ought to be present in every ANSI-compliant standard C library. The former function is used on the platforms which are known to have it; the Autotools-compliant HAVE_VSNPRINTF macro is also taken into consideration. Note that the choice is obviously made at compile-time.

If using the vsnprintf() and the allocated buffer is not large enough this function continuously expands the buffer to eventually accommodate the resulting string. On the contrary, when the unsafe vsprintf() is used, the buffer overrun causes this function to abort() in order to possible data corruption not to slip away uncaught.

Current implementation operates on the heap-allocated buffer whose initial size is determined by the AUTOC_BUFFER_SIZE macro. If not explicitly set it defaults to 4096 bytes.

Iteration over string’s characters

void itCtor(IteratorType * it, Type * self)

Create a new forward iterator it on string self.

NOTE: Previous contents of it is overwritten.

void itCtorEx(IteratorType * it, Type * self, int forward)

Create a new iterator it on string self. Non-zero value of forward specifies a forward iterator, zero value specifies a backward iterator.

NOTE: Previous contents of it is overwritten.

int itMove(IteratorType * it)

Advance iterator position of it and return non-zero value if new position is valid and zero value otherwise.

CharType itGet(IteratorType * it)

Return a copy of the character pointed to by the iterator it.

WARNING: current position must be valid otherwise the behavior is undefined. See itMove().

Instance Attribute Summary collapse

Attributes inherited from Type

#type, #type_ref

Instance Method Summary collapse

Methods inherited from Type

#==, #abort, #assert, #calloc, coerce, #comparable?, #copyable?, #destructible?, #entities, #extern, #free, #hash, #hashable?, #initializable?, #inline, #malloc, #method_missing, #orderable?, #prefix, #private?, #public?, #sortable?, #static, #static?, #write_decls, #write_defs, #write_intf

Methods inherited from Code

#attach, #entities, #priority, #source_size, #write_decls, #write_defs, #write_intf

Constructor Details

#initialize(type_name = :String, visibility = :public) ⇒ String

Returns a new instance of String



206
207
208
209
210
211
212
# File 'lib/autoc/string.rb', line 206

def initialize(type_name = :String, visibility = :public)
  super
  @it_ref = "#{it}*"
  @list = List.new(list, {:type => char_type_ref, :dtor => free}, :private) # List takes ownership over the strings put into it hence the custom element destructor
  initialize_redirectors
  @ctor = define_redirector(:ctor, Function::Signature.new([type_ref^:self, "const #{char_type_ref}"^:chars]))
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class AutoC::Type

Instance Attribute Details

#it_refObject (readonly)

Returns the value of attribute it_ref



204
205
206
# File 'lib/autoc/string.rb', line 204

def it_ref
  @it_ref
end

Instance Method Details

#char_typeObject



200
# File 'lib/autoc/string.rb', line 200

def char_type; :char end

#char_type_refObject



202
# File 'lib/autoc/string.rb', line 202

def char_type_ref; "#{char_type}*" end

#constructible?Boolean

No default constructor provided

Returns:

  • (Boolean)


215
# File 'lib/autoc/string.rb', line 215

def constructible?; false end

#write_impls(stream, define) ⇒ Object



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/autoc/string.rb', line 322

def write_impls(stream, define)
  super
  [@list].each {|obj|
    obj.write_intf_decls(stream, static, inline)
    obj.write_impls(stream, static)
  }
  stream << %$
    #include <stdio.h>
    #include <stdarg.h>
    #undef AUTOC_VSNPRINTF
    #if defined(_MSC_VER)
      #define AUTOC_VSNPRINTF _vsnprintf
    #elif defined(__DMC__) || defined (__LCC__)
      #define AUTOC_VSNPRINTF vsnprintf
    #elif defined(HAVE_VSNPRINTF) || __STDC_VERSION__ >= 199901L /* Be Autotools-friendly, C99 must have snprintf()  */
      #define AUTOC_VSNPRINTF vsnprintf
    #endif
    #ifndef AUTOC_VSNPRINTF
      /* #warning Using unsafe vsprintf() function */
    #endif
    #{define} void #{_join}(#{type_ref} self) {
      #{@list.it} it;
      #{char_type_ref} string;
      size_t* size; /* size sizes cache to avoid excessive calls to strlen() */
      size_t i, start = 0, total = 0;
      #{assert}(self);
      #{assert}(self->is_list);
      if(!#{@list.empty}(&self->data.list)) {
        size = (size_t*)malloc(#{@list.size}(&self->data.list)*sizeof(size_t)); #{assert}(size);
        #{@list.itCtor}(&it, &self->data.list);
        for(i = 0; #{@list.itMove}(&it); ++i) {
          total += (size[i] = strlen(#{@list.itGet}(&it)));
        }
        string = (#{char_type_ref})#{malloc}((total + 1)*sizeof(#{char_type})); #{assert}(string);
        #{@list.itCtor}(&it, &self->data.list);
        /* List is a LIFO structure therefore merging should be performed from right to left */
        i = 0; start = total;
        while(#{@list.itMove}(&it)) {
          start -= size[i];
          memcpy(&string[start], #{@list.itGet}(&it), size[i]*sizeof(#{char_type}));
          ++i;
        }
        string[total] = '\\0';
        #{free}(size);
      } else {
        string = (#{char_type_ref})#{calloc}(1, sizeof(#{char_type})); #{assert}(string);
      }
      #{@list.dtor}(&self->data.list);
      self->size = total;
      self->data.string = string;
      self->is_list = 0;
    }
    #{define} void #{_split}(#{type_ref} self) {
      #{@list.type} list;
      #{assert}(self);
      #{assert}(!self->is_list);
      #{@list.ctor}(&list);
      #{@list.push}(&list, self->data.string);
      /* self->size = strlen(self->data.string); not needed since the size shouldn't have changed */
      #{assert}(self->size == strlen(self->data.string));
      self->data.list = list;
      self->is_list = 1;
    }
    #{define} #{ctor.definition} {
      #{assert}(self);
      if(chars) {
        size_t nbytes;
        self->size = strlen(chars);
        nbytes = (self->size + 1)*sizeof(#{char_type});
        self->data.string = (#{char_type_ref})#{malloc}(nbytes); #{assert}(self->data.string);
        memcpy(self->data.string, chars, nbytes);
        self->is_list = 0;
      } else {
        /* NULL argument is permitted and corresponds to empty string */
        self->size = 0;
        #{@list.ctor}(&self->data.list);
        self->is_list = 1;
      }
    }
    #{define} #{dtor.definition} {
      #{assert}(self);
      if(self->is_list) #{@list.dtor}(&self->data.list); else #{free}(self->data.string);
    }
    #{define} #{copy.definition} {
      #{assert}(src);
      #{assert}(dst);
      #{assert}(src != dst);
      #{ctor}(dst, #{chars}(src));
    }
    #{define} void #{copyRange}(#{type_ref} dst, #{type_ref} src, size_t first, size_t last) {
      size_t size;
      #{char_type_ref} string;
      #{assert}(src);
      #{assert}(src != dst);
      #{assert}(first <= last);
      #{assert}(#{within}(src, first));
      #{assert}(#{within}(src, last));
      size = last - first + 1;
      string = (#{char_type_ref})#{malloc}((size + 1)*sizeof(#{char_type})); #{assert}(string);
      memcpy(string, &#{chars}(src)[first], size*sizeof(#{char_type}));
      string[size] = '\\0';
      #{ctor}(dst, string);
      #{free}(string);
    }
    #{define} #{equal.definition} {
      #{assert}(lt);
      #{assert}(rt);
      return strcmp(#{chars}(lt), #{chars}(rt)) == 0;
    }
    #{define} #{identify.definition} {
      size_t index, result = 0;
      #{assert}(self);
      #{join}(self);
      for(index = 0; index < #{size}(self); ++index) {
        result ^= self->data.string[index];
        result = AUTOC_RCYCLE(result);
      }
      return result;
    }
    #{define} int #{pushFormat}(#{type_ref} self, const char* format, ...) {
      va_list args;
      char* buffer;
      int i, c, buffer_size = #{_bufferSize};
      #{assert}(self);
      #{assert}(format);
      do {
        buffer = (char*)#{malloc}(buffer_size*sizeof(char)); #{assert}(buffer);
        va_start(args, format);
        #ifdef AUTOC_VSNPRINTF
          i = AUTOC_VSNPRINTF(buffer, buffer_size, format, args);
        #else
          i = vsprintf(buffer, format, args);
          if(i >= buffer_size) #{abort}();
          /* Since vsprintf() can not truncate its output this means the buffer overflow and
             there is no guarantee that some useful data is not corrupted so its better
             to crash right here than to let the corruption slip away uncaught */
        #endif
        c = (i > 0 && !(i < buffer_size));
        if(i > 0 && !c) #{pushChars}(self, buffer);
        va_end(args);
        #{free}(buffer);
        buffer_size *= 2;
      } while(c);
      return i >= 0;
    }
    #{define} void #{pushChars}(#{type_ref} self, const #{char_type_ref} chars) {
      #{char_type_ref} string;
      size_t size, nbytes;
      #{assert}(self);
      #{assert}(chars);
      #{split}(self);
      size = strlen(chars);
      nbytes = (size + 1)*sizeof(#{char_type});
      string = (#{char_type_ref})#{malloc}(nbytes); #{assert}(string);
      memcpy(string, chars, nbytes);
      #{@list.push}(&self->data.list, string);
      self->size += size;
    }
    #{define} void #{pushString}(#{type_ref} self, #{type_ref} from) {
      #{assert}(self);
      #{assert}(from);
      #{pushChars}(self, #{chars}(from));
    }
    #undef AUTOC_SNPRINTF
  $
end

#write_intf_decls(stream, declare, define) ⇒ Object



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/autoc/string.rb', line 242

def write_intf_decls(stream, declare, define)
  super
  write_redirectors(stream, declare, define)
  stream << %$
    #include <string.h>
    #ifdef AUTOC_BUFFER_SIZE
      #define #{_bufferSize} AUTOC_BUFFER_SIZE
    #else
      #define #{_bufferSize} 4096 /* Stay in sync with the documentation! */
    #endif
    #define #{join}(self) if(self->is_list) #{_join}(self);
    #define #{split}(self) if(!self->is_list) #{_split}(self);
    #{declare} void #{_join}(#{type_ref});
    #{declare} void #{_split}(#{type_ref});
    #{declare} #{ctor.declaration};
    #{declare} #{dtor.declaration};
    #{declare} #{copy.declaration};
    #{declare} void #{copyRange}(#{type_ref}, #{type_ref}, size_t, size_t);
    #{declare} #{equal.declaration};
    #{declare} #{identify.declaration};
    #define #{empty}(self) (#{size}(self) == 0)
    #{define} size_t #{size}(#{type_ref} self) {
      #{assert}(self);
      /* #{join}(self); assuming the changes to the contents are reflected in the size */
      #ifndef NDEBUG
        /* Type invariants which must hold true */
        if(!self->is_list) #{assert}(self->size == strlen(self->data.string));
        /* TODO self->is_list case */
      #endif
      return self->size;
    }
    #{define} int #{within}(#{type_ref} self, size_t index) {
      #{assert}(self);
      /* Omitting excessive call to #{join}() */
      return index < #{size}(self);
    }
    #{define} #{char_type} #{get}(#{type_ref} self, size_t index) {
      #{assert}(self);
      #{assert}(#{within}(self, index));
      #{join}(self);
      return self->data.string[index];
    }
    #{define} void #{set}(#{type_ref} self, size_t index, #{char_type} value) {
      #{assert}(self);
      #{assert}(#{within}(self, index));
      #{join}(self);
      self->data.string[index] = value;
    }
    #{define} const #{char_type_ref} #{chars}(#{type_ref} self) {
      #{assert}(self);
      #{join}(self);
      return self->data.string;
    }
    #{declare} int #{pushFormat}(#{type_ref}, const char*, ...);
    #{declare} void #{pushChars}(#{type_ref}, const #{char_type_ref});
    #{declare} void #{pushString}(#{type_ref}, #{type_ref});
    #define #{pushChar}(self, c) #{pushFormat}(self, "%c", (char)(c))
    #define #{pushInt}(self, i) #{pushFormat}(self, "%d", (int)(i))
    #define #{pushFloat}(self, f) #{pushFormat}(self, "%e", (double)(f))
    #define #{pushPtr}(self, p) #{pushFormat}(self, "%p", (void*)(p))
    #define #{itCtor}(self, type) #{itCtorEx}(self, type, 1)
    #{define} void #{itCtorEx}(#{it_ref} self, #{type_ref} string, int forward) {
      #{assert}(self);
      #{assert}(string);
      self->string = string;
      self->forward = forward;
      self->index = forward ? -1 : #{size}(string);
    }
    #{define} int #{itMove}(#{it_ref} self) {
      #{assert}(self);
      if(self->forward) ++self->index; else --self->index;
      return #{within}(self->string, self->index);
    }
    #{define} #{char_type} #{itGet}(#{it_ref} self) {
      #{assert}(self);
      return #{get}(self->string, self->index);
    }
  $
end

#write_intf_types(stream) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/autoc/string.rb', line 217

def write_intf_types(stream)
  stream << %$
    /***
    **** #{type}<#{char_type}>
    ***/
  $ if public?
  [@list].each {|obj| obj.write_intf_types(stream)} # TODO : this should be handled by the entity dependencies system 
  stream << %$
    typedef struct #{type} #{type};
    typedef struct #{it} #{it};
    struct #{type} {
      size_t size;
      union data {
        #{char_type_ref} string;
        #{@list.type} list;
      } data;
      int is_list;
    };
    struct #{it} {
      #{type_ref} string;
      int index, forward;
    };
  $
end