Contents

Introduction
Passing arrays
Variable types of arguments
Passing function names to functions and subs
Extra arguments to FUNC_NAME
Function overloading
IMPORT macro
CREATE_ARRAY macro
DYNAMIC_ARRAY macro
Two-dimensional arrays
Creating functions on the fly
ASSERT_TYPE_MATCH macro
Additional operators
Variadic functions
Type conversions
Troubleshooting
Changes in JBExtensions v2

 


Introduction

The aim of JBExtensions is to provide a few extensions to Just Basic concerning functions/subs and arrays to maximise code reuse. It is mainly targeted for advanced programmers that are well familiar with the concept of functions and subs.

The extensions include:

In addition JBExtensions provides a means to create and manage dynamically sized arrays easily, and to import routines from other source files.

Here's an example. This program takes an unspecified amount of input from the user, sorts it, removes duplicate entries and displays the list.

IMPORT array.bast
IMPORT algo.bast

CREATE_ARRAY Array$()

do
    input "Type a string, or press Enter to finish input "; s$
    'sub Append from "array.bast"
    if s$ <> "" then call Append Array$, s$
loop until s$ = ""

'call sub QSort from "algo.bast"
'(and function Size from "array.bast")
call QSort ARRAY_NAME Array$, 1, Size(Array$)

'call sub Unique from "array.bast"
call Unique Array$

print
print "Your entries: sorted and without duplicates"

'use sub PrintArray from "algo.bast"
call PrintArray ARRAY_NAME Array$, 1, Size(Array$), chr$(13)
end

While this code cannot be run directly in Just Basic, it can be understood as a template from which JBExtensions.exe can create valid JB code through a number of automated "find'n'replace" and "copy'n'paste" operations.

 


Passing arrays

To pass arrays to functions/subs, use the ARRAY_NAME extension. This should be typed before the name of the array (without using the brackets) both where the function or sub is called and in the function/sub definition.

Here's an example how to create a simple function and pass an array to it.

'Fill an array with some values
for i = 1 to 10
    NumArray(i) = i
next i

'Call a function that adds up the values in the array
print AddValues(ARRAY_NAME NumArray, 1, 10)
end

'Here's the definition of the function
function AddValues(ARRAY_NAME array, first, last)
    for i = first to last
        AddValues = AddValues + array(i)
    next i
end function

Note that the name of the array doesn't have to be the same in the function and in the calling code. Therefore you can use the same template for AddValues function to sum any number of different arrays in a program.

 


Variable types of arguments

Sometimes it is useful if the same routine would work both with strings and numeric values, for example a function that would count the occurrences of an item in either a string or a numeric array. Such a function would have to accept an argument with an unknown type.

To create such a variable, append a question mark (?) after the name of the variable in the function prototype, and use the question mark after the name of this variable throughout the routine.

However, question marks should not be used where the routine is called: JBExtensions will use the types at the place of call to determine the real type of unknown function arguments.

Here's an example program that counts the occurrences of "banana" in a string array and 42 in a numeric array:

s$(1) = "apple" : s$(2) = "banana"
s$(3) = "grapes" : s$(4) = "banana"
num(1) = 10 : num(2) = 20 : num(3) = 42
num(4) = 1 : num(5) = 100

print "Banana count = "; Count(ARRAY_NAME s$, 1, 4, "banana")
print "42 count = "; Count(ARRAY_NAME num, 1, 5, 42)

end

function Count(ARRAY_NAME array?, Start, Finish, value?)
    for i = Start to Finish
        if array?(i) = value? then Count = Count + 1
    next i
end function

However, sometimes other variables are needed in a routine whose type would depend on the type of some function parameter. The type of the arguments to the routine can be determined from how the routine was called, but any other variables only exist inside the routine. Therefore the REPLACE_TYPE keyword can be used in routines to associate the type of a variable with the type of some argument. The syntax is:

REPLACE_TYPE someVariable? someParameter?

When the type of someParameter? becomes known (when the function/sub is actually called), this makes someVariable? the same type as someParameter?: If someParameter? turns out to be a string then someVariable? will be replaced with someVariable$, otherwise it will be replaced with the numeric variable someVariable.

Here's an example, a sub to swap the value of two variables:

s1$ = "Apple" : s2$ = "Banana"
num1 = 5 : num2 = 55

call Swap s1$, s2$
call Swap num1, num2

print s1$; " "; s2$
print num1; " "; num2

end

sub Swap byref first?, byref second?
    REPLACE_TYPE temp? first?
    temp? = first?
    first? = second?
    second? = temp?
end sub

In this example, temp? will be either a string or a numeric variable depending of the actual type of first (the type could also be derived from second since first and second are expected to be of the same type in the first place for this routine to work).

Finally, the return type of the function itself can be unspecified. For example, here's a function that returns the smallest item in each array:

s$(1) = "apple" : s$(2) = "banana"
s$(3) = "grapes" : s$(4) = "banana"

num(1) = 10 : num(2) = 20 : num(3) = 42
num(4) = 1 : num(5) = 100

print "Smallest string = "; Smallest$(ARRAY_NAME s$, 1, 4)
print "Smallest number = "; Smallest(ARRAY_NAME num, 1, 5)

end

function Smallest?(ARRAY_NAME array?, Start, Finish)
    Smallest? = array?(Start)
    for i = Start + 1 to Finish
        if array?(i) < Smallest? then Smallest? = array?(i)
    next i
end function

 


Passing function names to functions and subs

The behaviour of a routine can be changed if that routine in turn called a different function for certain tasks. Function names can be passed by putting the keyword FUNC_NAME before it both in a routine prototype and the place of call. The function name may be followed by the opening and closing bracket (more on this in the next section).

The example program counts the number of primes between 2 and 100 and the number of values divisible by 7 between 1 and 49.

print "# of primes between 2 and 100: ";_
    Count(2, 100, FUNC_NAME IsPrime)
print "# of values divisible by 7 between 1 and 49: ";_
    Count(1, 49, FUNC_NAME DivisibleBySeven)
end

function Count(First, Last, FUNC_NAME condition)
    for i = First to Last
        if condition(i) then Count = Count + 1
    next i
end function

function IsPrime(n)
    IsPrime = 1
    for i = 2 to sqr(n)
        if n mod i = 0 then IsPrime = 0 : exit for
    next i
end function

function DivisibleBySeven(m)
    DivisibleBySeven = m mod 7 = 0
end function

 


Extra arguments to FUNC_NAME

The previous example shows a short-coming with this approach. Notice how the function condition is called with exactly one argument from the Count routine. As a result, the number 7 had to be hard-coded in the DivisibleBySeven routine. Therefore, if we also wanted to count numbers that are divisible by 3 as well, it seems that we would have to write another function DivisibleByThree.

In this way, you may end up writing a large number of DivisibleBy... routines. However, it would be better to write a single and more general DivisibleBy function that takes two arguments: a value and the number to test against. It might look like this:

function DivisibleBy(value, by)
    'avoid division by 0!
    if by <> 0 then DivisibleBy = value mod by = 0
end function

The problem is that function Count expects a function that is called with exactly one argument, but we want to use a function that has two.

To overcome this, JBExtensions allows passing additional arguments to FUNC_NAME functions. At the place of call, you can specify any number of extra arguments in brackets and separated by commas, right after the function name. These values will be added to the existing arguments, where the passed function is called.

The following code counts all numbers that are divisible by 3, 5 and 7 (using variable in one case).

print "# of values divisible by 3 between 1 and 49: ";_
    Count(1, 49, FUNC_NAME DivisibleBy(3))
print "# of values divisible by 5 between 1 and 49: ";_
    Count(1, 49, FUNC_NAME DivisibleBy(5))

'Additional argument can also be a variable
a = 7
print "# of values divisible by "; a; " between 1 and 49: ";_
    Count(1, 49, FUNC_NAME DivisibleBy(a))
end

'Function Count is still the same as in previous example
function Count(First, Last, FUNC_NAME condition)
    for i = First to Last
        if condition(i) then Count = Count + 1
    next i
end function

'DivisibleBySeven has been replaced with a more general and
'reusable routine
function DivisibleBy(value, by)
    if by <> 0 then DivisibleBy = value mod by = 0
end function

If you do not wish to specify any additional arguments, you can leave the brackets after the function name empty or omit them.

 


Function overloading

It is also possible to create several versions of a function/sub with differing types and number of arguments. When JBExtensions generates Just Basic source code from the "template" it will pick the first function/sub where the types and number of arguments matches the call.

For example, the file "algo.bast" contains two versions of sort routines: one that sorts in ascending order and another one that lets you pass a function that specifies how any two items should be compared.

The example program sorts one array in ascending and the copy in decending order:

'"algo.bast" contains sub InsertionSort and sub PrintArray
IMPORT algo.bast

'fill up two arrays
for i = 1 to 10
    a(i) = int(rnd(1)*10) + 1
    b(i) = a(i)
next i

'sort a in ascending order
call InsertionSort ARRAY_NAME a, 1, 10

'sort b in decending order
call InsertionSort ARRAY_NAME b, 1, 10, FUNC_NAME descending

call PrintArray ARRAY_NAME a, 1, 10, " "
call PrintArray ARRAY_NAME b, 1, 10, " "
end

'This function determines how items will be compared while sorting
'The function used for InsertionSort should return non-zero
'if the first argument (x) should come before the second argument (y)
function descending(x, y)
    descending = x > y
end function

However, plain JB routines whose names don't contain any extensions cannot be simply overloaded. To make them overloadable, JBExtensions needs to be told that it should treat these routines as containing extensions. For that there is the _EXTENDED keyword which is to be placed between the FUNCTION or SUB keyword and the routine name. Below is an example how this allows a sub to be overloaded to accept a string or a number.

call WhatItIs 100
call WhatItIs "Hello"

sub _EXTENDED WhatItIs number
    print "It's a number: "; number
end sub

sub _EXTENDED WhatItIs string$
    print "It's a string: "; string$
end sub

 


IMPORT macro

Routines can be saved in a different file and imported into the current source using the IMPORT macro (see example above). JBExtensions will look for the import file in the folders it is told to (see the launcher's help page for instructions how to specify search paths).

Until version 1.4.1 any code outside subs and function in the imported file was inserted at the beginning of the importing code. However, since version 1.4.1 only subs and routines are imported. If a library needs code to be inserted globally (such as DIM or GLOBAL statements) write the GLOBAL_CODE keyword before the line of code. The same line of code can be requested to be inserted from several places, but it will be inserted just once.

It is also possible (and recommended) to use the GLOBAL_CODE keyword within routines. If the routine's prototype contains any extensions these lines of code will be inserted only if the routine is actually generated. The advantage of this is that the generated code will not be cluttered with statements that are unnecessary (because the relevant routines were never used). Example:

'a library file
function foo(var?)
    GLOBAL_CODE global aVar
    GLOBAL_CODE aVar = 42
    ...
end function

As a result, if the function foo is called anywhere in the program, the lines global aVar and aVar = 42 will appear at the beginning of the source.

Files can also be imported recursively, i.e. the imported file may in turn import other files etc. JBExtensions makes sure that each file is imported only once and there is no risk in importing the same file from several other files.

Routines whose prototypes contain any of the extensions end up in the generated JB source only if they are actually used. Normal routines are pasted to the final source regardless. However, it is possible to make JBExtensions treat plain JB routines as containing extensions with the _EXTENDED keyword which is placed between the FUNCTION or SUB keyword and the routine name. More here.

The installation of JBExtensions comes with a few ready-made routines that can be found in the "library" folder. Read more in the Library Reference.

 


CREATE_ARRAY macro

With this macro you can create an array that is associated with a size variable (how many items are used) and a capacity variable (the real size of the array). In addition, the file "array.bast" has a few functions and subs meant for manipulating mainly arrays declared with this macro, which let you use arrays without worrying how large they must be (good whenever it is unknown how many items the array must store).

The macro itself can be used in several ways:

Create an array that can hold the default value of 10 strings (when the 11th string is inserted or appended the array will be resized automatically by the Insert and Append routines).

CREATE_ARRAY s$()

Create a numeric array that can hold up to 50 (n) values:

CREATE_ARRAY NumArray(50)
CREATE_ARRAY AnotherArray(n)

Create an array and initialize the elements (line continuation can be used):

CREATE_ARRAY months$() = "January", "February", "March",_
    "April", "May", "June", "July", "August", "September",_
    "October", "November", "December"

Create an array with three elements (1, 2 and 3) and room for 30:

CREATE_ARRAY numbers(30) = 1, 2, 3

CREATE_ARRAY cannot be used in functions/subs (after all arrays are global in JB and should not be DIMmed inside routines) and index 0 is unused (arrays start at index 1). (It is however possible to pass arrays created in this way to functions more conveniently with the DYNAMIC_ARRAY keyword

The CREATE_ARRAY macro itself will be replaced so:

CREATE_ARRAY names$(10)

becomes

names.s.size = 0 : names.s.capacity = 10
dim names$(names.s.capacity)

where .s signifies that names$ is a string array (it is possible to create also a numeric array with the same name).

names$ becomes a special token that will be replaced in the main code as follows:

If not followed by an opening bracket, names$ is expanded to:

ARRAY_NAME names$, names.s.size, names.s.capacity

This is only useful when passing these arrays to "template" functions/subs (see the function prototypes in array.bast).

However, when preceeded by the macro ARRAY_NAME, names$ will not be replaced. This way the array names$ can be passed to functions that don't accept the size and/or capacity argument (such functions, however, should not modify the size or capacity of the array).

When followed by the opening bracket, names$( will not be modified. That means that it can be accessed through indices just like regular arrays.

However, staying in array bounds is the programmer's responsibility, and any operation that modifies the size or capacity of the array should correctly update the respective variables (but it is recommended to use the routines provided in "array.bast" for inserting and removing elements into these arrays, instead of writing this functionality yourself).

Note: the initial capacity of an array should not be 0.

 


DYNAMIC_ARRAY macro

If a "magic" array is created with the CREATE_ARRAY macro, there are two ways to write user-defined routines that take it as a parameter.

The first way relies on knowing that at the place of call, the array name will be expanded into three arguments. Therefore one could make the routine accept an array (ARRAY_NAME) and two numeric arguments (the size and capacity of the array, using BYREF as needed). A function declaration might look like this:

function foo(ARRAY_NAME array, size, capacity)

Now, foo() could be called like this:

'First create an array with some initial values
CREATE_ARRAY primes() = 2, 3, 5, 7, 11, 13, 17, 19
'Then call it. primes will be replaced with 3 arguments
print foo(primes)

However, unless the routine would need to modify the size or capacity data directly (which should be quite rare), it would be more convenient if the programmer didn't need to bother about them (the size of the array can be found out with the Size() function, if needed).

The other, and better way to accept these arrays solves this problem. Function foo() could be written like this instead:

function foo(DYNAMIC_ARRAY array)

The DYNAMIC_ARRAY macro will be automatically replaced with the version taking three arguments (size and capacity are passed BYREF). Within the function body, the identifier array will be treated in exactly the same way as arrays declared with the CREATE_ARRAY macro in the main code.

Here's an example program. It draws 20 random numbers and inserts them into an array so that the array remains sorted (using IMPORTed routines that are described in Library Reference) and then prints the contents of the array:

IMPORT algo.bast
IMPORT array.bast

CREATE_ARRAY numbers()

'insert 20 random numbers into a sorted array
for i = 1 to 20
    n = int(rnd(1)*100)
    call InsertSorted numbers, n
next i
call PrintNumbers numbers
end

sub InsertSorted DYNAMIC_ARRAY arr?, item?
    'Function UpperBound from algo.bast returns the
    'highest index where item? could be inserted in arr?
    'while keeping the array sorted.
    place = UpperBound(ARRAY_NAME arr?, 1, Size(arr?), item?)
    
    'Function Insert inserts the item? in arr? at selected
    'position. We can ignore the return value
    'because the insertion cannot fail (place is guaranteed
    'to be a valid index for insertion).
    i = Insert(arr?, item?, place)
end sub

sub PrintNumbers DYNAMIC_ARRAY arr?
    for i = 1 to Size(arr?)
        print arr?(i); " ";
    next i
    print
end sub

 


Two-dimensional arrays

It is possible to write routines for two-dimensional arrays in the same way as for one-dimensional arrays. However, there is no equivalent of CREATE_ARRAY for two-dimensional arrays.

It is also possible to pass two-dimensional arrays to routines that are supposed to work on one-dimensional arrays and specify a value to be used for one of the dimensions with the DIM_1 or DIM_2 keyword.

The syntax is:

dim SomeArray(10, 20)
call Foo ARRAY_NAME SomeArray(DIM_1 5)
k = 14
call Foo ARRAY_NAME SomeArray(DIM_2 k)

First, without using this feature here's what it might take to write a Find routine for two-dimensional arrays. Since we might want to search both in a selected column and row, we would need two separate routines.

Width = 7
Height = 6
dim Table(Height, Width)
for i = 1 to Height
    for j = 1 to Width
        Table(i, j) = i * j
    next j
next i
value = 12
print "Looking for "; value; " in all columns:"
for i = 1 to Width
    print "Column: "; i; " - ";
    if FindInColumn(ARRAY_NAME Table, i, 1, Height, value) = -1 then
        print "not found"
        else
        print "found"
    end if
next i
print
print "Looking for "; value; " in all rows:"
for i = 1 to Height
    print "Row: "; i; " - ";
    if FindInRow(ARRAY_NAME Table, i, 1, Width, value) = -1 then
        print "not found"
        else
        print "found"
    end if
next i
end

function FindInRow(ARRAY_NAME array2d?, Row, Start, Finish, value?)
    FindInRow = -1
    for i = Start to Finish
        if array2d?(Row, i) = value? then FindInRow = i : exit for
    next i
end function

function FindInColumn(ARRAY_NAME array2d?, Column, Start, Finish, value?)
    FindInColumn = -1
    for i = Start to Finish
        if array2d?(i, Column) = value? then FindInColumn = i : exit for
    next i
end function

With the dimension-selecting keywords the task is a lot simpler: there is no need to write another Find function because a suitable one already exists in the "algo.bast" library.

IMPORT algo.bast

Width = 7
Height = 6
dim Table(Height, Width)
for i = 1 to Height
    for j = 1 to Width
        Table(i, j) = i * j
    next j
next i
value = 12
print "Looking for "; value; " in all columns:"
for i = 1 to Width
    print "Column: "; i; " - ";
    if Find(ARRAY_NAME Table(DIM_2 i), 1, Height, value) = -1 then
        print "not found"
        else
        print "found"
    end if
next i
print
print "Looking for "; value; " in all rows:"
for i = 1 to Height
    print "Row: "; i; " - ";
    if Find(ARRAY_NAME Table(DIM_1 i), 1, Width, value) = -1 then
        print "not found"
        else
        print "found"
    end if
next i
end

In this way many existing algorithms can be adapted to work with a selected column or row in a two-dimensional array.

However, there are algorithms that obviously cannot be adapted as easily. To sort 2-dimensional arrays according to one column, IMPORT the quicksort routine from "QSort2D.bast".

 


Creating functions on the fly

When a function expects a FUNC_NAME argument, it is not necessary to pass the name of an existing function. Instead, if the function is simple and probably not reusable enough, it might be less trouble to define the function at the place of call with the FUNCTION_ or FUNCTION_$ and RETURN_VALUE [1] keywords.

For example, here's another way to sort an array in descending order:

call QSort ARRAY_NAME array, 1, Size, FUNCTION_(lvalue, rvalue):( RETURN_VALUE = lvalue > rvalue)

JBExtensions will first translate that line in this way:

call QSort ARRAY_NAME array, 1, Size, FUNC_NAME UnnamedFn

function UnnamedFn(lvalue, rvalue)
    UnnamedFn = lvalue > rvalue
end function

After that it will continue processing the QSort call as a regular function containing ARRAY_NAME and FUNC_NAME extensions.

The syntax of the FUNCTION_ keyword is:

It is also possible to pass extra arguments to such functions. Simply add the extra arguments in their own brackets to the very end of the FUNCTION_ statement. The following sample code demonstrates it all:

IMPORT algo.bast

Size = 150
dim numbers(Size)

'fill with random numbers between 1 and 100
call Fill ARRAY_NAME numbers, 1, Size, FUNC_NAME Random(1, 100)

'now print only the numbers that are in a ten's range
for i = 1 to 100 step 10
    print "Values between "; i ; " and "; i + 9
    call ForEach ARRAY_NAME numbers, 1, Size, FUNCTION_(x, min, max):(if x >= min and x < max then print x; " ";)(i, i + 10)
    print
next i

[1] Note: since version 2, there's also a shorter _RESULT keyword which has the same meaning as RETURN_VALUE

 


ASSERT_TYPE_MATCH macro

This macro helps JBExtensions to detect a certain class of errors and is removed completely from the generated code. It is a way to ensure that arguments with a variable type that have to be of the same type indeed are of the same type.

The syntax is similar to a function call. Variables that have to share the same type are listed in parenthesis. Example:

ASSERT_TYPE_MATCH(first?, second?, third?)
ASSERT_TYPE_MATCH(s?, "string required")
ASSERT_TYPE_MATCH(n?, numericRequired)

For example, a Swap routine requires that both arguments are of the same type. Therefore we add a check if they indeed are the same:

sub MySwap Byref first?, Byref second?
    ASSERT_TYPE_MATCH(first?, second?)
    REPLACE_TYPE temp? first?
    temp? = first?
    first? = second?
    second? = temp?
end sub

If we tried to call MySwap with non-matching arguments, JBExtensions will abort generating code and produces an error message. Without the macro, the error would be only discovered when you try to run the generated code.

E.g. supposing we try this erraneous call:

call MySwap number, string$

This produces an error message like the following:

Syntax error:
Types are required to match.
Arguments are:
  first, second$

File: C:\Just BASIC v1.01\JBExtensions\Examples\AssertTypeMatch.bast
Line number: 8
    ASSERT_TYPE_MATCH(first?, second?)

...when sub 'MySwap' was called from
File: C:\Just BASIC v1.01\JBExtensions\Examples\AssertTypeMatch.bast
Line number: 3
call MySwap number, string$ 'will cause an error

All library functions use ASSERT_TYPE_MATCH if suitable.

Watch out when asserting the type of a DYNAMIC_ARRAY parameter. Because that name expands into three variables first, use brackets after the name to avoid this. The correct way is:

function Foo(DYNAMIC_ARRAY special?, value?)
    ASSERT_TYPE_MATCH(special?(), value?)

 


Additional operators

JBExtensions adds operators of type operator=. These are used in the very common situation where the result is assigned back to the variable that is used on the left side of operator. This way the variable name needs to be typed only once, thus reducing the risk of typos and introducing bugs when the variable name needs to be changed.

Added operators are listed below together with their meaning:

a += 1      ' a = a + 1
a -= 1      ' a = a - 1
a *= 2      ' a = a * 2
a /= 2      ' a = a / 2
a ^= 2      ' a = a ^ 2
a mod= 2    ' a = a mod 2
a and= 2    ' a = a and 2
a or= 2     ' a = a or 2
a xor= 1    ' a = a xor 1

Note: there must not be a space before the = symbol. String variables naturally support only operator +=.

Another operator that is added for convenience is [] for indexing and selecting a range in a string variable, literal or expression. The usage and meaning is explained below:

'select one character
    s$[x]     ' => mid$(s$, x, 1)

'select a range:
    'from x to y (including y)
    s$[x:y]   ' => mid$(s$, x, y - x + 1)

    'from x up to end of string
    s$[x:]    ' => mid$(s$, x)

    'from beginning up to x (including x)
    s$[:x]    ' => left$(s$, x)

 


Variadic functions

Variadic functions are functions which can take an unspecified number of arguments (read more in the Wikipedia article). They are useful, for example, to implement an InList function that can be called like that:

print InList(a, 3, 4, 5) 'if a equals 3 or 4 or 5
print InList(b, 1, 2) 'if b equals 1 or 2
'etc

To indicate that the routine accepts variadic arguments, use the VAR_ARGUMENTS keyword, followed by a variable name:

sub PrintSomeStrings VAR_ARGUMENTS strings$

Variadic arguments may be either strings, numeric or of unspecified type (VariableName?). You can't pass arrays or function names where variadic arguments are expected.

In addition to variadic arguments, the routine may also accept variables in the normal way. However, variadic arguments must always come last in the prototype and naturally only one set of variadic arguments is allowed.

sub MyRoutine aString$, ARRAY_NAME anArray?, VAR_ARGUMENTS variadic

Variadic arguments can also be passed by reference in which case the byref keyword must come after the VAR_ARGUMENTS keyword:

sub VariadicByRef VAR_ARGUMENTS byref args?

Within the routine body, individual variadic arguments can only be accessed within a loop that begins with the FOR_EACH_ARGUMENT macro and ends with the END_FOR_EACH_ARGUMENT macro. The function body may contain more than one such loop, but these loops must not be nested.

Below is a simple sub that simply prints all its arguments separated by spaces:

a$ = "John"
b$ = "Doe"
age = 42
call PrintArguments a$, b$, "- age:", age
end

sub PrintArguments VAR_ARGUMENTS args?
    FOR_EACH_ARGUMENT
    print args?; " ";
    END_FOR_EACH_ARGUMENT
    print 'newline
end sub

When the variadic argument is used outside the FOR_EACH_ARGUMENT loop, the identifier is replaced by a comma-separated list of actual variable names. This can be used to pass variadic arguments on to another (variadic) function, to check that all unspecified variadic arguments are of the same type, or simply to print the arguments separated by tab positions (the normal behaviour of JB print command with comma):

sub Variadic VAR_ARGUMENTS args?
    'pass to another variadic sub
    call OtherVariadic args?
    'assert all arguments are of the same type
    ASSERT_TYPE_MATCH(args?)
    'print all arguments
    print args?
end sub

In addition, the total number of variadic arguments can be found out with the special read-only variable/macro ARG_COUNT and the index of the current argument within the FOR_EACH_ARGUMENT loop with the ARG_NO macro. (These macros are case sensitive.)

Below is an example which demonstrates all the keywords:

s$ = "Hello world"
call PrintArguments 1, 2, 3
call PrintArguments s$, 3.14, -2, "I'm last"
end

sub PrintArguments VAR_ARGUMENTS args?
    print "PrintArguments received "; ARG_COUNT; " variadic arguments"
    FOR_EACH_ARGUMENT
    print "#"; ARG_NO; ": "; args?
    END_FOR_EACH_ARGUMENT
end sub

FOR_EACH_ARGUMENT and END_FOR_EACH_ARGUMENT can also appear on the same line. As before, anything code between them will be duplicated for each argument.

The examples below show how to use this feature to multiply variadic arguments and pass them on (perhaps to some other variadic routine) while applying the sqr() function to all arguments.

print CallWithRoots(FUNC_NAME Multiply, 1, 4, 9)

function Multiply(VAR_ARGUMENTS args)
    'using line continuation
    Multiply = _
        FOR_EACH_ARGUMENT _
            args * _
        END_FOR_EACH_ARGUMENT _
        1 'as otherwise the command would end with *
end function

function CallWithRoots(FUNC_NAME func, VAR_ARGUMENTS args)
    'the extra trailing comma is OK
    CallWithRoots = func(FOR_EACH_ARGUMENT sqr(args), END_FOR_EACH_ARGUMENT)
end function

Note: when a routine call matches both a variadic and a non-variadic routine, the latter will be used. However, when a call matches more than one variadic routine it is unspecified which one will be used for the code generation.

 


Type conversions

Since version 1.5, routines in "types.bast" have been replaced with the TYPE_, TYPE_$ and TYPE_? keyword which allows to convert variables with unknown type to desired type. All these keywords have a function-call syntax.

Keywords TYPE_ and TYPE_$ accept one argument and convert the type to numeric or string respectively by calling the val() and str$() function as needed, or leaving the argument as is if no conversion is necessary. For example, this function returns the length of the string or the string representation of the number passed to it:

print Length("Hello world") '11
print Length(3.14) '4

function Length(value?)
    Length = len(TYPE_$(value?))
end function

The third version, TYPE_?, is useful when the target type is unknown and depends on a variable with unknown type. This version takes two arguments and the first argument is converted to the same type as the second has. The second argument is not used for anything else. In the following example, the value 42 or "42" is returned, depending on whether the function is supposed to return a numeric or a string value:

AnswerAsString$ = FortyTwo$()
AnswerAsNumber = FortyTwo()

function FortyTwo?()
    FortyTwo? = TYPE_?(42, FortyTwo?)
end function

Since version 2.0, TYPE_? can be also used with one argument in an assignment expression. In this case the target type is determined by the left-hand side of the assignment. The form with two arguments must still be used in other contexts.

The previous example could also be written so:

AnswerAsString$ = FortyTwo$()
AnswerAsNumber = FortyTwo()

function FortyTwo?()
    'The type to convert 42 to is determined from the left-hand value: FortyTwo?
    FortyTwo? = TYPE_?(42)
end function

 


Troubleshooting

If the original source file doesn't contain syntax errors, JBExtensions should be able to generate the runnable code correctly.

A lot of the syntax errors can be detected by JBExtensions, such as unclosed brackets or calls to routines where the suitable routine definition does not exist – for example, because the number or types of arguments don't match up. The latter is somewhat less reliable in case of functions – after all, it is not easy to know whether something is an array, a call to a built-in or a user-defined functions.

If the generator fails, read the error message and try to fix the original code. If you discover bugs in the generated code, it is advisable to fix those in the original code and rerun JBExtensions – otherwise you might regenerate the bugs each time you generate code from the original.

One thing to keep in mind: JBExtensions requires all brackets to be closed. Whereas JustBASIC doesn't require the array bracket to be closed in FILES, COMBOBOX and LISTBOX commands, you should also include the closing bracket if you want to generate code from that source.

These are correct:

COMBOBOX #main.combo, ComboArray$(), [Branch], posX, posY, width, height
LISTBOX #main.list, ListboxArray$(), [Branch], posX, posY, width, height
FILES path$, file$, info$()

JBExtensions may also be confused by certain built-in functions in Just Basic when determining types. time$() and date$() can return both a numeric or a string type and using() returns a string, despite looking like a function returning a numeric value. These functions shouldn't be used directly in a place where JBExtensions determines types itself, and their results should be stored in a variable first.

In any case, JBExtensions should never close on its own, without displaying the "Program complete" message. If that happens, or you discover other bugs, you might write to uncleBen at Just Basic message board.

 


Changes in JBExtensions v2

--- Line continuation is supported. However, this breaks variadic routines where FOR_EACH_ARGUMENT block is within a continued line. This keyword also requires line continuation symbol after it.

--- IMPORT keyword now allows multiple filenames:

'import both array.bast and algo.bast
IMPORT array.bast, algo.bast

--- TYPE_? keyword can also take just one argument if it appears on the right of assignment operator. The target type is determined automatically from the left-hand type.

--- FOR_EACH_ARGUMENT and END_FOR_EACH_ARGUMENT can now appear on the same line. Since previous versions didn't handle line continuation, this may break some existing code: FOR_EACH_ARGUMENT will also require a line continuation mark after it.

--- String indexing operator s$[a:b] now avoids calling b multiple times by automatically generating a helper function JBE.Range$(s$, first, last).

--- _EXTENDED keyword to make JBExtensions treat regular JB functions and subs as extended code (routines marked _EXTENDED are only generated if used in the code and can be overloaded.)

--- In unnamed functions, the _RESULT keyword can be used in place of RETURN_VALUE.