Halcyon Calc can combine a series of numbers, strings, lists, expressions and operations together into a simple or complex program. By creating programs, you can define your own operations which can then use in expressions, just as you would use the built-in operations. Programs are only supported on Halcyon Calc. Halcyon Calc Lite does not support programs. Working with programs is described in the following sections:
- Entering Programs
- Executing Programs
- Conditional Execution
- Creating Custom Operations
- Debugging Programs
- Manipulating Programs
A program consists of a series of stack items and operation names between ≪ and ≫ characters. The final ≫ character is optional. If it is left out, one will be added automatically. Any valid stack item can appear inside a program, including complex numbers, strings, lists and even other programs.
By default, the calculator switches to alpha mode when entering a program. Nearly all operations are inserted onto the command line when pressed when in alpha mode. So, to add the EVAL operation in a program, it is as simple as hitting the "EVAL" button.
In the absence of loops and conditional evaluation, a program is evaluated sequentially. Stack items are pushed onto the stack. Operations are executed and operate on items onto the stack, perhaps pushing other items.
There are multiple ways to execute a program. You can use the EVAL or the →NUM to execute a program from the top of the stack. If you store an program into a symbol, pushing that symbol onto the stack will recall the program and execute it.
As an example, we will work with a program which calculates cubes of an input value. If we want to calculate 5 to the power of 3, we can push 5 onto the stack, followed by this program:
≪ 3 ^ ≫
At this point, you can use the EVAL operation to execute the program. During execution, the 3 is pushed onto the stack. Then the ^ (power) operation is executed. The 5 and 3 are popped off the stack and 125 is pushed onto the stack which is 53.
To make this easier, the same program can be pushed onto the stack, followed by the symbol CUBE. Then, using STO, we can store the program into a global symbol called CUBE.
Once that is done, we can execute the program a couple of ways. With 5 on the stack already, we can type the letters to spell "CUBE". Pushing this symbol on the stack will cause the calculator to lookup its value. Finding a program, the calculator will automatically execute the program which will then calculate the cube of 5.
Or, you can hit the "User" button to see the current set of global symbols on the menu buttons. Just hitting the "CUBE" button will execute the program. This is very much like hitting the "SIN" button from the "Trig" menu to calculate the sine of an input value.
There are still more ways to improve the CUBE example and we will return to it in the section about custom operations.
≪ ... IF ... operations ... THEN ... operations ... END ... ≫
When executing a program with an IF operation, execution continues with the stack items and operations up to the THEN operation. When execution reaches the THEN statement, the top of the stack is popped. A real value is expected at the top of the stack or an error will occur. The real value is tested. If it is non-zero, then the if condition is considered to be true, otherwise if the value is zero, it is false. If the condition is true, then the operations between THEN and END are executed. If the conditions are false, then the operations between THEN and END are skipped. Execution always continues after the END operation.
Optionally, you can include a ELSE operation which would look like this:
≪ ... IF ... operations ... THEN ... operations ... ELSE ... operations ... END ... ≫
In this case, if the real value popped at THEN is non-zero (true), then the operations between THEN and ELSE are executed. If the value is zero (false), then the operations between ELSE and END are executed. In both cases, execution continues after the END operation.
Imagine we want to divide two numbers but we want to handle the case where a divide by zero could happen. If a divide by zero would occur, the program pushes the string "Divide by zero". The program will assume that the two numbers to be divided are on the stack already. Here is the program:
≪ DUP IF 0 SAME THEN DROP DROP "Divide by zero" ELSE / END ≫
The program duplicates the top of the stack so it has a copy to test against. It pushes the 0 and then uses the SAME operation to compare the value against 0. If they are the same, the two numbers are dropped from the stack and the string is pushed on. Otherwise, the program executes the divide.
An alternate way to do this is to use the IFERR operation. This operation uses the same structure as IF, with a following THEN and END operation and optionally an ELSE operation. The divide example would now look like this:
≪ IFERR / THEN "Divide by zero" END ≫
With IFERR, execution of the operations between IFERR and THEN occurs normally. But, if an error is raised by any operation in that sequence, execution jumps from there to the operations following THEN. It may skip one or more operations between IFERR and THEN when the error occurs. Once the operations after the THEN block are executed, execution continues after the END operation normally as though no error occurred. If no-error occurs, execution will jump to an optional ELSE block if it exists or to the operations following END. The IFERR operation is a great way to handle unexpected failures in your programs.
Note that IF and IFERR structures can be nested within each other. You can have an IF/THEN/ELSE/END between the IF and THEN of a different conditional. The key is to keep your IF/THEN/END statements balanced. Similarly for IFERR. Note that if you leave one or more END's out of your program when you enter it, the calculator will add one or more for you to close out any open IF/IFERR statements. If you see END's added which you did not expect, review your program very closely because you likely forgot an END somewhere.
Finally, we can also use the IFT and the IFTE operations as an alternative. Unlike the compound statements used with IF, these operations get the true and an optional false else clause from the stack.
The divide example above could be written like this using IFTE:
≪ DUP 0 SAME ≪ DROP DROP "Divide by zero" ≫ ≪ / ≫ IFTE ≫
In this case, the top of the stack is duplicated and then compared against 0. The result of that compare is still on the stack when two programs are pushed onto the stack. The first program pushed on the stack is executed if the comparison was true (a divide by zero would have occurred). The second program is executed if the comparison was false (no divide by zero). The IFTE operation pops the two programs and the comparison. It then executes the appropriate program based on the value of the comparison.
Either IF or IFT/IFTE can be used for conditional execution. Use whatever you find easiest to work with.
Programs may need a place to store the result of a comparison for later use. The calculator makes available a global 64-bit set of flags. Of them, about half can be used by user programs to store comparison results temporarily. The upper bits hold global calculator state. Changing those bits can change the behaviour of the calculator. The flags changed by a running program continue to hold their values after the program finishes. So, if you change the value of a global configuration it is best to restore the flags when the program completes.
The following table describes the current flag bits which are available:
|1 - 30||Available for user programs|
|31||Set if LAST is on, default value is set|
|32 - 34||Reserved for future use|
|35||If set, constants like e will remain in symbolic form. If cleared, constants will automatically be replaced with their numeric values. The default value is set.|
|36||If set, evaluation of an expression will only lookup each symbol in the expression but when clear, the result of the lookup may itself result in more lookups until a numeric value is found. The default value is set.|
|37 - 42||These bits store the current integer word size minus one. So, if the word size is 64, then bits 37 through 42 are all set (ie 63). The default word size is 64.|
|43 - 44||These two bits encode the current integer base. With 44 being the leftmost binary digit and 43 the rightmost, 00 is decimal, 01 is octal, 10 is binary and 11 is hexadecimal. The default is decimal.|
|45||Set if ML is on, clear otherwise. The default is set.|
|46||Set if the previous PUTI or GETI operation wrapped the incoming index. Otherwise, the flag is clear.|
|47||Reserved for future use|
|48||Set if RDX, is on, clear otherwise. The default value of this bit depends on the international configuration of the iPhone.|
|49 - 50||These two bits encode the current number format. With 50 being the leftmost binary digit and 49 the rightmost, 00 is standard mode, 01 is fixed mode, 10 is scientific mode and 11 is engineering mode. The default is standard mode.|
|51||When clear, the calculator produces sounds including key clicks. When set, the calculator mutes all sounds.|
|52||Reserved for future use|
|53 - 56||These bits are used to store the number of digits displayed in the current number format. The default is 0, although in standard format, this value is ignored.|
|57 - 59||Reserved for future use|
|60||Set if RAD is on, clear if DEG is on. The default is set.|
|61 - 64||Reserved for future use|
You can use the FS?, FC? operations to test bits. The FS?C and FC?C operations allow you to test bits and clear them in a single operation. The SF and CF operations can be used to set and clear bits. Finally, you can use the RCLF operation to recall the value of all flags and the STOF operation to set them all in a single operation.
In some programs, you need to execute some operations multiple times. There are many ways to create loops in a program. The START operation is a simple loop which is best used to execute a series of commands a specific number of times.
In this example, we want to calculate the following: 1+2+3+4+5+6+7+8+9+10 We will calculate this using a START loop:
≪ 0 0 1 10 START 1 + DUP 3 ROLL + SWAP NEXT DROP ≫
This is a bit of a tricky program to follow because it does some stack manipulation to do the job. It pushes a 0 onto the stack which is the starting value for the answer to be calculated. It pushes another 0 onto the stack which will be incremented each time through the loop (ie it will be 1 then 2 etc). It pushes a 1 and a 10 which is the beginning and the end of the START loop. START maintains an internal index which begins at one and by default increments that index by one each time through the loop until the index exceeds the final value (10 in this example).
Between START and NEXT is the body of the loop. In the body, 1 is added to the top of the stack. This is the value which counts from 1 through 10 on the stack. It is duplicated. The first time through the loop, the stack now looks like 0, 1, 1. Then a 3 ROLL is performed. Now the stack looks like 1, 1, 0. The answer we are trying to calculate is at the top of the stack and under that is a copy of the current iteration number. So, we add them to add the current index onto our answer. Finally, we swap these numbers so the current loop count is again on the top of the stack.
Once the loop terminates, we drop the iteration count, leaving 55 which is the right answer. All of this stack manipulation is difficult to get right and often it is useful to have the iteration count handy. For that, use the FOR loop instead. Then, this example looks like this:
≪ 0 1 10 FOR x x + NEXT ≫
This looks much simpler. We still push a zero for a starting value where we will accumulate our answer. We push 1 and 10 for the start and end values for our loop. The FOR operation comes next followed by "x". The FOR operation is always followed by a symbol name and FOR creates a local variable which is valid only through the body of the loop which holds the current iteration number. After this symbol comes the actual body of the loop which continues until the NEXT operation in this example.
The body in this case is x +. The x will result in the value of the local variable being pushed onto the stack (because the symbol name is not in single quotes, it value will be looked up, finding the local value maintained by the FOR loop). Then + is executed adding the current iteration number onto our answer. And then it loops back and does it again.
With both START and FOR, you can use the STEP operation instead of NEXT. With STEP, you can control the value incremented to the loop index. You can add a value greater than 1, or even fractional values. In some cases, this may be useful for your loops.
Other types of loops may be used when you don't really know how many times you are going to iterate. In these loops, a condition will be tested to see if the loop will continue. The first is the DO, UNTIL and END loop. In this case, the operations between DO and UNTIL are executed repeatedly until the condition evaluated between UNTIL and END evaluates to true. At END, a real value is popped off the stack and the loop continues to execute if the real value is zero.
Our usual addition example now looks like this:
≪ 0 0 DO 1 + DUP 3 ROLL + SWAP UNTIL DUP 10 == END DROP ≫
Again we are back to some serious stack swapping, dropping and rolling. The first zero pushed is where we are accumulating our answer and the second 0 is our iteration count. In the body of the DO loop, we increment the iteration count. Then, we roll the stack, putting our answer on the top of the stack. We add our iteration count to our answer and then swap the current two values on the top of the stack. This puts the iteration count on the top again, our answer below it.
Then, we enter the conditional block. Here we duplicate the top of the stack which is our iteration count and compare it to 10. If the value is 10, then we exit the loop. Otherwise we evaluate the loop again. Once we exit the loop, we drop the iteration count, leaving our answer 55 on the stack.
The other loop type is the WHILE, REPEAT and END loop. With this loop, the condition is evaluated first between WHILE and REPEAT. At REPEAT, a real number is popped off the stack and if it is true (non-zero), then the loop body between REPEAT and END is executed. If it is false, the loop is exited and execution continues after the END. Because the condition is evaluated first, it is possible for the body of the loop to never execute if the condition is false the first time is is evaluated.
Our example now looks like this
≪ 0 0 WHILE DUP 10 < REPEAT 1 + DUP 3 ROLL + SWAP END DROP ≫
The body of the loop between REPEAT and END is the same as what we had in the DO example above between DO and UNTIL. The body of the loop increments the iteration count, adds the current iteration count to the answer and then swaps them just as before. But, before each iteration the condition between WHILE and REPEAT is evaluated. The condition duplicates the iteration count and then tests to see if the iteration count is less than 10. If it is, the loop continues. Once the iteration count reaches 10, the loop exits. The final time through the loop, 9 is compared to 10 and the condition is true. So, the body of the loop is evaluated one last time. The 9 is incremented to 10 and 10 is added to the answer. When the condition is evaluated again, 10 is compared to 10 and the condition is false so the loop exits. As before, the iteration count is dropped, leaving the answer 55 at the top of the stack.
In this example, the FOR loop is the most natural solution. But, these different forms of loops are available and you may find some more appropriate in different situations.
Creating Custom Operations:
Halcyon Calc has a large suite of built-in operations but you may find a specific operation missing which you use frequently. Above, we created a program to calculate cubes. Wouldn't it be great if you could use that program in your own expressions like 'CUBE(5)' or even better 'CUBE(SIN(X + Y))'?
To do this, we will use the → operation. If it is used like this:
≪ → symbol1 ... symboln ≪ anotherProgram ≫ ≫
or instead of a program, you can use an expression like this:
≪ → symbol1 ... symboln 'expression' ≫
Then when stored in a global variable, this program becomes as flexible as the other built-in operations in the calculator. When executed, the list of "n" symbols represents the arguments the operation takes. It can take these arguments from the stack or it can take them from an argument list in brackets. The values for these symbols, regardless of where they came from, are then set in local variables when the final program or expression is evaluated.
For our CUBE program, we can do this:
≪ → x ≪ x 3 ^ ≫ ≫
Then, store this program in a global variable called CUBE. What this does is takes one argument from the stack or from an argument list in brackets and sets the local variable "x" to this value. The inner program is executed with that local variable set. It pushes the value of x on the stack, followed by a 3 and then executes the power operation. This calculates the cube of the incoming argument.
Alternatively, you can use an expression instead of an embedded program:
≪ → x 'x^3' ≫
Again, store this in a global variable called CUBE to use it. Either way, the CUBE program becomes indistinguishable from built-in operations like SIN. You can push 5 onto the stack and hit the "CUBE" button from the User menu to calculate the cube of 5. You can push 'CUBE(5)' onto the stack and then evaluate it to get 125. Your custom operation can appear in expressions which evaluate with the solver, search for roots using the ROOT operation or just about anything else.
Often, you may find your program doesn't do what you expect. There are several things you can do to solve the problem. First, it is best to break programs up into smaller components. Better than having a large, complicated program is to create a series of smaller programs which call each other to get a larger job done. The art is finding the way to break it up appropriately. But, with a program split into smaller parts, you can debug a single part at a time to try and find the problem.
Given a program you want to debug is on the top of the stack, you can use ■Edit to edit the program. At an appropriate place where you want to see what is going on, you can insert the HALT operation and then execute the program. When the HALT operation is reached, the program stops executing. You can see the state of the stack at this point.
Perhaps the problem is obvious. If you think you can fix things in place, you can change the stack to what it should look like using a series of operations. When you are ready, you can use CONT to resume execution.
If you don't see the problem yet, you can single step the program using the SST operation. The program will execute a single item at a time, pushing items onto the stack or executing a single operation. As you step through the program, the problem may become apparent.
Once you know what the problem is, you can use ■Edit to edit the program and fix it. Don't forget to store it since most programs end up stored in global variables.
If after this you still can't figure out what the problem is, post a question to our forum and we will see if we can help out.
There are many resources which you can use when working with programs: