Child class extends parent’s function

The example in this section is TNumStr object. TNumStr is a child of TStr object. TNumStr holds a string and the string type, which is one of t_int, t_double or t_none.

A t_int or t_double type string is called a numeric string. It is not a common terminology and it is used only in this tutorial. In short, a numeric string is a string that expresses a number. For example, “0”, “-100” and “123.456” are numeric strings. They are strings and express numbers.

A numeric string is such a specific string.

Verification of a numeric string

Before defining TNumStr, we need a way to verify a numeric string.

Numeric string includes:

We need to be careful that “0” and “0.0” are different. Because their type are different. The type of “0” is integer and the type of “0.0” is double. In the same way, “1” is an integer and “1.” is a double.

“.5” and “0.5” are the same. Both are double and their values are 0.5.

Verification of a numeric string is a kind of lexical analysis. A state diagram and state matrix is often used for lexical analysis.

A numeric string is a sequence of characters that satisfies:

  1. ‘+’ or ‘-’ can be the first character. It can be left out.
  2. followed by a sequence of digits.
  3. followed by a period.
  4. followed by a sequence of digits.

The second part can be left out. For example, “.56” or “-.56” are correct.

The third and fourth parts can be left out. For example, “12” or “-23” are correct.

The fourth part can be left out. For example, “100.” is correct.

There are six states.

The input characters are:

The state diagram is as follows.

state diagram of a numeric string

The state matrix is:

state\input 0 1 2 3 4
0 1 2 3 6 6
1 6 2 3 6 6
2 6 2 3 4 6
3 6 3 6 5 6

This state diagram doesn’t support “1.23e5” style double (decimal floating point). If it is supported, the state diagram will be more complicated. (However, it will be a good practice for your programming skill.)

Header file

The header file of TNumStr is tnumstr.h. It is in the src/tstr directory.

#pragma once

#include <glib-object.h>
#include "tstr.h"
#include "../tnumber/tnumber.h"

#define T_TYPE_NUM_STR  (t_num_str_get_type ())
G_DECLARE_FINAL_TYPE (TNumStr, t_num_str, T, NUM_STR, TStr)

/* type of the numeric string */
enum {
  t_none,
  t_int,
  t_double
};

/* get the type of the TNumStr object */
int
t_num_str_get_string_type (TNumStr *self);

/* setter and getter */
void
t_num_str_set_from_t_number (TNumStr *self, TNumber *num);

// TNumStr can have any string, which is t_none, t_int or t_double type.
// If the type is t_none, t_num_str_get_t_number returns NULL.
// It is good idea to call t_num_str_get_string_type and check the type in advance. 

TNumber *
t_num_str_get_t_number (TNumStr *self);

/* create a new TNumStr instance */
TNumStr *
t_num_str_new_with_tnumber (TNumber *num);

TNumStr *
t_num_str_new (void);

C file

The C file of TNumStr is tnumstr.c. It is in the src/tstr directory.

#include <stdlib.h>
#include <ctype.h>
#include "tnumstr.h"
#include "tstr.h"
#include "../tnumber/tnumber.h"
#include "../tnumber/tint.h"
#include "../tnumber/tdouble.h"

struct _TNumStr {
  TStr parent;
  int type;
};

G_DEFINE_TYPE(TNumStr, t_num_str, T_TYPE_STR)

static int
t_num_str_string_type (const char *string) {
  const char *t;
  int stat, input;
  /* state matrix */
  int m[4][5] = {
    {1, 2, 3, 6, 6},
    {6, 2, 3, 6, 6},
    {6, 2, 3, 4, 6},
    {6, 3, 6, 5, 6}
  };

  if (string == NULL)
    return t_none;
  stat = 0;
  for (t = string; ; ++t) {
    if (*t == '+' || *t == '-')
      input = 0;
    else if (isdigit (*t))
      input = 1;
    else if (*t == '.')
      input = 2;
    else if (*t == '\0')
      input = 3;
    else
      input = 4;

    stat = m[stat][input];

    if (stat >= 4 || *t == '\0')
      break;
  }
  if (stat == 4)
    return t_int;
  else if (stat == 5)
    return t_double;
  else
    return t_none;
}

/* This function overrides t_str_set_string. */
/* And it also changes the behavior of setting the "string" property. */
/* On TStr => just set the "string" property */
/* On TNumStr => set the "string" property and set the type of the string. */
static void
t_num_str_real_set_string (TStr *self, const char *s) {
  T_STR_CLASS (t_num_str_parent_class)->set_string (self, s);
  T_NUM_STR (self)->type = t_num_str_string_type(s);
}

static void
t_num_str_init (TNumStr *self) {
  self->type = t_none;
}

static void
t_num_str_class_init (TNumStrClass *class) {
  TStrClass *t_str_class = T_STR_CLASS (class);

  t_str_class->set_string = t_num_str_real_set_string;
}

int
t_num_str_get_string_type (TNumStr *self) {
  g_return_val_if_fail (T_IS_NUM_STR (self), -1);

  return self->type;
}

/* setter and getter */
void
t_num_str_set_from_t_number (TNumStr *self, TNumber *num) {
  g_return_if_fail (T_IS_NUM_STR (self));
  g_return_if_fail (T_IS_NUMBER (num));

  char *s;

  s = t_number_to_s (T_NUMBER (num));
  t_str_set_string (T_STR (self), s);
  g_free (s);
}

TNumber *
t_num_str_get_t_number (TNumStr *self) {
  g_return_val_if_fail (T_IS_NUM_STR (self), NULL);

  char *s = t_str_get_string(T_STR (self));
  TNumber *tnum;

  if (self->type == t_int)
    tnum = T_NUMBER (t_int_new_with_value (atoi (s)));
  else if (self->type == t_double)
    tnum = T_NUMBER (t_double_new_with_value (atof (s)));
  else
    tnum = NULL;
  g_free (s);
  return tnum;
}

/* create a new TNumStr instance */

TNumStr *
t_num_str_new_with_tnumber (TNumber *num) {
  g_return_val_if_fail (T_IS_NUMBER (num), NULL);

  TNumStr *numstr;

  numstr = t_num_str_new ();
  t_num_str_set_from_t_number (numstr, num);
  return numstr;
}

TNumStr *
t_num_str_new (void) {
  return T_NUM_STR (g_object_new (T_TYPE_NUM_STR, NULL));
}

Child class extends parent’s function.

TNumStr is a child class of TStr, so it has all the TStr’s public funftions.

When you want to set a string to a TNumStr instance, you can use t_str_set_string function.

TNumStr *ns = t_num_str_new ();
t_str_set_string (T_STR (ns), "123.456");

TNumStr extends the function t_str_set_string and it sets not only a string but also the type for a TNumStr instance.

int t;
t = t_num_str_get_string_type (ns);
if (t == t_none) g_print ("t_none\n");
if (t == t_int) g_print ("t_int\n");
if (t == t_double) g_print ("t_double\n");
// => t_double appears on your display

TNumStr adds some public functions.

A child class extends the parent class. So, a child is more specific and richer than the parent.

Compilation and execution

There are main.c, test1.c and test2.c in src/tstr directory. Two programs test1.c and test2.c generates _build/test1 and _build/test2 respectively. They test tstr.c and tnumstr.c. If there are errors, messages will appear. Otherwise nothing appears.

The program main.c generates _build/tnumstr. It shows how TStr and TNumStr work.

Compilation is done by usual way. First, change your current directory to src/tstr.

$ cd src/tstr
$ meson setup _build
$ ninja -C _build
$ _build/test1
$ _build/test2
$ _build/tnumstr
String property is set to one.
"one" and "two" is "onetwo".
123 + 456 + 789 = 1368
TNumStr => TNumber => TNumStr
123 => 123 => 123
-45 => -45 => -45
+0 => 0 => 0
123.456 => 123.456000 => 123.456000
+123.456 => 123.456000 => 123.456000
-123.456 => -123.456000 => -123.456000
.456 => 0.456000 => 0.456000
123. => 123.000000 => 123.000000
0.0 => 0.000000 => 0.000000
123.4567890123456789 => 123.456789 => 123.456789
abc => (null) => abc
(null) => (null) => (null)

The last part of main.c is conversion between TNumStr and TNumber. There are some difference between them because of the following two reasons.

It is difficult to compare two TNumStr instances. The best way is to compare TNumber instances converted from TNumStr.