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.
 
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.
 
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
 
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
 
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.
 
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
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
 
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 andglobal aVar aVar = will appear at the beginning of the source.42
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.
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.
 
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
 
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".
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:
FUNCTION_ keyword to define a function returning a numeric value or the FUNCTION_$ keyword to define a function returning a string;RETURN_VALUE keyword. (Note that it is the same for both numeric and string values.) It is also possible to use more than one statement as the function body, in which case statements are separated by colons as in regular JB.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
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?)
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 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.