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.
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:
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.
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.)
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);
G_DECLARE_FINAL_TYPE
for TNumStr class. It
is a child class of TStr and a final type class.t_none
: No string is stored or the string isn’t a
numeric string.t_int
: The string expresses an integert_double
: The string expresses an real number, which is
double type in C language.t_num_str_get_string_type
returns the type of the string TStrNum object has. The returned value is
t_none
, t_int
or t_double
.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));
}
G_DEFINE_TYPE
macro.t_num_str_string_type
checks the
given string and returns t_int
, t_double
or
t_none
. If the string is NULL or an non-numeric string,
t_none
will be returned. The check algorithm is explained
in the first subsection “Verification of a numeric string”.t_num_str_real_set_string
sets
TNumStr’s string and its type. This is a body of the class method
pointed by set_string
member of the class structure. The
class method is initialized in the class initialization function
t_num_str_class_init
.t_num_str_init
sets the type to t_none
because
its parent initialization function set the pointer
priv->string
to NULL.t_num_str_class_init
assigns
t_num_str_real_set_string
to the member
set_string
. Therefore, the function
t_str_set_string
calls
t_num_str_real_set_string
, which sets not only the string
but also the type. The function g_object_set
also calls it
and sets both the string and type.t_num_str_get_string_type
returns the type of the string.TNumStr is a child class of TStr, so it has all the TStr’s public funftions.
TStr *t_str_concat (TStr *self, TStr *other)
void t_str_set_string (TStr *self, const char *s)
char *t_str_get_string (TStr *self)
When you want to set a string to a TNumStr instance, you can use
t_str_set_string
function.
*ns = t_num_str_new ();
TNumStr (T_STR (ns), "123.456"); t_str_set_string
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_num_str_get_string_type (ns);
t 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.
int t_num_str_get_string_type (TNumStr *self)
void t_num_str_set_from_t_number (TNumStr *self, TNumber *num)
TNumber *t_num_str_get_t_number (TNumStr *self)
A child class extends the parent class. So, a child is more specific and richer than the parent.
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.