Skip to content

Inline data sequence module

Tony Wang edited this page Apr 24, 2026 · 9 revisions

This module implements an inline data sequence library with MY-BASIC.

It was widly used to transfer inline data with the DATA, READ, RESTORE statements, but it's not common to see nowadays. Consider using better data management over this, however, it's still possible to implement it in some way.

I write this inline data sequence module for a purpose of habit compatibility. 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 location restoration: DATA returns total collected count until the last data argument; READ returns latest reading location; RESTORE accepts an indicated target location, 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(struct mb_interpreter_t* s, mb_value_t val) { /* Check whether a value is referenced */
	switch (val.type) {
#ifdef MB_ENABLE_USERTYPE_REF
	case MB_DT_USERTYPE_REF: // Falls through.
#endif /* MB_ENABLE_USERTYPE_REF */
#ifdef MB_ENABLE_ARRAY_REF
	case MB_DT_ARRAY: // Falls through.
#endif /* MB_ENABLE_ARRAY_REF */
#ifdef MB_ENABLE_COLLECTION_LIB
	case MB_DT_LIST: // Falls through.
	case MB_DT_DICT: // Falls through.
	case MB_DT_LIST_IT: // Falls through.
	case MB_DT_DICT_IT: // Falls through.
#endif /* MB_ENABLE_COLLECTION_LIB */
#ifdef MB_ENABLE_CLASS
	case MB_DT_CLASS:
#endif /* MB_ENABLE_CLASS */
		return true;
	case MB_DT_ROUTINE:
#ifdef MB_ENABLE_LAMBDA
		{
			mb_routine_type_e y = MB_RT_NONE;
			mb_get_routine_type(s, val, &y);
			mb_assert(y >= MB_RT_NONE && y <= MB_RT_NATIVE);

			return y == MB_RT_LAMBDA;
		}
#else /* MB_ENABLE_LAMBDA */
		return false;
#endif /* MB_ENABLE_LAMBDA */
	default:
		return false;
	}
}

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(struct mb_interpreter_t* s) {
	unsigned i = 0;
	for(i = 0; i < sequ->count; ++i) {
		mb_value_t &val = sequ->value[i];
		if(_is_ref(s, 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(s, 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(s, _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, true));
		if(v == 0) {
			result = MB_FUNC_ERR;

			break;
		}
		if(_data_seq->cursor >= _data_seq->count) {
			result = MB_FUNC_ERR;

			break;
		}
		if(_is_ref(s, _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(bas);

BASIC 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);

Clone this wiki locally