-
Notifications
You must be signed in to change notification settings - Fork 126
Inline data sequence module
This is a demonstration about how to implement an inline data sequence module with MY-BASIC.
Using of the classical DATA
, READ
, RESTORE
statements is an oversimplified and crude way for data enumeration, it fits more appropriately to it by using dedicated data file. But they works very well since BASIC was introduced, and even now it does make sense for short and quick code.
I write this inline data sequence module for a habit compatibility purpose. But there're some differences. Retro BASIC collects data before the program runs, and only accepts simple data; but with this module, MY-BASIC really executes a DATA
statement at runtime and it must appear before READ
, it can also collect advanced data types in MY-BASIC. The DATA
, READ
, RESTORE
statements act some different behaviour for cursor restoration: DATA
returns total collected count until the last data argument; READ
returns latest reading cursor; RESTORE
accepts an indicated target position, or zero as default. See the code for details.
Add implementation:
typedef struct _data_seq_t {
mb_value_t* value;
unsigned count;
unsigned size;
unsigned cursor;
} _data_seq_t;
static _data_seq_t* _data_seq = 0;
#define _DATA_SEQ_INC_STEP 32
static bool_t _is_ref(mb_value_t val); /* Check whether a value is referenced */
static void _alloc_data_seq(void) {
unsigned i = 0;
_data_seq = (_data_seq_t*)malloc(sizeof(_data_seq_t));
_data_seq->count = 0;
_data_seq->size = _DATA_SEQ_INC_STEP;
_data_seq->cursor = 0;
_data_seq->value = (mb_value_t*)malloc(sizeof(mb_value_t) * _data_seq->size);
for(i = 0; i < _data_seq->size; ++i) {
mb_make_nil(_data_seq->value[i]);
}
}
static void _free_data_seq(void) {
unsigned i = 0;
for(i = 0; i < sequ->count; ++i) {
mb_value_t &val = sequ->value[i];
if(_is_ref(val))
mb_unref_value(s, nullptr, val);
}
free(_data_seq->value);
free(_data_seq);
_data_seq = 0;
}
static int _data(struct mb_interpreter_t* s, void** l) {
int result = MB_FUNC_OK;
mb_value_t tmp;
mb_assert(s && l);
mb_check(mb_attempt_func_begin(s, l));
while(mb_has_arg(s, l)) {
mb_check(mb_pop_value(s, l, &tmp));
if(_is_ref(tmp))
mb_ref_value(s, l, tmp);
if(_data_seq->count + 1 == _data_seq->size) {
unsigned i = 0;
_data_seq->size += _DATA_SEQ_INC_STEP;
_data_seq->value = (mb_value_t*)realloc(_data_seq->value, sizeof(mb_value_t) * _data_seq->size);
for(i = _data_seq->count; i < _data_seq->size; ++i) {
mb_make_nil(_data_seq->value[i]);
}
}
if(_is_ref(_data_seq->value[_data_seq->count]))
mb_unref_value(s, l, _data_seq->value[_data_seq->count]);
_data_seq->value[_data_seq->count++] = tmp;
}
mb_check(mb_attempt_func_end(s, l));
mb_check(mb_push_int(s, l, (int_t)_data_seq->count));
return result;
}
static int _read(struct mb_interpreter_t* s, void** l) {
int result = MB_FUNC_OK;
mb_assert(s && l);
mb_check(mb_attempt_func_begin(s, l));
while(mb_has_arg(s, l)) {
void* v = 0;
mb_check(mb_get_var(s, l, &v));
if(v == 0) {
result = MB_FUNC_ERR;
break;
}
if(_data_seq->cursor >= _data_seq->count) {
result = MB_FUNC_ERR;
break;
}
if(_is_ref(_data_seq->value[_data_seq->cursor]))
mb_ref_value(s, l, _data_seq->value[_data_seq->cursor]);
mb_set_var_value(s, v, _data_seq->value[_data_seq->cursor++]);
}
mb_check(mb_attempt_func_end(s, l));
mb_check(mb_push_int(s, l, (int_t)_data_seq->cursor));
return result;
}
static int _restore(struct mb_interpreter_t* s, void** l) {
int result = MB_FUNC_OK;
unsigned cur = 0;
mb_assert(s && l);
mb_check(mb_attempt_func_begin(s, l));
if(mb_has_arg(s, l)) {
int_t val = 0;
mb_check(mb_pop_int(s, l, &val));
cur = (unsigned)val;
}
mb_check(mb_attempt_func_end(s, l));
if(cur > _data_seq->count)
result = MB_FUNC_ERR;
else
_data_seq->cursor = cur;
return result;
}
Register them:
mb_register_func(bas, "DATA", _data);
mb_register_func(bas, "READ", _read);
mb_register_func(bas, "RESTORE", _restore);
Referenced values may become unreachable by GC after they are obtained by the DATA
statement, nevertheless they should be marked as alive. Consider using mb_set_alive_checker
to tell the collector the aliveness of values.
The _alloc_data_seq
function should be called before mb_run
and _free_data_seq
after it, to allocate and free necessary structure:
_alloc_data_seq();
mb_run(bas, true);
_free_data_seq();
Script usage:
data 22, 7, 355, 113, "hello", list(1 to 42)
pos = read a, b
read c, d
print a / b; c / d;
restore pos
read w, x, y, z
print w; x; y; len(z);
- Principles
- Coding
- Data types
- Standalone shell
- Integration
- Customization
- More scripting API
- FAQ