Using usertype values

Wang Renxin edited this page Nov 19, 2017 · 2 revisions

You can extend MY-BASIC by adding new usertypes. MY-BASIC doesn’t know what a usertype is; it just retains a usertype value at a variable or an array element.

Simple usertype

Simple usertype is a value type which MY-BASIC simply copies a value to another, no matter whatever the value is; it never disposes a simple usertype value, you have to manage the lifecycle of it. A simple usertype is tagged with MB_DT_USERTYPE.

Simple pointer

There are two essential interfaces to get or set a simple usertype: mb_pop_usertype and mb_push_usertype; and these interfaces cannot be used for referenced usertype. You can push a void* to an interpreter and pop a value as void* as well. For the usage, you might need to interpret the data yourself.

Structure

If you need to assign a value, say, very often larger than sizeof(void*), such as customized structures, then follow these steps:

  1. Redefine typedef unsigned char mb_val_bytes_t[...]; in my_basic.h to enlarge the field, at least large enough to store your struct
  2. Mark mb_value_t.type with MB_DT_USERTYPE
  3. Initialize all bytes in mb_value_t.value.bytes with zero
  4. Copy a value to mb_value_t.value.bytes to assign it

Use the mb_push_value and mb_pop_value functions to communicate with MY-BASIC:

For example:

typedef struct Tag {
	...
} Tag;
Tag tag;

mb_value_t val;
val.type = MB_DT_USERTYPE;
memset(&val.value.bytes, 0, sizeof(mb_val_bytes_t));
memcpy(&val.value.bytes, &tag, mb_min(sizeof(mb_val_bytes_t), sizeof(Tag)));
mb_push_value(s, l, val);
Tag tag;

mb_pop_value(s, l, &val);
memcpy(&tag, &val.value.bytes, mb_min(sizeof(mb_val_bytes_t), sizeof(Tag)));

Referenced usertype

Basic data

A referenced usertype is tagged with MB_DT_USERTYPE_REF. MY-BASIC uses Reference Counting and GC to manage the lifecycle of it. You can use mb_make_ref_value to pass any void* pointer to initialize a referenced usertype.

For example I'll show how to use an integer pointer referenced usertype. It requires a few functions to get a referenced usertype working:

static void _unref(struct mb_interpreter_t* s, void* d) {
	int* p = (int*)d;

	mb_assert(s);

	free(p);
}

static void* _clone(struct mb_interpreter_t* s, void* d) {
	int* p = (int*)d;
	int* q = (int*)malloc(sizeof(int));

	mb_assert(s);

	*q = *p;

	return q;
}

static unsigned int _hash(struct mb_interpreter_t* s, void* d) {
	int* p = (int*)d;

	mb_assert(s);

	return (unsigned)*p;
}

static int _cmp(struct mb_interpreter_t* s, void* l, void* r) {
	int* p = (int*)l;
	int* q = (int*)r;

	mb_assert(s);

	return *p - *q;
}

static int _fmt(struct mb_interpreter_t* s, void* d, char* b, unsigned z) {
	int result = 0;
	int* p = (int*)d;

	mb_assert(s);

	result = snprintf(b, z, "%d", *p) + 1;

	return result;
}

And a MAKE_REF_INT statement which returns a referenced integer:

static int _make_ref_int(struct mb_interpreter_t* s, void** l) {
	int result = MB_FUNC_OK;

	mb_assert(s && l);

	mb_check(mb_attempt_open_bracket(s, l));

	mb_check(mb_attempt_close_bracket(s, l));

	{
		mb_value_t ret;
		int* p = (int*)malloc(sizeof(int));
		*p = 123;
		mb_make_ref_value(s, p, &ret, _unref, _clone, _hash, _cmp, _fmt);
		mb_check(mb_push_value(s, l, ret));
	}

	return result;
}

It uses the _unref to release the actual usertype. _clone to duplicate it when cloning with the CLONE statement directly, or from a collection to another, etc. _hash, _cmp, _fmt are optional to do hash and comparison for collections, to serialize in the PRINT statement, respectively.

Set with NULL, or set with a handler which returns NULL to _clone to make it non-clonable. Then it will return a nil when attempting to clone it.

The usage of the integer pointer as follow:

static int _use_ref_int(struct mb_interpreter_t* s, void** l) {
	int result = MB_FUNC_OK;

	mb_assert(s && l);

	mb_check(mb_attempt_open_bracket(s, l));

	{
		int* p = 0;
		mb_value_t arg;
		mb_make_nil(arg);
		mb_check(mb_pop_value(s, l, &arg));
		mb_check(mb_get_ref_value(s, l, arg, (void**)&p));

		printf("%d\n", *p);

		mb_check(mb_unref_value(s, l, arg));
	}

	mb_check(mb_attempt_close_bracket(s, l));

	return result;
}

Don't forget to call mb_unref_value to decrease the reference count after using a referenced usertype every time, because mb_pop_value has increased the reference count.

For testing it in BASIC:

i = make_ref_int()
use_ref_int(i)

Structure data

As it's mentioned before, MY-BASIC just retains whatever you give it. You may wonder in a practice of using a complex structure referenced usertype like:

typedef struct struct_t {
	int member0;
	int member1;
} struct_t;

First of all let's add necessary functions:

static void _unref(struct mb_interpreter_t* s, void* d) {
	struct_t* p = (struct_t*)d;

	mb_assert(s);

	free(p);
}

static void* _clone(struct mb_interpreter_t* s, void* d) {
	struct_t* p = (struct_t*)d;
	struct_t* q = (struct_t*)malloc(sizeof(struct_t));

	mb_assert(s);

	*q = *p;

	return q;
}

static unsigned int _hash(struct mb_interpreter_t* s, void* d) {
	struct_t* p = (struct_t*)d;

	mb_assert(s);

	return p->member0 + p->member1;
}

static int _cmp(struct mb_interpreter_t* s, void* l, void* r) {
	struct_t* p = (struct_t*)l;
	struct_t* q = (struct_t*)r;
	int tmp = 0;

	mb_assert(s);

	tmp = p->member0 - q->member0;
	if(tmp) return tmp;
	else return p->member1 - q->member1;
}

static int _fmt(struct mb_interpreter_t* s, void* d, char* b, unsigned z) {
	int result = 0;
	struct_t* p = (struct_t*)d;

	mb_assert(s);

	result = snprintf(b, z, "%d, %d", p->member0, p->member1) + 1;

	return result;
}

static int _make_ref_struct(struct mb_interpreter_t* s, void** l) {
	int result = MB_FUNC_OK;

	mb_assert(s && l);

	mb_check(mb_attempt_open_bracket(s, l));

	mb_check(mb_attempt_close_bracket(s, l));

	{
		mb_value_t ret;
		struct_t* p = (struct_t*)malloc(sizeof(struct_t));
		p->member0 = p->member1 = 0;
		mb_make_ref_value(s, p, &ret, _unref, _clone, _hash, _cmp, _fmt);
		mb_check(mb_push_value(s, l, ret));
	}

	return result;
}

Then member manipulators:

static int _get_member0(struct mb_interpreter_t* s, void** l) {
	int result = MB_FUNC_OK;
	int ret = 0;

	mb_assert(s && l);

	mb_check(mb_attempt_open_bracket(s, l));

	{
		struct_t* p = 0;
		mb_value_t arg;
		mb_make_nil(arg);
		mb_check(mb_pop_value(s, l, &arg));
		mb_check(mb_get_ref_value(s, l, arg, (void**)&p));

		ret = p->member0;

		mb_check(mb_unref_value(s, l, arg));
	}

	mb_check(mb_attempt_close_bracket(s, l));

	mb_check(mb_push_int(s, l, ret));

	return result;
}

static int _set_member0(struct mb_interpreter_t* s, void** l) {
	int result = MB_FUNC_OK;
	int_t val = 0;

	mb_assert(s && l);

	mb_check(mb_attempt_open_bracket(s, l));

	{
		struct_t* p = 0;
		mb_value_t arg;
		mb_make_nil(arg);
		mb_check(mb_pop_value(s, l, &arg));
		mb_check(mb_get_ref_value(s, l, arg, (void**)&p));

		mb_check(mb_pop_int(s, l, &val));
		p->member0 = val;

		mb_check(mb_unref_value(s, l, arg));
	}

	mb_check(mb_attempt_close_bracket(s, l));

	return result;
}

static int _get_member1(struct mb_interpreter_t* s, void** l) {
	int result = MB_FUNC_OK;
	int ret = 0;

	mb_assert(s && l);

	mb_check(mb_attempt_open_bracket(s, l));

	{
		struct_t* p = 0;
		mb_value_t arg;
		mb_make_nil(arg);
		mb_check(mb_pop_value(s, l, &arg));
		mb_check(mb_get_ref_value(s, l, arg, (void**)&p));

		ret = p->member1;

		mb_check(mb_unref_value(s, l, arg));
	}

	mb_check(mb_attempt_close_bracket(s, l));

	mb_check(mb_push_int(s, l, ret));

	return result;
}

static int _set_member1(struct mb_interpreter_t* s, void** l) {
	int result = MB_FUNC_OK;
	int_t val = 0;

	mb_assert(s && l);

	mb_check(mb_attempt_open_bracket(s, l));

	{
		struct_t* p = 0;
		mb_value_t arg;
		mb_make_nil(arg);
		mb_check(mb_pop_value(s, l, &arg));
		mb_check(mb_get_ref_value(s, l, arg, (void**)&p));

		mb_check(mb_pop_int(s, l, &val));
		p->member1 = val;

		mb_check(mb_unref_value(s, l, arg));
	}

	mb_check(mb_attempt_close_bracket(s, l));

	return result;
}

It's possible to use them as:

s = make_ref_struct()
s.set_member0(22)
s.set_member1(7)
r = s.get_member0() / s.get_member1()
print r;

But the fact is developers often want to use more than one type. Consider wrapping the actual pointer of data in a typed struct as following pseudo code:

struct TypedRef {
	enum type;
	void* data;
};

Or make them inheriting from a common C++ base class:

struct RefBase {
	virtual int getType(void) const = 0;
};

struct Ref0 : public RefBase {
	virtual int getType(void) const override {
		return 0;
	}
};

struct Ref1 : public RefBase {
	virtual int getType(void) const override {
		return 1;
	}
};

As whatever how you can check the type.

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.