BaCon 4.8 documentation Introduction BaCon is an acronym for BAsic CONverter. The BaCon BASIC converter is a tool to convert programs written in BASIC syntax to C. The resulting C code can be compiled using generic C compilers like GCC or CC. It can be compiled using a C++ compiler as well. BaCon intends to be a programming aid in creating small tools which can be compiled on different Unix-based platforms. It tries to revive the days of the good old BASIC. The BaCon converter passes expressions and numeric assignments to the C compiler without verification or modification. Therefore BaCon can be considered a lazy converter: it relies on the expression parser of the C compiler. BaCon usage and parameters To use BaCon, download the converter package and run the installer. The converter can be used as follows: bash ./bacon.sh myprog.bac Note that BASH 4.x or higher is required to execute the Shell script version of BaCon. By default, the converter will refer to '/bin/bash' by itself. It uses a so-called 'shebang' which allows the program to run standalone provided the executable rights are set correctly. This way there is no need to execute BaCon with an explicit use of BASH. So this is valid: ./bacon.sh myprog.bac Alternatively, also Kornshell93 (releases after 2012) or Zshell (versions higher than 4.x) can be used: ksh ./bacon.sh myprog.bac zsh ./bacon.sh myprog.bac All BaCon programs should use the '.bac' extension. But it is not necessary to provide this extension for conversion. So BaCon also understands the following syntax: ./bacon.sh myprog Another possibility is to point to the URL of a BaCon program hosted by a website. The program will then be downloaded automatically, after which it is converted: ./bacon.sh http://www.basic-converter.org/fetch.bac The BaCon Basic Converter can be started with the following parameters. * -c: determine which C compiler should create the binary (defaults to 'gcc') * -l: pass a library to the C linker * -o: pass a compiler option to the C compiler * -i: the compilation will use an additional external C include file * -d: determine the directory where BaCon should store the generated C files (defaults to the current directory) * -x: extract gettext strings from generated c sources * -z: allow the usage of lowercase statements and functions * -e: starts the embedded ASCII editor * -f: create a shared object of the program * -n: do not compile the C code automatically after conversion * -y: suppress warning about temporary files if these exist * -j: invoke C preprocessor to interpret C macros which were added to BaCon source code * -p: do not cleanup the generated C files (default behavior is to delete all generated C files automatically) * -q: suppress line counting during conversion and only show summary after conversion * -r: convert and execute the resulting program in one step * -s: suppress warnings about semantic errors * -t: tweak internal BaCon parameters for the string core engine and memory pool: pbc=: set the Pool Block Count to defaults to: 1024 pbs=: set each individual Pool Block Size to defaults to: 1024 hld=: set the Hash Linear Depth probing to defaults to: 16 hss=: set the Hash String Store size to defaults to: 0x100000 mrb=: set the Maximum Return Buffers to defaults to: 64 * -w: store command line settings in the configuration file ~/.bacon/bacon.cfg. This file will be used in subsequent invocations of BaCon (not applicable for the GUI version) * -v: shows the current version of BaCon * -h: shows an overview of all possible options on the prompt. Same as the '-?' parameter The shell script implementation can convert and compile the BaCon version of BaCon. This will deliver the binary version of BaCon which has an extremely high conversion performance. On newer systems, the average conversion rate usually lies above 10.000 lines per second. This documentation refers both to the shell script and binary implementation of BaCon. Here are a few examples showing the usage of command line parameters: * Convert and compile program with debug symbols: bacon -o -g program.bac * Convert and compile program, optimize and strip: bacon -o -O2 -o -s program.bac * Convert and compile program and export functions as symbols: bacon -o -export-dynamic yourprogram.bac * Convert and compile program using TCC and export functions as symbols: bacon -c tcc -o -rdynamic yourprogram.bac * Convert and compile program forcing 32bit and optimize for current platform: bacon -o -m32 -o -mtune=native yourprogram.bac * Convert and compile program linking to a particular library: bacon -l somelib program.bac * Convert and compile program including an additional C header file: bacon -i header.h yourprogram.bac * Store compile options permanently: bacon -w -l tcmalloc -o -O2 program.bac (subsequent invocations of BaCon will now always use the mentioned options) Most of the aforementioned options also can be used programmatically by use of the PRAGMA keyword. General syntax BaCon consists of statements, functions and expressions. Each line should begin with a statement. A line may continue onto the next line by using a space and the '\' symbol at the end of the line. The LET statement may be omitted, so a line may contain an assignment only. Expressions are not converted, but are passed unchanged to the C compiler (lazy conversion). BaCon does not require line numbers. More statements per line are accepted. These should be separated by the colon symbol ':'. All keywords must be written in capitals to avoid name clashes with existing C keywords or functions from libc. Keywords in small letters are considered to be variables unless the '-z' command line option is specified, in which case BaCon tries to parse lowercase keywords as if they were written in capitals. Note that this may lead to unexpected results, for example if the program uses variable names which happen to be BaCon keywords. Statements are always written without using brackets. Functions however must use brackets to enclose their arguments. Functions always return a value or string, contrary to subs. Functions created in the BaCon program can be invoked standalone, meaning that they do not need to appear in an assignment. Subroutines may be defined using SUB/ENDSUB and do not return a value. With the FUNCTION/ENDFUNCTION statements a function can be defined which does return a value. The return value must be explicitly stated with the statement RETURN. The three main variable types in BaCon are defined as STRING, NUMBER and FLOATING. These are translated to char*, long and double. A variable will be declared implicitly when the variable is used in an assignment (e.g. LET) or in a statement which assigns a value to a variable. By default, implicitly declared variables are of 'long' type. This default can be changed by using the OPTION VARTYPE statement. Note that implicitly declared variables always have a global scope, meaning that they are visible to all functions and routines in the whole program. Variables which are used and implicitly declared within a SUB or FUNCTION also by default have a global scope. When declared with the LOCAL statement variables will have a scope local to the FUNCTION or SUB. In case of implicit assignments, BaCon assumes numeric variables to be of long type, unless specified otherwise with OPTION VARTYPE. Also, it is possible to define a variable to any other C-type explicitly using the DECLARE and LOCAL statements. Next to this, BaCon accepts type suffixes as well. For example, if a variable name ends with the '$' symbol, a string variable is assumed. If a variable name ends with the '#' symbol, a float variable is assumed. If a variable name ends with the '%' symbol, it is considered to be an integer variable. The type suffixes also can be used when defining a function name. Mathematics, variables The standard C operators for mathematics can be used, like '+' for addition, '-' for subtraction, '/' for division and '*' for multiplication. For the binary 'and', the '&' symbol must be used, and for the binary 'or' use the pipe symbol '|'. Binary shifts are possible with '>>' and '<<'. C operator Meaning C Operator Meaning + Addition | Inclusive or - Subtraction ^ Exclusive or * Multiplication >> Binary shift right / Division << Binary shift left & Binary and Variable names may be of any length but may not start with a number or an underscore symbol. Equations Equations are used in statements like IF...THEN, WHILE...WEND, and REPEAT...UNTIL. In BaCon the following symbols for equations can be used: Symbol Meaning Type 0 Equal to String, numeric !=, <> Not equal to String, numeric > Greater than String, numeric also allows GT < Less than String, numeric also allows LT >= Greater or equal String, numeric also allows GE <= Less or equal String, numeric also allows LE EQ, IS Equal to Numeric NE, ISNOT Not equal to Numeric AND, OR Logical and,or String, numeric BETWEEN In between String, numeric BEYOND Outside String, numeric EQUAL() Equal to String The BETWEEN and BEYOND keywords When using equations in WHILE, IF, REPEAT, IIF or IIF$ it often happens that a check needs to be performed to see if a certain value lies within a range. For this purpose, BaCon accepts the BETWEEN comparison keyword. For example: IF 5 BETWEEN 0;10 THEN PRINT "Found" This comparison will return TRUE in case the value 5 lies within 0 and 10. The comparison will include the lower and upper boundary value during evaluation. Note that the lower and upper values are being separated by a semicolon. Alternatively, the keyword AND may be used here as well: IF 5 BETWEEN 0 AND 10 THEN PRINT "Found" Note that this may lead to confusing constructs when adding more logical requirements to the same equation. The BETWEEN comparison also accepts strings: IF "C" BETWEEN "Basic" AND "Pascal" THEN PRINT "This is C" The order of the mentioned range does not matter, the following code will deliver the exact same result: IF "C" BETWEEN "Pascal" AND "Basic" THEN PRINT "This is C" In case the boundary values should be excluded, BaCon accepts the optional EXCL keyword: IF variable BETWEEN 7 AND 3 EXCL THEN PRINT "Found" The last example will print "Found" in case the variable lies within 3 and 7, values which themselves are excluded. Similarly, to check if a value lies outside a certain range, the keyword BEYOND can be used: IF 5 BEYOND 1 AND 3 THEN PRINT "Outside" Note that in case of the BEYOND keyword the boundary values themselves are considered to be part of the outside range. The same EXCL keyword can be used to exclude them. Indexed arrays Declaration of static arrays An array will never be declared implicitly by BaCon, so arrays must be declared explicitly. This can be done by using the keyword GLOBAL or DECLARE for arrays which should be globally visible, or LOCAL for local array variables. Arrays must be declared in the C syntax, using square brackets for each dimension. For example, a local string array must be declared like this: 'LOCAL array$[5]'. Two-dimensional arrays are written like 'array[5][5]', three-dimensional arrays like 'array[5][5][5]' and so on. In BaCon, static numeric arrays can have all dimensions, but static string arrays cannot have more than one dimension. Declaration of dynamic arrays Also dynamic arrays must be declared explicitly. To declare a dynamic array, the statements GLOBAL or LOCAL must be used together with the ARRAY keyword, which determines the amount of elements. For example, to declare a dynamic array of 5 integer elements: 'LOCAL array TYPE int ARRAY 5'. The difference with a static array is that the size of a dynamic array can declared using variables, and that their size can be redimensioned during runtime. The latter can be achieved with the REDIM statement. This is only possible for arrays with one dimension. As with static numeric arrays, also dynamic numeric arrays can have all dimensions, and dynamic string arrays cannot have more than one dimension. The syntax to refer to elements in a dynamic array is the same as the syntax for elements in a static array. Dimensions Static arrays must be declared with fixed dimensions, meaning that it is not possible to determine the dimensions of an array using variables or functions, so during program runtime. The reason for this is that the C compiler needs to know the array dimensions during compile time. Therefore the dimensions of an array must be defined with fixed numbers or with CONST definitions. Also, the size of a static array cannot be changed afterwards. Dynamic arrays however can be declared with variable dimensions, meaning that the size of such an array also can be expressed by a variable. Furthermore, the size of a one dimensional dynamic array can be changed afterwards with the REDIM statement. This statement also works for implicitly created dynamic arrays in the SPLIT and LOOKUP statements. By default, if an array is declared with 5 elements, then it means that the array elements range from 0 to 4. Element 5 is not part of the array. This behavior can be changed using the OPTION BASE statement. If OPTION BASE is set to 1, an array declared with 5 elements will have a range from 1 to 5. The UBOUND function returns the dimension of an array. In case of multi-dimensional arrays the total amount of elements will be returned. The UBOUND function works for static and dynamic arrays, but also for associative arrays. Passing arrays to functions or subs In BaCon it is possible to pass one-dimensional arrays to a function or sub. The caller should simply use the basename of the array (so without mentioning the dimension of the array). When the function or sub argument mentions the dimension, a local copy of the array is created. CONST dim = 2 DECLARE series[dim] TYPE NUMBER SUB demo(NUMBER array[dim]) array[0] = 987 array[1] = 654 END SUB series[0] = 123 series[1] = 456 demo(series) FOR x = 0 TO dim - 1 PRINT series[x] NEXT This will print the values originally assigned. The sub does not change the original assignments. When the function or sub argument does not mention the dimension, but only uses square brackets, the array is passed by reference. CONST dim = 2 DECLARE series[dim] TYPE NUMBER SUB demo(NUMBER array[]) array[0] = 987 array[1] = 654 END SUB series[0] = 123 series[1] = 456 demo(series) FOR x = 0 TO dim - 1 PRINT series[x] NEXT This will modify the original array and prints the values assigned in the sub. Returning arrays from functions In BaCon, it is also possible to return a one dimensional array from a function. This only works for dynamic arrays, as the static arrays always use the stack memory assigned to a function. This means, that when a function is finished, also the memory for that function is destroyed, together with the variables and static arrays in that function. Therefore only dynamic arrays can be returned. The syntax to return a one dimensional dynamic array involves two steps: the declaration of the array must contain the STATIC keyword, and the RETURN argument should only contain the basename of the array without mentioning the dimensions. For example: FUNCTION demo LOCAL array TYPE int ARRAY 10 STATIC FOR x = 0 TO 9 array[x] = x NEXT RETURN array END FUNCTION DECLARE my_array TYPE int ARRAY 10 my_array = demo() This example will create a dynamic array and assign some initial values, after which it is returned from the function. The target 'my_array' now will contain the values assigned in the function. The statements SPLIT, LOOKUP, COLLECT, PARSE and MAP also accept the STATIC keyword, which allows the implicitly created dynamic array containing results to be returned from a function. Note that when returning arrays, the assigned array should have the same dimensions in order to prevent memory errors. Associative arrays Declaration An associative array is an array of which the index is determined by a string, instead of a number. Associative arrays use round brackets '(...)' instead of the square brackets '[...]' used by normal arrays. An associative array can use any kind of string for the index, and it can have an unlimited amount of elements. The declaration of associative arrays therefore never mentions the range. To declare an associative array, the following syntax applies: DECLARE info ASSOC int This declares an array containing integer values. To assign a value, using a random string "abcd" as example: info("abcd") = 1 Similarly, an associative array containing other types can be declared, for example strings: DECLARE txt$ ASSOC STRING As with other variables, declaring associative arrays within a function using LOCAL will ensure a local scope of the array. An associative array can have any amount of dimension. The indexes in an associative array should be separated by a comma. For example: DECLARE demo$ ASSOC STRING demo$("one") = "hello" demo$("one", "two") = "world" Alternatively, the indexes also can be specified in a delimited string format, using a single space as delimiter: demo$("one two") = "world" Note that the OPTION BASE statement has no impact on associative arrays. Also note that an associative array cannot be part of a RECORD structure. For the index, it is also possible to use the STR$ function to convert numbers or numerical variables to strings: PRINT txt$(STR$(123)) Relations, lookups, keys In BaCon, it is possible to setup relations between associative arrays of the same type. This may be convenient when multiple arrays with the same index need to be set at once. To setup a relation the RELATE keyword can be used, e.g: RELATE assoc TO other Now for each index in the array 'assoc', the same index in the array 'other' is set. It also is possible to copy the contents of one associative array to another. This can simply be done by using the assignment operator, as follows: array$() = other$() Next to this, the actual index names in an associative array can be looked up using the LOOKUP statement. This statement returns a dynamically created array containing all string indexes. The size of the resulting array is dynamically declared as it depends on the amount of available elements. Instead of creating a dynamic array, it is also possible to return the indexes of an associative array into a delimited string by using the function OBTAIN$. To find out if a key already was defined in the associative array, the function ISKEY can be used. This function needs the array name and the string containing the index name, and will return either TRUE or FALSE, depending on whether the index is defined (TRUE) or not (FALSE). The function NRKEYS will return the amount of members in an associative array. Deleting individual associative array members can be done by using the FREE statement. This will leave the associative array insertion order intact. The FREE statement also can be used to delete a full associative array in one step. The function INDEX$ allows looking up a specific key based on value. The function INVERT can swap the keys and values of an associative array. The SORT statement also can sort associative arrays based on their value, effectively changing the insertion order in the underlying hash table. Basic logic programming With the current associative array commands it is possible to perform basic logic programming. Consider the following Logic program which can be executed with any Prolog implementation: mortal(X) :- human(X). human(socrates). human(sappho). human(august). mortals_are: write('Mortals are:'), mortal(X), write(X), fail. The following BaCon program does the same thing: DECLARE human, mortal ASSOC int RELATE human TO mortal human("socrates") = TRUE human("sappho") = TRUE human("august") = TRUE PRINT "Mortals are:" LOOKUP mortal TO member$ SIZE amount FOR x = 0 TO amount - 1 PRINT member$[x] NEXT Records Declaration Records are collections of variables which belong together. A RECORD has a name by itself and members of the record can be accessed by using the . notation. The members should be declared using the LOCAL statement. For example: RECORD rec LOCAL value LOCAL nr[5] END RECORD rec.value = 99 As soon a record is created, it also exists as a type. The name of the type always consists of the record name followed by the '_type' suffix. From then on, it is possible to declare other variables as being of the same type. To continue with the same example: DECLARE var TYPE rec_type var.value = 123 Arrays of records Record definitions also can be created as static arrays or as dynamic arrays. The size of the static array is determined during compile time and the data will be stored in the stack frame of a SUB or FUNCTION. This means that the array data is lost when the SUB or FUNCTION is ended. Example of a static array definition: RECORD data[10] LOCAL info$ END RECORD To declare a dynamic array of records, the keyword ARRAY must be used. The size of a dynamic record array is determined during runtime, and therefore, can be set with variables and functions. The data is stored in the heap. The BaCon memory management will clean up the data when leaving a FUNCTION or SUB. Example: RECORD data ARRAY 10 LOCAL name$[5] LOCAL age[5] END RECORD Note that dynamic arrays of records do not allow members which are dynamic arrays themselves. Passing records to functions or subs To pass a record, simply declare the variable name with the appropriate record type in the header of the function or sub. Example code: RECORD rec LOCAL nr LOCAL area$ END RECORD SUB subroutine(rec_type var) PRINT var.nr PRINT var.area$ ENDSUB rec.nr = 123 rec.area$ = "europe" CALL subroutine(rec) Similarly, it is possible to pass an array of records as well. Note the square brackets in the function header: RECORD rec ARRAY 10 LOCAL nr LOCAL area$ END RECORD SUB subroutine(rec_type var[]) PRINT var[0].nr PRINT var[0].area$ ENDSUB rec[0].nr = 123 rec[0].area$ = "europe" CALL subroutine(rec) Returning records from functions In order to return a record from a function, the record type must be visible to the caller. The below example declares the record in the main program. The function declares a variable of the same type and initializes the record to 0. This initialization is obligatory for string members to work properly. Then some values are assigned. Lastly, the complete record is returned to the caller: RECORD rec LOCAL id LOCAL zip$[2] END RECORD FUNCTION func TYPE rec_type LOCAL var = { 0 } TYPE rec_type var.id = 1 var.zip$[0] = "XJ342" var.zip$[1] = "YP198" RETURN var ENDFUNCTION rec = func() PRINT rec.id PRINT rec.zip$[0] PRINT rec.zip$[1] Strings by value or by reference Strings can be stored by value or by reference. By value means that a copy of the original string is stored in a variable. This happens automatically when when a string variable name ends with the '$' symbol. Sometimes it may be necessary to refer to a string by reference. In such a case, simply declare a variable name as STRING but omit the '$' at the end. Such a variable will point to the same memory location as the original string. The following examples should show the difference between by value and by reference. When using string variables by value: a$ = "I am here" b$ = a$ a$ = "Hello world..." PRINT a$, b$ This will print "Hello world...I am here". The variables point to their individual memory areas so they contain different strings. Now consider the following code: a$ = "Hello world..." LOCAL b TYPE STRING b = a$ a$ = "Goodbye..." PRINT a$, b FORMAT "%s%s\n" This will print "Goodbye...Goodbye..." because the variable 'b' points to the same memory area as 'a$'. (The optional FORMAT forces the variable 'b' to be printed as a string, otherwise BaCon assumes that the variable 'b' contains a value.) Note that as soon an existing string variable is referred to by a reference variable, the string will not profit from the optimized high performance string engine anymore. ASCII, Unicode, UTF8 BaCon is a byte oriented converter. This means it always will assume that a string consists of a sequence of ASCII bytes. Though this works fine for plain ASCII strings, it will cause unexpected results in case of non-Latin languages, like Chinese or Cyrillic. However, BaCon supports UTF8 encoded strings also. The original text already may contain the UTF8 byte order mark 0xEF 0xBB 0xBF. The function HASBOM can be used to detect if such byte order mark is present. To add or delete a byte order mark, use EDITBOM$. In order to work with UTF8 strings, OPTION UTF8 needs to be enabled. This option will put all string related functions in UTF8 mode at the cost of some performance loss in string processing. Next to this option, BaCon also provides a few functions which relate to UTF8 encoding. The following functions work independently from OPTION UTF8: * ULEN will correctly calculate the actual characters based on the binary UTF8 sequence. * BYTELEN will show the actual amount of bytes used by a UTF8 string. * ISASCII can be used to verify if a string only consists of ASCII data. * UTF8$ needs the Unicode value as argument and returns the corresponding character depending on environment settings and the current font type. * UCS needs a UTF8 character as an argument and returns the corresponding Unicode value. * ESCAPE$ will convert a UTF8 string to an ASCII sequence with escape characters. * UNESCAPE$ will convert an ASCII sequence with escaped characters back to valid UTF8. * HASBOM will detect if the UTF8 byte order mark is present in the text * EDITBOM$ can be used to add or delete a UTF8 byte order mark Binary trees BaCon has a built-in API for binary trees. Compared to arrays, a binary tree is a data structure which can access its elements in a faster and more efficient manner. Regular arrays store an element in a linear way, which, in worst case, can end up in long sequential lookup times. A binary tree however uses an internal decision tree to lookup an element, of which the lookup time, depending on the tree position, is logarithmic. A typical application using a binary tree is a database, which needs to lookup information from a large amount of data. A search in a binary tree will be a lot faster compared to a plain linear search in a regular array. To declare a binary tree, BaCon uses the DECLARE or LOCAL keyword together with TREE. For example, to declare a binary tree containing strings: DECLARE mytree TREE STRING Subsequently, it is possible to declare other types as well, for example integers or floats: DECLARE myinttree TREE int DECLARE myfloattree TREE float After the declaration, new values (nodes) can be added to the tree. Adding a string is very straightforward. It can be added to a binary tree using the TREE statement: TREE mytree ADD "hello" text$ = "world" TREE mytree ADD text$ Similarly, to add an integer or float to the binary tree: TREE myinttree ADD 567 TREE myfloattree ADD 4.127 When adding a duplicate string or value nothing happens. Such attempt will silently be ignored. As a result, all entries in a binary tree are unique. The FIND function can lookup the presence of an element. If found, the FIND function will return TRUE (1), otherwise it will return FALSE (0). The following example looks up a string in a binary tree: IF FIND(mytree, "abc") THEN PRINT "Found!" Similarly it is possible to lookup an integer or float value: result = FIND(myfloattree, 2.2) var = 123 result = FIND(myinttree, var) It is also possible to remove an element from the tree using the DELETE statement. If an element is not found then nothing happens: DELETE result$ FROM mytree DELETE 1.2 FROM myfloattree The COLLECT statement can collect all the strings or values of all nodes in the binary tree and put them into a regular array: COLLECT mytree TO allnodes$ COLLECT myinttree TO intnodes Lastly, the function TOTAL can be used to find out the total amount of elements in a binary tree: PRINT "Amount of nodes:", TOTAL(mytree) Creating and linking to libraries created with BaCon With Bacon, it is possible to create libraries. In the world of Unix these are known as shared objects. The following steps should explain how to create and link to BaCon libraries. Step 1: create a library The below program only contains a function, which accepts one argument and returns a value. FUNCTION bla (NUMBER n) LOCAL i i = 5 * n RETURN i END FUNCTION In this example, the program will be saved as 'libdemo.bac'. Note that the name must begin with the prefix 'lib'. This is a Unix convention. The linker will search for library names starting with these three letters. Step 2: compile the library The program must be compiled using the '-f' flag: bacon -f libdemo.bac This will create a file called 'libdemo.so'. Step 3: copy library to a system path To use the library, it must be located in a place which is known to the linker. There are several ways to achieve this. For sake of simplicity, in this example the library will be copied to a system location. It is common usage to copy additional libraries to '/usr/local/lib': sudo cp libdemo.so /usr/local/lib Step 4: update linker cache The linker now must become aware that there is a new library. Update the linker cache with the following command: sudo ldconfig Step 5: demonstration program The following program uses the function from the new library: PROTO bla x = 5 result = bla(x) PRINT result This program first declares the function 'bla' as prototype, so the BaCon parser will not choke on this external function. Then the external function is invoked and the result is printed on the screen. Step 6: compile and link Now the program must be compiled with reference to the library created before. This can be done as follows: ./bacon -l demo program.bac With the Unix command 'ldd' it will be visible that the resulting binary indeed has a dependency with the new library. When executed, the result of this program should show 25. Remarks In case global dynamic string arrays are used by the BaCon shared object, then these need to be initialized prior to using the arrays. This can be done by calling a special function available in each shared object created in BaCon: the 'BaCon_init()' function. In case the shared object is compiled by a GNU C compatible compiler, then this function is executed automatically. Creating internationalization files It is possible to create internationalized strings for a BaCon program. In order to do so, OPTION INTERNATIONAL should be enabled in the beginning of the program. After this, make sure that each translatable string is surrounded by the INTL$ or NNTL$ function. Now start BaCon and use the '-x' option. This will generate a template for the catalog file, provided that the 'xgettext' utility is available on your platform. The generated template by default has the same name as your BaCon program, but with a '.pot' extension. Then proceed with the template file and fill in the needed translations, create the PO file as usual and copy the binary formatted catalog to the base directory of the catalog files (default: "/usr/share/locale"). The default textdomain and base directory can be changed with the TEXTDOMAIN statement. Below a complete sequence of steps creating internationalization files. Make sure the GNU gettext utilities are installed. Step 1: create program The following simple program should be translated: OPTION INTERNATIONAL TRUE PRINT INTL$("Hello cruel world!") x = 2 PRINT x FORMAT NNTL$("There is %ld green bottle", "There are %ld green bottles", x) This program is saved as 'hello.bac'. Step 2: compile program Now compile the program using the '-x' option. # bacon -x hello.bac Next to the resulting binary, a template catalog file is created called 'hello.pot'. Step 3: create catalog file At the command line prompt, run the 'msginit' utility on the generated template file. # msginit -l nl_NL -o hello.po -i hello.pot In this example, the nl_NL locale is used, which is Dutch. This will create a genuine catalog file called 'hello.po' from the template 'hello.pot'. Step 4: add translations Edit the catalog file 'hello.po' manually, by adding the necessary translations. Step 5: create object file Again at the command line prompt, run the 'msgfmt' utility to convert the catalog file to a binary machine object file. The result will have the same name but with an '.mo' extension: # msgfmt -c -v -o hello.mo hello.po Step 6: install Copy the resulting binary formatted catalog file 'hello.mo' into the correct locale directory. In this example, the locale used was 'nl_NL'. Therefore, it needs to be copied to the default textdomain directory '/usr/share/locale' appended with the locale name, thus: /usr/share/locale/nl_NL. In there, the subdirectory LC_MESSAGES should contain the binary catalog file. # cp hello.mo /usr/share/locale/nl_NL/LC_MESSAGES/ The TEXTDOMAIN statement can be used to change the default directory for the catalog files. Step 7: setup Unix environment Finally, the Unix environment needs to understand that the correct locale must be used. To do so, simply set the LANG environment variable to the desired locale. # export LANG=nl_NL After this, the BaCon program will show the translated strings. Networking TCP Using BaCon, it is possible to create programs which have access to TCP networking. The following small demonstration shows a client program which fetches a website over HTTP: OPEN "www.basic-converter.org:80" FOR NETWORK AS mynet SEND "GET / HTTP/1.1\r\nHost: www.basic-converter.org\r\n\r\n" TO mynet REPEAT RECEIVE dat$ FROM mynet total$ = total$ & dat$ UNTIL ISFALSE(WAIT(mynet, 5000)) CLOSE NETWORK mynet PRINT total$ The following program verifies if a remote site can be reached by a specific port, trying to access it via a specific interface on the localhost: CATCH GOTO Error OPEN "www.basic-converter.org:443" FOR NETWORK FROM "192.168.1.107" AS net PRINT "The host 'basic-converter.org' listens at port 443." CLOSE NETWORK net END LABEL Error PRINT "The host 'basic-converter.org' either is not reachable, filtered or has port 443 not open." The next program shows how to setup a simple TCP server. The main program uses OPEN FOR SERVER after which the ACCEPT function handles the incoming connection: PRINT "Connect from other terminals with 'telnet localhost 51000' and enter text - 'quit' ends." OPEN "localhost:51000" FOR SERVER AS mynet WHILE TRUE fd = ACCEPT(mynet) REPEAT RECEIVE dat$ FROM fd PRINT "Found: ", dat$; UNTIL LEFT$(dat$, 4) = "quit" CLOSE SERVER fd WEND UDP The UDP mode can be set with the OPTION NETWORK statement. After this, a network program for UDP looks the same as a network program for TCP. This is an example client program: OPTION NETWORK UDP OPEN "localhost:1234" FOR NETWORK AS mynet SEND "Hello" TO mynet CLOSE NETWORK mynet Example server program: OPTION NETWORK UDP OPEN "localhost:1234" FOR SERVER AS mynet RECEIVE dat$ FROM mynet CLOSE SERVER mynet PRINT dat$ BROADCAST BaCon also knows how to send data in UDP broadcast mode. For example: OPTION NETWORK BROADCAST OPEN "192.168.1.255:12345" FOR NETWORK AS mynet SEND "Using UDP broadcast" TO mynet CLOSE NETWORK mynet Example server program using UDP broadcast, listening to all interfaces: OPTION NETWORK BROADCAST OPEN "*:12345" FOR SERVER AS mynet RECEIVE dat$ FROM mynet CLOSE SERVER mynet PRINT dat$ MULTICAST If UDP multicast is required then simply specify MULTICAST. Optionally, the TTL can be determined also. Here are the same examples, but using a multicast address with a TTL of 5: OPTION NETWORK MULTICAST 5 OPEN "225.2.2.3:1234" FOR NETWORK AS mynet SEND "This is UDP multicast" TO mynet CLOSE NETWORK mynet Example server program using multicast: OPTION NETWORK MULTICAST OPEN "225.2.2.3:1234" FOR SERVER AS mynet RECEIVE dat$ FROM mynet CLOSE SERVER mynet PRINT dat$ SCTP BaCon also supports networking using the SCTP protocol. Optionally, a value for the amount of streams within one association can be specified. OPTION NETWORK SCTP 5 OPEN "127.0.0.1:12380", "172.17.130.190:12380" FOR NETWORK AS mynet SEND "Hello world" TO mynet CLOSE NETWORK mynet An example server program: OPTION NETWORK SCTP 5 OPEN "127.0.0.1:12380", "172.17.130.190:12380" FOR SERVER AS mynet RECEIVE txt$ FROM mynet CLOSE SERVER mynet PRINT txt$ TLS secured network connections The previous chapter demonstrated network connections where the data is transferred over the wire in plain text. However, with the increasing vulnerabilities in current network traffic, it usually is a good idea to apply Transport Layer Security (TLS). BaCon does not implement a propriety TLS standard by its own. However, it can make use of existing libraries like OpenSSL. Alternatively, BaCon also allows other TLS implementations, in case these provide an OpenSSL compatible API. Examples are the GnuTLS and WolfSSL libraries but also projects forking from OpenSSL, like BoringSSL and LibreSSL. To enable TLS, simply add OPTION TLS to the program. From then on, new network connections are considered to be TLS encapsulated: OPTION TLS TRUE This option enables the usage of OpenSSL by default. The presence of the OpenSSL libraries and header files on the system is required. BaCon will try to convert the source program and assumes the default locations of the OpenSSL development files. However, if these reside at a different location, it is possible to specify their location as follows: PRAGMA TLS openssl INCLUDE LDFLAGS -lssl -lcrypto The OPTION TLS statement is always required, but instead of OpenSSL, it is possible to specify a different library: PRAGMA TLS gnutls This will provide an indication that BaCon should make use of the development files from the GnuTLS implementation. If these files should be taken from a special location: PRAGMA TLS gnutls INCLUDE LDFLAGS -lgnutls -lgnutls-openssl Lastly, BaCon supports WolfSSL as well: PRAGMA TLS wolfssl Also for the WolfSSL library it is possible to specify the location of the development files: PRAGMA TLS wolfssl INCLUDE LDFLAGS -lwolfssl BaCon can use the function CA$ to discover the certificate authority of the connection, and CN$ to discover the common name. The CIPHER$ function can be used to obtain details on the encryption and the VERIFY function to verify the validity of the certificate. The following small program queries a Mac address API over TLS using default OpenSSL: OPTION TLS TRUE website$ = "api.macvendors.com" mac$ = "b0:52:16:d0:3c:fb" OPEN website$ & ":443" FOR NETWORK AS mynet SEND "GET /" & mac$ & " HTTP/1.1\r\nHost: " & website$ & "\r\n\r\n" TO mynet RECEIVE info$ FROM mynet CLOSE NETWORK mynet PRINT TOKEN$(info$, 2, "\r\n\r\n") The next program shows how to setup a simple webserver using TLS: OPTION TLS TRUE CERTIFICATE "key.pem", "certificate.pem" CATCH GOTO resume_on_error CONST Msg$ = "Hello from BaCon!" PRINT "Connect with your browser to 'https://localhost:51000'." OPEN "localhost:51000" FOR SERVER AS mynet WHILE TRUE client = ACCEPT(mynet) IF client < 0 THEN CONTINUE RECEIVE dat$ FROM client PRINT dat$ SEND "HTTP/1.1 200 Ok\r\nContent-Length: " & STR$(LEN(Msg$)) & "\r\n\r\n" & Msg$ TO client CLOSE SERVER client WEND LABEL resume_on_error RESUME The program below demonstrates a plain HTTPS connection using GnuTLS: OPTION TLS TRUE PRAGMA TLS gnutls INCLUDE LDFLAGS -lgnutls -lgnutls-openssl website$ = "www.google.com" OPEN website$ & ":443" FOR NETWORK AS mynet SEND "GET / HTTP/1.1\r\nHost: " & website$ & "\r\n\r\n" TO mynet WHILE WAIT(mynet, 2000) RECEIVE data$ FROM mynet total$ = total$ & data$ IF REGEX(data$, "") THEN BREAK WEND PRINT REPLACE$(total$, "\r\n[0-9a-fA-F]+\r\n", "\r\n", TRUE) PRINT "--------------------------" PRINT CIPHER$(mynet) PRINT CA$(mynet) PRINT CN$(mynet) PRINT VERIFY(mynet, pem_file_with_rootca$) PRINT "--------------------------" CLOSE NETWORK mynet Ramdisks and memory streams When creating programs which need heavy I/O towards the hard drive, it may come handy to create a ramdisk for performance reasons. Basically, a ramdisk is a storage in memory. While on Unix level administrator rights are required to create such a disk, BaCon can create an elementary ramdisk during runtime which is accessible within the program. First, some amount of memory needs to be claimed which has to be opened in streaming mode. This returns a memory pointer which indicates the current position in memory, similar to a file pointer for files. Then, the statements GETLINE and PUTLINE can be used to read and write lines of data towards the memory storage. For example: memory_chunk = MEMORY(1000) OPEN memory_chunk FOR MEMORY AS ramdisk PUTLINE "Hello world" TO ramdisk If the ramdisk needs to be read from the beginning, use MEMREWIND to reposition the memory pointer. In the next example, a GETLINE retrieves the line which was stored there: MEMREWIND ramdisk GETLINE text$ FROM ramdisk If the option MEMSTREAM was set to TRUE, BaCon can treat the created ramdisk also as a string variable, which allows manipulations by using the standard string functions. The variable used for the memory pointer must be a string variable: OPTION MEMSTREAM TRUE memory_chunk = MEMORY(1000) OPEN memory_chunk FOR MEMORY AS ramdisk$ PUTLINE "Hello world" TO ramdisk$ MEMREWIND ramdisk$ IF INSTR(ramdisk$, "world") THEN PRINT "found! PRINT REPLACE$(ramdisk$, "Hello", "Goodbye") Always make sure that there is enough memory to perform string changes to the ramdisk. The RESIZE statement safely can be used to enlarge the claimed memory during runtime, as this will preserve the data. The contents of the ramdisk can be written to disk using PUTBYTE. However, it must be clear how many bytes need to be written, as the total amount of memory reserved to the ramdisk may be bigger than the actual amount of data. The function MEMTELL can be used in case the memory pointer is positioned at the end of the ramdisk: memory_chunk = MEMORY(1000) OPEN memory_chunk FOR MEMORY AS ramdisk PUTLINE "Hello world" TO ramdisk OPEN "ramdisk.txt" FOR WRITING AS txtfile PUTBYTE memory_chunk TO txtfile CHUNK MEMTELL(ramdisk)-memory_chunk CLOSE FILE txtfile CLOSE MEMORY ramdisk FREE memory_chunk Alternatively, if the ramdisk was opened with OPTION MEMSTREAM set to TRUE, the string function LEN also will return the length of the data. Delimited strings A delimited string is a string which can be cut into parts, based on a character or on a set of characters. An example of such a string is a plain space delimited line in a textbook, where the words are separated by a whitespace. Another example is an ASCII file, in which the lines are separated by a newline. A very famous example of a delimited string is the Comma Separated Value (CSV) string. From another point of view, a delimited string also can be looked at as a list of items, which is the basis of LISP like languages. The SPLIT statement can be used to split a string into elements of an array, based on a delimiter. As with all statements and functions handling delimited strings, the SPLIT statement will ignore a delimiter when it occurs between double quotes. Such delimiter is considered to be part of the string. For example: csv$ = "This,is,a,CSV,string,\"with,an\",escaped,delimiter" SPLIT csv$ BY "," TO member$ SIZE x One of the resulting members of the array will contain "with,an" because the comma is enclosed within double quotes. BaCon will consider this a piece of text where the characters should be kept together. The behavior of skipping delimiters within double quotes can be changed by setting or unsetting OPTION QUOTED. The JOIN statement can be used to merge array elements back into one (delimited) string. Instead of SPLIT, it is possible to use FOR..IN as well. This statement will subsequently return the parts of the delimited text into a variable. Example: FOR i$ IN "aa bb cc" In this example, the variable 'i$' will subsequently have the value 'aa', 'bb' and 'cc' assigned. Also the FOR statement will skip a delimiter occurring within double quotes. Note that the OPTION COLLAPSE will prevent empty results, both for SPLIT and FOR. It is also possible to return a single member in a delimited string. This can be achieved with the TOKEN$ function. Note that all element counting is 1-based. The following returns the 5^th member of a space delimited string, being "e f". It does not use an optional third parameter, because for all delimited string processing, BaCon defines the default delimiter as a single space: PRINT TOKEN$("a b c d \"e f\" g h i j", 5) But this function also works in case some other delimiter is used. The delimiter must then be specified in the third optional argument. PRINT TOKEN$("1,2,3,4,5", 3, ",") All the functions handling delimited strings accept such an optional argument. Alternatively, OPTION DELIM can define the delimiter string which should be used in subsequent functions. As mentioned, the default value is a single space. The function EXPLODE$ will return a delimited string based on a specified amount of characters: PRINT EXPLODE$("aabbcc", 1) Alternatively, the inline loop function COIL$ can create a delimited string also, using an optional variable, for example the alphabet: PRINT COIL$(i, 26, CHR$(64+i)) The MERGE$ function will do the opposite: merging the elements of a delimited string to one regular string, again optionally specifying a delimiter, for example: PRINT MERGE$("aa,bb,cc", ",") The ISTOKEN function can verify if a text occurs as a token in a delimited string. If so, this function returns the actual position of the token: t$ = "Kiev Amsterdam Lima Moscow Warschau Vienna Paris Madrid Bonn Bern Rome" PRINT "Is this a token: ", ISTOKEN(t$, "Rome") To obtain the first members from a delimited string, the function HEAD$ can be used: PRINT "The first 2 elements: ", HEAD$(t$, 2) Similarly, it is possible to get the last elements by using TAIL$: PRINT "The last element: ", TAIL$(t$, 1) The TAIL$ and HEAD$ functions have their complementary functions in LAST$ and FIRST$. The following example will show all members of a delimited string except the first 2 members: PRINT "All except the first 2 elements: ", LAST$(t$, 2) The next code shows all members except the last: PRINT "All except the last element: ", FIRST$(t$, 1) It also is possible to obtain an excerpt using CUT$. The following piece of code will get the members from delimited string 't$' starting at position 2 and ending at position 4 inclusive: PRINT "Some middle members: ", CUT$(t$, 2, 4) Instead of fetching a member, BaCon also can change a member in a delimited string directly by using the CHANGE$ function: result$ = CHANGE$("a,b,c,d,e,f,g,h,i,j", 5, "Ok", ",") It is even possible to swap two members in a delimited string with the EXCHANGE$ function: result$ = EXCHANGE$("a b c d e f g h i j", 5, 4) The UNIQ$ function will return a delimited string where all members occur only once: city$ = "Kiev Lima Moscow \"New York\" Warschau \"New York\" Rome" PRINT "Unique member cities: ", UNIQ$(city$) To add more members to a delimited string, use APPEND$: t$ = APPEND$(t$, 2, "Santiago") And to delete a member from a delimited string, use DEL$: t$ = DEL$(t$, 3) There are also functions to sort the members in a delimited string (SORT$) and to put them in reversed order (REV$). With PROPER$ it is possible to capitalize the first letter of each individual element in a delimited string. The ROTATE$ function rotates the items in a delimited string. The COLLAPSE$ function will remove empty items in a delimited string. The MATCH function can compare elements between two delimited strings and PARSE can return parts of a delimited string based on wildcards. The WHERE function returns the actual character position of the indicated token. To determine if a string contains a delimiter at all, the HASDELIM function can be used, while the DELIM$ function can change the actual delimiter in a string to some other definition. If a member still contains double quotes and escaped double quotes, then this can be flattened out by using the FLATTEN$ function. This function will remove double quotes and put escaping one level lower: PRINT FLATTEN$("\"Hello \\\" world\"") Lastly, the function AMOUNT will count the number of members in a delimited string: nr = AMOUNT("a b c d e f g h i j") PRINT AMOUNT("a,b,c,d,e,f,g,h,i,j", ",") BaCon also has string functions available to handle delimited strings which use unbalanced delimiters. These are delimiters which consist of different characters, or different sets of characters. Examples of such strings are HTML or XML strings. They can be handled by functions like INBETWEEN$ and OUTBETWEEN$ very easily. For example, to obtain the title of a website from an HTML definition: PRINT INBETWEEN$("Website", "", "") By default, INBETWEEN$ will perform a non-greedy match, but the fourth optional argument can be set to specify a greedy match. Similarly, the OUTBETWEEN$ function will return everything but the matched substring, effectively cutting out a substring based on unbalanced delimiters. Note that OPTION COLLAPSE does not impact both INBETWEEN$ and OUTBETWEEN$. Regular expressions BaCon can digest POSIX compliant regular expressions when using the REGEX function or the string functions EXTRACT$, REPLACE$ and WALK$. For this, BaCon relies on the standard libc implementation. However, it is possible to define a different regular expression engine with the PRAGMA statement. For example, to specify the very fast NFA based regular expression library TRE, the following line must be added at the top of the program: PRAGMA RE tre This will include the header file from the TRE library and will link against its shared object. Of course, the system needs to have the required development files from the TRE library installed. BaCon will add the default locations of all necessary files to the compile flags. In case these files are kept at a different location, it is possible to define this explicitly as well: PRAGMA RE tre INCLUDE LDFLAGS -ltre Next to the TRE library, also the Oniguruma library can be specified: PRAGMA RE onig When specifying the required development files: PRAGMA RE onig INCLUDE LDFLAGS -lonig Also the famous PCRE library is supported: PRAGMA RE pcre The full definition looks like: PRAGMA RE pcre INCLUDE LDFLAGS -lpcreposix Basically, any regular expression library with a functional POSIX interface can be specified. This allows a lot of flexibility when certain features for regular expression parsing are required. The libraries TRE, Oniguruma and PCRE do not need a further INCLUDE or LDFLAGS specification if their development files have their default names and reside at their default location. Error trapping, error catching and debugging BaCon can distinguish between 4 types of errors. 1. System errors. These relate to the environment in which BaCon runs. 2. Syntax errors. These are detected during the conversion process. 3. Compiler errors. These are generated by the C compiler and passed on to BaCon. 4. Runtime errors. These can occur during execution of the program. When an error occurs, the default behavior of a BaCon program is to stop. Only in case of runtime errors, it is possible to let the program handle the error. * In case of statements, the CATCH GOTO command can jump to a self-defined error handling function. This is especially convenient when creating GUI applications, as runtime errors by default appear on the Unix command prompt. * In case of functions, the OPTION ERROR must be set to FALSE to prevent the program from stopping. The program then needs to check the reserved ERROR variable to handle any unexpected situation. Alternatively, it is possible to set a callback function for both statements and functions. This callback function can be defined by the CATCH ERROR statement. It should point to a function with three arguments: the first argument capturing the statement or function causing the error, the second the name of the file and the last the line number. To prevent BaCon from detecting runtime errors altogether, use TRAP SYSTEM. The reserved ERROR variable contains the number of the last error occurred. A full list of error numbers can be found in appendix A. With the ERR$ function a human readable text for the error number can be retrieved programmatically. Next to these options, the statement TRACE ON can set the program in such a way that it is executed at each keystroke, step-by-step. This way it is possible to spot the location where the problem occurs. The ESC-key will then exit the program. To switch of trace mode within a program, use TRACE OFF. Also the STOP statement can be useful in debugging. This will interrupt the execution of the program and return to the Unix command prompt, allowing intermediate checks. By using the Unix 'fg' command, or by sending the CONT signal to the PID of the program, execution can be resumed. Notes on transcompiling The process of translating a programming language into another language, and then compiling it, is also known as transcompiling. BaCon is a Basic to C translator, or a transcompiler, or transpiler. When using BaCon, three stages can be distinguished: 1. conversion time 2. compilation time 3. runtime It is important to realize that BaCon commands can function in all these stages. Examples of statements which have impact the on conversion stage are INCLUDE, RELATE, USEC, USEH, WITH and some of the OPTION arguments. These statements instruct BaCon about the way the Basic code should be converted. A statement impacting the compilation stage is PRAGMA. With this statement it is possible to influence the behavior of the compiler. Most other BaCon statements are effective during runtime. These form the actual program being executed. It should be clear that the aforementioned stages cannot be mixed. For example, it does not make sense to define the argument for the INCLUDE statement in a string variable, as the INCLUDE statement is effective during conversion time, while variables are used during runtime. Note that except for system errors, the logic of the error messages basically follows the same structure: there are syntax errors (conversion time), compiler errors and runtime errors. The system errors relate to the possibility of using BaCon itself. Using the BaCon spartanic editor (BaSE) BaCon comes with a limited built-in ASCII editor which can display BaCon code using syntax highlighting. The editor requires an ANSI compliant terminal. To start the editor, simply use the '-e' argument and a filename: # bacon -e prog.bac If the file does not exist, then an empty screen occurs. Pressing the button will pop up a menu down below the screen. The options are: - (H)elp: display the Help screen - (Q)uit: quit the editor The usual functionality for editing text applies. Most actions can be performed using the key and a regular key. The + key combination will pop up the Help screen. The Help screen will show the following information: - +: new file - +: load file - +: save file - +: cut line of text - +: copy line of text - +: paste line of text - + or +: delete line - : put cursor at start of line - : put cursor at end of line - : move one page upwards - : move page downwards - : navigate through text - +: find term in code - +: goto line number - +: compile and execute program - +: apply indentation - +: toggle line numbers - +: toggle text boldness - +: show context info - +: show this help - +: quit BaCon spartanic editor The context info works by first moving the cursor to the statement or function for which more information is desired. Then, the + key combination will try to lookup the keyword in the manpage. It is also possible to adjust the colors of the syntax highlighting. Create the BaCon configuration file ~/.bacon/bacon.cfg if it does not exist yet. The following keywords can set the coloring scheme, following the numbering of the COLOR statement: - statement_color (default: 2) - function_color (default: 6) - variable_color (default: 3) - type_color (default: 3) - number_color (default: 1) - comment_color (default: 4) - quote_color (default: 5) - default_color (default: 7) For example, to set the color for quoted text strings to green, the following definition can be added to the BaCon configuration file: quote_color 2 Support for GUI programming BaCon has a few functions available which enable basic GUI programming. These functions follow the "object/property" model and implement simple event handling. Enabling GUI functions To enable these functions, add the following line to your code: OPTION GUI TRUE By default, BaCon assumes Xaw as a backend. Though this is not the most attractive widget set in the world, it is always available on any Unix platform which has X installed. Alternatively, a different backend can be set using PRAGMA GUI. Currently, Xaw3d, Motif, TK, GTK2, GTK3, GTK4 and the console based CDK widgets (experimental) sets are supported. The following code will select Motif as a backend: PRAGMA GUI motif Instead of "motif", the PRAGMA GUI statement also accepts "xaw3d", "uil", "gtk2", "gtk3", "gtk4", "tk" and "cdk". BaCon generates the source code for the GUI so the required header files for the toolkits should be available on the compiling platform. Defining the GUI To define the GUI, the function GUIDEFINE can be used. This function may occur in the program only once. Its string argument should contain a list of declarative widget definitions, each set of definitions grouped between curly brackets. The GUIDEFINE function returns the GUI id number. This is an example for the Xaw widget toolkit: gui = GUIDEFINE( " \ { type=window name=window callback=window XtNwidth=300 XtNtitle=\"Information\" } \ { type=dialogWidgetClass name=dialog parent=window XtNlabel=\"Enter term:\" XtNvalue=\"\" } \ { type=commandWidgetClass name=submit parent=dialog callback=XtNcallback XtNlabel=\"Submit\" } ") As can be observed, each widget refers to properties which are specific to the Xaw toolkit. A widget must have a type and a name. Optionally, a widget can be attached to a parent, and it can submit a callback signal, in which case the signal name must be specified. The callback keyword can also define a particular string to be returned in the event loop, defined after the signal name. The following will return "click" when the button is pressed: { type=commandWidgetClass name=submit parent=dialog callback=XtNcallback,click XtNlabel=\"Submit\" } Setting properties The function GUISET can be used to specify more properties at a later stage in the program: CALL GUISET(gui, "label", XtNjustify, XtJustifyLeft) The function GUIGET is used to retrieve a value from a widget. The value is stored in a pointer variable: CALL GUIGET(gui, "text", XtNstring, &txt) For the TK backend, these functions can get and set variables from and to the several TK functions. Entering the mainloop Once the GUI is defined, the program can enter the event loop using GUIEVENT$. It requires the GUI id as an argument: WHILE TRUE event$ = GUIEVENT$(gui) SELECT event$ CASE "submit" BREAK ENDSELECT WEND When an event has happened, either the name of the widget causing the event is returned, or the defined string in the callback keyword. The BaCon program can decide what to do next. In some cases it may be necessary to obtain a value which is passed to the callback by the widget library. In such situation, the GUIEVENT$ function accepts an optional second boolean parameter. This will add the incoming callback value as a pointer attached as a string to the return value. For example, to obtain the selected item in a XawList widget, the Xaw library returns a struct containing information about the XawList. This can be fetched as follows: WHILE TRUE event$ = GUIEVENT$(gui, TRUE) SELECT TOKEN$(event$, 1) CASE "submit" BREAK CASE "list" info = (XawListReturnStruct*)DEC(TOKEN$(event$, 2)) PRINT TOKEN$(info->string, 1) ENDSELECT WEND Defining helper functions It is also possible to define supplementary helper functions. A widget library can implement additional functions to perform actions on widgets. These helper functions can be configured in the BaCon program by setting up a function pointer and then use GUIFN. For example, to define a helper function showing a Xaw widget: LOCAL (*show)() = XtPopup TYPE void CALL GUIFN(id, "window", show, XtGrabNonexclusive) Such definition may seem unnecessary, however, using GUIFN has several advantages: it is compliant with the overall API design, the source code becomes smaller in size, and most importantly, we do not need to worry about argument types of the helper function. For the TK backend, the GUIFN statement can define additional TCL code in the current TK context. Using native functions The function GUIWIDGET will return the memory address of a widget based on the defined name for that widget. This can come handy in cases where a GUI helper function is used natively. Overview of BaCon statements and functions ABS ABS(x) Type: function Returns the absolute value of x. This is the value of x without sign. Example without and with ABS, where the latter always will produce a positive output: PRINT x-y PRINT ABS(x-y) ACCEPT ACCEPT(fd) Type: function In a network program, this function waits for an incoming connection and returns a new descriptor to be used for SEND and RECEIVE. If the ACCEPT functions fails, then the returned value is a negative number. Example: OPEN "localhost:51000" FOR SERVER AS mynet WHILE TRUE fd = ACCEPT(mynet) REPEAT RECEIVE dat$ FROM fd PRINT "Found: ", dat$; UNTIL LEFT$(dat$, 4) = "quit" CLOSE SERVER fd WEND ACOS ACOS(x) Type: function Returns the calculated arc cosine of x, where x is a value in radians. ADDRESS ADDRESS(x) Type: function Returns the memory address of a variable or function. The ADDRESS function can be used when passing pointers to imported C functions (see IMPORT). ALARM ALARM ,