-
Notifications
You must be signed in to change notification settings - Fork 126
Using usertype values
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 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
.
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.
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:
- Redefine
typedef unsigned char mb_val_bytes_t[...];
inmy_basic.h
to enlarge the type, at least large enough to store your struct - Mark
mb_value_t.type
withMB_DT_USERTYPE
- Initialize all bytes in
mb_value_t.value.bytes
with zero - 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)));
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)
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.
- Principles
- Coding
- Data types
- Standalone shell
- Integration
- Customization
- More scripting API
- FAQ