home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Frozen Fish 1: Amiga
/
FrozenFish-Apr94.iso
/
bbs
/
alib
/
d5xx
/
d587
/
conlib.lha
/
ConLib
/
PROCDOC.ART
< prev
next >
Wrap
Text File
|
1992-01-04
|
30KB
|
873 lines
*******************************************************************************
* *
* 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:
;------------------------------------------------------------------
; @<Name>
;------------------------------------------------------------------
;
; ???
;
;------------------------------------------------------------------
; 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 <Library function>
Ex: CALL Forbid = jsr _LVOForbid(a6)
Push registers to the stack
-------------------------------------------------------------------------------
Syntax: PUSH <Registers>
Ex: PUSH d0/a0-a4 = movem.l d0/a0-a4,-(sp)
Pull registers from the stack
-------------------------------------------------------------------------------
Syntax: PULL <Registers>
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
;------------------------------------------------------------------
; @<Name>
;------------------------------------------------------------------
;
; ???
;
;------------------------------------------------------------------
; 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
------------------------------------------------------------------