Returning Values

Returning Values Like TI-Basic Functions

Starting from release 1.5 of the TIGCC library, it is possible to write programs which return a value to the TI-Basic, i.e. which act like TI-Basic function (but note that there are some serious limitations if you want to do this; read below for more info). To do this, put the following statement at the begining of the program (or at the begining of the main module of your program):

#define RETURN_VALUE

This will work in both NoStub and kernel mode. Such statement will cause the last value pushed to the expression stack to become the "result" of the program. For pushing values on expression stack, use routines from the estack.h header file. For example, use push_shortint or push_longint to push integer values, push_Float to push floating point values, push_zstr to push strings, etc. Note that if you declared RETURN_VALUE, you must push something on the expression stack.

Recently, I received some important information from Kevin Kofler. If you plan to write a function which returns a value to TI-Basic in either Assembly or C, you should also clean up all arguments of the function from the expression stack before pushing the result including the END_TAG. If you neglect to do this, then using the function as an argument of another one will not work correctly. Also, you should leave exactly one value on the expression stack (i.e. you should delete all eventual temporary results from the expression stack). Here is a sample code how you can clean up function arguments from the expression stack:

while (GetArgType (top_estack) != END_TAG)
  top_estack = next_expression_index (top_estack);
top_estack--;

Here is a complete example (called "Add Arguments") of a very simple program which gets two arguments (assuming that they are small positive integers, without any checking), and returns their sum (see args.h for more info about getting the arguments):

// Add the first two integers passed to the program

#define RETURN_VALUE

#define USE_TI89
#define USE_TI92PLUS
#define USE_V200

#define MIN_AMS 101

#include <args.h>
#include <estack.h>

void _main(void)
{
  ESI argptr = top_estack;
  short a = GetIntArg (argptr);
  short b = GetIntArg (argptr);
  while (GetArgType (top_estack) != END_TAG)  // Clean up arguments
    top_estack = next_expression_index (top_estack);
  top_estack--;
  push_longint (a + b);
}

Test this program from TI-Basic by giving add(2,3) (assuming that you compiled it and gave the name "add.c" to it). Note that if you neglect cleaning up arguments from the expression stack, then something like add(add(3,5),add(4,7)) will not give the correct result!

You can even return lists as the result. To do this, push first End_Of_List marker (using push_END_TAG), then push elements of the list in the reverse order, and finnaly push List marker on the expression stack using push_LIST_TAG. The following program (called "Folder") returns the list of all variables in the folder which is given as the argument:

// Get the variables in a folder as a list

#define RETURN_VALUE

#define USE_TI89
#define USE_TI92PLUS
#define USE_V200

#define MIN_AMS 101

#include <args.h>
#include <estack.h>
#include <vat.h>

void _main(void)
{
  ESI argptr = top_estack;
  SYM_ENTRY *SymPtr = SymFindFirst (GetSymstrArg (argptr), 1);
  while (GetArgType (top_estack) != END_TAG)  // Clean up arguments
    top_estack = next_expression_index (top_estack);
  top_estack--;
  push_END_TAG ();
  while (SymPtr)
    {
      push_ANSI_string (SymPtr->name);
      SymPtr = SymFindNext ();
    }
  push_LIST_TAG ();
}

Give the name "folder.c" to it, compile it using

tigcc -O2 folder.c

then try, for example, folder("main") from the TI-Basic. Happy? Many users asked me how to make such a program!

Now about problems. Everything works fine in AMS 1.xx, but AMS 2.xx forbids the use of ASM programs in expressions. So, in the above example, 'add(2,3)' will work perfectly, but '5+add(2,3)' or even 'add(2,3)->a' will not. This stupidity makes returning values mostly useless. What to do? Unfortunately, I can't do nothing from the TIGCC itself, because an ASM program will not be executed at all if AMS 2.xx detects its presence in an expression. However, there is a solution: it is possible to make a resident program which will intercept such "stupid" behaviour of AMS 2.xx and to allow executing ASM programs in expressions. Such interception is already implemented in the latest versions of DoorsOS, Universal OS, PreOS and KerNO (but not in TeOS). So, if you have installed a fresh release of DoorsOS, Universal OS, PreOS or KerNO, everything will work OK even on AMS 2.xx. There is also a TSR called IPR which intercepts only this error and the "ASAP or Exec string too long" error available at Cyril Pascal's (Paxal's) web page for those who don't want to install a full-featured kernel. But for HW2 calculators, the HW2 AMS 2 TSR support (h220xTSR) needs to be installed before IPR. This does not mean that your program must be compiled in "Doors" mode: it may be a "nostub" program, but DoorsOS, Universal OS, PreOs, KerNO or IPR must be present on the calculator (to intercept stupid behavior of AMS 2.xx). To conclude: if you have AMS 2.xx and if you want to use "returning a value" feature, you must have installed DoorsOS, Universal OS, PreOs, KerNO or IPR. If you are a programmer, please note this fact in the documentation of any program which uses this feature!

As an alternative, you may use returning values through variables.


Returning Values Through Variables

As mentioned in the previous section, returning a value from ASM program on a similar way like TI-Basic functions return values causes problems with AMS 2.xx. So, in this release of TIGCCLIB, another way for returning values (which works well unconditionally) is introduced. This method will be ilustrated using a concrete example. Look the following modified "Folder" program given in the previous section (called "Folder List"):

// Write the variables in a folder into a list variable

#define RETURN_VALUE dirlist

#define USE_TI89
#define USE_TI92PLUS
#define USE_V200

#include <args.h>
#include <estack.h>
#include <vat.h>

void _main(void)
{
  ESI argptr = top_estack;
  SYM_ENTRY *SymPtr = SymFindFirst (GetSymstrArg (argptr), 1);
  push_END_TAG ();
  while (SymPtr)
    {
      push_ANSI_string (SymPtr->name);
      SymPtr = SymFindNext ();
    }
  push_LIST_TAG ();
}

The only difference between this program and the program in the previous section is in the usage of the RETURN_VALUE directive: in this example, it is followed with a variable name (dirlist in this example). If RETURN_VALUE is followed with a variable name (which must be a legal TI-Basic variable name in lowercase), the last value pushed to the expression stack would be stored in the TI-Basic variable with the given name. So, in this example, after execution of the program, the result of the program (a folder list) will be stored in TI-Basic variable called dirlist (if such variable does not exist, it will be created). See the previous section for more info about this example. I hope that this explanation is clear enough, so more detailed explanation is not necessary.


Returning TI-Basic Errors

Basically, you can return errors using ER_throw or ER_throwVar from error.h. Many functions from this library throw errors, which you can simply pass on to the operating system, possibly using TRY...FINALLY...ENDFINAL blocks.

However, for the system to operate normally after this, you need to write

#define ENABLE_ERROR_RETURN

at the beginning of your program. Otherwise, the screen will not be updated properly and other cleanup code (for example for USE_INTERNAL_FLINE_EMULATOR) is not executed. Most importantly, however, the program handle remains locked in AMS 1.xx because of a bug in the operating system. If you try to start the program again, you will get an "Invalid Program Reference" error.

Note: The workaround for this bug works only in kernel-less programs. For kernel-based programs, working around the bug is the kernel's responsibility. If you want to use this directive in kernel mode, you need to check which kernels perform such a workaround, and tell your users about this fact.

The following example (called "Memory Error") demonstrates how this directive may be used together with TRY...FINALLY...ENDFINAL blocks:

// Allocate memory as long as possible, then throw an error
// All allocated memory will be freed again!

#define USE_TI89              // Compile for TI-89
#define USE_TI92PLUS          // Compile for TI-92 Plus
#define USE_V200              // Compile for V200

#define MIN_AMS 100           // Compile for AMS 1.00 or higher
#define ENABLE_ERROR_RETURN   // Enable Returning Errors to TIOS

#include <tigcclib.h>         // Include All Header Files

#define BLOCK_SIZE 1024

void AllocRecursively(void)
{
  void *ptr = malloc_throw (BLOCK_SIZE);
  TRY
    // Could do something with ptr here...
    AllocRecursively ();
    // Could still do something with ptr...
  FINALLY
    free (ptr);
  ENDFINAL
}

// Main Function
void _main(void)
{
  AllocRecursively ();
}

Return to the main index