******************************************************************************* * * * Program Documentation * * * ******************************************************************************* Copyright © 1991 by Bjørn Reese. This article may be distributed freely, as long as no charge is being made. Please note that the article is copyrighted, and may not be changed in any way without the knowledge and written approval of the author (address is stated somewhere below). Before we go on, I would like to apologize for any grammatical and spelling mistake, which you without doubt will encounter. This article was originally written in danish for a danish magazine. I later decided to translate it to a more universal language in the hope more people could benefit from it. The article presents some principles of how to make useful, understandable and self-evident names for labels, and how to comment sources. The article is specifically aimed at Amiga (M680x0) assembly programmers, but I believe that other programmers also may find something useful in the basic principles of the convention defined in this article. ******************************************************************************* Contents ******************************************************************************* 1. Introduction 2. Comments 3. Procedures 4. Variables 5. Constants 6. Various (Instructions, Assembly Directives & Macros) 7. Final Words ******************************************************************************* 1. Introduction ******************************************************************************* The purpose of this article is to present some guidelines for internal documentation of sources, i.e. comments and name assignment of labels (procedures, variables, constants etc.), which makes more readable sources. This text is primary aimed at Amiga assembly programmers, but other programmers may also study the basic priciples with advantage. The guidelines have been developed and tested over a longer period of time by yours truly. It may take some time to get used with them, but it's worth while in the long run. Whenever I use the term "procedure", it refers to a subroutine with a clearly limited and well-defined function. For instance, if I wanted to make a program, which sorts the contents of a file, it would consist of three procedures. The first procedure gets the file, the second sorts it and finally the third saves the result. We got three clearly limited and well-defined functions. These may be split into more procedures, like the sorting, which consists of an initializing part and a sorting part. Big sources offers an exellent opportunity to loose the general view, especially if you havn't been engaged in the program for some time and perhaps developed and/or changed your programming style. The situation is even worse if you have to decipher the sources of another programmer. Lack of documentation often put us in a situation where a disassembly would have been just as good as the source, and that is quite unsatisfactory. An unlucky habit of many programmers is, that they don't bother to write very much besides the instructions. Their sources contains a minimum of comments (if any) and short, meaningless names of procedures, variables and constants. Whenever you write comments or name labels, THINK about what you write. It is important to find something descriptive, significant and relevant to the matter. In the long run it pays to sit and consider, before you write any comments or name your labels. Unfortunately I often witness "over-abbreviations", that is abbreviations which are so extreme that there is no way telling what the abbreviation originally represented. For instance: "r" = "Rollover" "sfc1" = "Star Field Counter 1" Non-english people should also be alerted about mixing english and their native language. It is almost impossible to decipher these abbreviations. Please keep a clear style in your comments and labels. ******************************************************************************* 2. Comments ******************************************************************************* If other people should be able to read your sources, it is important to include as many explanatory comments as possible. You should also be able to profit by comments in your own sources. If you return to a source of yours after a certain period of time, it takes at least a couple of centuries to regain the necessary knowledge of the source (believe me, I am talking from experience!). With efficient comments this time can be considerably reduced. While developing a program, you are completely familiar with the problematic of your program and it may seem unnessesary to make comments of something which seems so obvious. But it is worth while, because something that seems so obvious during development, seldom seem so obvious a couple of weeks or months later. Also, the "obvious" may not seem so obvious to other programmers, who don't have the same knowledge, experience and/or thoughts as yourself. It is however also easy to go to the other extreme, and make meaningless and superfluous comments which most often confuses more than it helps whose who read your sources. Example of a meaningless comment: move.l d4,a0 ;Dingdong! Example of a superfluous comment: add.w #14,d0 ;Add 14 to D0 One of the best and most efficient ways to document sources is a header, i.e. a introductory comment of a procedure or parts of a procedure. This header must include information about what this procedure does, what input is required and what output is delivered. The header may also include more informations, like a pseudocode for the procedure or parts of it, references, general comments about the purpose of the procedure and about the expected result (especially around boundaries). The header often contributes to a optimization of the development because it forces you to think and to get a general view of the procedure belonging to the header. Example of a header: ;------------------------------------------------------------------ ; @DoFormat ;------------------------------------------------------------------ ; ; Convert a string using Exec.RawDoFmt(). ; ; D0 [Output] will point to the formatted string, which is the ; buffer specified by A3 [Input]. Since A3 currently is restored ; you can use either D0 or A3 to get the string, but for the sake ; of compatibility use D0. ; ;------------------------------------------------------------------ ; IN a1.l = ^String ; a2.l = ^List of Arguments ; a3.l = ^Text Buffer ; USED - ; OUT d0.l = ^Formatted String ;------------------------------------------------------------------ DoFormat: ... rts Remarks of the example above: o The character "@" is used as a keyword for searching (more about this in the "7. Final Word" chapter). o I took the character "^" from Pascal where it represents a pointer (APTR). This means "^String" is a POINTER TO String (C-fanatics will recognize the character "*"). o If no INput or OUTput is specified, it is noted by the character "-" instead of the registers. Is there another INput/OUTput than the registers, i.e. an array, it is surrounded by the characters "{" and "}". o USED is used by many, but personally I'm to lazy to check which registers have been used. It should only be used to specify registers which are changed by the procedure. o I prefer to write the majority of my comments and labels in english, since it is the computerlanguage. I strongly urge others (non-english) also to do so. Template of a header: ;------------------------------------------------------------------ ; @ ;------------------------------------------------------------------ ; ; ??? ; ;------------------------------------------------------------------ ; IN ??? ; { ??? } ; USED ??? ; OUT ??? ; { ??? } ;------------------------------------------------------------------ The midmost and the last section of the header may optionally be skiped if they are not used. When you use a header, it is not so important to use inline comments (those placed after the instructions). Inline comments are only used if some instructions require an extra description or to refer to certain parts of a possible pseudocode. Even though the comments have to be significant and descriptive, they don't have to be boring. Try to vary your language. I often used citations to spice my source. The citations must however have a relation to what they must comment. As example I could mention a citation from the movie Blade Runner: "Wake up, time to die" as a comment to a software reset. ******************************************************************************* 3. Procedures ******************************************************************************* To find a name for a label may be done in countless ways and especially if a label is compounded of more words. The underscore (_) is often used to seperate the words, because a space normally can't be used in labels: my_label: or MY_LABEL: The guidelines for programming in Modula-2 gives us an alternative to the ugly underscore. Instead of seperating the words with underscores, all words are written in lowercase and the first letter of each word is capitalized: MyLabel: This offers us a whole new prospecive. It is important to be consistent with the use of capital letters. Some assemblers offers as an option not to distinguish between the case of the letters. My advise: Turn this option off! Also, never use the same word with different cases. In assembly language it can be difficult to distinguish if a certain label refers to a procedure or a variable, as they are written in the same way (namely "Label:"). We now define that all procedure must start with an uppercase letter, and all variables must start with a lowercase letter: MyProcedure: myVariable: Now it is easy to see what type our label is, without having to jump back and forth in our source all the time. Furthermore we shall extend the definition of variables later on. Most assemblers supports the use of local labels (in case of doubt, look in your manual). If you use a local label it is only known until the assembler encounters the next normal label. Local labels are only used in procedures. This way it is possible to use a name in different procedures, whereby we avoid all these "loop1", "loop2" etc. Therefore, use local labels in the procedures as often as possible (Save the environment! Recycle! ;-) Example of local labels: FirstProc: lea text,a0 move.w #10,d0 .Loop tst.b (a0)+ beq.s .Exit dbra d0,.Loop .Exit rts SecondProc: lea text,a0 move.w #10,d0 .Loop clr.b (a0)+ dbra d0,.Loop rts Even though local labels theoretically also may be used as variable, it is too troublesome in reality, where they consequently seldom are used. ******************************************************************************* 4. Variables ******************************************************************************* We now extend the convention concerning variables, inspired by the "Hungarian Convertion" (developed by some guy at Microsoft). This method is particularly aimed at assembly programmers - because it is the language I have the biggest experience with, and I don't want to force some weird, untested convention on other programming languages - but other programmers may gain advantage of the corresponding principles. First and foremost: ALL VARIABLES MUST START WITH A TYPE DESCRIPTION! This type gives us information about the concerning variable. This way we avoid jumping back and forth in the source, to determine for example whether a variable is word or byte sized. This type could be "p" for "Pointer" or "w" or "Word": pDosBase = Pointer (APTR) to DosBase wIntReq = Interrupt Request (size = Word) I have reached the following practical types, during longer time of experiments and daily use: b = Byte w = Word l = Long p = Pointer [APTR] h = Handler [BPTR] f = Flag a = Array s = String [Array of Chars] d = (Arbitrary) Data Structure x = Extern Data [ex: Binary/Modules/Pictures] i = Index / Offset u = Unsigned v = Local Data [ex: Variables on Stack] Please remember: the type must be placed in front of the variable and consequently must be written in lowercase to comply with the principle of the distinction of procedures and variables from the previous chapter. Of course, it is possible to combine some of the types to reach a better clarification. This could be: ub = UBYTE (Unsigned Byte) uw = UWORD ul = ULONG fl = Flag Long iw = Index Word ap = Array of Pointers ab = Array of Bytes afb = Array of Flag Bytes auw = Array of UWORD Some of the types are very unlikely to appear in these combinations (though they might): "d" covers arbitrary data structures, like Window and Screen structures. "x" covers external binary files, like tables, pictures and music-modules. All of the types may be combined with "v", which indicates local data, like variables on the stack. Some of the types, like "x" are however rather unlikely to appear together with "v". NB! "v" doesn't make a variable local, it only indicates that the variable should be used in a local context. Examples of variables: fbDelay dc.b -1 iwSinus: dc.w 22 ;Index for Sinus Table hFile: dc.l 0 ;File Handle [BPTR] awBuffer: dcb.w 20,0 dWindow: dcb.b nw_SIZE,0 xMusic: INCBIN "ST-00:mod.freudian" ******************************************************************************* 5. Constants ******************************************************************************* Constants are classified as variables, but they have their own type: c = Constant There is nothing much to add about the constants as they are only a subtype of the variables in this convention. It is also possible to combine the constant type with some of the variable types. Examples of constants: cMaxSize = 128 clAddressAlert = $00000003 ciFirstEntry = 44 ******************************************************************************* 6. Various (Instructions, Assembly Directives & Macros) ******************************************************************************* The last definitions we need to complete this convention are about the instructions, the assembly directives and the macros. They must also be unique, to make the the convention effective. We define all instruction to solely consist of lowercase letters, and the assembly directives and the macros to solely consist of uppercase letters. The only exceptions are the dc/ds/dcb directives which of comprehensibility reasons are classified as instructions. Macros are often used to facilitate the readabillity of sources. For instance: Execute a function in an arbitrary library ------------------------------------------------------------------------------- Syntax: CALL Ex: CALL Forbid = jsr _LVOForbid(a6) Push registers to the stack ------------------------------------------------------------------------------- Syntax: PUSH Ex: PUSH d0/a0-a4 = movem.l d0/a0-a4,-(sp) Pull registers from the stack ------------------------------------------------------------------------------- Syntax: PULL Ex: PULL d0/a0-a4 = movem.l (sp)+,d0/a0-a4 ******************************************************************************* 7. Final Words ******************************************************************************* Then using modules (linkable object code) we have to specify external references of both procedures and variables. These are specified by using an underscore (_) at the first position of the label: XREF _LVOAddTask XDEF _ClearScreen [NOTE 2] contains a complete example of a source documented using this convention. It is an old source of mine which I have updated to comply with the principles of the convention. It is unfortunately not possible to use this convention a 100%. We get a problem when we use the includes from Commodore. It is a bad idea however to change the includes, but if we are attentive of this problem we may live with this restriction. Besides, I diverge from the principles of the convention in two specific points: "start" and "DataArea" (no rule without exception). These are however the ONLY exceptions! If you absolutely have to be inconsistent, when be consistent with the inconsistent! Another advise I will pass is not to choose a name for a label that is too difficult to spell or too hard to remember. Choose easy names. I have for instance always thought that words like "width", "height" and "depth" have increased the possibility for spelling mistakes. Hence I substituted the words with the following words: "xSize", "ySize" and "zSize". If you are working with a big source, you may define keywords for all the procedures, which may be placed in the header. This should speed up the search for a specific procedure. To avoid the searching to stop at each reference of the procedure (i.e. each time it is called or jumped to), you could let the keyword start with the character "@". If I want to find the "NewMenu" procedure, I have to search for "@NewMenu". The convention described in this text is only a couple of guidelines, and it is up to you, whether you will use them or not, but since I started using them and became familiar with them, my development- and debugging time has been hugely reduced. [NOTE 1] consists of a template which I use in my own programs (written for AsmOne - if you use another assembler delete the BASEREG line). The template may vary a little, depending on the type of program I have to write. Remember! Not matter what you do: BE CONSISTENT. - BReese My address is Bjørn Reese Stammen 55, -2 DK-5220 Odense SØ Denmark ******************************************************************************* [NOTE 1] ******************************************************************************* ;================================================================== ;=== ;=== Name: ??? ;=== ;=== Author: ??? ;=== ;=== Date: dd. mmm yyyy ;=== ;================================================================== ;--- Comment ------------------------------------------------------ ; ; TAB = 8 ; POINTER = ^ ; ;------------------------------------------------------------------ ;--- System ------------------------------------------------------- BASEREG DataArea,a5 INCDIR INCLUDE: ;--- Include ------------------------------------------------------ ;--- Macro -------------------------------------------------------- ;--- Constant ----------------------------------------------------- ;================================================================== ;=== ;=== CODE AREA ;=== ;================================================================== SECTION Reese,CODE start: moveq #0,d0 rts ;------------------------------------------------------------------ ; @ ;------------------------------------------------------------------ ; ; ??? ; ;------------------------------------------------------------------ ; IN ??? ; { ??? } ; OUT ??? ; { ??? } ;------------------------------------------------------------------ ;================================================================== ;=== ;=== DATA AREA ;=== ;================================================================== ;--- Structure ---------------------------------------------------- ;--- SmallCode ---------------------------------------------------- ;--- Variable ----------------------------------------------------- DataArea: ;--- Text --------------------------------------------------------- ;--- Table -------------------------------------------------------- ;--- Extern ------------------------------------------------------- ;================================================================== ;=== ;=== BUFFER AREA ;=== ;================================================================== ; SECTION buffer,BSS ******************************************************************************* [NOTE 2] ******************************************************************************* ;================================================================== ;=== ;=== Name: CountSort (Sort an array of integers) ;=== ;=== Author: Bjørn Reese ;=== ;=== Date: January 1991 ;=== ;================================================================== ;=== ;=== Copyright © 1991 by Bjørn Reese ;=== ;================================================================== ;--- Comment ------------------------------------------------------ ; ; TAB = 8 ; POINTER = ^ ; ; The numbers in awNumbers are sorted. ; NB! cMaxNum must be equal to or greater than the biggest number ; in awNumbers. ; ;------------------------------------------------------------------ ;--- System ------------------------------------------------------- BASEREG DataArea,a5 INCDIR INCLUDE: ;--- Constant ----------------------------------------------------- cMaxNum = 32 ;Biggest number in awNumbers ( [0..cMaxNum-1] ) cLength = 16 ;Numbers of words in awNumbers ;================================================================== ;=== ;=== CODE AREA ;=== ;================================================================== SECTION CountSort,CODE start: ;--- Do the entire sorting bsr ClearBuffer bsr Count bsr CountResult ;--- Exit without error messages moveq #0,d0 rts ;------------------------------------------------------------------ ; @ClearBuffer ;------------------------------------------------------------------ ; ; Clear awCountBuffer before use. ; cMaxNum must be even. ; ;------------------------------------------------------------------ ; IN - ; USED d0/d1/a0 ; OUT - ; { awCountBuffer empty } ;------------------------------------------------------------------ ClearBuffer: lea awCountBuffer,a0 moveq #0,d0 move.w #cMaxNum/2-1,d1 .Loop move.w d0,(a0)+ move.w d0,(a0)+ dbra d1,.Loop rts ;------------------------------------------------------------------ ; @Count ;------------------------------------------------------------------ ; ; Counts the appearence of the numbers in awNumbers and accumulate ; CountBuffer ; ; FOR cnt = 0 TO cLength-1 ; num := awNumbers[cnt] ; awCountBuffer[num] += 1 ; NEXT ; ; NB!!! [] indicates index (ex: Array[index]) ; ;------------------------------------------------------------------ ; IN - ; { Emtpy awCountBuffer } ; USED d0/d1/a0/a1 ; OUT - ; { awCountBuffer accumulated } ;------------------------------------------------------------------ Count: lea awNumbers(pc),a0 lea awCountBuffer,a1 move.w #cLength-1,d1 .Loop move.w (a0)+,d0 add.w d0,d0 ;Adjust to word boundary addq.w #1,(a1,d0.w) ;awCountBuffer[num] += 1 dbra d1,.Loop rts ;------------------------------------------------------------------ ; @MakeResult ;------------------------------------------------------------------ ; ; Stores numbers in awNumbers according to numbers in awCountBuffer. ; ; cnt2 := 0 ; FOR cnt = 0 TO cMaxNum-1 ; WHILE awCountBuffer[cnt] > 0 ; awNumbers[cnt2] := cnt ; awCountBuffer[cnt] -= 1 ; cnt2 += 1 ; WEND ; NEXT ; ; NB!!! The code has been optimized, so it doesn't look like the ; pseudocode above, but it's based on it. cLength is used instead ; of cMaxNum. ; ;------------------------------------------------------------------ ; IN - ; { Accumulated awCountBuffer } ; USED d0/d1/d2/a0/a1 ; OUT - ; { awNumbers sorted } ;------------------------------------------------------------------ MakeResult: lea awCountBuffer,a0 lea awNumbers(pc),a1 moveq #0,d0 move.w #cLength-1,d1 bra.s .Loop .Next addq.w #1,d0 ;Next number addq.w #2,a0 ;Next entry in awCountBuffer .Loop subq.w #1,(a0) ;CountArray[cnt] -= 1 blt.s .Next move.w d0,(a1)+ dbra d1,.Loop rts ;================================================================== ;=== ;=== DATA AREA ;=== ;================================================================== ;--- Variable ----------------------------------------------------- DataArea: ;--- Table -------------------------------------------------------- awNumbers: ;An example dc.w 5,22,6,31,5,1,3,3,5,6,7,25,15,9,0,21 ;================================================================== ;=== ;=== BUFFER AREA ;=== ;================================================================== SECTION buffer,BSS awCountBuffer ds.w cMaxNum ******************************************************************************* [NOTE 3] ******************************************************************************* ;================================================================== ;=== ;=== Name: Another test example (which finds Dos Library) ;=== ;=== Author: Bjørn Reese ;=== ;=== Date: October 1991 ;=== ;================================================================== ;--- Comment ------------------------------------------------------ ; ; TAB = 8 ; POINTER = ^ ; ; a5 is a global pointer to the DataArea. ; ;------------------------------------------------------------------ ;--- System ------------------------------------------------------- BASEREG DataArea,a5 INCDIR INCLUDE: ;--- Include ------------------------------------------------------ INCLUDE exec/exec_lib.i INCLUDE exec/execbase.i ;--- Macro -------------------------------------------------------- CALL: MACRO jsr _LVO\1(a6) ENDM ;--- Constant ----------------------------------------------------- cSysBase = 4 ;================================================================== ;=== ;=== CODE AREA ;=== ;================================================================== start: lea DataArea(pc),a5 ;GLOBAL lea sDosName(a5),a1 bsr GetLibrary move.l d0,pDosBase(a5) moveq #0,d0 rts ;------------------------------------------------------------------ ; @GetLibrary ;------------------------------------------------------------------ ; ; Find an already opened and initialized library in the LibList ; of Exec Library. ; ; NB!!! Only use this method for ROM-resident Libraries. ; ;------------------------------------------------------------------ ; IN a1.l = ^Library Name ; OUT d0.l = ^Library Base ;------------------------------------------------------------------ GetLibrary: move.l cSysBase,a6 move.l LibList(a6),a0 CALL FindName rts ;================================================================== ;=== ;=== DATA AREA ;=== ;================================================================== ;--- Variable ----------------------------------------------------- DataArea: pDosBase: dc.l 0 ;--- Text --------------------------------------------------------- sDosName: dc.b 'dos.library',0 EVEN ******************************************************************************* [NOTE 4] ******************************************************************************* Types Meaning ------------------------------------------------------------------ b = Byte w = Word l = Long p = Pointer [APTR] h = Handler [BPTR] f = Flag a = Array s = String [Array of Chars] d = (Arbitrary) Data Structure x = Extern Data [ex: Binary/Modules/Pictures] i = Index / Offset u = Unsigned v = Local Data [ex: Variables on Stack] c = Constant ------------------------------------------------------------------