Skip to content

Using usertype values

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

There are quite few built-in types in MY-BASIC. It’s easy to use usertype in MY-BASIC. It can accept data whatever the type is you give it.

MY-BASIC doesn’t care what the usertype is; it just retains a usertype value at a variable or an array element. Note MB_SIMPLE_ARRAY macro must be disabled when you wish to store usertype in arrayes.

Simple usertype

Simple usertype is a value type which MY-BASIC simply copies a value to another, no matter whatever the value is; but it doesn't dispose a simple usertype value, you have to manage its lifecycle. 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 encapsulate and decapsulate data yourself.

Structure

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

  1. Redefine typedef unsigned char mb_val_bytes_t[...]; in my_basic.h to enlarge the type, 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

The mb_push_value and mb_pop_value could be used to pass it through MY-BASIC:

For instance:

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 a referenced usertype value. You can use mb_make_ref_value to pass any void* pointer to initialize a referenced usertype.

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

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. set with NULL or a handler which returns NULL to make it non-clonable. _hash, _cmp, _fmt are optional to do hash and comparison for collections, to serialize in the PRINT statement, respectively.

It will return a nil instead if there's no cloning function assigned, in other words it's a non-copyable referenced usertype.

Then here's a function that uses the integer pointer:

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 increases the reference count.

You can test it in script:

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 able 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 would use more than one type. You may wrap 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. It's up to you.

Clone this wiki locally