Purpose
A programmer must understand why adhering to a set of standards is beneficial .
Initially, it takes a little extra time to incorporate such things as informative user prompts and comments into a program. However, when the program is fully debugged and tested, it is completely finished; there is no need to go back an 'clean it up'.
Last minute 'clean up' efforts spoil the satisfaction of having completed the program. They have also been known to produce program flaws - a line of good code can accidentally be overwritten in the editing process.
Adhering to programming standards makes a program much more readable, and pleasant to use. The person you are writing the program for will certainly enjoy this, but why not do it from the beginning, so that you can enjoy the same benefits?
Program Format
1. 'Pretty Print' is a term used to describe the pleasing appearance of a program. A program that is easy to read is likely to be easy to understand. Here are some guidelines:
Use vertical alignment and indentation to make the code readable. For example:
if condition then
do this
and this
and this
else
do this
and this
and this
end if
Put each END statement on a separate line and make sure that it is vertically aligned with its 'partner'.
Use blank lines to improve readability.
Do include a reasonable number of comments in your code; program, function, and procedure headers are mandatory, but you might also wish to add some explanatory comments before particularly complex sections of code.
Do not clutter up a program with too many comments; a comment for each line of code is definitely overdoing it.
2. Program header - a program should begin with a block of comments which includes the following information:
/***********************************************************
Filename: rootsearch.t
Author: your name
Student #: 123 456 789
Purpose: include a brief explanation of the purpose of the program
************************************************************/
If you have any functions or procedures in your program, their names should be included in the program header.
Functions: function1, function2, ...
Procedures: procedure1, procedure2, ...
Use asterisks in the top and bottom comment lines to set the header off. The start of the main program should be set off in a similar manner. e.g.
/******************* main program **********************/
3. Procedure, Function headers - procedures and functions should begin with a block of comments which includes the following information:
/* ------------------------------------------------------------------------------------------------------
Filename: rootsearch.t <- necessary when the module is in a separate file
Name: function or procedure name
Purpose: include a brief explanation of the purpose of the module
------------------------------------------------------------------------------------------------------* /
Use hyphens in the top and bottom comment lines to set the header off.
4. Choose meaningful variable names. An acceptable exception to this rule is that loop counters can have single character names. The variable names I, J, and K are generally recognized as loop counters.
5. Variable declarations should be grouped - all integers together, all reals together, and so on.
6. Avoid global variables whenever possible. A large program will consist of many modules (functions and procedures). A global variable can be changed in any module which makes a program harder to debug. Always start the name of a global with a capital letter so it can be easily recognized as being global.
Program Execution
1. Begin your program with a brief explanation of the purpose.
e.g. "Program to solve quadratic equations."
2. Use informative user prompts. Say what values are expected and how they are to be entered. e.g. "Enter 3 coefficients separated by spaces: "
3. Explain any output i.e. do not just print a value by itself; accompany it with a brief but clear explanation of the value.
e.g. "The roots are: "
4. Error messages must be meaningful. A message such as "bad value" is next to useless. What value is being referred to, why is the value bad? Make your error messages concise, but informative.
5. If you choose to stop the program execution under some conditions, display a message indicating why the program has been terminated.
6. Avoid goto statements whenever humanly possible. Too many gotos result in a tangled mess of convoluted program flow sometimes referred to as "spaghetti code".
Example:
/* **************************************************************
Filename: average.t
Author: Joe Blow
Student #: 123 456 789
Purpose: To average 2 numbers.
************************************************************/
/************************* main program *****************/
var num1, num2: int
var the_sum: real
put "This program will average 2 numbers."
put "Enter 2 numbers, separated by a space:"
get num1, num2
the_sum := (num1 + num2) / 2
put "The average is ", the_sum
Programming Assignments
In all programs you do for class assignments, you must follow the standards just described.
For lab assignments: Documentation and comments will not normally be required - this is only because of time constraints. Also, you do not need to include rigorous error checking in a lab assignment, unless this is specified in the assignment sheet. You should however, in all programs, attempt to format your code e.g. indent the code inside IF statements, FOR loops, and so on.
Programming Principles
Section 2.2. of the Users' Manual appears, at first glance, to be somewhat facetious. Despite the apparently light hearted approach, it contains many relevant observations. For example:
under 'Design': "Understand the problem. The best design is often the simplest."
under "Project Management': "Furious activity is no substitute for understanding."
under 'User Interface': "Details count."
under 'Coding': "If you can't write it down in English, you can't code it."
under 'Debugging': "Testing shows the presence of bugs, not their absence."
under 'Documenting': " Don't over decorate. Avoid over-documentation."
Section 2.2 is worth reading - use what you can to develop and improve your own programming style.
When there are too many statements in a program, the program become difficult to understand and update. One way to solve this problem is using subprograms.
Other advantages of using subprograms include:
dividing the program into parts that can be written by different people and over different time periods;
factoring out commonly used parts of programs so they can be used in different places within one program and in different programs;
separating program into different parts so that each part can be tested individually.
There are two ways for communicating data between the main program and a subprogram, one is using global data, the other is using parameters of the subprogram.
Some subprograms are available along with the programming language. These subprograms are said to be predefined or built-in or predeclared.
Subprograms can be divided into two classes: procedures and functions.
A procedure is a subprogram that consists of a set of statements. These statements act together completing a desired task.
The general form of a procedure is:
procedure procedure_name [ ( [var] parameter : type ... ) ]
[ local variable declarations ]
statement(s)
end procedure_name
The square brackets in the preceding description indicate that parameters may, or may not, be present, and that a parameter may, or may not, be preceded with the keyword `var'. Also, a procedure may, or may not, contain local variables. A local variable exists only within the scope of the procedure.
If a parameter is preceded with var, then the procedure will be able to alter the value of the variable; otherwise, the procedure will be able to obtain the value of a parameter, but will not be able to change the value.
Here is a simple procedure, without parameters, named Heading.
procedure Heading
put " ": 10, "The average mark"
put " ": 14, "in class"
put " ": 16, "for student"
end Heading
Line 1 is the procedure header. Line 5 is the procedure's end. Lines 2 through 4 are the procedure body. The procedure can be called by including the name of the procedure as a statement like this:
/* another part of the program */
Heading
The preceding line causes the computer to execute lines 2 through 4 and display the following message:
__________The average mark
______________in class
________________for student
The complete program would appear as:
/**************************************************************
Filename: heading.t
Author: Joe Blow
Student #: 123 456 789
Purpose: To experiment with procedures.
***************************************************************/
/*----------------------------------------------------
Name: Heading
Purpose: To print a heading
---------------------------------------------------------*/
procedure Heading
put " ": 10, "The average mark"
put " ": 14, "in class"
put " ": 16, "for student"
end Heading
/************************* main program *****************/
Heading /* this line just calls the procedure */
Now let's look at how parameters may be used in a procedure.
Remember that if a parameter is preceded with var, then the procedure will be able to alter the value of the variable; otherwise, the procedure will be able to obtain the value of a parameter, but will not be able to change the value.
If a parameter is a dynamic array, then in the header the upper limit of the array is given by an asterisk.
For example, given this procedure heading:
procedure GetMarks(className: string,
var studentList: array 1 .. * of int,
listSize : int)
the variable "className" can be obtained by the procedure, but not changed;
the "studentList" is a dynamic array, its values can be altered in the procedure;
the "listSize" variable can be accessed within the procedure, but not changed by it.
A call to the procedure must supply appropriate values for the parameters.
e.g.
GetMarks("Computer Science", studentList, listSize)
Let's look at a complete example.
/* **************************************************************
Filename: classes.t
Author: Joe Blow
Student #: 123 456 789
Purpose: To manipulate a set of student grades.
************************************************************** */
/*--------------------------------------------------------
Name: GetMarks
Purpose: To obtain a set of grades
---------------------------------------------------------------*/
procedure GetMarks(className: string,
var studentList: array 1 .. * of int,
listSize : int)
put "Enter the marks for ", className, ":"
for i : 1 .. listSize
get studentList(i)
end for
end GetMarks
/******************** main program ***************/
var listSize : int
put "Enter the number of students: "
get listSize
var studentList : array 1 .. listSize of int
% Call the procedure 'GetMarks', giving it 3 parameters:
GetMarks("Computer Science", studentList, listSize)
for i: 1..listSize
put studentList(i)
end for
Another type of subprogram is a function. The declaration of a function is very similar to that of a procedure. Other than the keywords "function" and "procedure", the difference between the two types of subprograms is that a function returns a value when it is called.
The general form of a function is:
function function_name [(parameter:type...)]:function_type<>
[ local variable declarations ]
statement(s)
[result variable_name or expression ]
end function_name
The function header must indicate the type of value that will be returned; this is the meaning of ": function_type " in the preceding description. Somewhere (it can be anywhere) in the function, the function must indicate, by the keyword result, the value that is to be returned; this is the meaning of "result variable_name or expression " in the preceding description.
In the preceding description of a function (as was the case with procedures):
The square brackets indicate that parameters may, or may not, be present, and that a parameter may, or may not, be preceded with the keyword `var'. Also, a function may, or may not, contain local variables. A local variable exists only within the scope of the function.
If a parameter is preceded with var, then the function will be able to alter the value of the variable; otherwise, the function will be able to obtain the value of a parameter, but will not be able to change the value.
Let's write a function that calculates the average of two numbers.
function avrg(num1, num2: real): real
var num_avrg: real
num_avrg := (num1+ num2) / 2
result num_avrg
end avrg
The first line is the header of the function. avrg is the name of this function. num1 and num2 in the parentheses are parameters. The colon and keyword real following them declare that num1 and num2 are variables of real type. The colon followed by the keyword real after the parentheses specifies that the result of this function is a real number. The second line declares that num_avrg is a variable of type real. When variables are declared within a function or a procedure, they can be only accessed within the function or procedure. These kind of declarations are called local declarations. The third line is the calculation. In the fourth line, result is a keyword that returns the value of num_avrg. We can shorten the preceding function as follows.
function avrg(num1, num2: real): real
result (num1+ num2) /2
end avrg
To use a function, you only need to call it. For example, to assign the result of function avrg to a variable z we write:
/* another part of the program */
var z: real
z := avrg(5, 6)
The value 5.5 is assigned to z now.
The complete program would appear as:
/* **************************************************************
Filename: functions.t
Author: Joe Blow
Student #: 123 456 789
Purpose: To experiment with functions.
************************************************************** */
/*-------------------------------------------------------
Name: avrg
Purpose: To compute the average of 2 numbers.
---------------------------------------------------------------*/
function avrg(num1, num2: real): real
result (num1+ num2) /2
end avrg
/******************** main program *****************/
var z: real
z := avrg(5, 6)
put "The average of 5 and 6 is ", z
Sometimes we need to use dynamic arrays or dynamic strings as parameters. This means that the size of the array or string can be varied. In the following example, a is a dynamic array parameter of the function sum.. In the body of the function, we can use the pre-defined function upper to get the upper bound of the formal parameter a.
%Calculate the sum of the elements in a dynamic array
function sum(a: array 1 .. * of real): real
var s : real := 0.
for i: 1 .. upper(a)
/* upper( ) is a pre-defined function
that returns the upper bound of an array */
s := s + a(i)
end for
result s
end sum
Here is an example of how to use a dynamic string. The procedure adds a comma in the string target at a place specified by the parameter place.
procedure dvd(var target: string(*), place: int)
target := target(1 .. place) + "," + target(place + 1 .. *)
end dvd
Some very useful functions and procedures are available along with the programming language. These functions or procedures are called pre-defined. Here are some examples.
Pre-defined Function Example:
sin(arg: real): real
The sin function takes a "real" argument, arg, and returns a "real" value, the sine of arg.
Pre-defined Procedure Example:
randint(var randvar : int, bottom, top: int)
The randint procedure generates a random integer in the range specified by the second and third parameters, bottom and top: The random integer is placed in the first parameter, randvar .
A list of Turing pre-defined functions and procedures follows.
abs(arg) returns the absolute value of a given argument
arctan(arg) returns the arctangent (in radians) of a given argument
arctand(arg) returns the arctangent (in degrees) of a given argument
ceil(arg) returns an integer greater than or equal to the given real argument
chr(arg) returns the character value of a given integer argument
cos(arg) returns the cosine (in radians) of a given argument
cosd(arg) returns the cosine (in degrees) of a given argument
exp(arg) returns the value of e taken to the power of the given argument
floor(arg) takes a real value and returns an integer less than or equal to the real
frealstr(arg1, arg2, arg3) returns a string `equivalent' to the real specified by arg1; string length will be n (specified by arg2) characters long; number of fractional digits to be converted specified by arg3
index(arg1, arg2) returns the starting position, in the character string specified by arg1, of the character string specified by arg2
intreal(arg) returns the real value of the given integer argument
intstr(arg) returns a string `equivalent' to the given integer argument
length(arg) returns the number of characters in the string in the given argument
ln(arg) returns log to the base e of the given argument
max(arg1, arg2) returns whichever is the larger of the two given arguments
maxint returns the largest integer value possible in the current installation
min(arg1, arg2) returns whichever is the smaller of the two given arguments
ord(arg) returns the integer value of a given character argument
rand(var) places a random number (between 0 and 1) in the given variable
randint(var, arg1,arg2) places a random integer (between the arg1 value and the arg2 value) in the given variable
randomize ensures that a different set of random numbers will be generated the next time the program is run
realstr(arg1, arg2) returns a string `equivalent' to the real specified by arg1; string length will be n (specified by arg2) characters long
repeat(arg1, arg2) returns a concatenated string composed of n (specified by arg2) occurrences of the string specified by arg1
round(arg) returns the nearest integer value to the given argument
sign(arg) returns: 1 if argument is positive, 0 if = zero, or -1 if negative
sin(arg) returns the sin (in radians) of a given argument
sind(arg) returns the sin (in degrees) of a given argument
sqrt(arg) returns the square root of a given argument
strint(arg) returns a integer `equivalent' to the given string argument
strreal(arg) returns a real `equivalent' to the given string argument
Information may be arranged into different structures. Some comman data structures are introduced in this section.
A queue is a collection of data items kept in order of their arrival. Items leave from the head of the queue and new items enter at the tail of the queue. We call this discipline first-in-first-out (FIFO).
The following is a very simple implementation of a queue using an array. The length of the queue is fixed to a constant number.
type entryType: int
const length := 10
var entry: array 1 .. length of entryType
var head: 1 .. length := 1
var tail: 1 .. length := length
procedure move(in: entryType, var out: entryType)
tail := (tail mod length) + 1
head := (head mod length) + 1
entry(tail) := in
out := entry(head)
end move
The discipline for a stack is last-in-first-out (LIFO). The following is an example of how to implement a stack. The two basic operations of a stack is push and pop. When you push an item, the item is put on topof the stack. When you pop an item, the item on top of the stack is picked out.
/**********************************************************
Filename: stack.t
Author: Anonymous
Date: July, 1995
Description: This example will print out an input string
in reverse order.
***********************************************************/
type entryType: string(1)
const len := 100
var entry: array 1 .. len of entryType
var top: 0 .. len := 0
procedure push(item: entryType)
pre top < len
top := top + 1
entry(top) := item
end push
procedure pop(var item: entryType)
pre top > 0
item := entry(top)
top := top - 1
end pop
var item: entryType
var count: int :=0
put "Enter a string no more than ",len," characters: "
loop
getch(item)
exit when item="\n" or count>len
count := count + 1
push(item)
end loop
put ""
put "The string in reverse order is: "
for i: 1..count
pop(item)
put item..
end for
/* end of program */
A linked list is a list of elements. Each element contains two parts. One is the data part, the other is a pointer. The pointer locates the next element. There is also a pointer pointing to the first element. The pointer in the last element points to no location and is called a nil pointer.
The following is an example. In this example, the procedure insertData is used to insert new data value into an ordered list. The procedure deleteData is used to find an element in the list and delete it.
/**********************************************************
Filename: list.t
Author: Zhiwei Wang
Date: July, 1995
Description: This is an example using linked list to
maintain an ordered name list. The basic functions
are inserting an item in the list, deleting an item in the list, and printing the list. You can modify the data part by adding more fields in it, such as student number, address, etc. You can also add some more functions such as searching an item.
***********************************************************/
var node: collection of
record
data: string
link: pointer to node
end record
var first: pointer to node := nil(node)
% insertData is used to insert new data into linked list
procedure insertData(item: string)
var entry: pointer to node
% Obtain storage element for new data
new node, entry
% Place data in element
node(entry).data := item
% See if new data goes first in list
if first = nil(node) or item < node(first).data then
node(entry).link := first
first := entry
else
% Find a place to insert new data
var previous: pointer to node := first
var next: pointer to node := node(first).link
loop
exit when next = nil(node) or item < node(next).data
previous := next
next := node(next).link
end loop
% Adjust links to make insertion
node(previous).link := entry
node(entry).link := next
end if
end insertData
% deleteData is used to delete specified data from the list
procedure deleteData(item : string)
var previous: pointer to node
var old: pointer to node := first
loop
exit when old = nil(node) or item = node(old).data
previous := old
old := node(old).link
end loop
% Remove node from list
if old = nil(node) then
put item, " not found in link list"
return
elsif first = old then % Node first in list
first := node(old).link
else
node(previous).link := node(old).link
end if
% Free unused node
free node, old
end deleteData
% printData is used to print the linked list
procedure printData
if first = nil(node) then
put "Empty list"
else
var temp: pointer to node := first
loop
exit when temp = nil(node)
put node(temp).data
temp := node(temp).link
end loop
end if
end printData
% Procedure menu prints out a menu
procedure menu
put "***********************************************"
put " 1. Enter an item "
put " 2. Delete an item "
put " 3. Print list "
put " 4. Quit"
put "***********************************************"
end menu
/* main program */
var choice : string(1)
var name : string
cls
menu
loop
put " Enter your choice: " ..
getch(choice)
put ""
case choice of
label "1":
put "Enter an name"
get name
insertData(name)
label "2":
put "Enter the name to be deleted"
get name
deleteData(name)
label "3":
printData
label "4":
put "Thank you for using this program"
exit
label: put "Type 1, 2, 3, or 4, try again"
end case
end loop
/* end of program */
In the above procedure, the problem of trying to delete from a data item that is not present in the list is taken care of by outputting an error message.
A tree consists of many nodes, each node contains the data itself and two pointers, one pointing to the left subtree, the other pointing to the right subtree. If there is no subtree from a particular node, the corresponding pointer is set to nil.
See below for an example of how a tree, in this case a binary one, can be conveniently implemented by using pointers and collections.
Here are the declarations, the procedures and the main part of the program:
(end of section)/**********************************************************
Filename: linesort.t
Author: Anonymous
Date:
Description: Read lines from a file called "text",
then output them in a sorted order in screen.
***********************************************************/
var node: collection of
record
data: string % Holds one line of data
left: pointer to node % Left subtree
right: pointer to node % Right subtree
end record
type nodePointer: pointer to node
var root: nodePointer := nil(node) % Tree starts empty
procedure enterLine(line: string)
% "line" is inserted into tree
const nilNode := nil(node)
var L: nodePointer
new node, L % allocate then fill in new node L
node(L).data := line
node(L).left := nilNode
node(L).right := nilNode
if root = nilNode then
root := L % Attach first node to root
else
var p: nodePointer := root
loop % Search for place to attach new node
bind var n to node(p) % Shorten name to n
if line < n.data then
if n.left = nilNode then
n.left := L % Attach new node here
exit
else
p := n.left % Search left subtree
end if
else
if n.right = nilNode then
% Attach new node here
n.right := L
exit
else
% Search right subtree
p := n.right
end if
end if
end loop
end if
end enterLine
procedure outputLines(p: nodePointer)
if p not= nil(node) then
outputLines(node(p).left) % Output left subtree
put node(p).data % Output this node
outputLines(node(p).right) % Output right subtree
end if
end outputLines
% Main part of the program
% the data structure used is a binary tree built using
% collections. Successive lines are inserted as new leaves
% in the tree. Read and enter lines; then output all lines.
var fileNo: int
open: fileNo, "text", get
assert fileNo not= 0
var line: string
loop
exit when eof(fileNo)
get: fileNo, line: * % Read next line
enterLine(line) % Insert line into binary tree
end loop
outputLines(root) % Output all lines in tree
/* end of program */