• Print

Author Topic: Array and variable-length string in TYPE library  (Read 284 times)

DSMan195276

  • Hero Member
  • *****
  • Posts: 1985
  • Yes
    • Email
Array and variable-length string in TYPE library
« on: January 14, 2013, 10:45:38 PM »
A few people have asked recently about arrays and strings in TYPE's. Since we have _MEM you can do it similar to how it's done in C. And since I'm using that concept in a program I'm writing I compiled some functions I wrote-out into some $INCLUDE files for people to use, it's a pretty simple library to use but fairly effective.

How to use:
The library has to main types (And two extra types which I'll note at the end) called 'array_type' and 'string_type', both fairly self-explanatory. They're very similar types, except array has an element size parameter.

array_type:
To use arrays, say you have a variable like this:

Code: [Select]
DIM a AS array_type
You can then use the SUB "INIT_ARRAY" to dimension that array type. Example:

Code: [Select]
INIT_ARRAY a, 40, 4
That dimensions the array 'a' with 40 elements (These arrays are zero based, so you will get elements from 0 to 39). The size of each element is 4, which could correspond to a LONG or SINGLE variable in this case. If you wanted to REdimension this array later, then you could do this:

Code: [Select]
REINIT_ARRAY a, 50
This will re-size the array to 50 elements (Of the same element_size as before, and it will contain the same data).

To use theses arrays, there is one function named "put_array_value" and many functions named "get_array_value_TYPE". To put a value into an array, use this notation:

Code: [Select]
put_array_value a, 19, _MK$(LONG, 24)
This notation is actually simpler in the long run since QB64 doesn't support any type of function overloading (And thus, I don't need a SUB for each different type of value). It uses the _MK$ to turn your value into a STRING which contains the exact same data (If you need a different type, replace LONG with the type_name). To get a value is a bit differnet, you call the corresponding "get_array_value" function/sub. For example:

Code: [Select]
l = get_array_value_LONG& (a, 19)
That get's the 20'th LONG from the array a (20 because the array is zero based). If you instead wanted a SINGLE, you'd call 'get_array_value_SINGLE(a, 19)', etc. There are two special cases, one for string_type and one for _MEM. both of them are SUBS which return the string/_MEM in the third argument because you can't return a TYPE from a function.

If you ever need the number of elements in an array, just read a.length (remember zero based, so if a.length = 19 then you have 20 elements). a.element_size is the element_size you choose (And you can change it, though not recommend unless you realize what you're doing).

An important point to remember is that since we're using _MEM, you need to free your memory when you're done with it. I wrote two SUB's that make this process very easy, and I would recommend just having a second at the end of your SUB's FUNCTION's that uses these commands to free the memory being used if the variable isn't going to be used when the SUB exits. If the variable is global then you don't need to worry about freeing it as it'll be freed when the program exits. To free an array, just do this:

Code: [Select]
free_array a
Where a is an array_type

string_type:
String type is much easier, just two functions,  'put_str' and 'get_Str$'. To get a string:

Code: [Select]
DIM s as string_type
And if we want to assign some text to this string:

Code: [Select]
set_Str s, "TEST STRING"
And if we want to read it:

Code: [Select]
st$ = get_Str$(s)
Fairly simple. You can use all of the normal string functions on the string_type as long as you use get_Str$() on it first. Like Array_type, it does need to be freed when you're done with it (So, if the string_type is going to be discarded when you exit a SUB or FUNCTION for instance, you'll want to free it before the variable is deleted). To free a string_type just do this:

Code: [Select]
free_string s
code:
mem_lib.bi
Code: [Select]
TYPE string_type
    mem AS _MEM
    length AS LONG 'length of string
    allocated AS LONG 'number of allocated bytes
    is_allocated AS _BYTE 'if -1 then string has been allocated
END TYPE

TYPE array_type
    mem AS _MEM
    length AS LONG 'number of elements STARTS AT 0
    element_size AS LONG 'size of each array piece
    allocated AS LONG 'number of bytes allocated -- may be larger then needed if resized
    is_allocated AS _BYTE 'will be 0 if array has not yet been allocated
END TYPE

TYPE mem_type_extra 'Just used to get the size of _MEM
    m AS _MEM
END TYPE

TYPE offset_type_extra 'just to get the size of _OFFSET
    o AS _OFFSET
END TYPE

mem_lib.bm:
Code: [Select]

SUB INIT_ARRAY (a AS array_type, num_of_elements, element_size)
IF a.is_allocated AND element_size = a.element_size THEN
    REINIT_ARRAY a, num_of_elements
ELSEIF a.is_allocated THEN 'wrong element size, error out
    ERROR 255
ELSE
    $CHECKING:OFF
    a.mem = _MEMNEW(num_of_elements * element_size)
    $CHECKING:ON
    a.element_size = element_size
    a.length = num_of_elements
    a.allocated = num_of_elements + element_size
    a.is_allocated = -1
END IF
END SUB

SUB REINIT_ARRAY (a AS array_type, num_of_elements)
DIM buf AS _MEM
IF NOT a.is_allocated THEN 'um... problem. we don't have an element size. error
    ERROR 255
ELSE
    IF num_of_elements * a.element_size <= a.allocated THEN 'we can use the memory we already allocated.
        a.length = num_of_elements
    ELSE 'we have to resize our memory
        $CHECKING:OFF
        buf = _MEMNEW(a.length * a.element_size)
        _MEMCOPY a.mem, a.mem.OFFSET, (a.length * a.element_size) TO buf, buf.OFFSET
        _MEMFREE a.mem
        a.mem = _MEMNEW(num_of_elements * a.element_size)
        _MEMCOPY buf, buf.OFFSET, buf.SIZE TO a.mem, a.mem.OFFSET
        _MEMFREE buf
        $CHECKING:ON
        a.length = num_of_elements
        a.allocated = num_of_elements * a.element_size
    END IF
END IF
END SUB

'ZERO based
'recast this FUNCTION into different types to account
'for different typed arrays.
'Call the function coresponding to your element_size

'If QB64 ever supports Templates, that could be used to easily overload this function so you don't need multiples
FUNCTION get_array_value_BYTE%% (a AS array_type, element_number)
$CHECKING:OFF
_MEMGET a.mem, a.mem.OFFSET + element_number, get_array_value_BYTE%%
END FUNCTION

FUNCTION get_array_value_LONG& (a AS array_type, element_number)
$CHECKING:OFF
_MEMGET a.mem, a.mem.OFFSET + element_number * 4, get_array_value_LONG&
$CHECKING:ON
END FUNCTION

FUNCTION get_array_value_SINGLE (a AS array_type, element_number)
$CHECKING:OFF
_MEMGET a.mem, a.mem.OFFSET + element_number * 4, get_array_value_SINGLE
$CHECKING:OFF
END FUNCTION

FUNCTION get_array_value_INTEGER% (a AS array_type, element_number)
$CHECKING:OFF
_MEMGET a.mem, a.mem.OFFSET + element_number * 2, get_array_value_INTEGER%
$CHECKING:ON
END FUNCTION

FUNCTION get_Array_value_INTEGER64&& (a AS array_type, element_number)
$CHECKING:OFF
_MEMGET a.mem, a.mem.OFFSET + element_number * 8, get_Array_value_INTEGER64&&
$CHECKING:ON
END FUNCTION

FUNCTION get_array_value_DOUBLE## (a AS array_type, element_number)
$CHECKING:OFF
_MEMGET a.mem, a.mem.OFFSET + element_number * 8, get_array_value_DOUBLE##
$CHECKING:ON
END FUNCTION

'SUB because we can't return a string_type
SUB get_array_value_STRINGTYPE (a AS array_type, element_number, s AS string_type)
$CHECKING:OFF
_MEMGET a.mem, a.mem.OFFSET + element_number * LEN(string_type), s
$CHECKING:ON
END SUB

SUB get_array_value_MEM (a AS array_type, element_number, m AS _MEM)
$CHECKING:OFF
_MEMGET a.mem, a.mem.OFFSET + element_number * LEN(mem_type_extra), m
$CHECKING:ON
END SUB

FUNCTION get_array_value_OFFSET%& (a AS array_type, element_number)
$CHECKING:OFF
_MEMGET a.mem, a.mem.OFFSET + element_number * LEN(offset_type_extra), get_array_value_OFFSET%&
$CHECKING:ON
END FUNCTION

SUB put_array_value (a AS array_type, element_number, dat AS STRING)
'dat is a STRING. Use _MK$ to convert anything into a STRING
$CHECKING:OFF
_MEMPUT a.mem, a.mem.OFFSET + element_number * a.element_size, dat
$CHECKING:ON
END SUB

FUNCTION get_str$ (s AS string_type)
$CHECKING:OFF
IF s.is_allocated <> 0 AND s.length > 0 THEN
    FOR x = 1 TO s.length
        get_s$ = get_s$ + _MEMGET(s.mem, s.mem.OFFSET + x - 1, STRING * 1)
    NEXT x
END IF
get_str$ = get_s$
$CHECKING:ON
END FUNCTION

SUB put_str (s AS string_type, stri$)
$CHECKING:OFF
IF NOT s.is_allocated OR s.allocated < LEN(stri$) THEN
    IF s.is_allocated THEN _MEMFREE s.mem
    s.mem = _MEMNEW(LEN(stri$) + 10) 'allocate 10 extra bytes
    s.allocated = LEN(stri$) + 10
    s.is_allocated = -1
END IF
_MEMPUT s.mem, s.mem.OFFSET, stri$
s.length = LEN(stri$)
$CHECKING:ON
END SUB

SUB free_array (a as array_type)
$CHECKING:OFF
if a.is_allocated then
  _MEMFREE a.mem
  a.is_allocated = 0
  a.allocated = 0
end if
$CHECKING:ON
END SUB

SUB free_string (s as string_type)
$CHECKING:OFF
if s.is_allocated then
  _memfree s.mem
  s.is_allocated = 0
  s.allocated = 0
end if
$CHECKING:on
END SUB

example usage:
Code: [Select]
'$INCLUDE:'mem_lib.bi'

TYPE test_type
    a AS array_type
    s AS string_type
END TYPE

DIM k as test_type

'array k.a, 40 elements (NOT the highest element, will create elements from 0 to 39).
'element size of 4
'Could either be LONG's or SINGLE's, doesn't matter until you call the get_value functions
INIT_ARRAY k.a, 40, 4

'zero based, so 19 is the 20'th spot
'Put a LONG value at that location
put_array_value k.a, 19, _MK$(LONG, 5002)
PRINT get_array_value_LONG&(k.a1, 19)

put_str k.s, "TEST STRING..."
print get_str$(k.s)

'$INCLUDE:'mem_lib.bm'

It's a very bare-bones library, but that's the idea, it's simple and fast. The Strings are very easy to use because once you convert them you can treat them exactly like normal strings. Arrays are a bit more clunky, but shouldn't be to hard to use. These arrays should be faster then normal QB64 arrays because I don't do any bounds checking (Which is good and bad..).

And a note on the extra types "offset_type_extra" and "mem_type_extra". Both of those types are a different size based 32-bit or 64-bit, and since these user-defined types only include one element of those types, I can use LEN on them to get the size of those types. You can't do LEN(_OFFSET) or LEN(_MEM) directly, though it would be a useful feature and perhaps worth adding.

Hope that helps, I'd be glad to fix any errors you find or add in any changes that seem like a good/useful idea.

Matt

EDIT: Please note the two subs I added for freeing these types.
« Last Edit: January 17, 2013, 06:56:06 PM by DSMan195276 »
"Cast your cares on the Lord and he will sustain you; he will never let the righteous be shaken" -- Psalm 55:22
QB64 Linux Installer

Clippy

  • Hero Member
  • *****
  • Posts: 16440
  • I LOVE π = 4 * ATN(1)    Use the QB64 WIKI >>>
    • Pete's Qbasic Site
    • Email
Re: Array and variable-length string in TYPE library
« Reply #1 on: January 15, 2013, 08:23:21 AM »
You can put TYPE code inside of SUB procedures to set them up so that you don't need a BI include file.
QB64 WIKI: Main Page
Download Q-Basics Code Demo: Q-Basics.zip
Download QB64 BAT, IconAdder and VBS shortcuts: QB64BAT.zip
Download QB64 DLL files in a ZIP: Program64.zip

DSMan195276

  • Hero Member
  • *****
  • Posts: 1985
  • Yes
    • Email
Re: Array and variable-length string in TYPE library
« Reply #2 on: January 15, 2013, 10:13:38 AM »
Quote from: Clippy on January 15, 2013, 08:23:21 AM
You can put TYPE code inside of SUB procedures to set them up so that you don't need a BI include file.

But will you still be able to declare a variable with that type in the main code if the TYPE is in a SUB?

I'll have to try that and see if it works, thanks for the tip.

Matt
"Cast your cares on the Lord and he will sustain you; he will never let the righteous be shaken" -- Psalm 55:22
QB64 Linux Installer

TerryRitchie

  • Hero Member
  • *****
  • Posts: 2264
  • FORMAT C:\ /Q /U /AUTOTEST (How to repair Win8)
    • Email
Re: Array and variable-length string in TYPE library
« Reply #3 on: January 18, 2013, 04:36:37 PM »
Quote from: DSMan195276 on January 15, 2013, 10:13:38 AM
Quote from: Clippy on January 15, 2013, 08:23:21 AM
You can put TYPE code inside of SUB procedures to set them up so that you don't need a BI include file.

But will you still be able to declare a variable with that type in the main code if the TYPE is in a SUB?

I'll have to try that and see if it works, thanks for the tip.

Matt

Let us know your findings on this.  I was not aware of this behavior either.

SMcNeill

  • Hero Member
  • *****
  • Posts: 2414
    • Email
Re: Array and variable-length string in TYPE library
« Reply #4 on: January 18, 2013, 04:45:29 PM »
You can put a TYPE anywhere and it defines that type for the whole program.  I've gotten where I tend to move types inside of subs anymore and then toss them at the complete rear of the program so they don't keep pestering me as I'm trying to scroll back and forth between things.  :)
http://bit.ly/TextImage -- Library of QB64 code to manipulate text and images, as a BM library.
http://bit.ly/Color32 -- A set of color CONST for use in 32 bit mode, as a BI library.

http://bit.ly/DataToDrive - A set of routines to quickly and easily get data to and from the disk.  BI and BM files

DSMan195276

  • Hero Member
  • *****
  • Posts: 1985
  • Yes
    • Email
Re: Array and variable-length string in TYPE library
« Reply #5 on: January 18, 2013, 04:53:36 PM »
Quote from: SMcNeill on January 18, 2013, 04:45:29 PM
You can put a TYPE anywhere and it defines that type for the whole program.  I've gotten where I tend to move types inside of subs anymore and then toss them at the complete rear of the program so they don't keep pestering me as I'm trying to scroll back and forth between things.  :)

I think it's because QB64 doesn't care about TYPEs. I believe that QB64 just converts the periods to _042_ (it's something similar like that) and then moves on treating it as a normal variable that's not related to a TYPE at all. It uses the TYPE info to decide what the variable's type should be (IE. Integer, string, etc.) but it doesn't care about whether two variables are related by their type, so as long as you have the info somewhere so it knows what they are, it's fine with it.

Matt
"Cast your cares on the Lord and he will sustain you; he will never let the righteous be shaken" -- Psalm 55:22
QB64 Linux Installer

TerryRitchie

  • Hero Member
  • *****
  • Posts: 2264
  • FORMAT C:\ /Q /U /AUTOTEST (How to repair Win8)
    • Email
Re: Array and variable-length string in TYPE library
« Reply #6 on: January 18, 2013, 05:20:44 PM »
Ok, that explains why Clippy has told me in the past to simply move my types to a SUB or FUNCTION and I would not have need for another include file at the top.  I never really understood what was meant by this.

SMcNeill

  • Hero Member
  • *****
  • Posts: 2414
    • Email
Re: Array and variable-length string in TYPE library
« Reply #7 on: January 18, 2013, 05:34:03 PM »
You still need a BI file if you use CONST or DIM SHARED variables.  It'd be nice if you could track them with a special sub like with the _GL sub.   SUB _COMMON would be nice, and then we could drop the need for BI libraries completely.  :)
http://bit.ly/TextImage -- Library of QB64 code to manipulate text and images, as a BM library.
http://bit.ly/Color32 -- A set of color CONST for use in 32 bit mode, as a BI library.

http://bit.ly/DataToDrive - A set of routines to quickly and easily get data to and from the disk.  BI and BM files

DSMan195276

  • Hero Member
  • *****
  • Posts: 1985
  • Yes
    • Email
Re: Array and variable-length string in TYPE library
« Reply #8 on: January 18, 2013, 05:48:37 PM »
Quote from: SMcNeill on January 18, 2013, 05:34:03 PM
You still need a BI file if you use CONST or DIM SHARED variables.  It'd be nice if you could track them with a special sub like with the _GL sub.   SUB _COMMON would be nice, and then we could drop the need for BI libraries completely.  :)

I think you'd still be suck though, unless you can multiple _COMMON SUB's. (IE. Every library that does this would either have it's own _COMMON sub, or just tell you to make a _COMMON sub and include it's BI file inside, pretty much defeating the purpose).

Personally I don't really have a gripe about BI files. Code get's harder to maintain the more you shove into a single file. I like splitting my TYPE's up just because it makes it very simple if I need to take a look at them. Of course that's just my preference though. I kinda feel like adding in a whole new SUB like _GL is kinda pushing how much should be added just so you can get rid of one line and a file.

Matt
"Cast your cares on the Lord and he will sustain you; he will never let the righteous be shaken" -- Psalm 55:22
QB64 Linux Installer

  • Print