This month, we're going to get started with programming! By now, you've hopefully had a chance to purchase a copy of NS BASIC. The programs described in this and future columns will work with any version of NS BASIC at, or newer than, 2.0. If I use a feature that is only available in the latest version, or that does not work with the DEMO NS BASIC bundled in my book, I'll note it (I don't expect to, but no one likes surprises). The first few programs will use PRINT and INPUT to display output and receive user entry via the on-screen keyboard. This style of I/O is very easy to create, and is easy to learn. In a future article we'll get graphical and show how to use NS BASIC widgets to create more Newton-like programs.
I'm going to assume that you've installed the package, and are familiar with its use. In other words, how to use the environment, use NEW and SAVE to create and save programs, and how to edit them. I use a Macintosh and communications software (ZTerm works well) to create and debug my NS BASIC programs. As you know, NS BASIC uses line numbers for each statement. While this is less than ideal for large programs, for small ones it's not a big problem. Keywords in NS BASIC will be shown in capital letters, but you can enter them in mixed or lower case. I'll also try and make the programs available via my WWW page: http://metro.turnpike.net/J/jschettino/index.html.
I'm a software engineer. As the saying goes, you can use software engineering even with assembly language. I don't worry about line numbers, or the lack of multi-line functions, in NS BASIC. While I won't be delving into the proper design and implementation of programs, I will be using a structured programming approach for most of the larger examples. This generally means that I'll break the problem down into smaller modules (often called functional decomposition) and implement each module as a subroutine or function. I won't talk about this design step for small programs, but I may present a brief sketch of what I'm doing for larger ones, so you can see this method in action. When using NewtonScript, this method does not work very well, since it is a more object oriented language. If none of this paragraph makes any sense to you, don't worry! You can create programs with NS BASIC simply by trial and error, or hacking. If it gets the job done, it's correct!
George Henne suggested that I try and present one "undocumented feature" of NS BASIC at the end of each article. What this generally means is using NS BASIC to access a NewtonScript function that is not described in the Handbook. There are lots of these functions, and some of them can be very useful in NS BASIC. Today's Undocumented Tip shows how to use NS BASIC to patch the Newton's on-screen keyboard to disable the "add to dictionary" prompt. It is a specific use of some advanced features of NS BASIC, in a way that some of you may not have thought possible.
If you have a particular NS BASIC programming problem or question, or an idea for an example program (either beginner or advanced) you'd like to see presented in this column, please email me (email@example.com). If I get several questions in a month, I'll add a Q&A section.
One problem with small example programs is that they usually don't do anything useful. Let's go ahead and write one such program so we can get it out of the way and move on to more meaty problems. Here is a simple program that prompts you for your name and then prints out a greeting:
10 REM GREET.BAS
20 PRINT "Please enter your name:"
30 INPUT name$
40 PRINT "Hello, "; name$; ". Glad to meet you."
Not much to it! Here's a sample run:
Please enter your name:
Hello, John. Glad to meet you.
This program illustrates several key concepts in NS BASIC: Line numbers are used to determine the order that statements are executed. When you run a program, the lowest line number is executed first. If you don't use GOTO, GOSUB, or a FOR/NEXT loop, statements are executed sequentially by line number. Execution stops when an END or STOP statement is reached, or the highest line number has been executed.
The PRINT statement is used to display strings, numbers, and other data to the NS BASIC text window or attached serial terminal. You can print one or more items in a single PRINT statement: using a semicolon between items does not insert any blanks, using a comma inserts enough blanks to move to the next tab stop.
The INPUT statement is used to print a prompt (the question mark) and accept entry via the keyboard. Entry of text (called strings) and numbers is supported. Entry ends when the return key is pressed.
Variables (name$) are names you create to hold data in your programs. You may end a variable with a $ to tell NS BASIC that it should always contain a string. If you don't, then the variable can hold whatever kind of data you assign to it. The phrase "kind of data" is also known as data type. We'll get to that in a bit, for now just remember that you can either make variables that only hold strings (by ending the name with $), or variables that can hold anything at all. The use of string variables in INPUT statements, as we have done here, is a good way to make sure that whatever the user enters will be treated as a string. Also notice that variables are not declared before they are used. Simply put, you can just start using a variable name, and it will be created for you. If you've ever programmed in another form of BASIC, this should all seem very familiar.
Let's try something a bit more ambitious. Installment loans are a common curse of our times, and it is usually helpful to be able to see an amortization schedule for a loan. The program below uses the NewtonScript ANNUITY() function to compute the monthly payment for a loan. Once this is known, the payment schedule can be printed. The printout is paused every 12 lines (every year) to give the user a chance to quit.
10 REM LOAN.BAS
20 PRINT "Enter amount borrowed:"
30 INPUT borrowed
40 PRINT "Enter interest rate (enter 8 for 8%):"
50 INPUT baseRate
55 monthlyRate = baseRate/1200
60 PRINT "Enter number of years:"
70 INPUT years
80 REM Calculate monthly payment
90 mpay = borrowed/ANNUITY(monthlyRate, years*12)
110 PRINT "Monthly Payment: $"; FORMATTEDNUMBERSTR(mpay,"%.2f")
130 PRINT "Pmt Bal Prin Int"
140 pmt = 1
150 FOR i = 1 to 12
160 interest = borrowed*monthlyRate
170 principal = mpay-interest
180 PRINT pmt; FORMATTEDNUMBERSTR(borrowed,"%11.2f"); FORMATTEDNUMBERSTR(principal,"%11.2f"); FORMATTEDNUMBERSTR(interest,"%11.2f")
190 borrowed = borrowed - principal
200 pmt = pmt + 1
210 NEXT i
240 IF pmt >= years*12 THEN END
250 PRINT "Enter Q to Quit, return to continue:"
260 INPUT more$
270 IF more$ <> "Q" THEN GOTO 150
Here is a sample run of the program for a typical 30 year home mortgage:
Enter amount borrowed:
Enter interest rate (enter 8 for 8%):
Enter number of years:
Monthly Payment: 1,537.83
Pmt Bal Prin Int
1 200,000.00 121.16 1,416.67
2 199,878.84 122.02 1,415.81
3 199,756.82 122.88 1,414.94
4 199,633.94 123.75 1,414.07
5 199,510.19 124.63 1,413.20
6 199,385.56 125.51 1,412.31
7 199,260.04 126.40 1,411.43
8 199,133.64 127.30 1,410.53
9 199,006.34 128.20 1,409.63
10 198,878.15 129.11 1,408.72
11 198,749.04 130.02 1,407.81
12 198,619.02 130.94 1,406.88
Enter Q to Quit, return to continue:
13 198,488.08 131.87 1,405.96
14 198,356.21 132.80 1,405.02
15 198,223.40 133.74 1,404.08
16 198,089.66 134.69 1,403.14
17 197,954.96 135.65 1,402.18
18 197,819.32 136.61 1,401.22
19 197,682.71 137.57 1,400.25
20 197,545.14 138.55 1,399.28
21 197,406.59 139.53 1,398.30
22 197,267.06 140.52 1,397.31
23 197,126.54 141.51 1,396.31
24 196,985.03 142.52 1,395.31
Enter Q to Quit, return to continue:
Let's look at the program in detail. First, note that it's not very large at 28 lines. The use of the ANNUITY() function saves us quite a bit of work, and make the program considerably faster than computing the present value factor manually. The ANNUITY() function takes two arguments: monthly interest rate and number of pay periods. For this program, we're assuming a monthly payment schedule, so we divide the user entered interest rate by 1200 to get the monthly rate in line 55. 1200 is just 100 * 12, so this is equivalent to dividing by 100 to get a percentage from the whole number entered in line 50, and then dividing by 12 to get the monthly rate. In general, you should perform these types of simplifications to your code when using NS BASIC to get the most speed out of it. The ANNUITY() function is used in line 90, and is passed the monthly interest rate and the total number of payments. Dividing the total amount borrowed by the value returned by this function yields the monthly payment. Note that you can compute the payments for bi-monthly, semi-annual, annual, and other non-traditional payment schedules simply by adjusting the two parameters to the ANNUITY() function.
Once we have the monthly payment in hand, we're ready to produce the report. I've used another NewtonScript function named FORMATTEDNUMBERSTR() to format the real numbers to display only 2 digits after the decimal point in lines 110 and 180. This is completely optional. In fact, you should edit the program and see the output without this conversion. I think you'll find that the formatted version is easier to read! The FORMATTEDNUMBERSTR() function takes two arguments, the real number, and a string containing the format specification. The string "%.2f" in line 110 yields a floating point (real) number with 2 digits after the decimal point and no leading blanks. Change the 2 to 0, 1, or any other number to get fewer or more digits. The string "%11.2f" in line 180 pads each value with blanks so that it takes up exactly 11 characters before and 2 after the decimal point. This is useful for formatting a table.
The basic structure of the remaining code is two loops, one inside the other. Recall that I said we'd print out one year's worth of values and then pause so the user could quit? Those are the two loops! The inner loop is the FOR/NEXT loop in lines 150-210. This inner loop computes the values needed for the line, and keeps subtracting the principal payment off of the loan balance. The outer loop is a GOTO loop: Line 270's GOTO statement causes the program to loop back and re-execute the inner FOR/NEXT loop as long as there are more lines to print and the user hasn't entered Q to Quit. Line 240 checks if we've printed all the lines. The IF THEN ELSE statement is used to test an expression to see if it is true or false. If it is true, the statement in the THEN portion is executed. If it is false (which is nil in NS BASIC), the statement in the ELSE portion is executed. If there is no ELSE (as we have in line 240) then nothing is done if the expression is false. The next statement is executed as it normally would be. In our program, this line will end the program if the number of payments printed is greater than or equal to the total number of payments. If not, then the next statement is executed, and the prompt is printed out. Line 270 contains another IF THEN ELSE statement. This line checks to see if a "Q" was entered. If not, then the program loops back to line 150 and the next 12 payments are printed.
There's actually quite a bit of work that could be done on this program to make it better. A number of changes are required to support different payment schedules (bi-monthly, etc.) and it could also be changed to print just a range of payments. You could add the ability to enter an additional principal only payment to each payment, to perform "what-if" evaluations. I expect we'll revisit this program again, with a graphical user interface, to create a more general loan program in a later column.
I'll revisit some of the items we glossed over this time, including data types (NS BASIC supports all these NewtonScript types: Boolean, int, real, array, and frame,) flow control (FOR/NEXT loops, GOTOs, GOSUBs, and IF THEN ELSE) and input/output. We'll also get into some file input and output. If you have any questions on this month's programs, send me email.
I've noticed several requests on the internet for a shareware program that disables that annoying 'Add "xyzzy" to word list?' prompt in the built-in keyboard. Did you know that you can use NS BASIC to perform that (very simple) patch? Note that all such patches are potentially dangerous, and may depend on the Newton Operating System version. This patch works with all known 1.3 versions of the Newton Operating System.
You can create very small NewtonScript functions using NS BASIC's FUNCTION statement. The following statement creates a seemingly useless NewtonScript function:
FUNCTION noAsk(a,b) begin nil end
This function takes two arguments, and always returns NIL. There is a function within the alpha keyboard named AddReviewWord that takes two arguments and returns true if the word should be displayed or nil if it should not. If we could just insert our useless function we just defined, it would disable the keyboard prompt until the next reset (resets undo all such patches!)
Here's how. Enter these two statements directly into NS BASIC:
FUNCTION noAsk(a,b) begin nil end
getroot().alphakeyboard.AddReviewWord := ENSUREINTERNAL(U.noAsk)
This patches the function in the alpha keyboard. We use the ENSUREINTERNAL() function so that a copy of the function is placed into the internal heap memory, so the patch will work even if you quit NS BASIC. When you want to remove the patch, you can either reset your Newton or enter the following directly into NS BASIC:
unused := removeslot(getroot().alphakeyboard, 'AddReviewWord)
That's all there is to patching! You can actually do some serious hacking and exploring in NS BASIC, but perhaps we better leave that topic for another time. Next month in Undocumented Tips we'll take a closer look at soups - the NewtonScript version of the NS BASIC file.
Return to John Schettino's home page