Skip to content

Using usertype values

Wang Renxin edited this page Jul 9, 2022 · 4 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