home *** CD-ROM | disk | FTP | other *** search
Text File | 1987-05-17 | 58.1 KB | 1,167 lines |
- Draco Quick Reference Guide
-
- Copyright 1983 by Chris Gray
-
-
- I. Using the compiler under AmigaDOS
-
- draco f1[.d] f2[.d] ... fn[.d]
-
- Each file is a separate compilation; they need not be related. If no
- extension is given, then .d is assumed. For each file, if the
- compilation is successful, a corresponding .r file is produced.
-
- II. Using the assembler (not available in Amiga version)
-
- III. Using the link editor
-
- The Draco linker has not yet been ported. Draco programs are currently
- linked using BLink or ALink. See "Draco Other:Introduction" for some
- details and the proper writeups for BLink and ALink for more details.
- The description here of the CP/M-80 version of the Draco linker is kept
- around so that if/when it is ported, the description can just be modified
- as needed.
-
- link f1[.rel] f2[.rel] ... fn[.rel] fa.lib fb.lib ... fz.lib
-
- Each file is a .REL file produced by DAS or DRACO, a .LIB file produced
- by DLIB, or a .PLD file produced by LINK. If no extension is given on a
- file name, then .REL is assumed; thus libraries must have the .LIB given
- explicitly. Flags can be interspersed with the file names. Each flag
- starts with a minus sign (UNIX convention) and consists of several flag
- letters, and perhaps one flag value. The recognized flags and their
- meanings are:
-
- m - produce a map of the load address of the various procedures and
- the addresses of all local and global variable groups. This map
- is sent to a file whose base name is the same as that of the
- resulting .COM file (see later) and whose extension is ".MAP".
- The symbols will be sorted alphabetically.
- a - produce a map file as above, but sort the symbols by their load
- addresses. This is useful when debugging.
- i - suppress the normal Draco initialization code. This option should
- only be used by assembler programmers. Note: LINK allocates
- data areas before code areas if -d is not specified. Thus, if
- neither -s nor -d are specified, the first .REL file must not
- have any file variables, and the first procedure in it must not
- have any local variables. If either set of variables exist,
- they will be first in the .COM file, and will be at the entry
- point to the program. This option also prevents the standard
- libraries TRRUN.LIB and TRCPM.LIB from being automatically
- searched. (The 'TR' is from a previous name of the language.)
- q - produce a program which will return to CP/M quickly. This is done
- by using an alternate initialization section which leaves CP/M's
- CCP untouched in memory, and which will simply return to CP/M
- without doing a warm start. This flag should not be given when
- linking programs which use CP/M's location 6 (pointer to warm
- boot routine) to determine the top of available memory. The
- pointer so returned does not take the CCP into account, and so
- the resulting program will probably not run. A very smart program
- could determine if it had been compiled with '-q' and subtract
- the size of the CCP from the top-of-memory pointer. The standard
- storage allocator does this by referencing the special symbols
- '_DataEnd' and '_CodeEnd' which point to the ends of the data and
- code portions of the final object file.
- o - specifies the name for the resulting .COM file. The name must
- immediately follow the 'o', with no intervening spaces or other
- flags. If no explicit name is given, then the name is derived
- from the name of the first .REL file in the parameter list.
- c - specifies the first address to be used for program (code). The
- value must follow immediately and is in hexadecimal. This option
- is normally only useful for people who wish to produce .COM files
- suitable for PROM burning. The default program start address is
- 0x100, which is the standard CP/M entry point.
- d - specifies the first address to be used for data (variables). The
- value must follow immediately and is in hexadecimal. This option
- should be used for programs which have large data areas, or for
- programs which are to be burned into PROMS. If no value is given,
- then data areas are intermixed with code areas.
- s - the linker is forced to take two passes for it's operation. The
- first pass determines the total code size of the resulting
- program, and the second pass does the actual linking, using a
- data start address (as with the '-d' flag) just past the address
- of the last byte of code. The 's' stands for small - the
- resulting .COM file will be as small as possible (it will contain
- only the code of the program) and the total space occupied will
- be a minimum, since no gaps will exist. In linking with no flags
- given, code and data will be intermixed, thus the data space will
- occupy disk space in the .COM file
- v - verbose. The linker prints out the names of the .REL and .LIB
- files it is processing. This gives you something to watch when
- linking a large program on slow disks.
- p - requests that a .PLD file be produced representing the entire
- program. This file is a machine readable map, in address order,
- of all symbols loaded. The format is a 4 character hexadecimal
- address, a space, the symbol name, and CR/LF for each symbol, and
- an extra CR/LF at the end of the file. When such a PLD file is
- given to LINK as input, the named symbols are assumed to pre-
- exist at the given addresses. This could be because they are in
- ROM or because they are in a program which has dynamically loaded
- the program that is referencing them.
-
- The Draco linker provides partial support for a type of module, i.e. for
- a fully independent package of routines with its own local variables,
- it's own initialization and termination code, and a set of procedures
- which are exported to its 'clients' or users. Most of this is provided
- by the normal features of the Draco language. A module is written as a
- single Draco source file, with its own local variables. Clients import
- procedures from it in the normal way, using 'extern' declarations.
-
- The additional support provided by the linker works as follows. If a
- procedure in a library is called, and the file which that procedure came
- from contains another procedure called "_initialize", then the linker
- will load "_initialize" and will generate code at the beginning of the
- program to call it. Similarly, a routine called "_terminate" will be
- automatically loaded and called at the end of the program (directly
- returning to the system via "exit" or "SystemReset" will bypass the
- termination call). There can be multiple occurrences of these special
- symbols, so long as there is only one per source file.
-
- In the interests of portability, all versions of Draco will have
- available a routine called "exit", which has a single integer parameter.
- This routine will return directly to the host operating system. The
- parameter passed is an error indicator, and should be 0 to indicate
- successful execution. CP/M cannot make use of this returned error code,
- but other systems can, so this facility is provided to simplify the
- transportation of Draco programs among different systems.
-
- LINK's operation can involve one or two passes, each pass consisting of
- a read of the .REL files and one or more reads of the .LIB files. The
- second pass is necessary only if the '-s' flag is given or if the program
- is too large to fit into the available memory. When operating in two-pass
- mode, LINK can produce a final .COM file larger than the amount of memory
- on the machine on which LINK is running. LINK will automatically switch
- to two-pass mode whenever the available memory runs out.
-
- Libraries produced by DLIB have a directory at the front which indicates
- where in the library all of the individual procedures can be found. LINK
- loads only those procedures which have been referenced, and it will scan
- the libraries several times if needed to resolve all references. If a
- procedure is loaded which references file-level global variables, then
- space for those variables is allocated, and all procedures from that
- original source file will reference them. When running under CP/M 1.0,
- random access is not supported, so the entire library is actually read
- in, but under later versions of CP/M, random access is used to reduce the
- amount of actual disk I/O done.
-
- If a given symbol is present in more than one of the libraries being
- scanned, then it is loaded from the first library encountered after the
- first reference to the symbol. All .REL files are scanned first, in the
- order they appear on the LINK command, then all .LIB files are scanned,
- in the order they appear on the LINK command. The entire set of .LIB
- files is rescanned if further unresolved references occur. If the first
- reference to a symbol comes from a library member, then the search for
- that symbol starts with the remainder of that library and continues on
- with later ones.
-
- Because of this strictly forward searching, the order of placement of
- symbols in libraries can be important. If a procedure in a given source
- file which is to be part of a library references another procedure in
- that source file, then the referenced procedure should be forward
- declared and appear LATER in the source file than its referencer. This
- approach minimizes the number of library scans needed to resolve all
- references. All of the standard libraries are set up this way.
-
- Unless the '-i' flag is given, LINK will automatically add the libraries
- 'TRRUN.LIB' and 'TRCPM.LIB' to the end of the set of libraries searched.
- TRRUN.LIB contains the run-time system, including support needed by the
- compiler, the I/O library and the utility library described later.
- TRCPM.LIB is an interface library which provides interface routines to
- all of the CP/M entry points. Entry point names are exactly as given in
- the CP/M manuals - e.g. SetDMAAddress, ReadSequential, etc. Most simple
- programs will not need any other libraries, and thus can be linked by
- simply giving all of the .REL files. A program with only one source file
- xxx.drc can thus be compiled and linked by:
-
- draco xxx
- link xxx
-
- A program with source files p1.drc, p2.drc and p3.drc, which references
- the CRT library can be fully compiled and linked by:
-
- draco p?
- link p1 p2 p3 crt.lib
-
- For a final version, the '-s' flag should probably be given.
-
- IV. Using the disassembler (not available in Amiga version)
-
- V. Using the librarian (not available in Amiga version)
-
- VI. Using the cross-referencer (not available in Amiga version)
-
- VII. Draco source files
-
- Source files for the Draco compiler are either normal source files,
- usually with an extension of .d, or are declaration include files,
- usually with an extension of .g. Declaration include files can contain
- only declarations (constant, type, external, variable), and the symbols
- declared in them are called 'global', and are available to all procedures
- in all source files which include that particular include file.
-
- Normal source files contain, in the order given:
-
- - 0 or more include file references. These must start in the first
- column of the first line, and consist of a backslash (\) or
- number sign (#) followed by the name of the include file being
- referenced. Several such references may occur, one per line,
- with no intervening spaces or comments. When a program consists
- of more than one source file, each of the source files will
- usually have the same set of include file references. The link
- editor requires that the 'global' variables be consistent among
- all .r files being linked. .r files being put into libraries
- may not have any 'global' variables.
-
- - declarations. These declarations are called 'file' declarations,
- and are available to all procedures in that particular source
- file. Short Draco programs, consisting of only one source file,
- will not have any 'global' declarations - the 'file' declarations
- will play that role. In larger programs, 'file' declarations are
- still useful, in that procedures associated with a given portion
- of a program can be assembled in one file, and any declarations
- private to those procedures can be 'file' declarations in that
- file, and thus will not be accessible to any other procedures.
- Also, 'file' declarations are common in files which are to be
- used as part of a library, since any 'file' variables will be
- allocated (assigned real memory addresses) by the link-editor
- whenever a routine from that file is referenced.
-
- - procedures. Each procedure can have it's own set of local
- declarations (it's parameters are assumed to be part of that
- set). These declarations are accessible only within that
- procedure, and the values of any variables are not preserved
- between activations of the procedure. Procedures defined in a
- source file are considered to be declared for the remainder of
- that source file. If circular referencing of procedures is
- required, then a 'file' level extern declaration of the procedure
- can be given before the procedure is used, and, so long as that
- declaration is consistent with the final definition, the compiler
- will not complain. Because of the compiler's requirement that
- all symbols be declared before they are used, the simplest
- arrangement of source files is that which puts the lowest-level
- routines first, and the highest-level routines (those which call
- the lower-level ones) last. Thus, short programs consisting of
- only one source file will normally put procedure 'main' last.
- (All Draco programs must have a procedure 'main', since the
- initialization code starts program execution by calling 'main'.)
-
- In keeping with a highly readable convention used by some UNIX
- programmers, it is suggested that all 'global' and 'file'
- symbols begin with a capital letter, and all local symbols
- begin with a small letter (other than constants). In keeping
- with this convention the names of all routines in all libraries
- supplied with Draco begin with a capital letter. Even though
- procedure definitions are technically 'file' declarations, such
- procedures, unless they are to be part of an externally
- available library, should have names beginning with small
- letters.
-
- Draco takes a different approach to scope than many standard
- algorithmic languages. Languages such as Pascal, Algol68, etc.
- allow the declaration of an identifier, at an inner scope level,
- which is already declared at an outer scope level. For the
- duration of that scope, the new, inner, declaration masks the
- outer declaration so that the outer meaning of the identifier
- is not available. In Draco, it is illegal to attempt to declare
- an identifier which already exists, regardless of the scope
- levels of the declarations (Draco only has 3 levels anyway -
- global, file and local). Several procedures can declare the same
- identifier locally (names such as 'i', 'p', 'n', etc. are quite
- common), but they cannot declare a name which exists at either
- the global or file level. Similarly, at the file level, a name
- cannot be used which is already in use at the global level. This
- approach is used so as to eliminate the problems arising from
- accidentally masking an outer meaning in a situation which uses
- that outer meaning, but also declares a similar, inner meaning.
- This situation can result in bugs which are very difficult to
- detect. If the naming convention suggested above is used, little
- inconvenience will occur. Also, since Draco imposes no limit on
- the length of identifiers, there should be no problem in choosing
- meaningful ones.
-
- VIII. Declarations
-
- Declarations in Draco can be of constants, types, variables or external
- procedures.
-
- Constant declarations consist of the type, followed by the identifiers,
- along with an '=' and their value. Constants can be numeric (signed or
- unsigned), single character, or 'chars' values. In keeping with a highly
- readable convention used on UNIX systems, it is suggested that the names
- of constants be fully capitalized. Example constant declarations:
-
- uint MAX_LENGTH = 1000,
- ENTRY_COUNT = 10;
- char BEL = '\(7)', BS = '\b', LETTER_F = 'f';
- *char AUTHOR = "Sam Spade";
- unsigned 255 LIMIT = 255 - 1;
-
- The values used for constants can be any expression whose value can be
- determined at compile time by the compiler. This can include conditional
- expressions, so long as the conditions are known at compile time.
-
- Type declarations consist of the word 'type', followed by the names of
- the new types, each followed by '=' and the type's definition. The
- various kinds of types in Draco are as follows:
-
- - signed numeric types. These are specified by the word 'signed',
- followed by a constant expression giving the upper bound on the
- positive values allowed. The negative values have a similar
- limit (one less for 2's complement machines). The maximum value
- of the limit will vary from machine to machine and possibly from
- version to version of the compiler. All versions will allow at
- least 'signed 32767'. In programs where execution time must be
- minimized, the programmer should use numeric types with the
- smallest possible range. This allows the compiler to generate
- more efficient code for CPU's which do some types of arithmetic
- better than others.
-
- - unsigned numeric types. These are specified by the word 'unsigned',
- followed by a constant expression giving the upper bound on the
- values allowed (the lower bound is 0). Signed and unsigned values
- can be mixed in arithmetic operations. The two kinds of values
- can be compared for equality, but not for magnitude (they use the
- same bit pattern for different values).
-
- - enumerated types. These are specified by the word 'enum', followed
- by an open brace ({), followed by a list of the named values of
- this type, followed by a close brace (}). The values named in the
- list are the only allowed values of this type. They can be
- compared (all comparisons are meaningful), subtracted (the result
- is an unsigned numeric), and can have a numeric added to or
- subtracted from them (the result is another value of the same
- enumerated type). This kind of type is usually used for flag
- values, where the flag can take on a limited set of values. A
- sample enumerated type:
-
- enum {c_red, c_yellow, c_blue, c_black, c_white}
-
- - pointer types. These are specified by an '*' preceding the type
- which is to be pointed to. Thus, '*int' is a type specification
- meaning 'pointer to integer'. In the language's single exception
- to the requirement that all identifiers be declared before they
- can be used, the pointed to type can be an undeclared symbol,
- which is assumed to be an as-yet undeclared type, which must be
- declared before the end of the current set of declarations. This
- rule relaxation is used to allow the construction of circularly
- referencing structures. Pointer values can be compared,
- subtracted (as long as the values are of the same pointer type),
- and can have a numeric value added to or subtracted from them.
- Unlike similar operations in the 'C' language, the value being
- added or subtracted is not multiplied by the size of the
- pointed-to type. Pointer values are usually generated via the '&'
- address-of operator, or by using a 'chars' constant, which is of
- type *char. The predefined value 'nil' is compatible with all
- pointer types.
-
- - array types. These are specified by a left square bracket ([),
- followed by a list of constant expressions giving the size of the
- array in that dimension, followed by a right square bracket (]),
- followed by the type of the array elements. There is no limit on
- the number of dimensions of an array, but the user must keep in
- mind the amount of memory occupied by the array as compared to
- the amount of memory available on the computer system. Arrays
- are stored in row-major order, i.e. when scanning along an array
- in memory, the last index varies most frequently. Array values
- can be assigned. Sample array types:
-
- [MAX_NAME_LENGTH + 1] char
- [M, N, P] int
- [BLOCK_COUNT] [BLOCK_LEN] unsigned 32
-
- - structure types. These are specified by the word 'struct', followed
- by a left brace bracket ({), followed by the types and names of
- the fields of the structure, followed by a right brace bracket
- (}). Unlike some other languages, Draco does not allow field
- names to be re-used; all must be unique. The easiest way to do
- this is to follow yet another highly readable UNIX convention
- which names all fields of a structure as a short abbreviation of
- the structure name (1 - 3 letters), followed by an underscore (_)
- and the mnemonic name of the field. Like array types, structure
- types can be assigned. Some structure declarations:
-
- type
- ProcessState_t = struct {
- uint st_programCounter, st_stackPointer;
- [8] uint st_registers;
- byte st_statusRegister;
- },
-
- Process_t = struct {
- int pr_priority;
- *Process_t pr_parent, pr_children, pr_nextSibling;
- ProcessState_t pr_state;
- *ProcessQueue_t pr_waitQueue;
- },
-
- ProcessQueue_t = struct {
- *ProcessQueue_t pq_next;
- *Process_t pq_this;
- };
-
- - union types. Union types are declared exactly like structure types
- except that the word 'union' replaces the word 'struct'. Union
- types are similar to unions in 'C', in that they specify a type
- which is a set of types. The space allocated for a value of a
- union type is the maximum of the spaces needed for the various
- member types in the union. The programmer informs the compiler
- which of the member types is currently active by selecting the
- member type, exactly as a field of a structure is selected, when
- the union value is referenced or assigned to as other than the
- union type. Union types are useful when constructing networks of
- nodes, and the nodes are of differing natures, but all are
- pointed to by other nodes. The alternative of having separate
- pointers for each possible node type is very wasteful of memory.
- Sample union type: (this one from a railroad simulation)
-
- type
- Track_t = union {
- int tr_straight; /* straight track option */
- struct { /* turnout option */
- int trn_length;
- bool trn_open;
- bool trn_isRight;
- } tr_turnout;
- };
-
- - procedure types. These types are declared similarly to actual
- procedure headers, except that no procedure name occurs, no
- machine specific options (e.g. 'nonrec') can occur, and the names
- of the parameters are required, but are irrelevant. Procedure
- values can be compared for equality, assigned, and called. Sample
- procedure types:
-
- proc (int a, b)int
- proc (proc (char c)void putChar; *char charsPtr)void
- proc ([12]**int x, y)[12]**int
-
- - operator types. This kind of type is Draco's (somewhat limited) way
- of being an extensible language. Syntactically an operator type
- consists of an open parenthesis, a string constant, a comma, a
- base type, a comma, a numeric constant and a close parenthesis.
- The string constant is a prefix which is used to build the names
- of the procedures that the compiler will generate calls to in
- order to do operations on values of this new type. The base type
- is the type that is the underlying representation of this new
- type, and the numeric constant is a set of 16 bits, indicating
- which operations are enabled for this type. Operator types will
- be explained in a later section.
-
- Types in Draco can be combined in arbitrary ways. The only
- limitations imposed by the compiler are those inherent in the sizes
- of the type table and the type information table. The question of
- type equivalence is answered in Draco in the following way: two
- types are equivalent if they are equivalently constructed from
- equivalent component types. The determination of type equivalence is
- done while the compiler is parsing the type specification. Thus, in
- the following:
-
- [12] int a;
- [10 + 4 / 2] int b;
-
- 'a' and 'b' will have the same type. The type of 'b' is equivalent
- to the type of 'a', and so will BE the type of 'a'. If a type is
- given a name via a type declaration, however, then that type is
- unique and is not equivalent to any other named type. Thus, if we
- declare:
-
- type T1 = [10] int,
- T2 = [10] int;
- T1 a;
- T2 b;
- [10] int c;
-
- Types T1 and T2 are not equivalent, and 'a' and 'b' cannot be
- assigned to one-another. Both can be assigned to or from 'c',
- however, else there would be no way to generate values of named
- types. This scheme is an attempted compromise between the need for
- usability of named types, and the desire to have the compiler
- protect us from mistakes when two named types just happen to have
- equivalent definitions. Signed or unsigned numeric types which are
- named are always compatible with other named or unnamed numeric
- types, whether equivalent or not.
-
- The following types are supplied predefined:
-
- int - signed numeric using the standard fully supported word
- size on the host processor
- short - smaller sized signed value (often 8 bit)
- long - longer sized signed value (often 32 bit)
- uint - unsigned numeric, same size as int
- ushort - unsigned numeric, same size as short
- ulong - unsigned numeric, same size as long
- byte - unsigned numeric, one byte long (8 bits)
- char - enumeration type of all 256 character values
- bool - enumeration type consisting of 'false' and 'true'
-
- Most programs can safely use types 'int' and 'uint', since they will
- always be at least 16 bits long. The careful programmer will usually
- use his/her own signed and unsigned types, however, so that the
- reader is always aware of the range of possible values, and so that
- compilers can optimally decide the implemented size of variables
- (which may vary from processor to processor).
-
- Variable declarations consist of the type (either named or explicit)
- followed by a comma separated list of identifiers. This format is
- similar to that used for constants, but combining the two is not
- advised, since doing so can be confusing.
-
- External procedure declarations consist of the word 'extern' followed
- by a list of procedure headers, complete with procedure name, parameter
- types and names, and result type.
-
- IX. Procedures
-
- Each Draco procedure definition begins with the word 'proc', followed
- by any special machine dependent modifiers, followed by the name of the
- procedure, followed by a procedure header, followed by a colon, followed
- by the body of the procedure and a final terminating word, 'corp'. A
- procedure header consists of '(', optional parameter declarations, ')',
- and the result type (or 'void' for procedures which don't return a
- result). Note that the parentheses are required, even if no parameters
- are declared. Parameter declarations are just like variable declarations.
-
- Unlike Pascal and C, Draco provides a way for arrays of differing sizes
- to be passed to a common procedure. An array parameter can use an
- asterisk (*) for the size of one or more of its dimensions, instead of
- the normal constant expression. When such a procedure is called, the
- compiler will automatically pass the true size of the dimensions of the
- passed array along with the array. These true sizes can be determined
- inside the procedure via the 'dim' construct. Note that this method can
- only be used for parameter arrays, and can only be used for top level
- arrays (e.g. if the parameter is an array of arrays, then only the top-
- level array can have '*' sizes).
-
- If the procedure is to return a result, the type placed between the
- closing ')' of it's header and the following ':' is the type expected
- by the compiler. Conversions among various numeric types are allowed
- here as elsewhere. The result is returned by placing it at the end of
- the procedure's body, just before the closing 'corp'. There must not be
- a semicolon after the result, since the compiler uses the semicolon as
- a signal that the previous unit should have been a statement. As a sample
- procedure, here is the old standard, "Towers of Hanoi":
-
- proc hanoi(int n; *char from, to, using)void:
-
- if n > 0 then
- hanoi(n - 1, from, using, to);
- writeln("Move disk ", n, " from peg ", from, " to peg ", to);
- hanoi(n - 1, using, to, from);
- fi;
- corp;
-
- A standard procedure with a result:
-
- proc minimum([*] int a)int:
- int i, min;
-
- min := a[0];
- for i from 1 upto dim(a, 1) - 1 do
- if a[i] < min then
- min := a[i];
- fi;
- od;
- min
- corp;
-
- X. Statements in Draco
-
- Draco is a fairly standard programming language, along the lines of
- Pascal, C, and Algol. Where several statements are allowed, they are
- separated by semicolons (the semicolon is a separator, not a terminator).
- The standard statement forms in Draco are:
-
- - assignment statement. This is the usual, consisting of the destination,
- a ':=', and the source expression.
-
- - procedure call statement. This consists of the procedure's name (or an
- expression yielding a procedure), followed by the procedure's
- parameters, enclosed in parentheses. The parentheses must be present,
- even if the procedure has no parameters (this makes it very clear
- when something is being called - useful for procedures such as random
- number generators which have no parameters but return a result).
- The parameters passed must be compatible with those specified in
- the defining procedure header, in terms of both type and number.
-
- - if statements. If statements in Draco are syntactically identical to
- if statements in Algol68. The simplest form consists of the word
- 'if', followed by an expression of type 'bool', followed by the word
- 'then', followed by a sequence of statements to be executed when the
- bool yields 'true', followed by the word 'fi'. An 'else' clause,
- which is executed when the bool yields 'false', can be placed between
- the 'true' statements and the 'fi'. An 'else' clause consists of the
- word 'else' and a sequence of statements. As in Algol68, alternate
- conditions, consisting of the word 'elif', a bool expression, the
- word 'then', and a statement sequence, can be placed between the
- first statement sequence and the 'else' (or 'fi' if there is no
- 'else'). In that case, the conditions are evaluated one at a time,
- until one is found that yields 'true'. The corresponding statement
- sequence is then executed. Only if no condition yields 'true' will
- the 'else' statements be executed. When a condition has yielded
- 'true', no more conditions will be evaluated. As an example,
-
- if a then
- b
- elif c then
- d
- elif e then
- f
- else
- g
- fi
-
- is equivalent to
-
- if a then
- b
- else
- if c then
- d
- else
- if e then
- f
- else
- g
- fi
- fi
- fi
-
- The advantage of the 'elif' form is fairly obvious - it has far less
- indentation for the same logic.
-
- The standard if statement is the basis for the conditional
- compilation feature of the Draco compiler. If the condition for an
- if statement can be evaluated at compile time, then no code is
- generated for the if statement, and code for only one of the branches
- is generated. This feature is not as flexible as that provided by
- full macro preprocessors, but it has the advantage that the compiler
- always checks all branches for correct syntax and semantics, thus the
- programmer can be sure that changing the flag value controlling the
- conditional compilation will not cause the program to stop compiling.
- (With macro pre-processors, and conditional inclusion, as supported
- by most C compilers, the compiler does not even see the code which
- has been conditioned out.) By including conditional compilation
- in the compiler, rather than requiring a separate pre-processor,
- compilation times for Draco programs can be significantly less.
- One common use for conditional compilation is that of including
- debugging statements, dependent on a global debugging flag. E.g.
-
- bool DEBUG = false;
- ...
- if DEBUG then
- writeln(DebugOut; "We got to this point, key values are:");
- ...
- fi;
-
- In situations like this, the Draco compiler will generate no code at
- all for the entire if statement. If the DEBUG flag is set to 'true'
- instead, then the debugging code will appear, but there will still be
- no code to actually test DEBUG (DEBUG doesn't even exist). For more
- complex debugging, the DEBUG flag can be a number, specifying the
- level of debugging required. Another common use of conditional
- compilation is to have one source file which can produce two or more
- different versions of a program, depending on one or more flags.
-
- - while statements. The standard while statement consists of the word
- 'while', followed by a bool expression, followed by the word 'do',
- followed by a sequence of statements (the loop body), followed by
- the word 'od'. Draco allows an extension of this form, in which a
- sequence of statements can be placed between the 'while' and the
- bool expression. This extension allows the same 'while' construct to
- serve as beginning, middle and end exit loops. E.g.
-
- while
- write("Enter command: ");
- command := getCommand();
- command ~= HALT
- do
- processCommand(command);
- od;
-
- - for statements. The for statement is the standard way in Draco of
- iterating over a fixed sequence of values. It is similar to the
- for statements in most programming languages. It consists of the
- word 'for', followed by the name of the variable to use as an index
- variable, followed by the word 'from' and an expression giving the
- start of the range, optionally followed by the word 'by' and an
- expression giving the step amount, followed by either the word
- 'upto' or the word 'downto' and an expression giving the end of the
- range, followed by the word 'do', followed by a statement sequence,
- and finally, the word 'od'.
-
- In Draco, the direction of the loop (increasing or decreasing) is
- set at compile time, by the selection of 'upto' or 'downto'. If the
- 'by' part is omitted, then either +1 or -1, whichever is appropriate,
- is used. The for loop terminates when the index variable attains the
- last possible value between the two limits (inclusive). Thus we have
- the loop
-
- for i from 1 by 5 upto 13 do
- ...
- od;
-
- stepping 'i' through the values 1, 6, and 11. The index variable can
- be numeric (signed or unsigned), an enumeration value, or a pointer
- value. The limits must be compatible with the index variable, and
- the 'by' value, if present, must be numeric. Thus we can have a loop
- which steps through every second letter of the alphabet:
-
- for ch from 'a' by 2 upto 'z' do
- ...
- od;
-
- Most programs which do a lot of computation have a lot of for loops
- in them (fancy compilers are an exception). Thus, it is beneficial
- if the compiler can generate fairly fast code for for loops. The
- Draco compiler does a number of fancy tricks with for loops. Because
- of this, it is important that none of the assumptions made by the
- compiler are broken. Thus, the program should never attempt to assign
- a value to the for index variable within the for loop. (A later
- version of the compiler may be able to flag such usages as errors.)
-
- - case statements. Case statements in Draco are similar to those in
- many languages; they are of the variety where the individual
- alternatives being selected among are an explicit part of the case
- statement. A default alternative is also available. The syntax is as
- follows: the word 'case'; followed by the expression being used as a
- selector; followed by several alternatives, each consisting of 1 or
- more alternative index values given as the word 'incase', a constant
- expression, and a colon. Each alternative then has a body, which is
- a sequence of statements to be executed when that alternative is
- selected. The entire case statement is terminated by the word 'esac'.
- The default case, if present, can occur anywhere among the
- alternatives, and consists of the word 'default' and a colon,
- followed by the statements of the default case. The alternative index
- values can be a pair of values separated by '..', in which case all
- values between the two (inclusive) are used. The index expression
- can be of any numeric or enumerated type. The alternative index
- values must be compatible with the index expression. A sample case
- statement:
-
- case ch
- incase 'a':
- incase 'A':
- writeln("It was an A.");
- incase 'b' .. 'd':
- incase 'B' .. 'D':
- x := y;
- y := z;
- default:
- flag := true;
- esac;
-
- The various Draco compilers will use different code sequences to
- handle case statements. At least two forms will probably be
- supported - one form which uses the index expression as a direct
- index in a (perhaps sparse) table of code addresses, and one form
- which uses a binary search through a sorted table of the alternative
- index values. The appropriate form will be selected by the compiler,
- based on the range and number of alternative index values.
-
- - I/O statements are discussed in a separate section later.
-
- - the 'free' construct, which can be applied to any value of a pointer
- type, returns the storage pointed to to the storage allocator. That
- storage must have been previously allocated by using 'new'. 'free' is
- a statement since it returns no result.
-
- - the 'pretend' type-cheating construct can be used as a statement
- if the type being forced is 'void'. This form is used to throw
- away a value, usually from a procedure, which is not needed.
-
- - the 'error' construct, which accepts a parenthesized string constant
- as its argument, simply uses that string as the text of an error
- message to print AT COMPILE TIME. This construct is useful for
- putting consistency checks into code. For example, if a program has
- been written with the assumption that "IDENTIFIER"s fit in one byte,
- then the following check, done somewhere in the program, would be
- appropriate:
-
- if range(IDENTIFIER) > 255 then
- error("IDENTIFIER range must be <= 255");
- fi;
-
- Then, when someone comes along later and changes the definition of
- the IDENTIFIER numeric type, if the type is made bigger than
- 'unsigned 255', a compile time error message will be produced when
- compiling the file containing the above check. Near the check would
- be a good place to put comments saying why the limitation exists.
-
- - some machine dependent constructs are formulated as statements.
-
- XI. Expressions in Draco
-
- Most small processors are more efficient at doing 8 bit operations then
- they are at doing 16 or 32 bit operations. Because of this, the Draco
- compiler will normally attempt to use the smallest possible size for
- a given numeric type. One result of this is that the operands to an
- operator may not be of the same size. In such cases, the compiler will
- expand the smaller value (doing sign-extension on signed values) and do
- the operation in the larger size. The one exception to this rule involves
- the shift operators - the operation is always done in the size of the
- value being shifted (the left operand). Also, the type of numeric
- constants will be overridden by any non-constant operand, so long as
- their value will fit in that size. If both operands are constants, the
- larger type will be used as the result type.
-
- Similarly, the result of an operation can depend on whether that
- operation is done using signed or unsigned arithmetic. In cases where
- one operand to an arithmetic operator is signed and the other is
- unsigned, the operation is done as a signed operation, and the result
- is considered to be signed. This only affects the result for the
- division and remainder operations. Note that this rule is opposite to
- that of C, which would yield an unsigned result. This can be thought of
- as follows: in C, the normal numeric type is signed, while in Draco, the
- normal numeric type is unsigned. In either language, any ocurrence of
- a non-normal value forces non-normal operation and result. This choice
- in Draco is likely to be contentious - the reasoning is that most numbers
- used in most programs are unsigned. I personally find C's habit of
- reserving '-1' as an error flag to be quite disgusting. As with size, the
- signedness of a constant is ignored unless both operands are constants.
-
- Draco has a fairly large set of operators. These include the familiar
- arithmetic operators of addition, subtraction, etc., along with a full
- set of bit operators (and, xor, etc.), and a few special operators. The
- operators are at various levels of priority, meaning that a higher
- priority operator will be evaluated before a lower priority one, unless
- there are parentheses explicitly governing the order of evaluation. This
- reflects the usual view that multiplication comes before addition, etc.
- Draco also has the usual constructs for calling functions, indexing
- arrays, selecting fields of structures, etc. These are included in the
- following table, to indicate their position in the precedence scheme.
- The operators and constructs, in order of decreasing precedence are:
-
- ----------
-
- * - postfix dereferencing operator. This operator is postfix in
- Draco, rather than prefix as in C, so that there is never any
- ambiguity about the order in which the various constructs are to
- be applied (consider *a[i] in C, which is either a[i]* or a*[i]
- in Draco (I can never remember how C evaluates these)).
-
- [] - postfix array indexing. Array indexing is 0-origin in Draco,
- i.e. the first element of an array has index 0. The compiler will
- attempt to be efficient with indexing, but most microprocessors
- have little direct support for array indexing, so if the
- application is critical in terms of CPU time or program size, it
- may be necessary to use pointer arithmetic instead of array
- indexing. Values used for indexing can be of any numeric or
- enumeration type.
-
- . - field selection. Field selection in Draco is fairly efficient,
- usually requiring little, if any, extra machine code. The same
- notation (structure '.' field-name) is used to select the current
- form from a union type value.
-
- () - function calling. Function calls are identical to procedure
- calls, except that they return a value. The function to be called
- can be the result of an expression. (E.g. many versions of UNIX
- contain an array of structures of procedures, which is used to
- direct I/O calls based on the device being accessed (the array
- index), and the particular function requested.)
-
- ----------
-
- & - prefix address-of operator. This operator takes the address of
- it's operand. The type of the value generated is 'pointer-to-X',
- where 'X' is the type of the operand. This operator cannot be
- applied to expressions which do not have an inherent address,
- e.g. '&(a + 1)' will not work, but '&a[i].name[j]' will. In
- general, these constructs are arranged in Draco in such a way
- that if you need brackets to express it, it's probably illegal.
-
- ----------
-
- ~ - prefix bitwise complement operator. This and the other bit
- operators can only be applied to numeric values.
-
- ----------
-
- & - bitwise and operator.
-
- >< - bitwise exclusive-or operator.
-
- << - logical left shift operator. In both shift operators, the left
- operand must be an unsigned numeric, while the right operand can
- be any numeric. The operation and result are done using the size
- of the left operand.
-
- >> - logical right shift operator.
-
- ----------
-
- | - bitwise inclusive-or operator.
-
- ----------
-
- | - prefix numeric absolute value operator. This, and other
- arithmetic operators, can only be applied to numeric values.
- (Exceptions for binary + and - are listed there.) Both the
- absolute value and negation unary operators always yield a
- signed type, regardless of the signedness of their operand.
-
- - - prefix numeric negation operator.
-
- + - prefix numeric do-nothing operator. This operator is included so
- that forms like '+0' can be allowed.
-
- ----------
-
- * - multiplication operator.
-
- / - division operator.
-
- % - remainder operator.
-
- ----------
-
- + - addition operator. In addition to numeric operands, one
- operand can be of an enumeration or pointer type. The resulting
- value will be of the same type, incremented by the other,
- numeric, operand. Unlike C, which pre-multiplies the numeric
- value by the size of the pointed-to type, Draco doesn't modify
- the numeric value at all.
-
- - - subtraction operator. Similar to incrementing a pointer or
- enumeration value, these values can be decremented by using them
- as the left-hand operand in subtraction. Two enumeration or
- pointer values of the same type can also be subtracted, yielding
- an unsigned numeric value.
-
- ----------
-
- >, <, >=, <=, =, ~= - comparison operators. Most values in Draco can
- be compared. For some comparisons, only the equality comparisons
- (= and ~=) are meaningful. For example, comparing a signed
- numeric with an unsigned numeric can yield two different results,
- depending on whether a signed or unsigned comparison is used.
- Because of this, the compiler will not allow a signed value to be
- compared with an unsigned value with other than = or ~=. The
- values being compared must be of compatible types. Structure and
- array types cannot be compared, since these types might contain
- internal gaps due to alignment requirements, and the contents of
- these gaps is undefined.
-
- ----------
-
- Along with the capability of conditional compilation provided by the
- if statement, the Draco compiler attempts to evaluate expressions at
- compile-time, so that they need not be evaluated at run-time. If both
- operands to an operator can be evaluated at compile time, then the
- operation is done at compile time, producing a constant. The evaluation
- is done using the highest precision supported by the compiler. The
- nature of the evaluation will be the same as if it was done at run-time,
- i.e. mixing signed and unsigned values will yield a signed result, etc.
- This facility is used in all places where constants appear, e.g. in
- array declarations, signed/unsigned declarations, case statement
- alternative index values, etc.
-
- There are several forms of expressions in Draco which do not involve
- actual operators. These include the boolean 'and', 'or' and 'not'
- operations. These are not classed with the normal operators, since they
- are actually language constructs instead. Both 'and' and 'or' will not
- evaluate their right-hand operand if the value of the left-hand operand
- is sufficient to determine the result. This is known as 'short-circuit-
- evaluation', or the McCarthy form of the 'and' and 'or' operators. There
- is no exclusive-or operation for bools, but the same result can be
- achieved using the ~= operator, which can be applied to bool values.
-
- Draco also allows conditional expressions - the if expression and the
- case expression. These forms are identical to their statement forms,
- except that the various statement sequences used as their alternatives
- must end with an expression, which is the result for that alternative.
- Also, if expressions must have an else part, since they must yield a
- result in all cases. The same feature which allows if statements to be
- used for conditional compilation allows the use of if expressions in
- constant expressions, so long as the conditions and all alternative
- values are themselves constant expressions.
-
- The unwise programmer can 'type cheat' (convince the compiler to allow
- him to do things which he would not normally be allowed to do) by
- misusing union types. In the hope of preventing this, Draco has an
- explicit construct for type cheating. It uses the word 'pretend'. The
- form 'pretend(expr, type)' instructs the compiler to consider 'expr' to
- be of type 'type', regardless of what it thinks the type must be. As a
- special case, 'type' can be 'void', in which case the value of 'expr' is
- simply discarded (this action, called voiding, is done automatically by
- most C compilers, often resulting in programming errors, since it is
- easy to do it unintentionally). The pretend construct should be used
- with great care, since some values cannot possibly be of some types. For
- example, what is supposed to happen in something like 'pretend(x + y,
- [10] int)'? A more innocuous form of the pretend construct uses the word
- 'make' instead of the word 'pretend'. This form requests that the
- compiler convert the given expression to the given type. This form will
- only allow those conversions which make sense. 'make' is normally used
- to expand a short value to a longer form, to force an operation to be
- in a longer form (e.g. to force 16 bit arithmetic on 8 bit values).
-
- The form 'dim(arrayname, number)' will be replaced by the size of the
- named array in the given dimension (the first dimension is dimension
- number 1). If the array is a parameter array and the selected dimension
- was declared as '*', then the value will be obtained at run-time from
- a hidden parameter passed along with the normal parameters, otherwise,
- the value is a compile-time constant and can be used in constant
- expressions. Note that the value is the size of the array in that
- dimension, which is one greater than the maximum legal index in that
- dimension.
-
- The form 'sizeof(type)' yields a numeric constant which is the number of
- bytes needed to store an object of the given type. The type can be the
- name of a declared type, or can be a more complex type description.
- Proper use of this construct is needed to allow some programs to be
- portable among machines which have, say, different sized integers. Most
- programmers will not have to use it, however.
-
- The construct 'new(type)' creates a call to the standard storage
- allocator to allocate a new object of type 'type'. It can be thought of
- as equivalent to 'pretend(Malloc(sizeof(type)), *type). Note that the
- value returned is a pointer to the newly allocated storage, and thus its
- type is *type.
-
- The form 'range(type)' can be applied to signed or unsigned numeric types
- to return the upper limit of that type (the value given when the type was
- declared); or to an enumeration type to return the number of values in
- that type. Thus 'range(bool)' is equivalent to '2', and 'range(int)'
- returns the maximum signed numeric value allowed with the normal integer
- values supported by that version of the compiler. Note that 'range(byte)'
- is not legal since 'byte' is not considered to be a normal numeric type,
- since it is forced to be exactly 1 byte long, regardless of whether that
- is efficient for the target machine.
-
- XII. Basic components of Draco programs
-
- Identifiers in Draco can be any length. This applies to variables,
- constants, types and procedure names. The link editor maintains the lack
- of a limit - the full name of an external procedure is used when
- searching for it in other files and libraries. Draco treats upper and
- lower case letters as distinct, thus the identifiers 'A' and 'a' are not
- the same. The programmer can use any convention he wishes with regard to
- capitalization, but the conventions mentioned previously are highly
- recommended. Note also that keywords in Draco are recognized only in the
- exact case in which they are specified. Identifiers in Draco must start
- with a letter or an underscore (_), and must consist of letters, digits
- and underscores.
-
- Comments in Draco consist of the delimiters '/*' and '*/' around the
- portion of the source to be commented out. Comments can span several
- input lines. Comments can be nested, i.e. a comment entirely within an
- outer comment is recognized and handled properly by the compiler. Thus, a
- section of code can be commented out by enclosing it in /* and */,
- regardless of whether it has any comments in it or not. Comments, along
- with 'whitespace' (blanks, tabs, carriage-returns and linefeeds) can
- occur between any two tokens, as well as in string breaks (see below).
-
- Numeric constants in Draco can be in decimal, octal, hexadecimal or
- binary. Simple numbers like '10' and '6348' are treated as decimal. Other
- bases are selected by preceeding the number by a prefix consisting of a
- '0' and a base indicator. The base indicators, which can be in upper or
- lower case, are 'x' for hexadecimal, 'o' for octal, and 'b' for binary.
- Hexadecimal digits 'a' - 'f' can be in upper or lower case. The compiler
- checks for proper digits for a given base and for numeric overflow in
- constants.
-
- Character constants in Draco come in two forms. The apostrophe (') is
- used to delimit single character constants, as in 'a', '.', etc. Quotes
- (") are used to delimit C - style strings, consisting of a sequence of
- characters terminated by a 0 character. In both forms, an escape
- convention is available. The escapes consist of a backslash followed
- either by a single character, or by a numeric expression enclosed in
- parentheses. The single character forms are:
-
- \b - the ASCII backspace character
- \t - the ASCII tab character
- \r - the ASCII carriage return character
- \n - the ASCII linefeed (newline) character
- \e - the C - style string termination character (0)
-
- Any other character used this way will be passed through unchanged. This
- can be used to put backslashes and quotation marks of the same type as
- the delimiter into the string. The convention of doubling a quote mark to
- produce a single one is also supported. The escape form consisting of a
- numeric value in parentheses must yield a constant between 0 and 255.
- This form can be used for special named characters, as in:
-
- write('\(BEEP)'); /* ring terminal's bell */
-
- The multi-character form of character constants ('chars' values using ")
- supports the 'string break'. This is a convention which allows a long
- string to be split up over several input lines, and to be indented
- nicely. If the last thing (other than spaces, comments, etc.) on an input
- line is a portion of a chars constant, and the first thing (other than
- spaces, comments, etc.) on the next input line is a similar constant,
- then the two are concatenated at compile time to yield a single, longer
- constant. This can be carried on for as many input lines as are needed to
- nicely format the constant.
-
- Many CP/M systems in use today do not have full ASCII keyboards (e.g.
- CP/M on the Apple-II or Apple-II+). In such systems, it could be
- difficult to use Draco, since the language uses characters not found on
- the keyboards. To help alleviate this problem, the compiler recognizes
- the following alternate forms for some operators and characters:
-
- standard alternate
-
- \ #
- [ (:
- ] :)
- { ($
- } $)
- ~= /=
- ~ $-
- | $/
- _ ^
-
- Draco allows the construction of array and structure constants for
- named array and structure types. The form is that of a parenthesized
- list of values. Such constants can be arbitrarily complex. If one is
- used in a constant declaration, it simply appears after the '='. If one
- is desired inside executable code, it must be preceeded by the name of
- the type in question, so that the compiler has some clue as to what is
- going on. For example:
-
- type type1 = struct {int field1, field2; char field3};
- type type2 = [2] type1;
- type2 CONST = ((1, 2, 'a'), (3, 4, 'a' - FRED / 2));
- type2 var;
- ...
- var := type2((-26, 13 + 2 / 7, 'a' + 2), (+1, -1, '\e'));
-
- The only valid value for pointer types is 'nil'. Union values must be in
- parentheses like struct values, and must be of the type of the first
- variant in the union. One special case exists - a one dimensional array
- of characters can be initialized by a string constant - the constant,
- including the terminating \e, must not be longer than the dimension of
- the array. If it is shorter, the array will be padded with \e's.
-
- XIII. Machine specific constructs
-
- The 68000 (Amiga) version of the compiler has several additional features,
- which can make certain types of programming easier.
-
- When a variable (global, file or local) is declared, it can be followed
- by an '@' and a numeric constant. This informs the compiler that that
- variable is to be located at that address. This is useful for things
- like memory-mapped displays and memory-mapped I/O. This same modifier
- can be appended to 'extern' procedures, enabling Draco programs to call
- routines at absolute addresses in ROMS.
-
- When declaring variables, the value given after the '@' can also be the
- name of some other variable. In this case, the second named variable must
- occupy at least as many bytes of storage as the first, and the two will
- then occupy the same storage. This technique can be used to "type-cheat",
- but the programmer is strongly advised to use 'pretend' instead, unless
- unreadable code is desired. This feature of the compiler is intended to
- be used to conserve storage space as used for variables.
-
- Since the Draco compiler directly emits object code, rather than
- assembler source code, it is not possible to allow in-line assembler
- language statments. Instead, Draco has the 'code' construct, which
- consists of the keyword 'code' followed by a parenthesized list of
- constant expressions and symbol references. The values of constant
- expressions are emitted directly into the code stream. The type of the
- constants controls its size as emitted. References to procedures and
- global or file variables yield 32 bit addresses of them. References to
- local variables or parameters yield 16 bit stack offsets. (Draco code on
- the 68000 references locals directly from the stack pointer; a separate
- "frame pointer" is not used.)
-