11.5 Sample Program
This chapter presents a simple "Reverse Polish Notation" calculator that demonstrates the use of the 80x86 FPU. In addition to demonstrating the use of the FPU, this sample program also introduces a few new routines from the HLA Standard Library, specifically the arg.c, arg.v, and conv.strToFlt routines.
The HLA Standard Library conversions module ("conv.hhf") contains dozens of procedures that translate data between various formats. A large percentage of these routines convert data between some internal numeric form and a corresponding string format. The conv.strToFlt routine, as its name suggests, converts string data to a floating point value. The prototype for this function is the following:procedure conv.strToFlt( s:string; index:dword );
The first parameter is the string containing the textual representation of the floating point value. The second parameter contains an index into the string where the floating point text actually begins (usually the index parameter is zero if the string contains nothing but the floating point text). The conv.strToFlt procedure will attempt to convert the specified string to a floating point number. If there is a problem, this function will raise an appropriate exception (e.g., ex.ConversionError). In fact, the HLA stdin routines that read floating point values from the user actually read string data and call this same procedure to convert that data to a floating point value; hence, you should protect this procedure call with a TRY..ENDTRY statement exactly the same way you protect a call to stdin.get or stdin.getf. If this routine is successful, it leaves the converted floating point value on the top of the FPU stack.
The HLA Standard Library contains numerous other procedures that convert textual data to the corresponding internal format. Examples include conv.strToi8, conv.strToi16, conv.strToi32, conv.strToi64, and more. See the HLA Standard Library documentation for more details.
This sample program also uses the arg.c and arg.v routines from the HLA Standard Library's command line arguments module ("args.hhf"). These functions provide access to the text following the program name when you run a program from the command line prompt. This calculator program, for example, expects the user to supply the desired calculation on the command line immediately after the program name. For example to add the two values 18 and 22 together, you'd specify the following command line:rpncalc 18 22 +
The text "18 22 +" is an example of three command line parameters. Programs often use command line parameters to communicate filenames and other data to the application. For example, the HLA compiler uses command line parameters to pass the names of the source files to the compiler. The rpncalc program uses the command line to pass the RPN expression to the calculator.
The arg.c function ("argument count") returns the number of parameters on the command line. This function returns the count in the EAX register. It does not have any parameters. In general, you can probably assume that the maximum possible number of command line arguments is between 64 and 128. Note that the operating system counts the program's name on the command line in this argument count. Therefore, this value will always be one or greater. If arg.c returns one, then there are no extra command line parameters; the single item is the program's name.
A program that expects at least one command line parameter should always call arg.c and verify that it returns the value two or greater. Programs that process command line parameters typically execute a loop of some sort that executes the number of times specified by arg.c's return value. Of course, when you use arg.c's return value for this purpose, don't forget to subtract one from the return result to account for the program's name (unless you are treating the program name as one more parameter).
The arg.v function returns a string containing one of the program's command line arguments. This function has the following prototype:procedure arg.v( index:uns32 );
The index parameter specifies which command line parameter you wish to retrieve. The value zero returns a string containing the program's name. The value one returns the first command line parameter following the program's name. The value two returns a string containing the second command line parameter following the program's name. Etc. The value you provide as a parameter to this function must fall in the range 0..arg.c()-1 or arg.v will raise an exception.
The arg.v procedure allocates storage for the string it returns on the heap by calling stralloc. It returns a pointer to the allocated string in the EAX register. Don't forget to call strfree to return the storage to the system after you are done processing the command line parameter.
Well, without further ado, here is the RPN calculator program that uses the aforementioned functions.// This sample program demonstrates how to use // the FPU to create a simple RPN calculator. // This program reads a string from the user // and "parses" that string to figure out the // calculation the user is requesting. This // program assumes that any item beginning // with a numeric digit is a numeric operand // to push onto the FPU stack and all other // items are operators. // // Example of typical user input: // // calc 123.45 67.89 + // // The program responds by printing // // Result = 1.91340000000000000e+2 // // // Current operators supported: // // + - * / // // Current functions supported: // // sin sqrt program RPNcalculator; #include( "stdlib.hhf" ) static argc: uns32; curOperand: string; ItemsOnStk: uns32; realRslt: real80; // The following function converts an // angle (in ST0) from degrees to radians. // It leaves the result in ST0. procedure DegreesToRadians; @nodisplay; begin DegreesToRadians; fld( 2.0 ); // Radians = degrees*2*pi/360.0 fmul(); fldpi(); fmul(); fld( 360.0 ); fdiv(); end DegreesToRadians; begin RPNcalculator; // Initialize the FPU. finit(); // Okay, extract the items from the Windows // CMD.EXE command line and process them. arg.c(); if( eax <= 1 ) then stdout.put( "Usage: `rpnCalc <rpn expression>'" nl ); exit RPNcalculator; endif; // ECX holds the index of the current operand. // ItemsOnStk keeps track of the number of numeric operands // pushed onto the FPU stack so we can ensure that each // operation has the appropriate number of operands. mov( eax, argc ); mov( 1, ecx ); mov( 0, ItemsOnStk ); // The following loop repeats once for each item on the // command line: while( ecx < argc ) do // Get the string associated with the current item: arg.v( ecx ); // Note that this malloc's storage! mov( eax, curOperand ); // If the operand begins with a numeric digit, assume // that it's a floating point number. if( (type char [eax]) in `0'..'9' ) then try // Convert this string representation of a numeric // value to the equivalent real value. Leave the // result on the top of the FPU stack. Also, bump // ItemsOnStk up by one since we're pushing a new // item onto the FPU stack. conv.strToFlt( curOperand, 0 ); inc( ItemsOnStk ); exception( ex.ConversionError ) stdout.put("Illegal floating point constant" nl ); exit RPNcalculator; anyexception stdout.put ( "Exception ", (type uns32 eax ), " while converting real constant" nl ); exit RPNcalculator; endtry; // Handle the addition operation here. elseif( str.eq( curOperand, "+" )) then // The addition operation requires two // operands on the stack. Ensure we have // two operands before proceeding. if( ItemsOnStk >= 2 ) then fadd(); dec( ItemsOnStk ); // fadd() removes one operand. else stdout.put( "`+' operation requires two operands." nl ); exit RPNcalculator; endif; // Handle the subtraction operation here. See the comments // for FADD for more details. elseif( str.eq( curOperand, "-" )) then if( ItemsOnStk >= 2 ) then fsub(); dec( ItemsOnStk ); else stdout.put( "`-' operation requires two operands." nl ); exit RPNcalculator; endif; // Handle the multiplication operation here. See the comments // for FADD for more details. elseif( str.eq( curOperand, "*" )) then if( ItemsOnStk >= 2 ) then fmul(); dec( ItemsOnStk ); else stdout.put( "`*' operation requires two operands." nl ); exit RPNcalculator; endif; // Handle the division operation here. See the comments // for FADD for more details. elseif( str.eq( curOperand, "/" )) then if( ItemsOnStk >= 2 ) then fdiv(); dec( ItemsOnStk ); else stdout.put( "`/' operation requires two operands." nl ); exit RPNcalculator; endif; // Provide a square root operation here. elseif( str.eq( curOperand, "sqrt" )) then // Sqrt is a monadic (unary) function. Therefore // we only require a single item on the stack. if( ItemsOnStk >= 1 ) then fsqrt(); else stdout.put ( "SQRT function requires at least one operand." nl ); exit RPNcalculator; endif; // Provide the SINE function here. See SQRT comments for details. elseif( str.eq( curOperand, "sin" )) then if( ItemsOnStk >= 1 ) then DegreesToRadians(); fsin(); else stdout.put( "SIN function requires at least one operand." nl ); exit RPNcalculator; endif; else stdout.put( "`", curOperand, "` is an unknown operation." nl ); exit RPNcalculator; endif; // Free the storage associated with the current item. strfree( curOperand ); // Move on to the next item on the command line: inc( ecx ); endwhile; if( ItemsOnStk = 1 ) then fstp( realRslt ); stdout.put( "Result = ", realRslt, nl ); else stdout.put( "Syntax error in expression. ItemsOnStk=", ItemsOnStk, nl ); endif; end RPNcalculator; Program 11.1 A Floating Point Calculator Program
11.6 Putting It All Together
Between the FPU and the HLA Standard Library, floating point arithmetic is actually quite simple. In this chapter you learned about the floating point instruction set and you learned how to convert arithmetic expressions involving real arithmetic into a sequence of floating point instructions. This chapter also presented several transcendental functions that the HLA Standard Library provides. Armed with the information from this chapter, you should be able to deal with floating point expressions just as easily as integer expressions.