In the late 70's and early 80's, I developed a set of utilities for the Apple ]['s AppleSoft BASIC and DOS 3.3 that were published by Micro Lab, Inc.
This is the saga of their creation.
The Amper technology I developed had it's roots in a system I wrote for Loyola Hospital in (as we called it) Chicagoland (any suburb of Chicago) for the Apple ][ computer back in 1978-79, when I was working at the Data Domain of Schaumburg, IL as a salesman/programmer.
The (deep breath) Emergency Medical Services, Mobile Intensive Care Units, Data Base Management System, or EMS MICU DBMS, was used to enter ambulance run results sheets and then generate monthly reporting to be sent to the State of Illinois.
The creation of that system is yet another saga to be related at another time.
It was programmed in AppleSoft BASIC using the Apple DOS 3.3 operating system.
AppleSoft BASIC had an INPUT statement, but it did not have a way to strictly control the entry of data that the EMS MICU DBMS was going to require.
However, AppleSoft BASIC had a special character, the Ampersand (&) that would transfer control to the machine code address $3F5 so you could write specialized Assembly Language routines to use within AppleSoft BASIC.
So to deal with the constrained input requirements, I wrote a custom function to handle keyboard entry. Since it began with the ampersand, we took to calling them Amper Routines.
This specialized INPUT statement would have parameters that would specify what type of input was required. You could specify Alphanumeric, Numeric, Leading Zeroes, length of entry, and the variable to store the result in.
There was also a variable to store the return status in. This variable, would end up being one of the keys to the Amper Routines I would develop.
This input routine would be the first of several business grade Amper routines I would later develop.
After the Loyola sytem (and later deployment at Ingalls Hospital in Harvey, IL), I started thinking of different routines to write using this methodology. I kept a lab notebook that I wrote my designs in.
Some time in 1979, I was approached by a friend, Mike Hatlak, and he asked me to interview at a company called Micro Lab, Inc. in Highland Park, IL.
Micro Lab was selling a flat file database program called The Data Factory (yes, "The was part of it's name), and I was told they needed help making the application more robust and faster for the business market.
I interviewed with Micro Lab's president, Stan Goldberg, at the co-owner, Rosalee Dixler's, house. No doubt, for the purpose of impressing me. The room we talked in had a stream running through it, a full sized antique street light, and the head of a giraffe, from the shoulders up, mounted on one of the walls. There was also the obligatory grand piano in the distance.
I don't remember the exact details of the meeting, but mostly it was about using my skills to beef up The Data Factory, which was their cash cow at the time. I remember that since Micro Lab was a publisher of software for the Apple ][, that I inquired about the possibility of publishing my finished Amper Routines. Stan agreed to the possibility, but his main concern was getting the deficiencies of The Data Factory dealt with.
Production of The Data Factory at that time was in Rosalee's basement. But soon they were going to be moving to their own office and I was told this was an excellent time to get in on the ground floor of an up and coming business. The old, as we grow, you'll grow, speech was given.
On the hour drive home with Mike, we discussed the offer. Mike was already there and really wanted me on board.
I was a little hesitant. Something about Stan just didn't sit right with me, but by the end of the drive, Mike had won me over.
Before going to their new offices to sign their employment contract, I gathered up my notes and took them to another friend, Chris Oberth, to look over and then sign and date each of my designs. This was the age when employers wanted to claim anything you wrote on their computers belonged to them. You may have dreamed it up, wrote it, and tested it on your own time, but they wanted to own it if your fingers ever touched one of their computers while you were developing it. I wanted something to prove I had conceived of these routines before coming to Micro Lab.
One of the first of my designs that I implemented, was the Disk I/O commands. Stan told me he wanted the disk I/O to be sped up for The Data Factory so it would "DMA the data." I didn't bother telling Stan that the Apple ][ hardware did not support Direct Memory Access, I knew he was just a marketer that had latched onto some tech buzzwords to pepper his presentations with.
When I was finished, my Disk I/O commands were 50% faster than Apple's, faster, if you did array I/O. I did that by talking to the same low level hardware I/O routines that Apple had created, I just bypassed the slow command interpreter.
Steve Wozniak had hooked Apple DOS into the existing hardware by making DOS the "man in the middle" when it came to character I/O. He had been genius enough to always have character I/O go through a software vector. When DOS was added, he had that vector point to the DOS command interpreter before passing the characters to their final destination.
To differentiate characters going to the screen or printer, you had to send a carriage return, followed by a Ctrl‑D character (for DOS) followed by a DOS command, like "OPEN FILENAME".
It became the custom to store a carriage return and Ctrl-D in a string variable named D$. That way your DOS command statements would look like PRINT D$;"READ FILENAME"
So Apple DOS had to parse R E A D to figure out the disk operation, and then parse the filename.
You'd then have input statement(s) to read string data from the disk files. All data on Apple ][ drives were stored as strings. The target variable type would convert the data to the internal storage.
The Amper routine would start with an Integer data variable two characters long, like &RE%, to be followed by any parameters the command required. The RE% variable would be used to store the result of the operation after execution.
So in comparison,
100 PRINT D$;"OPEN FILENAME"
110 PRINT D$;"READ FILENAME"
120 INPUT A$,B$,C$
130 PRINT D$;"CLOSE FILENAME"
The Amper equivalent would be,
Assuming "FILENAME" was stored in the string variable FILE$.
When it came to Disk I/O, the status variable was a real timesaver.
Apple DOS commands did not return status. It was assumed they were successful, unless an error occurred. To deal with errors, AppleSoft had the command ONERR GOTO that you supplied a line number to transfer control to in the event of an error.
ONERR GOTO was a blunt instrument to deal with any errors that occurred. You could only have one line number assigned to go to at any time. So to deal with errors, you either had to have a complex routine to deal with any error that may occur, or you had to pepper your program with multiple ONERR GOTOs for each instance you thought an error could occur. Good programming dictated that you determine what error occurred at each of those locations. Rarely did you encounter good programmers. Usually they assumed that if they got control at a particular location, it was because of an error they were expecting to happen. Controlling where to return control to after an error had been handled made for messy programming.
AppleSoft had RESUME, which took you back to the line that caused the error. Alternatively, you could do a GOTO to any line number, but you had to do a machine language call to a routine that would remove the return address from the system stack.
With the Amper routine, if a disk error occurred, it did not trigger the ONERR GOTO routine, instead, it would pass back a status code indicating the error and your program could deal with it locally. BTW The Disk I/O Amper routines were compatible with ONERR GOTO if you wanted to use it to handle other errors.
So each time you were doing disk I/O, you could determine the proper action to take for that particular Disk I/O request.
Example: Copy one file to another.
20 PRINT CHR$(4);"BRUN AMPER ROUTINES.OBJ0"
30 INPUT " INPUT FILE NAME:";AFILE$
40 INPUT "OUTPUT FILE NAME:";BFILE$
50 &OP%,AFILE$:IF OP% THEN 200
60 &CR%,BFILE$:IF CR% THEN 200
80 IF RE%=5 THEN 120
90 IF RE% THEN 200
100 &WR%,BFILE$,INFO$:IF WR% THEN 200
110 GOTO 70
120 &CL%,AFILE$:IF CL% THEN 200
130 &CL%,BFILE%:IF CL% THEN 200
140 PRINT "-- DONE --"
210 PRINT "--DOS ERROR--";OP%;"-";CR%;"-";RE%;"-";WR%;"-";CL%;"--"
Line 10 sets the screen to text mode (as opposed to graphics mode), sets the text to normal (as opposed to inverse or flashing), and clears the screen, placing the cursor on the top line.
Line 20 loads and initializes the Amper pack. CHR$(4) is the ASCII equivalent to Ctrl-D.
Lines 30 and 40 ask for filenames, one for input and the other for output.
Line 50 uses the Amper routine to open the input file. If the variable OP% comes back with a value other than 0 (false), an error has occurred and we jump to line 200, where we print out the status variables so we can figure out what the error was.
Line 60 creates the output file. I separated Open from Create so it would be clear what the intent was. Create deletes an existing file and if one did not exist, creates a new one. Open will not damage an existing file.
Line 70 reads a string of characters from the source file.
Line 80 checks to see if the return value of the RE% equals 5, which in DOS 3.3, signals End Of File. Which is technically, an error. If so, we jump to line 120 where we close the input and output files.
Line 90 checks for any error other than 5, that was handled by the previous line, and transfers control to line 200, where we dump out the status variables and quit.
Line 100 writes the string to the output file and checks to see if an error occurred, transfering control to line 200 if one did.
Line 110 loops us back to the read statement to get the next string from the input file.
Lines 120 and 130 we get to if everything worked correctly in the file copy and now we close the input and output files.
Line 140 and 150 we announce we are done and end the execution of the program.
Lines 200 through 220 is where we end up if an error occurred.
Truthfully, this example is not much different then an ONERR GOTO, except you can see we had the option to go anywhere after each of the errors, especially when we hit EOF.
Initially I wanted to call the Amper technology, "Snap-on Software", but I figured the Snap-on Tools company might take issue with it. Essentially, this is the idea behind my Amper routines that differed from all the other products on the market.
All the other Amper routines that were on the market could not work with each other. You could only have one Amper routine in use at a time.
My design let you stack as many or as few routines together into an Amper pack as you needed.
The secret ingredient was that each routine stood by itself. At the beginning of each, it would assume the first two letters of the amper routine call had been loaded into the X & Y registers of the 6502 processor.
Each routine would test the X & Y registers to see if the characters corresponded with the calling letters for the routine. If not, control would be transfered to the location after the current routine, where the next routine would do the same.
To make this work, we had to have a HEADER and a TRAILER to put at the beginning and end of the Amper pack.
The HEADER routine had a two fold task, first, it had to set up the $3F5 vector to point to the second task, it would bring down HIMEM (a system pointer) to position the top of variable space just below second task's location. This would recover the space taken up by the first task, since it only had to run once. The second task would verify the calling variable was an Integer type, save the address of the variable so the status could be stored in it, and then load the first two letters of the variable name into the X & Y registers.
The TRAILER would just call AppleSoft's SYNTAX ERROR routine if none of the amper routines claimed the call.
For the amper packs we were initially putting into Micro Lab products, I was assembling them by hand.
I'd take the source code for each module and paste them all together manually. I'd do a test Assemble to see how big the pack was going to be, and then I'd calculate the address to finally assemble the code so it would be positioned at the highest point in memory below DOS.
Needless to say, it was a bit of a tedious task.
What nagged at me was that the Apple Assember could be told to create relocatable code.
Computer machine code is assembled to work at a specific memory location. Load it 1 byte off of that location, and it won't work. Some addresses are relative, but jumping to a location, calling a subroutine, or referencing variables, all required a hard location. Locations that would cause the program to fail if it was not loaded into the location it was assembled to work at.
When you told the Assembler to create a relocatable file, it still assembled the code to work at a specific location in memory, but it would remember all the locations that were sensitive to change and would then generate a relocation dictionary at the end of the file.
Problem was, there were no programs on the market that could take those relocatable files and relocate them.
Obviously the solution was to write one.
Relocating programs written in Assembly would be big and not adept at handling symbols that would be in the relocation dictionary. It would be a major project.
It occurred to me that AppleSoft BASIC was good at handling symbolic data, but it couldn't read and write binary disk files.
I could fix that. I wrote a variation of my Disk I/O routines that could read and write binary data that would be callable from AppleSoft BASIC.
Little side story here.
I had borrowed my father's truck to move and the thermostat failed and left me on the side of the road with an overheated engine.
I called Mike Hatlak since he was the only person I knew who was into automotive repair.
Mike came out with the part, replaced the thermostat, and I "paid" for it with the two of us sitting in the truck cab on the side of the road, while I explained to Mike what a "Relocating Linking Loader" was.
So I gave the low level binary Disk I/O routines to Mike and he wrote the high level AppleSoft BASIC routines that made the Relocating Linker Loader a reality.
Technically it wasn't a loader, but my previous programming of mainframes had burned that phrase into my brain and I figured others would relate to it as well. But it was relocating and a linker.
From then on, we assembled our Amper routines to relocatable modules and used the RLL to build the amper packs.
At this point we had something we felt was commercially viable as a standalone package we could publish through Micro Lab.
It was a hard sell to Stan Goldberg to let us publish it.
First and foremost, Stan thought it was the magic that made Micro Lab business products stand out in the industry. He really didn't want it to be loose in the wild. His argument was that once we published it, anyone else could make products as good as ours. He was afraid our competitors could buy one copy and use it forever. He wanted to license it.
Mike and I weren't keen on licensing. We felt it would kill sales immediately. This was the age of NIH - Not Invented Here, nobody wanted to license other people's products because it put a hose in their wallet.
We argued that commercial products using our amper routines would have to put a notice in their documentation giving us credit and thereby drive sales to us.
Mike and I were really thinking of the home programmers and how useful the routines would be to them.
Stan finally agreed to publish the amper routines. He said he would write the manual for it. Writing manuals for Micro Lab products was his thing back then, but Stan's manuals tended to be tour guides. He'd take you on a walk through the product and you'd figure out how to use the product from that.
The amper routines were targeted for programmers and that method of documenting wouldn't work.
So I wrote the bulk of the manual. Mike wrote the directions on how to use the Relocating Linking Loader and I wrote the documentation for all the amper routines and programming examples.
I believed that the documentation should be a reference guide first. Something you could flip to and remind yourself of the syntax.
Deeper, would be the explanations of the parameters and deeper still, would be an example of the routine(s) in use.
Link to the PDF manual: Relocating Linking Loader and Language Plus (1.8 MB).
Somewhat beyond my control was Stan naming the amper routines "Language Plus", which I thought was the stupidest name possible. It conveyed nothing. I wanted something with "Amper" in it. At least some of our audience might pick up on what it was.
Then came the cost. I argued for $50 ($49.95) because that would make it an almost impulse buy. Instead, Stan decided the cost was going to be $150, which I thought was ridiculously over priced. My argument was that $150 took food off the table. If you were going to spend $150 when most software at the time was less than $50, you'd better be damn sure what you were buying and "Language Plus" was not going to convince you. The "Relocating Linking Loader" was going to appeal to a small subset of buyers. The RLL was what made the amper routines usable.
It didn't help that the manual was going to be in a baggie, making it difficult to peruse.
This is what the customer saw...
Flip it over and what did you see? Nothing.
I'm amazed we had any sales at all.
So I went to Stan and complained. I presented him with an alternate back cover and he agreed that the second printing (how small was the first?) would have it.
At least now, we had some hope of reaching our target audience.
As you can imagine, sales were dismal.
We had a few other internal problems, but Mike and I were quite disappointed in how things turned out.
We thought we had something that could have helped a lot of programmers develop robust applications for the Apple ][.
As much as I'd like to document each and every one of the amper routines, that would be beating a dead horse.
Instead, I do want to mention a few of the memorable ones.
I had heard AppleSoft's string garbage collection was awful, but I had never experienced it until the day I was entering a catalog of my albums.
I had entered thousands of lines of text and after one entry, I pressed Return and the Apple seized up.
After several minutes, I finally hit Reset and found myself rebuilding some of the disk structure by hand. I knew Apple DOS's disk organization and used a disk diagnostic to rewrite some of the disk blocks.
I went back to entering my album collection at the point I had left off and at the exact same point, it seized up again.
Pissed off, I left it alone and called a friend to vent.
After we had been talking for about 20 minutes, the Apple unfroze and I realized I had finally encountered the infamous AppleSoft garbage collection problem.
To explain what that is, as you create strings and/or change strings, BASIC (not just AppleSoft BASIC) allocates new string space and puts the characters of the string into it. It then updates the variable pointer. BASIC stores the variable in a different location than it does the string. A BASIC string variable consists of a one byte string length (0-255) and a two byte string pointer to the actual string data.
As strings are assigned and reassigned, instead of trying to reuse the space or move the strings around, BASIC takes the easy way out and just allocates another set of memory locations to put the new string data in. So the old string space is no longer in use. Normally string space starts high and then works it's way down in memory until it comes in danger of colliding with the variable space, which grows from the end of the program in memory, upwards.
At this point in time, the string space pointer is reset to the high end of memory and the garbage collection routine starts searching through the variable space, looking for the string variable that points to the highest location, not above the string memory pointer.
When it finds it (after going through all the variables), the string characters are moved to the string pointer downward, and the string memory pointer is pulled down to contain the garbage collected string.
And then it starts again.
Until all the strings in the variable space have been moved to the most compact location up high.
If you have a lot of strings, this will take a long time, because it only moves one string at a time!
Randy Wiggington of Apple tackled this problem and wrote a routine that he published that collected 16 strings at a time instead of just one. Needless to say, it was MUCH faster.
As I was looking through Randy's code, I discovered a bug in it. It successfully performed the garbage collection, but he accidentally called the internal subroutine that did the garbage collection twice. The string space was already compacted, making the second pass a waste of processing time.
So I set about writing my own variation of his garbage collection routine.
I eliminated the superfluous second pass and added a test to see if the string to be packed, was already located at its optimal location. Performing tests takes time, but I reasoned it would be less time than picking up a character and putting it down in the same place would take. Which was what both Randy's and Microsoft's (Microsoft wrote AppleSoft) garbage collection routines did.
Unless all the strings were dynamic, as a program would run, all the static strings would eventually migrate to a block in high memory. By doing my location test, I left those strings alone and concentrated on the dynamic strings.
My garbage collection routine was vastly faster than Randy's or Microsoft's.
I didn't write the original overlay routine. I wish I remembered who did to give them credit.
This would be another case of me looking at someone else's code and finding the bug within it.
Micro Lab had shared the design of the amper routines with some of the authors that were publishing their programs through us.
One had attempted to write an overlay amper routine for AppleSoft BASIC code.
The user would specify another BASIC program in the amper call and the overlay routine would look into the specified program file to find the first line number it used.
It would then search the existing program's memory to find that line number and then load the new program from that point on, after moving the variable space so it wouldn't get stepped on.
It was a great idea, but it just wasn't working.
Existing string variables were getting trashed in the overlay process.
I looked through the source code and found the problem.
He had forgotten (or didn't know) that assignment of string literals (like A$="HELLO") does not create a string variable and then move the literal into high memory string space. To save memory, the string variable would point to the string literal within the program itself.
So if the overlay was replacing the code the literal was stored in, the contents would be trashed.
I used my knowledge from the Garbage Collection to sweep through the string variables and if I found any strings not in string space, I'd move them into it before doing the program overlay.
One of the features of The Data Factory that I created was the Custom Output Processor (COP) that let you create custom reports from your data.
To do that, I used the overlay amper routine.
Usable space in a 64K Apple ][ is approximately 32K. AppleSoft and DOS take up the other 32K.
The final size of the COP was around 64K, so I broke it into two pieces and used the overlay routine to swap them in and out of memory.
Normally when you'd change programs, you'd have to write the important variables out to a disk file, load the new program and then load the variables back in. A slow process.
The overlay let me replace the program, but keep the variables intact in memory, eliminating the write and then read process that would also take up program space.
I must say I laughed my ass off when I saw Microsoft Access's report generator. They had the exact same design I had used 10 years earlier in the Custom Output Processor. There was a Header, a Footer, a Detail, a Subtotal, and a Grand Total sections to their report. I did in 64K what took them megabytes. Granted their interface was graphical and drag&drop, but in the end, we both did the same thing.
Disk I/O was the crown jewel of the Amper Routines.
The primary goal was to provide an interface that allowed better error handling and the ability to handle any type of input. The speed boost was a great plus.
AppleSoft's INPUT statement was very picky about the data stream. You couldn't have commas and other special characters or they would be taken as end of string.
Imagine trying to have an address book database and you couldn't have commas.
So Disk I/O was designed to allow any character until a <Carriage Return> as the contents of the input string.
Another feature that was added was the ability to read or write arrays. This gave yet another boost in speed. Instead of having a loop to read or write string array elements, Disk I/O would loop within the amper routine. Looping in machine code is vastly faster than interpreted AppleSoft BASIC.
Compare a standard AppleSoft/DOS loop.
100 DIM A(100)
120 PRINT D$;"OPEN TEST"
130 ONERR GOTO 300
140 PRINT D$;"READ TEST"
150 FOR X=1 TO 100
160 INPUT A(X)
170 NEXT X
180 PRINT D$;"CLOSE TEST"
300 PRINT "ERROR OCCURRED"
To an Amper routine version.
100 DIM A(100)
120 &OP%,FILE$:IF OP% THEN 300
130 &RE%,A(0):IF RE% THEN 300
140 &CL%,FILE$:IF CL% THEN 300
300 PRINT "ERROR OCCURRED - ";OP%;" - ";RE%;" - ";CL%
The zero element A(0) on a READ, would contain the number of array elements that were read in.
Assuming we didn't hit end of file (EOF), A(0) should contain 100. AppleSoft BASIC arrays are zero based. So our DIM statement created 101 elements, A(0) through A(100).
On writes, the zero element can be used to specify how many elements to output. When A(0)=0, the array limit is written. If A(0)=25, elements 1 through 25 would have been written.
Not to get entirely into the weeds, Disk I/O worked because I followed the conventions.
I've encountered lots of programmers over the years and some follow the rules, and others don't. Many take the easy way out and start writing code immediately and if it works in the end, they are happy. In the hobbyist world, which early home computers like the Apple ][, were considered at the time, this was not uncommon.
I'm one that takes the time to do the research and think about the code before I write it.
For example, an early version of Apple DOS, Version 3.1, formatted the floppy disks with 13 sectors per track. If you wrote a program to read the catalog of the files on the disk, you could start at Track 11, Sector 12 (sector counting started at 0) and then count down the sector number until you hit 1. The catalog stored seven file names and their pointers in each sector.
Problem was, when Version 3.3 came out, (they quickly skipped over Version 3.2), it formatted disks with 16 sectors per track.
The catalog now started at sector 15, not 12, so your program would now miss the first 21 filenames. Therefore your list of files would probably come up empty. Oops.
The proper way to code this catalog program would be to go to Track 11, Sector 0, where the VTOC (Volume Table Of Contents) was stored whether you had 13 or 16 sector disks.
The VTOC contains a pointer to the head of the catalog.
Each catalog block contains a pointer to the next catalog block. When the pointer is (0,0), you are at the end of the catalog.
My routines followed the pointers, so when the new version of the operating system came out, my programs kept working. The same could not be said for those that took the shortcuts.
My catalog routine (see page 22 of Language Plus manual for my &CA% routine) kept working when they started to hack Apple DOS so it could support the larger 8" floppies and hard drives.
It takes more time to write the programs, but the benefits are worth it.
So when it came to Disk I/O, I followed the rules.
Apple DOS maintains buffer space to hold the contents of a disk block (sector) as you read or write a file. That way you didn't need to spin up the disk and read in a disk block, just to read or write one character.
So when it came time to OPEN a file, I walked through the system disk buffers looking for one that was unused and then flagged it so Apple DOS would know I was using it.
I'd store the filename in the buffer block the same as Apple DOS would.
When a file was closed, I'd mark the buffer free for use.
This allowed the Amper Disk I/O routines to co-exist with the standard Apple DOS file commands.
Amper Disk I/O got it's speed from not having to parse the Apple DOS commands out of the character I/O stream. The variable name, right after the ampersand (&), would quickly establish which function was required. The filename would already be stored in a string variable, (although you could use string constants, it was discouraged), so there was no need to parse that out.
After that, reading (or writing) the data in the disk buffers was fairly straightforward. The handling of array variables was the only tricky part. But talking to an array of variables, just meant I had to advance a pointer instead of searching the variable pool for the location of the target variable.
I didn't replace the internal Apple DOS disk I/O routines, I co-opted them.
We used the Amper Disk I/O routines in all the Micro Lab products that required disk I/O, which was virtually all of the non-game products.
An enhanced version that could do binary I/O was used to create the Relocating Linking Loader.
So you can probably see why I was disappointed that they didn't get the distribution they deserved.
Copyright © Curt Rostenbach 2016