-
Notifications
You must be signed in to change notification settings - Fork 122
Using usertype values
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 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
.
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.
If you need to assign a value, say, very often larger than sizeof(void*)
, such as customized structures, then follow these steps:
- Redefine
typedef unsigned char mb_val_bytes_t[...];
inmy_basic.h
to enlarge the field, 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
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)));
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)
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.
- Principles
- Coding
- Data types
- Standalone shell
- Integration
- Customization
- More scripting API
- FAQ