Declaring Variables

A variable is a declaration consisting of a declarator, an identifier, and a data type that is used to allocate space for storage of a scalar or array value. In BCX, as in the C language, before a variable can be assigned a value, it must be declared.

The chief BCX variable declarator is

A BCX variable declaration is made, most commonly, in this syntax format.

which, also, can be expressed in this order

As well as DIM, the following keywords can be used as variable declarators

BCX Identifiers

BCX allows the underscore, ASCII alphanumerics and UTF-8 symbols as variable identifiers.

A C compilers definition of allowable identifiers is implementation-defined. To use UTF-8 identifiers,

BCX Data Types

BOOL data type

Syntax 1:

DIM AS BOOL BoolVar

Syntax 2:

DIM BoolVar AS BOOL

Purpose:

  • The above declaration allocates an 32 bit space for a BOOL data type variable named BoolVar.

Remarks:

  • A BOOL data type variable can hold a value of either TRUE and FALSE, respectively defined in BCX, with values of 1 and 0.

BYTE data type

Syntax 1:

DIM AS BYTE ByteVar

Syntax 2:

DIM ByteVar AS BYTE

Purpose:

  • The above declaration allocates an 8 bit space for a C language unsigned char data type variable named ByteVar.

Remarks:

  • BYTE can hold a value in the range 0 to 255.

CHAR data type

Syntax 1:

DIM AS CHAR ChrVar

Syntax 2:

DIM ChrVar AS CHAR

👉 Do not use syntax: DIM ChrVar AS SIGNED CHAR

Purpose:

  • The above declaration allocates an 8 bit space for a C language char data type variable named ChrVar.

Remarks:

  • The BCX code above translates to C language code
    static char ChrVar;
    

    The Borland, Digital Mars, LCCWin32, Microsoft, MinGW, and Pelles C compilers, use a default char data type that is a signed char which can hold a value in the range -128 to 127.

    • For Borland, the signed char default can be changed to unsigned char with the /K command line flag.
    • For MinGW, the signed char default can be changed to unsigned char with the -funsigned-char command line flag.
    • In the Microsoft and Pelles C compiler, the signed char default can be changed to unsigned char with the /J compiler option.
      👉 If the /J compiler option is used with ATL/MFC, an error might be generated. This error could be disabled by defining _ATL_ALLOW_CHAR_UNSIGNED, however, this workaround is not supported and may not always work.
    • The Open Watcom C compiler uses a default char data type that is an unsigned char which can hold a value in the range 0 to 255. This default can be changed to a signed char by using the command line flag -j.

Example:

The following code can be used to check the extremity values of the implementation dependent signed and unsigned char data types.

#INCLUDE <limits.h>

PRINT "CHAR_MIN  = ", CHAR_MIN
PRINT "CHAR_MAX  = ", CHAR_MAX
PRINT "SCHAR_MIN = ", SCHAR_MIN
PRINT "SCHAR_MAX = ", SCHAR_MAX
PRINT "UCHAR_MAX = ", UCHAR_MAX

👉 "UCHAR_MIN" is not defined since it is always 0.

SCHAR data type

Syntax 1:

DIM AS SCHAR SChrVar

Syntax 2:

DIM SChrVar AS SCHAR

👉 Do not use syntax: DIM SChrVar AS SIGNED CHAR

Purpose:

  • The above declaration allocates an 8 bit space for a C language signed char data type variable named SChrVar.

Remarks:

  • SCHAR can hold a value in the range -128 to 127.

UCHAR data type

Syntax 1:

DIM AS UCHAR UChrVar

Syntax 2:

DIM UChrVar AS UCHAR

👉 Do not use syntax: DIM UChrVar AS UNSIGNED CHAR

Purpose:

  • The above declaration allocates an 8 bit space for a C language unsigned char data type variable named UChrVar.

Remarks:

  • UCHAR can hold a value in the range 0 to 255.

SHORT data type

Syntax 1:

DIM AS SHORT ShortVar

Syntax 2:

DIM ShortVar AS SHORT

👉 Do not use syntax: DIM ShortVar AS SIGNED SHORT

Purpose:

  • The above declaration allocates a 16 bit space for a C language signed short data type variable named ShortVar.

Remarks:

  • SHORT can hold a value in the range -32768 to 32767

SSHORT data type

Syntax 1:

DIM AS SSHORT SShortVar

Syntax 2:

DIM ShortVar AS SSHORT

👉 Do not use syntax: DIM SShortVar AS SIGNED SHORT

Purpose:

  • The above declaration allocates a 16 bit space for a C language signed short data type variable named SShortVar.

Remarks:

  • SSHORT can hold a value in the range 0 to 65535.

USHORT data type

Syntax 1:

DIM AS USHORT UShortVar

Syntax 2:

DIM UShortVar AS USHORT

👉 Do not use syntax: DIM UShortVar AS UNSIGNED SHORT

Purpose:

  • The above declaration allocates a 16 bit space for a C language unsigned short data type variable named UShortVar.

Remarks:

  • USHORT can hold a value in the range 0 to 65535.

INTEGER data type

Syntax 1:

DIM AS INTEGER IntVar

Syntax 2:

DIM IntVar AS INTEGER

Syntax 3:

DIM IntVar AS INT

Syntax 4:

A % (percent sign) sigil appended to the variable name indicates an INTEGER variable.

DIM IntVar%

Syntax 5:

If the data type of a variable is not indicated, BCX assumes that the variable is an INTEGER.

DIM IntVar

👉 Do not use syntax: DIM IntVar AS SIGNED INTEGER

Purpose:

  • The above declarations allocate a 32 bit space for a C language int data type variable named IntVar.

Remarks:

  • INTEGER can hold a value in the range -2147483648 to 2147483647.

UINT data type

Syntax 1:

DIM AS UINT UIntVar

Syntax 2:

DIM UIntVar AS UINT

👉 Do not use syntax: DIM UIntVar AS UNSIGNED INTEGER

Purpose:

  • The above declaration allocates a 32 bit space for a C language unsigned int data type variable named UIntVar.

Remarks:

  • UINT can hold a value in the range 0 to 4294967295.

LONG data type

Syntax 1:

DIM AS LONG LongVar

Syntax 2:

DIM LongVar AS LONG

👉 Do not use syntax: DIM LongVar AS SIGNED LONG

Purpose:

  • The above declaration allocates a 32 bit space for a C language long data type variable named LongVar.

Remarks:

  • LONG can hold a value in the range -2147483648 to 2147483647.

ULONG data type

Syntax 1:

DIM AS ULONG ULongVar

Syntax 2:

DIM ULongVar AS ULONG

👉 Do not use syntax: DIM ULongVar AS UNSIGNED LONG

Purpose:

  • The above declaration allocates a 32 bit space for a C language unsigned long data type variable named ULongVar.

Remarks:

  • ULONG can hold a value in the range 0 to 4294967295.
  • The data type COLORREF, used in several BCX procedures, is a synonym for ULONG.

LONGLONG data type

Syntax 1:

DIM AS LONGLONG LLongVar

Syntax 2:

DIM LLongVar AS LONGLONG

👉 Do not use syntax: DIM LLongVar AS SIGNED LONG LONG

Purpose:

  • The above declaration allocates a 64-bit space for a C language long long data type variable named LLongVar.

Remarks:

  • LONGLONG can hold a value in the range -9223372036854775807 to 9223372036854775807.

ULONGLONG data type

Syntax 1:

DIM AS ULONGLONG ULLongVar

Syntax 2:

DIM ULLongVar AS ULONGLONG

👉 Do not use syntax: DIM ULLongVar AS UNSIGNED LONG LONG

Purpose:

  • The above declaration allocates a 64-bit space for a C language unsigned long long data type variable named ULLongVar.

Remarks:

  • ULONGLONG can hold a value in the range 0 to 18446744073709551615.

FLOAT data type

Syntax 1:

DIM AS FLOAT FltVar

Syntax 2:

DIM FltVar AS FLOAT

Syntax 3:

DIM AS SINGLE FltVar

Syntax 4:

DIM FltVar AS SINGLE

Syntax 5:

An ! (exclamation mark) sigil appended to the variable name indicates a FLOAT variable.

DIM FltVar!

Purpose:

  • The above declarations allocate a 32-bit space for a C language float data type variable named FltVar.

👉 When dealing with floating point operations, expressions should include, at least, one operation that explicitly informs the compiler that a floating point operation is occurring. Without that explicit information in the right side expressions, the example below produces unexpected results.

DIM AS FLOAT FltVar
DIM AS DOUBLE DblVar
DIM AS LDOUBLE LDblVar
DIM AS DOUBLE DblVarVar

FltVar = 4 / 5
DblVar = 4 / 5
LDblVar = 4 / 5L
DblVarVar = 4 : DblVarVar = DblVarVar / 5

PRINT FltVar
PRINT DblVar
PRINT LDblVar
PRINT DblVarVar
PRINT 4 / 5

PAUSE
0
0
0
0.8
0.8

Press any key to continue . . .

The corrected example, below, which contains a decimal point in the right side statements, will output the expected results.

DIM AS FLOAT FltVar
DIM AS DOUBLE DblVar
DIM AS LDOUBLE LDblVar
DIM AS DOUBLE DblVarVar

FltVar = 4 / 5.0
DblVar = 4.0 / 5
LDblVar = 4 / 5.0L
DblVarVar = 4 : DblVarVar = DblVarVar / 5.0

PRINT FltVar
PRINT DblVar
PRINT LDblVar
PRINT DblVarVar
PRINT 4 / 5.0

PAUSE
0.8
0.8
0.8
0.8
0.8

Press any key to continue . . .

DOUBLE data type

Syntax 1:

DIM AS DOUBLE DblVar

Syntax 2:

DIM DblVar AS DOUBLE

Syntax 3:

An # (octothorpe) sigil appended to the variable name indicates a DOUBLE variable.

DIM DblVar#

Purpose:

  • The above declarations allocate a 64-bit space for a C language double data type variable named DblVar.

👉 When dealing with floating point operations, expressions should include, at least, one operation that explicitly informs the compiler that a floating point operation is occurring. Without that explicit information in the right side expressions, the example below produces unexpected results.

DIM AS FLOAT FltVar
DIM AS DOUBLE DblVar
DIM AS LDOUBLE LDblVar
DIM AS DOUBLE DblVarVar

FltVar = 4 / 5
DblVar = 4 / 5
LDblVar = 4 / 5L
DblVarVar = 4 : DblVarVar = DblVarVar / 5

PRINT FltVar
PRINT DblVar
PRINT LDblVar
PRINT DblVarVar
PRINT 4 / 5

PAUSE
0
0
0
0.8
0.8

Press any key to continue . . .

The corrected example, below, which contains a decimal point in the right side statements, will output the expected results.

DIM AS FLOAT FltVar
DIM AS DOUBLE DblVar
DIM AS LDOUBLE LDblVar
DIM AS DOUBLE DblVarVar

FltVar = 4 / 5.0
DblVar = 4.0 / 5
LDblVar = 4 / 5.0L
DblVarVar = 4 : DblVarVar = DblVarVar / 5.0

PRINT FltVar
PRINT DblVar
PRINT LDblVar
PRINT DblVarVar
PRINT 4 / 5.0

PAUSE
0.8
0.8
0.8
0.8
0.8

Press any key to continue . . .

LDOUBLE data type

Syntax 1:

DIM AS LDOUBLE LDblVar

Syntax 2:

DIM LDblVar AS LDOUBLE

Syntax 3:

A ` (backquote) sigil appended to the variable name indicates an LDOUBLE variable.

DIM LDblVar`

👉 Do not use syntax: DIM LDblVar AS LONG DOUBLE

Purpose:

  • The above declaration allocates space for a C language long double data type variable named LDblVar.

👉 When LDOUBLE variables are initialized the value assigned must be appended with an "L" The following code, without an appended "L" on the LDOUBLE initialization value

DIM AS LDOUBLE pi = 3.141592653589793238
PRINT              "3.141592653589793238"
PRINT USING$      ("#.##################", pi)

outputs

3.141592653589793238
3.141592653589793116

where the LDOUBLE is treated as a DOUBLE.

Appending the "L" to the LDOUBLE initialization value

DIM AS LDOUBLE pi = 3.141592653589793238L
PRINT              "3.141592653589793238"
PRINT USING$      ("#.##################", pi)

outputs the correct LDOUBLE value.

3.141592653589793238
3.141592653589793238

👉 When dealing with floating point operations, expressions should include, at least, one operation that explicitly informs the compiler that a floating point operation is occurring. Without that explicit information in the right side expressions, the example below produces unexpected results.

DIM AS FLOAT FltVar
DIM AS DOUBLE DblVar
DIM AS LDOUBLE LDblVar
DIM AS DOUBLE DblVarVar

FltVar = 4 / 5
DblVar = 4 / 5
LDblVar = 4 / 5L
DblVarVar = 4 : DblVarVar = DblVarVar / 5

PRINT FltVar
PRINT DblVar
PRINT LDblVar
PRINT DblVarVar
PRINT 4 / 5

PAUSE
0
0
0
0.8
0.8

Press any key to continue . . .

The corrected example, below, which contains a decimal point in the right side statements, will output the expected results.

DIM AS FLOAT FltVar
DIM AS DOUBLE DblVar
DIM AS LDOUBLE LDblVar
DIM AS DOUBLE DblVarVar

FltVar = 4 / 5.0
DblVar = 4.0 / 5
LDblVar = 4 / 5.0L
DblVarVar = 4 : DblVarVar = DblVarVar / 5.0

PRINT FltVar
PRINT DblVar
PRINT LDblVar
PRINT DblVarVar
PRINT 4 / 5.0

PAUSE
0.8
0.8
0.8
0.8
0.8

Press any key to continue . . .

STRING data type (Default Size)

Syntax 1:

DIM DfltStrVar AS STRING

Syntax 2:

A $ (dollar sign) sigil appended to the variable name indicates a STRING variable.

DIM DfltStrVar$

Purpose:

  • The above declarations create a variable named DfltStrVar and allocate a 2048 byte space for a C language char data type string.

STRING data type (Custom Size)

Syntax 1:

DIM CustStrVar AS STRING * 4096

Syntax 2:

DIM CustStrVar [4096] AS CHAR

Purpose:

  • The above declarations create a variable named CustStrVar and allocate a 4096 byte space for a C language char data type string.
  • 👉 The following code declaration

    DIM CustStrVar [4096] AS STRING
    

    does not translate the same as this declaration

    DIM CustStrVar [4096] AS CHAR
    

Sigil suffix

In addition to the above data type keywords, a sigil suffix appended to the variable name, can be used to inform the BCX translator of the data type of the variable.

More than one variable can be declared on a single line. For example, this code

DIM a AS INTEGER, b AS INTEGER, c AS INTEGER

is equivalent to this code

DIM a AS INTEGER
DIM b AS INTEGER
DIM c AS INTEGER

BCX also allows different data type variables to be declared on a single line. For example,

DIM A%, B!, D$ * 1000, E[10,10]

creates an integer, a single, a string, and a 2 dimensional integer array.

Forward Propagation of Variable Type

The declaration of variables with forward propagation of variable type is allowed in BCX, for example, this declaration

DIM a AS INTEGER, b AS INTEGER, c AS INTEGER

can be expressed as

DIM AS INTEGER a, b, c

👉 However, the BCX parser will not parse, correctly, this code

DIM AS INTEGER a, AS INTEGER b, AS INTEGER c

Here are some other examples showing the declaration of variables with forward propagation of variable type.

Example:

DIM AS DOUBLE A, B, C[2]                    ' all variables are doubles

DIM AS INTEGER A[] = {4,5}, B, C[2] = {0,1} ' all variables are integer

DIM AS CHAR PTR PTR A, B[3]                 ' all variables are POINTERS to POINTERS of CHAR

Storage Class Specifiers

BCX recognizes the AUTO, REGISTER, EXTERN and STATIC storage class specifiers. Variables declared with the AUTO or REGISTER specifier have local persistence, that is, the values in those variables are lost when the subroutine or function in which they were declared is exited. Variables declared with the EXTERN or STATIC specifier have global persistence that is, the values in those variables are retained when the subroutine or function in which they were declared is exited.

AUTO storage class specifier

When AUTO is used within a SUB or FUNCTION procedure, an automatic variable with scope limited to the block in which it was declared, is created. AUTO is the default storage class for local variables but must be explicitly specified when programming threads. When invoked outside of a procedure, AUTO is processed in the same way as the DIM declarator.

Syntax:

AUTO AutoVar AS DataType

Parameters:

  • Data type: Identifier
    AutoVar name of variable
  • DataType any valid data type.

Remarks:

👉 If compiling with C++, be aware that since 2011, the C++11 standard defines a revised meaning for the auto keyword. Before C++11, the auto keyword declares a variable in the automatic storage class as described above. Starting with C++11, the auto keyword was repurposed and declares a variable whose type is deduced from the initialization expression in its declaration. The Microsoft C++ compiler has the /Zc:auto- option to enable the pre-C++11 automatic storage class meaning of the auto keyword.

The following BCX example will not compile with a C++ compiler default settings.

CALL UsedAUTO()

SUB UsedAUTO ()
 AUTO AutoVar
 AutoVar = 1
 PRINT AutoVar
END SUB

EXTERN storage class specifier

When EXTERN is used, the variable declared with EXTERN becomes a reference to a variable with the same name defined externally in any source files of the program. The EXTERN declaration makes the external-level variable definition visible within the block. A variable declared with the EXTERN keyword is visible only in the block in which it is declared unless the external variable has been declared as a global.

Syntax:

EXTERN ExtVar AS DataType

Parameters:

  • Data type: Identifier
    ExtVar name of variable, same as externally defined name.
  • DataType The type of ExtVar must match the type originally used to declare it in the external module.

Remarks:

For an EXTERN usage example, please see the SHAREDSET demonstration program.

STATIC storage class specifier

When STATIC is used within a SUB or FUNCTION, to declare a variable, the variable will retain its value from call to call. When DIM or LOCAL is used within a SUB or FUNCTION to declare a variable, the variable will not retain its value from call to call. STATIC variables are automatically initialized (set to zero value) only the first time they are declared.

Syntax:

STATIC StatVar AS DataType

Parameters:

  • Data type: Identifier
    StatVar name of variable.
  • DataType any valid data type.

Example 1:

An example showing the STATIC difference:

DIM add1more%, i%, int1%

add1more% = 1

FOR i% = 1 TO 5
 int1% = Count%(add1more%)
 PRINT "Total is "; int1%
NEXT i%

FUNCTION Count% (it%)
 STATIC total%
 total% = total% + it%
 FUNCTION = total%
END FUNCTION

Result:

Total is  1
Total is  2
Total is  3
Total is  4
Total is  5

Example 2:

Here is the same example without STATIC

DIM add1more%, i%, int1%

add1more% = 1

FOR i% = 1 TO 5
 int1% = Count%(add1more%)
 PRINT "Total is "; int1%
NEXT i%

FUNCTION Count% (it%)
 DIM total%
 total% = total% + it%
 FUNCTION = total%
END FUNCTION

Result:

Total is  1
Total is  1
Total is  1
Total is  1
Total is  1

REGISTER storage class specifier

👉 Do not use REGISTER if compiling with C++. It was deprecated in the C++11 standard and removed from C++17 standard.

REGISTER is used to define local variables to be stored in a register instead of RAM.

Syntax:

REGISTER RegVar AS DataType

Parameters:

  • Data type: Identifier
    RegVar name of variable.
  • DataType The data type must be either an INTEGER type or a pointer.

Remarks:

A REGISTER variable has a maximum size equal to the register size. The unary address-of operator(&) cannot be applied to a REGISTER variable nor can the REGISTER keyword be used on arrays. REGISTER is best used with variables that need quick access.
👉 Specifying REGISTER does not mean that the variable will be stored for certain in a register.

OBJECT type definition

BCX definition:
TYPE AS OBJECT
 p_unknown AS IUnknown PTR
 pObjects[COM_STACK_SIZE] AS VARIANT
 pName[COM_STACK_SIZE][128] AS TCHAR
 pStatus AS BOOL
 ipointer AS INT
END TYPE

C/C++ definition:

typedef struct _OBJECT
 {
  IUnknown*  p_unknown;
  VARIANT    pObjects[COM_STACK_SIZE];
  TCHAR      pName[COM_STACK_SIZE][128];
  BOOL       pStatus;
  int        ipointer;
 }OBJECT, *LPOBJECT;

Example:

BCX_SHOW_COM_ERRORS(TRUE)

DIM app AS OBJECT
COMSET app = CREATEOBJECT("Excel.Application")

app.workbooks.add
app.visible = true
app.ActiveSheet.Cells(3,1).Value="Hello"
app.ActiveSheet.Cells(4,1).Value="From BCX"
app.ActiveSheet.Cells(5,1).Value="Console program!"

DIM temp_var$

temp_var$ = app.ActiveSheet.Cells(3,1).Value
MSGBOX temp_var$, "value of cell(3,1)", 4096

MSGBOX "BCX COM Example!" & CRLF$ _
& "Using Office automation to manipulate Excel." & CRLF$ _
& "Program will close Excel in 1 second.","finished!", 4096
SLEEP(1000)
app.activeworkbook.saved = true
app.quit
COMSET app = NOTHING

For more examples of the BCX COM functions see the COM directory at the https://bcxbasiccoders.com/archives/YahooGroups/Com/ website.

Related topics: CreateObject | Set Nothing | List of all COM Interface Functions

RAW statement

Purpose: RAW can be used to create uninitialized variables. RAW does not clear the memory block of the created variable by filling it with zeros. A RAW variable created in a subroutine or function is not STATIC in retaining a value between calls to the procedure.

Syntax:

RAW VariableName

Parameters:

  • Data type: Identifier
    VariableName The name of the variable being declared.

Remarks:

RAW does not initialize variables inside SUB or FUNCTION procedures, for example,

SUB RawSub1 ()
 RAW str1$
END SUB

translates to C source code

void RawSub1 (void)
{
  char    str1[BCXSTRSIZE];
}

while

SUB RawSub1 ()
 DIM str1$
END SUB

translates to C source code

void RawSub1 (void)
{
  char    str1[BCXSTRSIZE]={0};
}

RAW used outside of a SUB or FUNCTION procedure is the same as STATIC, for example,

RAW a$

translates to C source code

static char    a[BCXSTRSIZE];

LOCAL variables slow things down a bit because they need to be zero'd out each time. RAW are the fastest but require that you give them meaningful values as needed. Consider this ... why take the time to zero an integer if you unconditionally assign it a value.

Type Qualifiers

CONST data type qualifier

The CONST data type qualifier specifies that the value of the data in the identifier is unmodifiable.

When using CONST in BCX it must be declared, on one line, with an initialized value, in this syntactic order,
DIM AS CONST DataType Identifier = Value

Remarks:

BCX will preprocess this:

CONST SINGLE MyVar = -3.14159

into this:

DIM AS CONST SINGLE MyVar = -3.14159

BCX allows the data types in the following table to be used for this purpose.

CONST CHAR[]    MyVar = "Hello, World"
CONST CHAR      MyVar = 0 -TO- 255
CONST BYTE      MyVar = 0 -TO- 255
CONST LDOUBLE   MyVar = teeny weenie -TO- SUPER GIGANTIC
CONST DOUBLE    MyVar = 4.94065645841246544 × 10^-324 -TO- ±1.79769313486231570 × 10^308
CONST SINGLE    MyVar = 1.17549435 × 10^-38 -TO- ±3.40282347 × 10^38
CONST FLOAT     MyVar = 1.17549435 × 10^-38 -TO- ±3.40282347 × 10^38
CONST DWORD     MyVar = 0 -TO- 4,294,967,295
CONST INT       MyVar = −32,768 -TO- 32,767
CONST INTEGER   MyVar = −32,768 -TO- 32,767
CONST LLONG     MyVar = −9,223,372,036,854,775,808 -TO- 9,223,372,036,854,775,807
CONST LONG      MyVar = −2,147,483,648 -TO- 2,147,483,647
CONST LONGLONG  MyVar = −9,223,372,036,854,775,808 -TO- 9,223,372,036,854,775,807
CONST SBYTE     MyVar = -128 -TO- 127
CONST SHORT     MyVar = -32,768 -TO- 32,767
CONST UBYTE     MyVar = 0 -TO- 255
CONST UCHAR     MyVar = 0 -TO- 255
CONST UINT      MyVar = 123456789
CONST UINT64    MyVar = 0 -TO- 18,446,744,073,709,551,615
CONST ULONG     MyVar = 0 -TO- 4,294,967,295
CONST ULONGLONG MyVar = 0 -TO- 18,446,744,073,709,551,615
CONST USHORT    MyVar = 0 -TO- 65,535

Example 1:

The contents of the string constant, TheWord, cannot be changed in this example.

DIM AS CONST CHAR TheWord[] = "My word is immutable !"
PRINT TheWord

Result:

My word is immutable !

If code is added to attempt a change, as in this example,

DIM AS CONST CHAR TheWord[] = "My word is immutable !"
PRINT TheWord
TheWord = "Nothing lasts forever !"
PRINT TheWord

the compiler will error out on the third line where an attempt is made to change TheWord,

Result:

Pelles C
error #2140: Type error in argument 1 to 'strcpy'; expected 'char * restrict' but found 'const char *'.

Microsoft
error C2664: 'char *strcpy(char *,const char *)': cannot convert argument 1 from 'const char [23]' to 'char *'

Nuwen MinGW
error: invalid conversion from 'const char*' to 'char*'

Example 2:

DIM AS CONST CHAR A[] = "I Like Dogs."   ' This has GLOBAL SCOPE and cannot be changed 
DIM AS CONST INT  B    = 12345           ' This has GLOBAL SCOPE and cannot be changed 

PRINT A$
PRINT B

PRINT
CALL Foo
PRINT

PRINT A$
PRINT B


SUB Foo
 ' 
 ' All of these variables have LOCAL SCOPE and cannot be changed 
 ' 
 DIM AS CONST CHAR   A[] = "I Like Frogs."
 DIM AS CONST INT    B   = 56789
 DIM AS CONST INT    C   = 123
 DIM AS CONST SINGLE D   = 123.456
 DIM AS CONST DOUBLE E   = 456.789

 PRINT A$
 PRINT B
 PRINT C
 PRINT D
 PRINT E
END SUB

Result:

I Like Dogs.
 12345

I Like Frogs.
 56789
 123
 123.456
 456.789

I Like Dogs.
 12345

VOLATILE data type qualifier

The VOLATILE data type qualifier specifies that the memory access to the variable, array or other data object is to be consistent. VOLATILE data can have its value changed without the control or detection of the compiler, for example, by the system clock or other program updating a variable.

Here are some examples of data declarations with the VOLATILE data type qualifier.

TYPE z
 DIM VOLATILE a AS INTEGER
 DIM c[20] AS CHAR
END TYPE

DIM VOLATILE ddd
GLOBAL VOLATILE aaa AS INTEGER
SHARED VOLATILE BBB AS z
EXTERN VOLATILE ccc AS INTEGER

SUB v ()
 STATIC VOLATILE zz AS z

END SUB

Declaring Global Variables

Variables declared with DIM at the file scope level of the program are automatically given GLOBAL scope. They can be used anywhere in a program.

Also, using the GLOBAL or SHARED keywords will create GLOBAL variables anywhere in a program .

All variable names with GLOBAL scope must be unique, including variables created using DIM in the main portion of the program. That means that a program cannot have a GLOBAL variable named A$ and another named A%. However, a variable named A$ and another named a$ can co-exist as GLOBAL variables because BCX variable names are case sensitive. Therefore, A$ and a$ are seen as different variables.

When declared within a SUB or FUNCTION, a GLOBAL variable, in the C translation, is moved to the file scope level and, there, is specified as a static storage class.

DIM add1more%, i%, int1%

add1more% = 1

FOR i% = 1 TO 5
 int1% = Count%(add1more%)
 PRINT "Total is "; int1%
NEXT i%
?
PRINT "total%,", total%, ", is accessible from file scope level."

FUNCTION Count% (it%)
 GLOBAL total%
 total% = total% + it%
 FUNCTION = total%
END FUNCTION

Result:

Total is  1
Total is  2
Total is  3
Total is  4
Total is  5

total%, 5, is accessible from file scope level.

If, in the above example, the

GLOBAL total%

is changed to

LOCAL total%

the program would be translated by BCX but the C compiler would error the 'total' identifier, as undeclared, when used outside of the 'Count%' function.

👉 In a BCX GUI program, all variable initializations must be inside a FUNCTION, SUB or the BEGIN EVENTS ... END EVENTS procedure.

Declaring Local Variables

When DIM or LOCAL is used within a BLOCK to declare a variable, the variable is LOCAL to that BLOCK, in scope, which begins at the point of declaration and terminates at the end of the BLOCK, procedure or statement in which it was declared. In other words, the variable is unknown to the rest of the program.

Further to the above, nested BLOCK structures have scope limited to to each nesting level.

BLOCK structures include

A variable declared with DIM or LOCAL in a subroutine or function retains, and can return, the value on exit, but will lose it on re-entry due to automatic initialization to NULL. This is not the case with returning a LOCAL scope variable pointer.
👉 The only time it is "safe" to return the address of a local scope variable, declared inside a BLOCK is when that variable is declared STATIC.

STATIC ensures that a variable's storage location and memory allocation will not change. The contents may change under your app's control but the address of the variable won't.

Returning the address of a DIM or LOCAL variable is, at least, unreliable and, at most, dangerous because when the function concludes, those variables go out of scope. Those variables were created on the Windows memory stack which is constantly changing and being reused. In fact, most compilers try to warn you of this in one way or another:

This is important when returning pointers, as demonstrated in the following example.

GLOBAL B AS CHAR PTR

B = foo("Hello")
'B$ is now using the storage provided by
'STATIC A$ in function foo
B$ = B$ + " World"
PRINT B$

PRINT foo$("Second call to foo")
PRINT B$

END

FUNCTION foo (text$) AS LPSTR
STATIC A$ ' If this were changed to
' DIM  A$, LOCAL A$, or RAW A$
' a compile warning would occur with unpredictable results on execution.
A$ = "(" + text$ + ")"
FUNCTION = A
END FUNCTION

Result:

(Hello) World
(Second call to foo)
(Second call to foo)

Example:

DIM A!            ' Global
DIM B!            ' Global
DIM C!            ' Global

C! = 100.123     '<<< This value Should Not Change!
B! = 123         '<<< This Value Should Not Change!
A! = Fun!(B!, C!) '<<< C allows type translation automatically!

PRINT "The Value Of A! = ", A!
PRINT "The Value Of B! Should Still Be <123> ...", B!

FUNCTION Fun! (Y%, Z%)
  DIM A$
  DIM B!
  A$ = "Hello from inside our function!" ' A$ is a local string variable
  PRINT A$
  ' On the next code line, B! is of local scope, only accessible in FUNCTION Fun
  ' C! is global, accessible from anywhere
  ' Z% and Y% are function parameter variables of local scope
  B! = 3 * Z% + C! + Y%
  FUNCTION = B!
END FUNCTION

Result:

Hello from inside our function!
The Value Of A! =  523.123
The Value Of B! Should Still Be <123> ... 123

Remarks:

👉 In a BCX GUI program, all variable initializations must be inside a FUNCTION, SUB or the BEGIN EVENTS ... END EVENTS procedure.

👉 LOCAL quoted literal string initialized declarations inside a SUB or FUNCTION can be made. This will not work for GLOBAL strings. Neither will it work for dynamic strings that are created using any of the memory allocation functions: malloc/calloc/realloc. String functions and concatentations are not allowed -- only plain quoted string literals will work, like in the examples below:

$BCXVERSION "7.7.2"

CALL OnlyInProcedures()

SUB OnlyInProcedures ()
  DIM A$     = "This is handy!"
  DIM RAW B$ = "This is too!"
  LOCAL C$   = "This also works!"

  PRINT A$
  PRINT B$
  PRINT C$
END SUB

Result:

This is handy!
This is too!
This also works!

BCX translates the following initialized quoted string literal:

DIM A$ = "This is handy!"

to the following C language code.

char A[BCXSTRSIZE];  strcpy(A,"This is handy");

BCX Console Sample Programs using the LOCAL declaration declaration.

PRIVATE CONST statement

Purpose:

PRIVATE CONST creates an integer constant that is local in scope within a SUB or FUNCTION.

👉 PRIVATE CONST must be translated with the -c command line flag or with $CPP or $CPPHDR directives and compiled with a C++ compiler. PRIVATE CONST cannot be used with strings or expressions.

Example:

CALL One()
CALL Two()

SUB One ()
 PRIVATE CONST ONE = 1
 PRIVATE CONST TWO = 2
 PRINT ONE,TWO
END SUB

SUB Two ()
 PRIVATE CONST ONE = 3
 PRIVATE CONST TWO = 4
 PRINT ONE,TWO
END SUB

Declaring Dynamic Strings

BCX provides dynamically sized, one dimensional strings.

Dynamically declared strings are limited only by available memory. A string sized as

DIM DynaVar$ * 5000000

would allocate five megabytes for the string variable DynaVar$.

It is important to remember, when sizing a dynamic string, that BCX uses C strings which are terminated with a single byte NULL character to mark the end of the string. All BCX dynamic strings must be sized large enough to include this terminator. For example, if a string contains 15 characters then it must be sized to at least 16 bytes.

FREE statement

It is possible to create huge  dynamically declared string variables which can consume multi-megabytes of memory. When a dynamic string variable is declared and used inside a SUB or FUNCTION, BCX takes care of the string memory deallocation code.
👉 When a dynamic string variable has been declared and used outside of a SUB or FUNCTION, after the variable is no longer needed, the memory space allocated must be returned back to Windows by using the FREE keyword. It is up to the programmer to determine at what point in the program to FREE the variable. This must be done to guard against memory leaks which can adversely affect system performance, and in worse cases cause a crash due to an out of memory condition.

Here is a complete example:

DIM Buffer$ * (1000 * LEN("Line No. ") + 1000 * 10)
DIM A AS INTEGER

FOR A = 1 TO 1000
 Buffer$ = Buffer$ & "Line No. " & STR$(A) & CHR$(13) & CHR$(10)
NEXT

PRINT Buffer$
A = LEN(Buffer$)
PRINT "The length of Buffer$ = ", A , " bytes."
FREE Buffer$        'release memory back to Windows

Result:

Line No.  1
 ...
Line No.  1000

The length of Buffer$ = 14893 bytes.

Remarks:

Freeing strings is very important if they are in a loop or in a procedure that may be called several times. If the variable is not freed before it is declared again, a "memory leak" occurs with an additional chunk of memory allocated in which to store the string each time the dynamic variable is declared. Unless deallocated with FREE, the last chunk of memory is not available for use. If this happens in an often repeated loop, the memory can be used up to the point of causing the machine to crash. Finding the appropriate point to FREE a variable is not simple and requires a thorough understanding of how the program is structured.

Declaring Dynamic Strings inside a SUB or FUNCTION

Dynamic strings can be declared inside a SUB or FUNCTION using the following syntax:

Syntax:

DIM A3$ * 2048

Purpose:

  • Allocates space for a 2048 byte LOCAL dynamic string.

Syntax:

 GLOBAL Buffar$ * lenbuf

Purpose:

  • Allocates space the size of lenbuf for a GLOBAL dynamic string.

Syntax:

 DIM LOCAL A4$ * 1

Purpose:

  • Purpose: Allocates space for a 1 byte local dynamic string

Syntax:

 LOCAL A5$ * 1024

Purpose:

  • Purpose: Allocates space for a 1024 byte local dynamic string

👉 BCX uses C strings which are terminated with an NULL character. A string must be sized large enough to include this terminator.

If declared in a SUB or FUNCTION, the variable will be local in scope. Locally declared dynamic strings must exist on the base level of the SUB or FUNCTION. They must not be declared inside any IF...ENDIF, FOR...NEXT, SELECT...END SELECT, or LOOP structures. The most appropriate place for these statements is immediately after the SUB or FUNCTION declaration.

Although it is perfectly legal to declare GLOBAL dynamic strings within a FUNCTION or SUB procedure, it is best if the string is declared in the initialization section of the program and REDIM then is used to modify the size of the string in the procedure. Be sure to FREE the memory allocated for the GLOBAL dynamic string when it is no longer needed.

GLOBAL Z$ * 1000             ' Z$ is global

SUB FOO
 DIM    a$ * 1000            ' a$ is LOCAL with automatic Free
 LOCAL  b$ * 1000            ' b$ is LOCAL with automatic Free
 GLOBAL c$ * 1000, d$ * 1000 ' c$ and d$ are global
 DoSomeThing()
 FREE c$                      ' GLOBAL MUST be freed before exit
 FREE d$                      ' GLOBAL MUST be freed before exit
END SUB

Declaring Dynamic Strings outside a SUB or FUNCTION

Creating a dynamic variable using DIM or GLOBAL outside a SUB or FUNCTION will create a GLOBAL dynamic string.

Dynamic strings outside a SUB or FUNCTION procedure can be declared with the following syntaxes:

Syntax
DIM DynStrVar$ * 2048

Purpose:

  • The above declaration creates a C language char * data type variable named DynStrVar and allocates a 2048 byte space for a GLOBAL string.
Syntax
GLOBAL Buffar$ * lenbuf

Purpose:

  • Allocates space the size of lenbuf% for a GLOBAL dynamic string.

Dynamically declared string and array variables can be cleared and resized, increasing or decreasing a variable's size, using the REDIM statement.
👉 In GUI programs, a dynamic string must be declared inside a BEGIN EVENTS ... END EVENTS structure or inside a FUNCTION or a SUB procedure.
👉 When using $NOMAIN

BEGINBLOCK ...ENDBLOCK statements

Purpose:

The BEGINBLOCK ... ENDBLOCK statements allow BCX to limit the scope of allocated varibles. A BEGINBLOCK statement is placed before the section to which you want to limit the scope and an ENDBLOCK statement specifies the end of the scope limited section.

Syntax:

BEGINBLOCK
 Variables defined here will be limited in scope
 to the section between the BEGINBLOCK ... ENDBLOCK statements.
ENDBLOCK

Example:

DIM sProg$

sProg$ = "This Program will show two MSGBOXs with different results" & CRLF$
sProg$ = sProg$  & "DIM i" & CRLF$
sProg$ = sProg$  & "i = 2" & CRLF$
sProg$ = sProg$  & "BEGINBLOCK" & CRLF$
sProg$ = sProg$  & "DIM i" & CRLF$
sProg$ = sProg$  & "i = 5" & CRLF$
sProg$ = sProg$  & "MSGBOX " & ENC$("i =") & " & STR$(i)" & CRLF$
sProg$ = sProg$  & "ENDBLOCK" & CRLF$
sProg$ = sProg$  & "MSGBOX " & ENC$("i =") & " & STR$(i)" & CRLF$

MSGBOX sProg$
DIM i
i = 2

BEGINBLOCK
 DIM i
 i = 5
 MSGBOX "i =" & STR$(i)
ENDBLOCK

MSGBOX "i =" & STR$(i)

Declaring and Dimensioning Arrays

As with BCX common variables, an array declaration is made, most commonly, using this syntax.

DIM ArrayName[NumberOfElements] AS DataType

An array declaration also can be made, with a forward propagation of variable type, using this syntax

DIM AS DataType ArrayName[NumberOfElements]

In a declaration without a data type as, for example,

DIM ArrayName[NumberOfElements]

the array is considered to be an INTEGER data type.

Any BCX sigil (%, !, # or $) can be used to indicate the data type of the array as, for example, the % sigil appended to ArrayName

DIM ArrayName%[NumberOfElements]

specifies that the array is to be declared as an INTEGER data type.

Dimensions

A one dimensional array can be declared using this syntax.

DIM AS DataType TheArray[NumberOfElements]

Example:

One dimensional array.

DIM AS INTEGER TheArray[10]

TheArray[0] =  1
TheArray[1] =  2
TheArray[2] =  3
TheArray[3] =  4
TheArray[4] =  5
TheArray[5] =  6
TheArray[6] =  7
TheArray[7] =  8
TheArray[8] =  9
TheArray[9] = 10

FOR INTEGER i = 0 TO 9
 PRINT TheArray[i]
NEXT i

Result:

1
2
3
4
5
6
7
8
9
10

The following code shows the syntax for a two dimensional array

DIM AS DataType TheArray[NumberOfElements,NumberOfElements2]

which also can be declared using this syntax.

DIM AS DataType TheArray[NumberOfElements][NumberOfElements2]

Example:

Two dimensional array.

DIM AS INTEGER TheArray[3, 3]
 
TheArray[0, 0] = 1
TheArray[0, 1] = 2
TheArray[0, 2] = 3
TheArray[1, 0] = 4
TheArray[1, 1] = 5
TheArray[1, 2] = 6
TheArray[2, 0] = 7
TheArray[2, 1] = 8
TheArray[2, 2] = 9
 
FOR INTEGER i = 0 TO 2
 FOR INTEGER i2 = 0 TO 2
  PRINT TheArray[i, i2]
 NEXT i2
NEXT i

Result:

1
2
3
4
5
6
7
8
9

Initialization of Arrays

The elements of an array can be initialized, that is, given a value, at the time of declaration by using a brace-enclosed list of comma-separated constant expressions.

👉 There is a length limitation of 128 bytes for the total length of the initializers list between the first brace "{" and the final brace "}". For arrays containing larger sets of elements, use the SET ... END SET statements.

Example 1:

The one-dimensional array definition in this example is a completely initialized demonstration of this technique.

DIM AS INTEGER TheArray[10] = {1,2,3,4,5,6,7,8,9,10}
 
FOR INTEGER i = 0 TO 9
 PRINT "TheArray%[", STR$(i%, 1), "] = ", TheArray%[i]
NEXT i

Result:

TheArray%[0] =  1
TheArray%[1] =  2
TheArray%[2] =  3
TheArray%[3] =  4
TheArray%[4] =  5
TheArray%[5] =  6
TheArray%[6] =  7
TheArray%[7] =  8
TheArray%[8] =  9
TheArray%[9] =  10

Example 2:

Here is a two-dimensional array demonstration of this technique.

DIM AS INTEGER TheArray[2,5] = {{1,2,3,4,5},{6,7,8,9,10}}

 FOR INTEGER i = 0 TO 1
  FOR INTEGER i2 = 0 TO 4
   PRINT "TheArray%[", STR$(i, 1), ",", STR$(i2, 1),"] = ", TheArray[i, i2]
  NEXT i2
 NEXT i

Result:

TheArray%[0,0] =  1
TheArray%[0,1] =  2
TheArray%[0,2] =  3
TheArray%[0,3] =  4
TheArray%[0,4] =  5
TheArray%[1,0] =  6
TheArray%[1,1] =  7
TheArray%[1,2] =  8
TheArray%[1,3] =  9
TheArray%[1,4] =  10

Example 3:

And this is a three-dimensional array demonstration of this technique.

DIM AS INTEGER TheArray[2,3,5] = _
{ _
 { {1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15} }, _
 { {16,17,18,19,20}, {21,22,23,24,25}, {26,27,28,29,30} } _
}

FOR INTEGER i = 0 TO 1
 FOR INTEGER i2 = 0 TO 2
  FOR INTEGER i3 = 0 TO 4
   PRINT "TheArray%[", STR$(i, 1), ",", STR$(i2, 1), ",", STR$(i3, 1),"] = ", TheArray[i, i2, i3]
  NEXT i2
 NEXT i3
NEXT i

Result:

TheArray%[0,0,0] =  1
TheArray%[0,0,1] =  2
TheArray%[0,0,2] =  3
TheArray%[0,0,3] =  4
TheArray%[0,0,4] =  5
TheArray%[0,1,0] =  6
TheArray%[0,1,1] =  7
TheArray%[0,1,2] =  8
TheArray%[0,1,3] =  9
TheArray%[0,1,4] =  10
TheArray%[0,2,0] =  11
TheArray%[0,2,1] =  12
TheArray%[0,2,2] =  13
TheArray%[0,2,3] =  14
TheArray%[0,2,4] =  15
TheArray%[1,0,0] =  16
TheArray%[1,0,1] =  17
TheArray%[1,0,2] =  18
TheArray%[1,0,3] =  19
TheArray%[1,0,4] =  20
TheArray%[1,1,0] =  21
TheArray%[1,1,1] =  22
TheArray%[1,1,2] =  23
TheArray%[1,1,3] =  24
TheArray%[1,1,4] =  25
TheArray%[1,2,0] =  26
TheArray%[1,2,1] =  27
TheArray%[1,2,2] =  28
TheArray%[1,2,3] =  29
TheArray%[1,2,4] =  30

Example 4:

shows a partially initialized one dimensional array.

DIM TheArray%[3] = { 2, 4 }

PRINT "TheArray%[0] =", TheArray%[0]
PRINT "TheArray%[1] =", TheArray%[1]
PRINT "TheArray%[2] =", TheArray%[2]

Result:

TheArray%[0] = 2
TheArray%[1] = 4
TheArray%[2] = 0

Example 5:

shows how to specify which elements of an array are to be initialized.

DIM TheArray%[3] = { [0] = 2, [2] = 8 }

PRINT "TheArray%[0] =", TheArray%[0]
PRINT "TheArray%[1] =", TheArray%[1]
PRINT "TheArray%[2] =", TheArray%[2]

Result:

TheArray%[0] = 2
TheArray%[1] = 0
TheArray%[2] = 8

Example 6:

shows how to initialize elements of an array in which the index size is not specified.

DIM TheArray%[ ] = { 2, 4, 8 }

PRINT "TheArray%[0] =", TheArray%[0]
PRINT "TheArray%[1] =", TheArray%[1]
PRINT "TheArray%[2] =", TheArray%[2]

Because no index size was specified for array2%, three initialized elements are defined by the compiler.

Result:

TheArray%[0] = 2
TheArray%[1] = 4
TheArray%[2] = 8

Example 7:

shows how to use variables as elements in an array in which both the array and the element variables have global scope. The example also shows how to access the values efficiently using the CRT function "memmove", which is wrapped into a macro.

DIM Ind0
DIM Ind1
DIM Ind2

DIM RAW TheArray[3]= {&Ind0, &Ind1, &Ind2} AS LPVOID

Ind0 = 1
Ind1 = 2
Ind2 = 3

CALL Ordinate()

SUB Ordinate ()
  DIM i AS INTEGER

  STORE(i,TheArray[0]) : PRINT i
  STORE(i,TheArray[1]) : PRINT i
  STORE(i,TheArray[2]) : PRINT i
END SUB

MACRO STORE(src,des) memmove(&src,des,SIZEOF(src))

Result:

1
2
3

FILLARRAY function

Purpose:

FILLARRAY will fill an INTEGER, SINGLE, DOUBLE, or LDOUBLE data type array with values from a comma separated string.

Syntax:

RetVal = FILLARRAY(InputStr AS STRING, _
              vt_ArrayType AS INTEGER, _
                    MaxCnt AS INTEGER, _
                                Array)

Return Value:

  • Data type: INTEGER
    RetVal upper bound of the array.

Parameters:

  • Data type: STRING
    InputStr String holding a comma separated list of numbers.
  • Data type: INTEGER
    vt_ArrayType Can be vt_INTEGER or 2, vt_SINGLE or 3, or vt_DOUBLE or 4, or vt_LDOUBLE or 5.
  • Data type: INTEGER
    MaxCnt Maximum useable upper bound of array.
  • Data type: Identifier
    Array Array to be filled.

Example:

DIM in$, i, j, k, y, f#
DIM d#[20,20]

'Create a test file(normally this file would be created by some other program) 
OPEN "test.data" FOR OUTPUT AS FP1
FPRINT FP1, "1.1,3.3,5.5,7.7,9.9,11.11"
FPRINT FP1, "1.2,4.3,6.5,8.6,9.9,11.11"
FPRINT FP1, "5.2,4.4,6.4,3.7,2.9,12.11"
FPRINT FP1, "3.2,1.4,4.6,3.8,3.9,12.13"
FPRINT FP1, "4.2,2.4,6.4,3.5,2.9,10.11"
FPRINT FP1, "2.6,1.2,4.5,9.1,7.9,10.01"
CLOSE FP1


OPEN "test.data" FOR INPUT AS FP1
j = 20
i = 0
WHILE NOT EOF(FP1)
  LINE INPUT FP1, in$
  y = FILLARRAY(in$, vt_DOUBLE, j, &(d#[i,0]))
  i++
WEND
CLOSE FP1

PRINT "Input Matrix"
FOR i = 0 TO 5
  FOR j = 0 TO 5
    PRINT USING$("###.### " ,d#[i,j]);
  NEXT
  PRINT
NEXT

FOR i = 0 TO 4
  IF d#[i,i] = 0 THEN
    FOR k = i+1 TO 5
      IF d#[k,i] <> 0.0 THEN
        EXIT FOR
      END IF
    NEXT
    IF k > 5 THEN
      EXIT FOR
    END IF
    FOR j = 0 TO 5
      SWAP d#[i,j], d#[k,j]
    NEXT
  END IF
  FOR j = i+1 TO 5
    f# = -d#[j,i]/d#[i,i]
    FOR k = i TO 5
      d#[j,k] = d#[j,k] + f# * d#[i,k]
      IF ABS(d#[j,k]) < .00000001 THEN d#[j,k] = 0.0
    NEXT
  NEXT
NEXT

PRINT
PRINT "Transformed Matrix"
FOR k = 0 TO 5
  FOR j = 0 TO 5
    PRINT USING$("####.### ", d#[k, j]);
  NEXT
  PRINT
NEXT

f# = 1.
FOR i = 0 TO 5
  f# = f# * d#[i,i]
NEXT
PRINT
PRINT "Determinate = "; USING$("#####.#####", f#)

Result:

Input Matrix
 1.1000 3.3000 5.5000 7.7000 9.9000 11.1100
 1.2000 4.3000 6.5000 8.6000 9.9000 11.1100
 5.2000 4.4000 6.4000 3.7000 2.9000 12.1100
 3.2000 1.4000 4.6000 3.8000 3.9000 12.1300
 4.2000 2.4000 6.4000 3.5000 2.9000 10.1100
 2.6000 1.2000 4.5000 9.1000 7.9000 10.0100

Transformed Matrix
 1.1000 3.3000 5.5000 7.7000 9.9000 11.1100
 0.0000 0.7000 0.5000 0.2000 -0.9000 -1.0100
 0.0000 0.0000 -11.6000 -29.5000 -58.3000 -56.5700
 0.0000 0.0000 0.0000 -2.1611 -7.5852 -4.9904
 0.0000 0.0000 0.0000 0.0000 4.1363 -1.2320
 0.0000 0.0000 0.0000 0.0000 0.0000 -16.8836

Determinate = -1,348.03105

BCX Console Sample Programs using the FILLARRAY function.

BYTE_AT macro

If referencing by pointer a location in a string element of an array of strings, the BYTE_AT macro must be used. For example:

DIM A$[5]  : A$[3] = "FUN!"
IF BYTE_AT(A[3][0]) = ASC("F") THEN PRINT "TRUE"

👉 While BCX will translate the following code witout error, the output C code will not compile.

DIM A$[5]  : A$[3] = "FUN!"
IF  A[3][0] = ASC("F") THEN PRINT "TRUE"

DYNAMIC arrays

DYNAMIC arrays can be any data type and may be global or local. DYNAMIC arrays differ from static arrays in that DYNAMIC arrays can be resized by using REDIM.

Syntax:

DIM DYNAMIC A[10, 10]

Purpose:

  • Allocates a two dimensional array of integers.

Syntax:

DIM DYNAMIC B![10, 10]

Purpose:

  • Allocates a two dimensional array of single floating point numbers.

Syntax:

DIM DYNAMIC C#[10, 10]

Purpose:

  • Allocates a two dimensional array of double floating point numbers

Syntax:

DIM DYNAMIC D$[10, 10]

Purpose:

  • Allocates a two dimensional 10 by 10 array of 2048 byte strings.

Syntax:

   DIM DYNAMIC E[10, 10]

Purpose:

  • Allocates an single dimensioned array of 10 10 byte strings

The default string length of each element in a DYNAMIC string array is 2048 bytes, consistent with the rest of BCX. However, the default can be overridden by adding the AS CHAR data type qualifier. This causes the second parameter to define the string length of each element, similar to declaring static string arrays:

Default:      DIM DYNAMIC A$[1000] ' 2048 bytes per cell
User defined: DIM DYNAMIC A$[1000,80] AS CHAR ' 80 bytes per cell

GLOBAL DYNAMIC arrays must be deallocated using

FREE ArrayName

LOCAL DYNAMIC arrays in most instances are automatically freed. However, one exception to this is that when they are declared within a FUNCTION MAIN() under a $NOMAIN directive.

👉 When declaring a DYNAMIC array of files, the data type must be specified AS FILE PTR.

👉 When dimensioning a DYNAMIC array, do not append a % sigil to an array index subscript variable name.

DIM DYNAMIC A$[E]

is legal, but

DIM DYNAMIC A$[E%]

is not legal and will cause compiler errors.
👉 When dimensioning a DYNAMIC array, using a floating point variable as an index subscript in an array will result in undefined behavior.

👉 In GUI programs, when declaring a DYNAMIC array, the DIM DYNAMIC, LOCAL DYNAMIC, or GLOBAL DYNAMIC statement must appear inside a BEGIN EVENTS ... END EVENTS structure or inside a FUNCTION or a SUB. A good place to do this would be in the SUB FORMLOAD or a dedicated initialization procedure.

If a GLOBAL DYNAMIC array is to be used inside a FUNCTION or a SUB, it is best if the array is declared in the initialization section of the program and REDIM then is used to modify the size of the array in the procedure.

Here is a program that demonstrates resizing a DYNAMIC array.

CLS

DIM DYNAMIC Buffer$[7,5] AS CHAR 'Seven cells, 5 bytes each 

FOR INTEGER i = 0 TO 6
 Buffer$[i] = "No" & STR$(i)
 PRINT Buffer$[i]
NEXT

? "******************"
? "Resizing ..."
? "******************"

REDIM Buffer$[20,10] AS CHAR 'twenty cells, 10 bytes each 

FOR INT i = 0 TO 19
 Buffer$[i] = "No" & STR$(i)
 PRINT Buffer$[i]
NEXT

FREE Buffer

KEYPRESS

Result:

No 0
No 1
No 2
No 3
No 4
No 5
No 6
******************
Resizing ...
******************
No 0
No 1
No 2
No 3
No 4
No 5
No 6
No 7
No 8
No 9
No 10
No 11
No 12
No 13
No 14
No 15
No 16
No 17
No 18
No 19

OPTION BASE directive

The default lower bound for an index in a user defined array in a BCX program, normally 0, can be set to another value using the OPTION BASE directive.
👉 Arrays defined in the runtime functions such as SPLIT and DSPLIT cannot use the value set by OPTION BASE but always will use the BCX default lower bound of 0.

Syntax:

OPTION BASE Number AS INTEGER

Parameters:

  • Data type: INTEGER
    Number can be any value but most commonly will be 0 or 1.

Here are a few comments and a small sample explaining how BCX treats this directive.

OPTION BASE works with static and dynamic arrays. Whenever BCX detects an OPTION BASE directive, BCX sets a global integer variable named "OptionBase" to the size of the OPTION BASE. This process can be seen in the following snippet from the BCX translator:

IF L_Stk_1$ = "option" AND L_Stk_2$ = "base" THEN
 OptionBase = VAL(Stk$[3])
 Ndx = 0
 EXIT SUB
END IF

Later on in the translation process, this code takes over:

IF OptionBase THEN
 IF Stk$[i] = "[" THEN Stk$[i] = "[" & LTRIM$(STR$(OptionBase)) & "+"
END IF

This means that you can use numerous OPTION BASE directives in your BCX source code. The thing to remember is that the current OPTION BASE value is a function of its location (line number) in your BASIC source code. Also remember that BCX reads source code in a linear manner, including BASIC source files that are merged into your main BASIC source code file using the $INCLUDE directive.

Example 1:

OPTION BASE 20
GLOBAL MyStrings$[10] ' Translated to MyStrings[30][2048]

GUI "OpBase"

SUB FORMLOAD
 DIM F AS CONTROL
 F = BCX_FORM("Option Base")
 CENTER(F)
 SHOW(F)
 OPTION BASE 1
 DIM a[10] ' Translated to a[11]
END SUB

OPTION BASE 0

SUB FOO
 DIM b[10] ' Translated to b[10]
END SUB

SUB MOO

 OPTION BASE 5

 DIM c[10] ' Translated to c[15]
END SUB

BEGIN EVENTS
END EVENTS

Example 2:

Here is an example using a DYNAMIC array.

OPTION BASE 1

DIM DYNAMIC A$[5]

FOR INTEGER I = 1 TO 5
  A$[I] = "A$[] ... THIS IS LINE " & STR$(I)
  PRINT A$[I]
NEXT

FREE A$  ' Release memory back to the operating system
FREE A$  ' An intentional error -- BCX handles it automatically

CALL FOO_TEST

SUB FOO_TEST ()
  DIM RAW E = 5
  DIM DYNAMIC A$[E]
  DIM DYNAMIC B$[E]
  DIM DYNAMIC C$[E]
  DIM DYNAMIC D$[E]

  PRINT "Storing Items In A$[]"
  FOR INTEGER I = 1 TO E
    A$[I] = "A$[] ... THIS IS LINE " & STR$(I)
  NEXT

  PRINT "Storing Items In B$[]"
  FOR INTEGER I = 1 TO E
    B$[I] = "B$[] ... THIS IS LINE " & STR$(I)
  NEXT

  PRINT "Storing Items In C$[]"
  FOR INTEGER I = 1 TO E
    C$[I] = "C$[] ... THIS IS LINE " & STR$(I)
  NEXT

  PRINT "Storing Items In D$[]"
  FOR INTEGER I = 1 TO E
    D$[I] = "D$[] ... THIS IS LINE " & STR$(I)
  NEXT

  FOR INTEGER I = 1 TO E
    PRINT A$[I]
    PRINT B$[I]
    PRINT C$[I]
    PRINT D$[I]
  NEXT

END SUB

Result:

A$[] ... THIS IS LINE  1
A$[] ... THIS IS LINE  2
A$[] ... THIS IS LINE  3
A$[] ... THIS IS LINE  4
A$[] ... THIS IS LINE  5
Storing Items In A$[]
Storing Items In B$[]
Storing Items In C$[]
Storing Items In D$[]
A$[] ... THIS IS LINE  1
B$[] ... THIS IS LINE  1
C$[] ... THIS IS LINE  1
D$[] ... THIS IS LINE  1
A$[] ... THIS IS LINE  2
B$[] ... THIS IS LINE  2
C$[] ... THIS IS LINE  2
D$[] ... THIS IS LINE  2
A$[] ... THIS IS LINE  3
B$[] ... THIS IS LINE  3
C$[] ... THIS IS LINE  3
D$[] ... THIS IS LINE  3
A$[] ... THIS IS LINE  4
B$[] ... THIS IS LINE  4
C$[] ... THIS IS LINE  4
D$[] ... THIS IS LINE  4
A$[] ... THIS IS LINE  5
B$[] ... THIS IS LINE  5
C$[] ... THIS IS LINE  5
D$[] ... THIS IS LINE  5

BCX Console Sample Programs using the OPTION BASE statement.

UBOUND function

Purpose:

UBOUND will return the largest index value of a STATIC or DYNAMIC array subscript. UBOUND is useful, in particular, for calculating the size of array SET ... END SET data.

Syntax 1:

RetVal = UBOUND(ArrayName)

Return Value:

  • Data type: INTEGER
    RetVal is the largest index value, that is, the dimensioned size - 1, of the subscript of the ArrayName array.

Parameters:

  • ArrayName The name of an array from which to retrieve the upper bound of the subscript.

Remarks:

UBOUND relies on the C/C++ preprocessor's ability to determine whether the UBOUND argument is a STATIC or a DYNAMIC array. One case where this determination becomes impossible is when UBOUND is used in a MACRO, for example,

MACRO pSTYLE_(style) = style, UBOUND(style)

in which a dependent MACRO set is used inside another MACRO. In this cirumstance neither the C/C++ preprocessor nor the BCX Translator is capable of determining whether

UBOUND(style)

refers to a STATIC or a DYNAMIC array.

👉 It is, therefore, up to the coder to determine if the array is STATIC and use the UBOUND_S function or if the array is DYNAMIC use the UBOUND_D function

Example 1:

TYPE foo
  member_1 AS INTEGER
  member_2 AS SINGLE
  member_3 AS DOUBLE
END TYPE

DIM DYNAMIC  AAA [10] AS foo         ' This is a dynamic array - the number of cell CAN be REDIM. 
DIM DYNAMIC  BBB [20] AS INTEGER     '                     --- Ditto --- 
DIM DYNAMIC  CCC [30] AS SINGLE      '                     --- Ditto --- 
DIM DYNAMIC  DDD [40] AS DOUBLE      '                     --- Ditto --- 
'===================================================================================================== 
DIM          EEE [50] AS ULONGLONG   ' This is a static array - the number of cells CANNOT be REDIM. 
DIM          FFF [60] AS STRING      '                     --- Ditto --- 

PRINT "AAA [10] Cells run from 0 to ", UBOUND (AAA)
PRINT "BBB [20] Cells run from 0 to",  UBOUND (BBB)
PRINT "CCC [30] Cells run from 0 to",  UBOUND (CCC)
PRINT "DDD [40] Cells run from 0 to",  UBOUND (DDD)
PRINT "EEE [50] Cells run from 0 to",  UBOUND (EEE)
PRINT "FFF [60] Cells run from 0 to",  UBOUND (FFF)
PAUSE

Result:

AAA [10] Cells run from 0 to  9
BBB [20] Cells run from 0 to 19
CCC [30] Cells run from 0 to 29
DDD [40] Cells run from 0 to 39
EEE [50] Cells run from 0 to 49
FFF [60] Cells run from 0 to 59

Example 2:

TYPE Prov
  Abbreviation AS STRING
  ProvName AS STRING
END TYPE

SET Province[] AS Prov
  "AB", "Alberta",
  "BC", "British Columbia",
  "MB", "Manitoba",
  "NB", "New Brunswick",
  "NL", "Newfoundland",
  "NT", "Northwest Territories",
  "NS", "Nova Scotia",
  "NU", "Nunavut",
  "ON", "Ontario",
  "PE", "Prince Edward Island",
  "QC", "Quebec",
  "SK", "Saskatchewan",
  "YT", "Yukon Territory"
END SET

FOR SIZE_T TheLoop = 0 TO UBOUND(Province)
  PRINT Province[TheLoop].Abbreviation
NEXT

PAUSE

Result:

AB
BC
MB
NB
NL
NT
NS
NU
ON
PE
QC
SK
YT

Press any key to continue . . .

UBOUND_S function

Purpose:

UBOUND_S will return the largest index value of a STATIC array subscript.

Syntax 1:

RetVal = UBOUND_S(ArrayName)

Return Value:

  • Data type: INTEGER
    RetVal is the largest index value, that is, the dimensioned size - 1, of the subscript of the ArrayName STATIC array.

Parameters:

  • ArrayName The name of a STATIC array from which to retrieve the upper bound of the subscript.

Remarks:

👉 The coder must determine if the array is STATIC and use the UBOUND_S function or if the array is DYNAMIC use the UBOUND_D function

UBOUND relies on the C/C++ preprocessor's ability to determine whether the UBOUND argument is a STATIC or a DYNAMIC array. One case where this determination becomes impossible is when UBOUND is used in a MACRO, for example,

MACRO pSTYLE_(style) = style, UBOUND(style)

in which a dependent MACRO set is used inside another MACRO. In this cirumstance neither the C/C++ preprocessor nor the BCX Translator is capable of determining whether

UBOUND(style)

refers to a STATIC or a DYNAMIC array.

UBOUND_D function

Purpose:

UBOUND_D will return the largest index value of a DYNAMIC array subscript.

Syntax:

RetVal = UBOUND_D(ArrayName)

Return Value:

  • Data type: INTEGER
    RetVal is the largest index value, that is, the dimensioned size - 1, of the subscript of the ArrayName DYNAMIC array.

Parameters:

  • ArrayName The name of an array from which to retrieve the upper bound of the subscript.

Remarks:

👉 The coder must determine if the array is STATIC and use the UBOUND_S function or if the array is DYNAMIC use the UBOUND_D function

UBOUND relies on the C/C++ preprocessor's ability to determine whether the UBOUND argument is a STATIC or a DYNAMIC array. One case where this determination becomes impossible is when UBOUND is used in a MACRO, for example,

MACRO pSTYLE_(style) = style, UBOUND(style)

in which a dependent MACRO set is used inside another MACRO. In this cirumstance neither the C/C++ preprocessor nor the BCX Translator is capable of determining whether

UBOUND(style)

refers to a STATIC or a DYNAMIC array.

ISPTR macro

ISPTR is a macro that simply says, if this is a valid element belonging to a dynamic string array, return it, otherwise return zero. This eliminates the need to know how many elements are being passed to a SUB or FUNCTION.

STRARRAY data type-declaration

STRARRAY, instructs BCX to generate code specifying that a dynamic string array is being passed to a user defined SUB or FUNCTION.

DIM DYNAMIC Buf$ [10]

Buf$[0] = "Zero"
Buf$[1] = "One"
Buf$[2] = "Two"
Buf$[5] = "Five"

CALL Foo(Buf$)

SUB Foo (A$ AS STRARRAY)
 LOCAL i
 WHILE ISPTR(A$[i])
   IF A$[i] > "" THEN PRINT A$[i]
    INCR i
 WEND
END SUB

Result:

Zero
One
Two
Five

PTR data type qualifier

Pointer variables can be created using the reserved keyword PTR.

Any of the integer, floating point or string data types listed above can be used with AS PTR appended to create a pointer variable of that data type. Here are two examples:

Syntax:

DIM LOCAL a AS INTEGER PTR

Purpose:

  • Allocates space for a pointer to integer.

Syntax:

DIM STATIC a AS SINGLE PTR

Purpose:

  • Allocates space for a pointer to single floating point number.

PTR also can be used in SUB and FUNCTION parameter lists, for example,

DIM rct AS RECT

 rct.left = 1
 rct.top = 2
 rct.right = 100
 rct.bottom = 100

 rectProc(&rct) ' The argument being passed by reference
                ' must be preceded by an ampersand.
PAUSE

 SUB rectProc (rct AS RECT PTR)
  ? rct->left
  ? rct->top
  ? rct->right
  ? rct->bottom
 END SUB

Result:

1
2
100
100

PTR PTR data type qualifier

Pointers to pointer variables can be created using the reserved keyword PTR PTR. Any of the integer, floating point or string data types listed above can be used with AS PTR PTR appended to create a a pointer to a pointer variable of that data type. Here are two examples:

Syntax:

DIM LOCAL a AS INTEGER PTR PTR

Purpose:

  • Allocates space for a pointer to a pointer to integer.

Syntax:

DIM STATIC a AS SINGLE PTR PTR

Purpose:

  • Allocates space for a pointer to a pointer to single floating point number.

PTR PTR also can be used in SUB and FUNCTION parameter lists, for example,

 DIM str1$

 str1$ = "Hello worlds"

 DIM pstr1 AS CHAR PTR

 pstr1 = str1$

 CALL Increment(&pstr1)

 PRINT pstr1$

 SUB Increment (ppstr1 AS CHAR PTR PTR)
  ++*ppstr1   
 END SUB

Result:

ello worlds

REDIM statement

Purpose: Dynamically declared arrays and string variables can be cleared and resized, increasing or decreasing an array's size, using the REDIM statement.

Syntax 1:

REDIM DynaString$ * Length AS INTEGER

Parameters:

  • Data type: STRING
    DynaString$ Name of a dynamic string to be resized.
    👉 must have an appended sigil ($) data type specifier.
  • Data type: INTEGER
    Length The resized length of the string.

Syntax 2:

REDIM Array§[Index]

Parameters:

  • Data type: Identifier
    Array§ The name of an array to be resized.
    👉 must have an appended sigil (% or ! or # or $) data type specifier.
  • Data type: INTEGER
    Index Resized subscript of the array.

Syntax 3:

REDIM Array[Index] AS data type

Parameters:

  • Data type: Identifier
    Array The name of an array to be resized, with data type specified by using the AS data type syntax.
  • Data type: INTEGER
    Index Resized subscript of the array.

Remarks:

When REDIM is used, the values in the array or string variable are not preserved because a new array is created.
👉 Although the number of elements in a dimension can be altered, the number of dimensions cannot be changed, for example, a two dimensional array cannot be changed to a three dimensional array with REDIM.

Also, here is a warning to remember that when REDIM is used to resize a global variable in a function or subroutine, the initial declaration code must physically precede the code where REDIM is used. For example,

This is valid

SUB YaGood1 ()
 GLOBAL DYNAMIC Buffer$[100]
END SUB

SUB YaGood2
 REDIM Buffer$[200]
END SUB

while this is not valid.

SUB NoGood1 ()
 REDIM Buffer$[200]
END SUB

SUB NoGood2
 GLOBAL DYNAMIC Buffer$[100]
END SUB

Example 1:

This example resizes a dynamic string.

DIM A$ * 14
A$ = "Hello, World!"
PRINT A$

REDIM A$ * 26
A$ = REPEAT$(25,"A")
PRINT A$
FREE A$

Result:

Hello, World!
AAAAAAAAAAAAAAAAAAAAAAAAA

Example 2:

This example resizes a single dimension array.

DIM DYNAMIC A$[10]
REDIM       A$[20]
A$[19] = "Hello"
PRINT A$[19]

Result:

Hello

Example 3:

This example redimensions GLOBAL DYNAMIC global arrays within SUB procedures.

DIM DYNAMIC a [0] AS STRING
DIM DYNAMIC b [0] AS DOUBLE
DIM Cells
    
CALL Resize_String(ADDRESSOF a)
Cells = UBOUND_D(a)

FOR INT i = 0 TO Cells
  IF LEN(a[i]) THEN PRINT a[i]
NEXT

PRINT

Resize_Double(ADDRESSOF b)
Cells = UBOUND_D(a)

FOR INT i = 0 TO Cells
  IF b[i] THEN PRINT b[i]
NEXT
PAUSE

SUB Resize_String (BYREF R AS CHAR PTR PTR)
  REDIM R$[10]

  FOR INT i = 0 TO 9
    R$[i] = ""
  NEXT

  R$[0] = "this"
  R$[1] = "is"
  R$[2] = "a"
  R$[3] = "very"
  R$[4] = "good"
  R$[5] = "test"
END SUB

SUB Resize_Double (BYREF R AS DOUBLE PTR)
  REDIM R[10]
  
  FOR INT i = 0 TO 9
    R[i] = 0
  NEXT
  
  R[0] = PI * 1
  R[1] = PI * 2
  R[2] = PI * 3
  R[3] = PI * 4
END SUB

Result:

 this
 is
 a
 very
 good
 test

  3.14159265358979
  6.28318530717959
  9.42477796076938
  12.5663706143592

REDIM PRESERVE statement

Dynamically declared variables and arrays can be resized, increasing or decreasing the size of a dimension, with the contents of the object unchanged using the REDIM PRESERVE statement.

Syntax 1:

REDIM PRESERVE DynaString$ * Length

Parameters:

  • Data type: STRING
    DynaString$ Name of a dynamic string to be resized.
    👉 must have an appended sigil ($) data type specifier.
  • Data type: INTEGER
    Length The resized length of the string.

Syntax 2:

REDIM PRESERVE Array§[Index]

Parameters:

  • Data type: Identifier
    Array§ The name of an array to be resized.
    👉 must have an appended sigil (% or ! or # or $) data type specifier.
  • Data type: INTEGER
    Index Resized subscript of the array.

Syntax 3:

REDIM PRESERVE Array[Index] AS data type

Parameters:

  • Data type: Identifier
    Array The name of an array to be resized, with data type specified by using the AS data type syntax.
  • Data type: INTEGER
    Index Resized subscript of the array.

Remarks:

When REDIM PRESERVE is used, the values in the string or array variable are preserved up to the lesser of the new and old sizes.
👉 Although the size of a dimension can be altered, the number of dimensions cannot be changed, for example, a two dimensional array cannot be changed to a three dimensional array with REDIM PRESERVE.

REDIM PRESERVE supports all data types in both single and multiple dimension arrays.

Example 1:

shows that data is preserved after REDIM PRESERVE has been applied to an array.

TYPE Foo
  A$
  B$
END TYPE

DIM DYNAMIC myfoo[3] AS Foo

myfoo[2].A$ = "This is our initial data"
myfoo[2].B$ = "prior to REDIM and is preserved."

REDIM PRESERVE myfoo[9]

myfoo[8].A$ = "This data has been added "
myfoo[8].B$ = "after REDIM has been applied to the array."

PRINT myfoo[2].A$
PRINT myfoo[2].B$
PRINT
PRINT myfoo[8].A$
PRINT myfoo[8].B$

Result:

This is our initial data
prior to REDIM and is preserved.

This data has been added
after REDIM has been applied to the array.

Example 2:

This example resizes a dynamic string.

DIM b$ * 11
b$ = "1234567890"
?  b$
REDIM b$ * 15
b$ = b$ & "ABCD"
?  b$
REDIM PRESERVE b$ * 21
b$ = b$ & "EFGHIJKLMNOPQRST"
?  b$

Result:

1234567890
ABCD
ABCDEFGHIJKLMNOPQRST

Example 3:

This example resizes a single dimension array.

DIM DYNAMIC a$[4]

a$[0] = "This"
a$[1] = "is"
a$[2] = "a"
a$[3] = "test"

? "Initial values"
? "a$[0] = ", a$[0]
? "a$[1] = ", a$[1]
? "a$[2] = ", a$[2]
? "a$[3] = ", a$[3]

REDIM PRESERVE a$[6]

a$[4] = "that shows the above"
a$[5] = "contents preserved."

? "After REDIM PRESERVE"
? "a$[0] = ", a$[0]
? "a$[1] = ", a$[1]
? "a$[2] = ", a$[2]
? "a$[3] = ", a$[3]
? "a$[4] = ", a$[4]
? "a$[5] = ", a$[5]

REDIM a$[10]

a$[6] = "the contents above"
a$[7] = "are not preserved."

? "After REDIM"
? "a$[0] = ", a$[0]
? "a$[1] = ", a$[1]
? "a$[2] = ", a$[2]
? "a$[3] = ", a$[3]
? "a$[4] = ", a$[4]
? "a$[5] = ", a$[5]
? "a$[6] = ", a$[6]
? "a$[7] = ", a$[7]

Result:

Initial values
a$[0] = This
a$[1] = is
a$[2] = a
a$[3] = test
After REDIM PRESERVE
a$[0] = This
a$[1] = is
a$[2] = a
a$[3] = test
a$[4] = that shows the above
a$[5] = contents preserved.
After REDIM
a$[0] =
a$[1] =
a$[2] =
a$[3] =
a$[4] =
a$[5] =
a$[6] = the contents above
a$[7] = are not preserved.

Example 4:

This example resizes a multiple dimension array.

GLOBAL DYNAMIC a[6,7,4,4] AS INTEGER

a[0,0,0,0] = 4
a[1,1,0,0] = 1
a[2,2,1,0] = 3
a[3,3,0,0] = 2

? a[0,0,0,0]
? a[1,1,0,0]
? a[2,2,1,0]
? a[3,3,0,0]

REDIM PRESERVE a[7,7,4,4] AS INTEGER
? a[0,0,0,0]
? a[1,1,0,0]
? a[2,2,1,0]
? a[3,3,0,0]

REDIM a[5,5,4,4] AS INTEGER
? a[0,0,0,0]
? a[1,1,0,0]
? a[2,2,1,0]
? a[3,3,0,0]

PAUSE

Result:

4
1
3
2
4
1
3
2
0
0
0
0

$GENFREE directive

To free all global variables place the $GENFREE directive at the beginning of the program then CALL FREEGLOBALS from the point at which the global variables are to be freed.

Example:

Here is a complete example.

$GENFREE

GLOBAL DYNAMIC aa$[100] AS CHAR
DIM DYNAMIC bb$[100] AS CHAR
aa$ = REPEAT$(20, "aa$")
bb$ = REPEAT$(20, "bb$")

PRINT "Before FREEGLOBALS"
PRINT "aa$ = ", aa$
PRINT "bb$ = ", bb$

CALL x()

CALL FREEGLOBALS

PRINT " "
PRINT "After FREEGLOBALS"
PRINT "aa$ = ", aa$
PRINT "bb$ = ", bb$
PRINT "cc$ = ", dd$
PRINT "dd$ = ", dd$

SUB x ()
  GLOBAL DYNAMIC cc$[100] AS CHAR
  GLOBAL dd$ * 100
  cc$ = REPEAT$(20, "cc$")
  dd$ = REPEAT$(20, "dd$")
  PRINT "cc$ = ", cc$
  PRINT "dd$ = ", dd$  
END SUB

Result:

Before FREEGLOBALS
aa$ = aa$aa$aa$aa$aa$aa$aa$aa$aa$aa$aa$aa$aa$aa$aa$aa$aa$aa$aa$aa$
bb$ = bb$bb$bb$bb$bb$bb$bb$bb$bb$bb$bb$bb$bb$bb$bb$bb$bb$bb$bb$bb$
cc$ = cc$cc$cc$cc$cc$cc$cc$cc$cc$cc$cc$cc$cc$cc$cc$cc$cc$cc$cc$cc$
dd$ = dd$dd$dd$dd$dd$dd$dd$dd$dd$dd$dd$dd$dd$dd$dd$dd$dd$dd$dd$dd$

After FREEGLOBALS
aa$ = (null)
bb$ = (null)
cc$ = (null)
dd$ = (null)

$TYPEDEF directive

$TYPEDEF <simple one line C/C++ typedef>
Example:
$TYPEDEF long(CALLBACK *CPP_FARPROC)(char *)
translates to:
typedef long(CALLBACK *CPP_FARPROC)(char *);

User Defined TYPE

BCX supports individual and arrays of User Defined Types.

It is important to remember, when declaring a string within a user defined TYPE, that BCX uses C strings which are terminated with a single byte NULL terminator character to mark the end of the string. All BCX strings must be sized large enough to include this terminator. For example, if a string contains 15 characters then it must be sized to at least 16 bytes.

A SUB can be included in a user defined TYPE, as can a FUNCTION, however, OVERLOADED or OPTIONAL FUNCTION or SUB procedures are NOT allowed in a user defined type structure.

TYPE FOO
 MyVar
 SUB Process (ME AS Foo_CLASS)
 FUNCTION Calc (ME AS Foo_CLASS, Arg AS DOUBLE) AS DOUBLE
END TYPE

BCX versions up to 7.8.8 automatically created a structure pointer by prepending *LP to the upper-case name of your user defined TYPE.
For example, this BCX code,

TYPE Spc
 self   AS VOID*
 parent AS VOID*
 child  AS VOID*
 daDaTa AS VOID*
 mtbl   AS VOID*
END TYPE

produces this C structure

typedef struct _Spc
{
  VOID*  self;
  VOID*  parent;
  VOID*  child;
  VOID*  daDaTa;
  VOID*  mtbl;
}SPC, *LPSPC;

👉 Since BCX version 7.8.9, an additional user defined TYPE structure pointer is created with "_PTR" appended to the upper-case name of the user defined TYPE. The C translation from the above BCX example, would be

typedef struct _Spc
{
  VOID*    self;
  VOID*    parent;
  VOID*    child;
  VOID*    daDaTa;
  VOID*    mtbl;
}SPC, *LPSPC, *SPC_PTR;

The first example below shows how the BCX version 7.8.9 new user defined TYPE structure pointer can be used as a parameter argument passing the user defined TYPE structure into a subroutine. The example also demonstrates the BCX version 7.8.9 improved user defined scalar variable type detection and translation when not using the type-identifying sigils. The changes involved, primarily, affect the INPUT, FINPUT, and PRINT commands and expression parsing during translation.

Example 1:

TYPE FOO
 a AS DOUBLE
 b AS INT
 c AS STRING
END TYPE

DIM F AS FOO

F.a = 1.123456789
F.b = 2
F.c = "Hello World"

PRINT "These are our initial values:"
PRINT F.a
PRINT F.b
PRINT F.c
PRINT

CALL TestUDT(ADDRESSOF(F))

PRINT
PRINT "These were passed back from the SUB:"
PRINT F.a
PRINT F.b
PRINT F.c
PRINT

SUB TestUDT (z AS FOO_PTR)
 DIM K AS FOO
 COPY_UDT(z, ADDRESSOF(K), SIZEOF(FOO)) ' Copy z contents to K 
 PRINT "These were passed to this SUB"
 PRINT K.a
 PRINT K.b
 PRINT K.c
 PRINT
 INPUT "Enter a DOUBLE:  ", K.a
 INPUT "Enter a INTEGER: ", K.b
 INPUT "Enter a STRING:  ", K.c
 COPY_UDT(ADDRESSOF(K), z, SIZEOF(FOO)) ' Copy K contents to z 
END SUB

SUB COPY_UDT (Source AS PVOID, Destination AS PVOID, Count AS INT)
 POKE(Destination, PEEK$(Source, Count), Count)
END SUB

Result:

These are our initial values:
 1.123456789
 2
Hello World

These were passed to this SUB
 1.123456789
 2
Hello World

Enter a DOUBLE:  1.2345
Enter a INTEGER: 54321
Enter a STRING:  Not a number.

These were passed back from the SUB:
 1.2345
 54321
Not a number.

Example 2:

Similar to Example 1: above, the following example demonstrates declaring, passing, and redimensioning a local

$BCXVERSION "8.1.2"

CALL STEP_ONE()
PAUSE
END

SUB STEP_ONE

  TYPE UDTFOO
    a AS DOUBLE
    b AS INT
    c AS STRING
  END TYPE

  LOCAL DYNAMIC F[1] AS UDTFOO    ' This array is LOCAL to this SUB 

  F[0].a = 1.123456789
  F[0].b = 2
  F[0].c = "Hello World"

  PRINT "These are our initial values:"
  PRINT F[0].a
  PRINT F[0].b
  PRINT F[0].c
  PRINT

  CALL STEP_TWO(F, UBOUND(F)) ' Pass info about our LOCAL array to another SUB 

  PRINT
  PRINT "These were passed back from the SUB:"
  PRINT F[1].a ' newly added to the array 
  PRINT F[1].b ' ditto 
  PRINT F[1].c ' ditto 
  PRINT
END SUB

SUB STEP_TWO (z[] AS UDTFOO, ub AS INT)
  PRINT "These were passed to SUB STEP_TWO"
  PRINT z[ub].a
  PRINT z[ub].b
  PRINT z[ub].c
  PRINT

  DIM RAW idx = ub + 1

  REDIM PRESERVE z[ub + 1] AS UDTFOO ' Now re-dimension the array passed in from SUB ONE() 

  INPUT "Enter a DOUBLE:   ", z[idx].a
  INPUT "Enter an INTEGER: ", z[idx].b
  INPUT "Enter a STRING:   ", z[idx].c
END SUB

Result:

These are our initial values:
 1.123456789
 2
Hello World

These were passed to SUB STEP_TWO
 1.123456789
 2
Hello World

Enter a DOUBLE:   1.23456789
Enter an INTEGER: 98765
Enter a STRING:   A String

These were passed back from the SUB:
 1.23456789
 98765
A String

Press any key to continue . . .

Example 3:

TYPE MYBOX
  Top%
  Left%
  Width%
  Height%
  Fill AS BOOL
END TYPE

DIM abc AS MYBOX

abc.Top%     = 100
abc.Left%    = 300
abc.Width%   = 100
abc.Height%  = 100
abc.Fill     = TRUE

PRINT abc.Top%
PRINT abc.Left%
PRINT abc.Width%
PRINT abc.Height%
PRINT abc.Fill

Result:

100
300
100
100
1

Example 4:

TYPE MYREC
 B$ [2083] AS CHAR
END TYPE

DIM Test AS MYREC
DIM A$ * 2083

A$ = "test string"
Test.B$ = "Next"

PRINT A$
PRINT Test.B$

Result:

test string
Next

Example 5:

Here is a short example in which a user defined type is used to return multiple values from a FUNCTION.

TYPE test
 a$[5] AS CHAR
 b$[5] AS CHAR
END TYPE

DIM x$

DIM v AS test

x$ = "ABC and XYZ"

v = dfunc(x$)
PRINT v.a$
PRINT v.b$

FUNCTION dfunc (d$) AS test
 LOCAL f AS test
 f.a$ = LEFT$(d$,3)
 f.b$ = RIGHT$(d$,3)
 FUNCTION = f
END FUNCTION

Result:

ABC
XYZ

Example 6:

Below is a variation of Example 5, on how to return multiple values from a function, that shows some additional techniques.

TYPE MyObject
  x AS INT
  y AS INT
  n[10] AS CHAR
END TYPE

GLOBAL p AS MyObject

p = MyFunc() : PRINT p.x, SPC$, p.y, SPC$, p.n
p = MyFunc(1) : PRINT p.x, SPC$, p.y, SPC, p.n
p = MyFunc(2) : PRINT p.x, SPC$, p.y, SPC, p.n
p = MyFunc(3) : PRINT p.x, SPC$, p.y, SPC, p.n
PAUSE


FUNCTION MyFunc (Opt = 0 AS INT) AS MyObject
  '*************************************** 
  SELECT CASE Opt
  CASE 0 : p = (MyObject) { 0 }
  CASE 1 : p = (MyObject) { 1 }
  CASE 2 : p = (MyObject) { 1, 2 }
  CASE 3 : p = (MyObject) { 1, 2, "Bananas!" }
  END SELECT
  '*************************************** 
  FUNCTION = p
END FUNCTION

Result:

0  0
1  0
1  2
1  2 Bananas!

Press any key to continue . . .

Example 7:

Here is a more complex program showing off multi-dimensional user defined TYPE, including a struct, RECT, within the TYPE.

TYPE QWERTY
  DIM a
  DIM b!
  DIM c$[80] AS CHAR
  DIM q AS RECT
END TYPE

GLOBAL MyType[10, 10, 10] AS QWERTY

MyType[2, 3, 4].a  = 1
MyType[2, 3, 4].b! = 2.345
MyType[2, 3, 4].c$ = "hello world from a poly-dimensional udt!"

MyType[2, 3, 4].q.left = 6
MyType[2, 3, 4].q.top = 7
MyType[2, 3, 4].q.right = 8
MyType[2, 3, 4].q.bottom = 9

PRINT        MyType[2, 3, 4].a
PRINT        MyType[2, 3, 4].b!
PRINT UCASE$(MyType[2, 3, 4].c$)

PRINT  MyType[2, 3, 4].q.left
PRINT  MyType[2, 3, 4].q.top
PRINT  MyType[2, 3, 4].q.right
PRINT  MyType[2, 3, 4].q.bottom

Result:

 1
 2.345
HELLO WORLD FROM A POLY-DIMENSIONAL UDT!
 6
 7
 8
 9

Example 8:

Using the WITH ... END WITH control flow statement, the multi-dimensional user defined types Example 5 above can be written as follows.

TYPE QWERTY
DIM a
DIM b!
DIM c$ [80] AS CHAR
END TYPE

GLOBAL MyType [10,10,10] AS QWERTY

WITH MyType[2,3,4]
 .a = 1
 .b! = 2.345
 .c$ = "hello world from a poly-dimensional udt!"
 PRINT .a
 PRINT .b!
 PRINT UCASE$(.c$)
END WITH

Result:

 1
 2.345
HELLO WORLD FROM A POLY-DIMENSIONAL UDT!

Example 9:

Here is an example demonstrating dynamic memory allocation of the members in a user defined type.

TYPE NODE_TYP
  id AS INTEGER 'element number 
  name$[32] AS CHAR 'Storage area 
  A1 AS CHAR PTR   'Storage area determined at run-time 
  A2 AS CHAR PTR   'Storage area determined at run-time 
  A3 AS CHAR PTR   'Storage area determined at run-time 
  next_node AS NODE_TYP PTR
  previous_node AS NODE_TYP PTR
END TYPE

DIM F AS NODE_TYP
DIM X AS NODE_TYP PTR

CALL AllocateStringSpace(&F, 100, 1000, 10000)
F.A1 = "this holds 99"
F.A2 = "this holds 999"
F.A3 = "this holds 9999"
? F.A1$
? F.A2$
? F.A3$

F.next_node = AllocateNode()
X = F.next_node
CALL AllocateStringSpace(X, 1000, 2000, 80000)
X->A1 = "this holds 999"
X->A2 = "this holds 1999"
X->A3 = "this holds 79999"
? X->A1$
? X->A2$
? X->A3$

PAUSE

SUB AllocateStringSpace (Node AS NODE_TYP PTR, _
  A1Length AS LONG, _
  A2Length AS LONG, _
  A3Length AS LONG)
  ! Node->A1 = (PTCHAR)calloc(1,A1Length);
  ! Node->A2 = (PTCHAR)calloc(1,A2Length);
  ! Node->A3 = (PTCHAR)calloc(1,A3Length);
END SUB

FUNCTION AllocateNode () AS NODE_TYP PTR
  ! return (NODE_TYP_PTR)calloc(1,sizeof(NODE_TYP));
END FUNCTION

Result:

this holds 99
this holds 999
this holds 9999
this holds 999
this holds 1999
this holds 79999

Example 10:

Like Example 9, this example also demonstrates dynamic memory allocation of the members in a user defined type. The syntax in this example is much simpler because of new code introduced in BCX version 5.12 to allow the use of the DYNAMIC type qualifier a with member of a user defined type.

TYPE NODE_TYP
 id AS INTEGER 'element number
 name$[32] AS CHAR 'Storage area
 DYNAMIC A1$ 'Storage area determined at run-time
 DYNAMIC A2$ 'Storage area determined at run-time
 DYNAMIC A3$ 'Storage area determined at run-time
 DYNAMIC next_node[] AS NODE_TYP
 DYNAMIC prev_node[] AS NODE_TYP
END TYPE

DIM F AS NODE_TYP
DIM X AS NODE_TYP PTR

REDIM F.A1$ * 100
REDIM F.A2$ * 1000
REDIM F.A3$ * 10000

F.A1$ = "this holds 99"
F.A2$ = "this holds 999"
F.A3$ = "this holds 9999"
? F.A1$
? F.A2$
? F.A3$

REDIM F.next_node[1]
X = &F.next_node[0]

REDIM X->A1$ * 1000
REDIM X->A2$ * 2000
REDIM X->A3$ * 80000

X->A1$ = "this holds 999"
X->A2$ = "this holds 1999"
X->A3$ = "this holds 79999"

? X->A1$
? X->A2$
? X->A3$

PAUSE

Example 10:

How to REDIM a dynamic array inside a user defined type.

TYPE First
 a$
 b%
END TYPE

TYPE Second
 c%
 DIM Something AS First PTR
END TYPE

DIM i

DIM DYNAMIC Third[0] AS Second 'need an array size [0] will do

REDIM Third[6]

Third[0].Something = calloc(10,SIZEOF(First))
FOR i = 0 TO 9
 Third[0].Something[i].b% = i
 Third[0].Something[i].a$ = "this" + STR$(i)
NEXT
FOR i = 0 TO 9
 ? Third[0].Something[i].b%
 ? Third[0].Something[i].a$
NEXT

PAUSE

Result:

 0
this 0
 1
this 1
 2
this 2
 3
this 3
 4
this 4
 5
this 5
 6
this 6
 7
this 7
 8
this 8
 9
this 9

Example 12:

Like Example 11, this example also demonstrates how to REDIM a dynamic array inside a user defined type. The syntax in this example is much simpler because of new code introduced in BCX version 5.12 to allow the use of the DYNAMIC type qualifier a with member of a user defined type.

TYPE First
  a$
  b%
END TYPE

TYPE Second
  c%
  DYNAMIC Something[] AS First
END TYPE

DIM DYNAMIC Third[6] AS Second
DIM i, ii

FOR ii = 0 TO 5
  REDIM Third[ii].Something[10]
  FOR i = 0 TO 9
    Third[ii].Something[i].b% = i
    Third[ii].Something[i].a$ = "this is Something" + _
                                            STR$(i) + _
                                        " of Third" + _
                                            STR$(ii)
  NEXT
NEXT

FOR ii = 0 TO 5
  FOR i = 0 TO 9
    ? Third[ii].Something[i].b%
    ? Third[ii].Something[i].a$
  NEXT
NEXT

FOR ii = 0 TO 5
  FREE Third[ii].Something
NEXT

FREE Third

PAUSE

Result:

 0
this is Something 0 of Third 0
 1
this is Something 1 of Third 0
 2
this is Something 2 of Third 0
 3
this is Something 3 of Third 0
 4
this is Something 4 of Third 0
 5
this is Something 5 of Third 0
 6
this is Something 6 of Third 0
 7
this is Something 7 of Third 0
 8
this is Something 8 of Third 0
 9
this is Something 9 of Third 0
 0
this is Something 0 of Third 1
 1
this is Something 1 of Third 1
 2
this is Something 2 of Third 1
 3
this is Something 3 of Third 1
 4
this is Something 4 of Third 1
 5
this is Something 5 of Third 1
 6
this is Something 6 of Third 1
 7
this is Something 7 of Third 1
 8
this is Something 8 of Third 1
 9
this is Something 9 of Third 1
 0
this is Something 0 of Third 2
 1
this is Something 1 of Third 2
 2
this is Something 2 of Third 2
 3
this is Something 3 of Third 2
 4
this is Something 4 of Third 2
 5
this is Something 5 of Third 2
 6
this is Something 6 of Third 2
 7
this is Something 7 of Third 2
 8
this is Something 8 of Third 2
 9
this is Something 9 of Third 2
 0
this is Something 0 of Third 3
 1
this is Something 1 of Third 3
 2
this is Something 2 of Third 3
 3
this is Something 3 of Third 3
 4
this is Something 4 of Third 3
 5
this is Something 5 of Third 3
 6
this is Something 6 of Third 3
 7
this is Something 7 of Third 3
 8
this is Something 8 of Third 3
 9
this is Something 9 of Third 3
 0
this is Something 0 of Third 4
 1
this is Something 1 of Third 4
 2
this is Something 2 of Third 4
 3
this is Something 3 of Third 4
 4
this is Something 4 of Third 4
 5
this is Something 5 of Third 4
 6
this is Something 6 of Third 4
 7
this is Something 7 of Third 4
 8
this is Something 8 of Third 4
 9
this is Something 9 of Third 4
 0
this is Something 0 of Third 5
 1
this is Something 1 of Third 5
 2
this is Something 2 of Third 5
 3
this is Something 3 of Third 5
 4
this is Something 4 of Third 5
 5
this is Something 5 of Third 5
 6
this is Something 6 of Third 5
 7
this is Something 7 of Third 5
 8
this is Something 8 of Third 5
 9
this is Something 9 of Third 5

UNION ... END UNION statement

A UNION is similar to a user defined TYPE but a UNION can hold the value of only one of its members at any one time but the active member can be changed at runtime and the UNION will then hold the value of the active member. The total size of a UNION is the size of the data type of its largest member.

In the sample below, you might think that the size of the UNION Foo would be 4 + 2048 + 4, counting the integer, the string and the single members of the UNION Foo, but that is not the case. The size of the UNION Foo will be the size of the largest member, that is, the string b$, which has a size of 2048 bytes.

UNION Foo
  a  AS INTEGER
  b$
  c  AS SINGLE
END UNION

DIM Bloof AS Foo

Bloof.a  = 1             
PRINT Bloof.a

Bloof.b$ = "Hello, World!"
PRINT Bloof.b$

PRINT Bloof.a

Bloof.c! = 3.14159       
PRINT Bloof.c!

Result:

1
Hello, World!
1819043144
3.14159

The result above shows that a UNION can hold the value of only one of its members at any one time. After Bloof.b$ has been been made the active member of the UNION, and assigned a string "Hello World", the original value of 1 assigned to the UNION member Bloof.a is no longer valid. The value, 1819043144, that PRINT Bloof.a then produces is a 32 bit integer representing, in little endian order, the first 4 bytes of the string "Hello World".

1819043144 = 0x6C6C6548 Hex

6C = ASCII l
6C = ASCII l
65 = ASCII e
48 = ASCII H

Here's another example. If you create a union like this:

UNION Blurf
 A$
 B$
 C$
END UNION

DIM Quarf AS Blurf

You might think that the size of Quarf would be 3 x 2048 bytes but, in fact, it is only 1 x 2048 bytes, since a UNION can hold only one value at a time.

A UNION can hold any type of data, even other UNION or user defined types.

Example:

TYPE and UNION can be nested as in the following program.

TYPE BE_CONFIG
  dwConfig AS DWORD
  UNION format
    TYPE mp3
      dwSampleRate AS DWORD
      byMode AS BYTE
      wBitrate AS WORD
      bPrivate AS BOOL
      bCRC AS BOOL
      bCopyright AS BOOL
      bOriginal AS BOOL
    END TYPE
    TYPE aac
      dwSampleRate AS DWORD
      byMode AS BYTE
      wBitrate AS WORD
      byEncodingMethod AS BYTE
    END TYPE
  END UNION
END TYPE

DIM T AS BE_CONFIG

T.format.mp3.byMode = 1
T.format.mp3.bPrivate = 2

T.format.aac.byEncodingMethod = 3
T.format.aac.dwSampleRate = 44100

PRINT T.format.mp3.byMode
PRINT T.format.mp3.bPrivate

PRINT T.format.aac.byEncodingMethod
PRINT T.format.aac.dwSampleRate

Result:

1
3
3
44100