TM1 Stack Frame for Function Returns
Posted: Fri Jul 10, 2020 1:19 am
* Mods feel free to move this if it is in the wrong place!
In an attempt to simplify child -> parent return data, I decided to see if I could implement a reliable stack structure within TM1 to easily pass values between functions without having to write anything to a cube. My main requirements were:
1) No cube writes - I wanted to see if I could easily pass values between processes without having to rely on cubes (and views); I want simple, functional programming style returns
2) Minimal code - this will never fly if I have to remember to initialize/call 5+ TIs
3) No outside code - can only use TM1 functions and objects
What I came up with expands the functionality of session variables by allowing multiple values to be returned through a reference to a dimension acting as a stack. The basic concept is as follows:
Each time a value is pushed to the stack, the stack_pointer is incremented and the prefix is prepended to the value in order to ensure it is unique. When the value is popped, the opposite happens - stack_pointer is decremented and the value is deleted from the dimension (stack). The full pushed value on the stack will look like: "1: test1" after the prefix is added. When popped, the value will be loaded to session.stack_return, which can be accessed by any process.
The stack_sanity variable is included as a sort of macro to ensure that our stack is always in sync. On each successful pop or push the stack_sanity variable is redefined to DIMSIZ(session.stack) - stack_pointer. In this way, we can ensure that our stack is synced in case of a bad run that causes our pointer to be misaligned with the current element count. It is probably safer to check that stack_sanity is equal to 0 before using a popped value.
A simple implementation could look something like the following:
This is obviously a pretty rough draft and the idea could definitely be expanded on to add some much needed quality-of-life features, but it's functional as it currently stands. This was built to avoid having to use cubes (and therefore assembled views) to transfer linear lists of data. I have found it useful in capturing errors in child processes by pushing a reference to them to the stack as they occur, and then popping them off when I get back to the parent process - basically pushing pointers to the error messages. In this way, I can easily record what happened during the entire execution chain without needing to keep a logging cube or something similar.
If you want to pop multiple values, consider pushing the number of elements you want to pop as the final element of the stack. Then when you pop the first value in a different process, you can use that as the condition for a while loop to pop the rest of the values.
This may not be game changing and probably won't ever see anything more than a development environment, but it was at least fun to play with. If you're interested, the code with the sample implementation can be found at my github link: https://github.com/bgregs514/tm1_code
In an attempt to simplify child -> parent return data, I decided to see if I could implement a reliable stack structure within TM1 to easily pass values between functions without having to write anything to a cube. My main requirements were:
1) No cube writes - I wanted to see if I could easily pass values between processes without having to rely on cubes (and views); I want simple, functional programming style returns
2) Minimal code - this will never fly if I have to remember to initialize/call 5+ TIs
3) No outside code - can only use TM1 functions and objects
What I came up with expands the functionality of session variables by allowing multiple values to be returned through a reference to a dimension acting as a stack. The basic concept is as follows:
Code: Select all
StringSessionVariable('session.stack'); /* reference to the stack name (private) */
StringSessionVariable('session.stack_prefix'); /* reference to a predefined prefix (private) */
StringSessionVariable('session.stack_return'); /* reference to a popped value (public)
NumericSessionVariable('session.stack_pointer'); /* reference to the stack pointer (private) */
NumericSessionVariable('session.stack_sanity'); /* reference to the sanity check (public) */
The stack_sanity variable is included as a sort of macro to ensure that our stack is always in sync. On each successful pop or push the stack_sanity variable is redefined to DIMSIZ(session.stack) - stack_pointer. In this way, we can ensure that our stack is synced in case of a bad run that causes our pointer to be misaligned with the current element count. It is probably safer to check that stack_sanity is equal to 0 before using a popped value.
A simple implementation could look something like the following:
Code: Select all
StringSessionVariable('session.stack_return');
NumericSessionVariable('session.stack_sanity');
ExecuteProcess('push', 'psStr', 'test1'); /* push "test1"; the stack will initialize if not already done */
ExecuteProcess('push', 'psStr', '15'); /* push "15"; all values are pushed as strings - ints must be cast
appropriately before use after popping */
ExecuteProcess('pop', 'pnType', 0); /* pop the last value ("15") LIFO style */
if (session.stack_sanity = 0); /* check that we're synced */
asciioutput('test_file.csv', session.stack_return); /* session.stack_return contains our popped value */
endif;
If you want to pop multiple values, consider pushing the number of elements you want to pop as the final element of the stack. Then when you pop the first value in a different process, you can use that as the condition for a while loop to pop the rest of the values.
This may not be game changing and probably won't ever see anything more than a development environment, but it was at least fun to play with. If you're interested, the code with the sample implementation can be found at my github link: https://github.com/bgregs514/tm1_code