C++ Was a Joke
03-05-2014, 09:47 AM
Post: #9
 Garth Wilson Senior Member Posts: 502 Joined: Dec 2013
RE: C++ Was a Joke
I haven't seen that book, and perhaps I should get it, although I have read "Thinking Forth" which is very good and really is at least as much about programming concepts and habits and structure as it is about Forth itself, and the things there can be applied to some extent in other languages as well. I'm not sure what the relation of OOP is to how Forth allows you to define custom defining words and what their children will do, allowing for example advanced custom data structures.

As for program structures in assembly, I do that with macros I've written to get better control of code by making it much more clear what you're doing, and getting rid of the mass of labels and jumps that commonly constitute assembly code. The macros let you keep full control of every speck of code laid down by the assembler, without having to keep looking at the internal details. In most cases there is absolutely zero penalty in program memory taken or in execution speed. You get a lot of the benefits of higher-level languages, without losing the performance benefit of assembly. Code becomes quicker to develop, more bug-free, and easier to maintain, meaning that it's easier to come back later and figure out what you did when you decide to add a feature or change something.

For an example, take this short piece of code from my I²C sample code, actual code I used on a PIC12CE673 which had no SSP (even though the I²C serial EEPROM was onboard, with CLK and SDA on bits 6 & 7 of the GPIO) so the I²C had to be bit-banged:

Code:
RCV_I2C_BYTE:         I2C_DATA_UP         CLRF      EEPROM_DATA           ; Init EEPROM_DATA as 0.         MOVLW     8         MOVWF     LOOP_COUNT1 rib1:   I2C_DATA_UP         I2C_CLK_UP         CALL      RD_I2C_BIT         I2C_CLK_DN         DECFSZ    LOOP_COUNT1, F         GOTO      rib1         RETURN                          ; The ACK or NAK bit must be sent separately.  ;----------------------------

(Even there, you can see the use of macros, as for example I2C_DATA_UP is more clear than BSF GPIO,6 but assembles exactly the same thing.) The loop is to gather the 8 bits of data to read for one byte. If we do a FOR...NEXT loop with the macros, we get:

Code:
RCV_I2C_BYTE:         I2C_DATA_UP         CLRF      EEPROM_DATA              ; Init EEPROM_DATA as 0.         FOR LOOP_COUNT1, 8, DOWN_TO, 0             I2C_DATA_UP             I2C_CLK_UP             CALL  RD_I2C_BIT             I2C_CLK_DN         NEXT      LOOP_COUNT1         RETURN                             ; The ACK or NAK bit must be sent separately.  ;----------------------------

Looking at the .hex file output, you would have no way of telling which source code it came from, because the resulting assembled code is exactly the same for the two versions. In BASIC you would probably have the FOR...NEXT count up instead of down, but here it's more efficient to count down and end at zero since it allows branching on the Z flag without doing another comparison first. This way it just assembles the DECFSZ LOOP_COUNT1,F and then the GOTO like the more-manual version above.

Here's an example of using an "IF...END_IF" in a PIC16F72 (and as you can see, it's also in part of a CASE statement-- "CASE" being from Forth, but called "SELECT CASE" in BASIC, and "switch" in C).

Code:
            CASE_OF  4                         ; We were waiting for the low address byte to be shifted out to the flash memory.                RAM_BANK  1                    ; Is that finished yet?  (Check buffer-full bit in SSP-status register.)                IF_BIT  SSPSTAT, BF, IS_SET    ; If so,                    RAM_BANK  0                    CLRF  SSPBUF               ; send out a dummy byte in order to get the first data byte to read,                    INCF  FLASH_RD_STATE, F    ; then increment the state.                END_IF            END_OF

In this case, the serial-port shifting for the given hardware takes time, and there's too much to do to twiddle our thumbs, so I have kind of a multitasking system where every time a task is called up, it looks to see if there's anything it can or should do at the moment, and if not, lets the computer move on to do other necessary things while it's waiting. (Don't worry about it being left in RAM bank 1 if the buffer-full bit is not set. There's a RAM_BANK 0 instruction right after the END_CASE.)

This is still assembly language, although I patterned and named many of my structure macros after common Forth control structures. They are formed much the way Forth compiles them. A stack is formed in the assembler for the assembler itself to use to keep track of things like jump addresses that will need to be filled in when they are found, all in the first pass (since we're not using labels). Since it's a stack, the nesting of structures is automatically accommodated.

As you get a lot of decision-making, branching, looping, and exceptions to unevetful straight-line code, the macros become more and more valuable. It's nice going a hundred lines or more without a single label, and being able to more easily see the structure and the conditions under which the program carries out various operations. See the article for more info.