\chapter{The Occam Program} The overall structure of the program is simple. We first interpret the command line arguments and open the program files. The parser is then called to convert the program into abstract machine code, which is then interpreted if required. \label{fn_main} @d The main program @{ int main( int argc, char *argv[] ) { int arg = 1; /* index into argv */ @ @ @ initialise_symbol_table(); yyparse(); /* code placed in global program_code */ clear_symbol_table(); /* to reclaim storage */ line_number = 0; /* to disable spurious printing of line numbers in run-time errors */ if (print_flag) print_code(program_code); if (run_flag) interpret(build_ccode(io_code(), program_code, END)); return(EXIT_SUCCESS); } @} \subsection{Globals} The parser needs to know how big a channel control block is so that it can calculate the appropriate stack offsets. The size of this block is one stack slot in the interpreter, but will almost certainly be different in a compiler's runtime system. For flexibility we therefore hold the size in the global variable \verb|CHANNEL_BLOCK_SIZE|. @d Global variable dec... @{extern int CHANNEL_BLOCK_SIZE; @} @d Global variable def... @{int CHANNEL_BLOCK_SIZE = 1; /* for interpreter */ @| CHANNEL_BLOCK_SIZE @} We save the invocation name of the command in a global variable \verb|command_name| for use in error messages. @d Global variable dec... @{extern char *command_name; @} @d Global variable def... @{char *command_name = NULL; @| command_name @} The invocation name is conventionally passed in \verb|argv[0]|. @d Interpret com... @{command_name = argv[0]; @} The output from the parser, i.e. the abstract machine code for the program, is held in a global variable called \verb|program_code|. @d Global variable dec... @{extern struct instruction_sequence *program_code;@} @d Global variable def... @{code program_code = NULL; @| program_code @} \section{Command-Line Arguments} There are a variety of possible command-line arguments: \begin{description} \item[\tt -p] Prints out the pool storage statistics after execution. \item[\tt -c] Prints out the abstract machine code produced by the compiler. \item[\tt -r] Suppresses the evaluation of the abstract machine code. \item[\tt -t] Prints out the interpreter state before the execution of each instruction. \item[\tt -d] Sets a breakpoint at the first instruction for the initial process. \item[\tt -w time] Affects how long the program waits for input, if none available, before scheduling another process. The default is 0. \item[\tt -s num] Maximum number of instructions in timeslice (i.e. maximum number of instructions a process can execute before being rescheduled). The actual number of instructions executed is randomly chosen up to this limit. \end{description} \noindent Global flags are declared for each of the arguments. @d Global variable dec... @{extern int pool_flag; /* if TRUE, emit pool statistics at end */ extern int run_flag; /* if TRUE, evaluate the program */ extern int print_flag; /* if TRUE, print the code produced by the comp */ extern int tracing_flag; /* if TRUE, print machine state after each inst */ extern int debug_flag; extern int waiting_time; extern int max_slice; @} The flags are all initialized for correct default behavior. @d Global variable def... @{int pool_flag = FALSE; int run_flag = TRUE; int print_flag = FALSE; int tracing_flag = FALSE; int debug_flag = FALSE; int waiting_time = 0; int max_slice = 10; @| pool_flag print_flag tracing_flag debug_flag waiting_time max_slice @} We need to examine the entries in \verb|argv|, looking for command-line arguments. @d Interpret com... @{while (arg < argc) { char *s = argv[arg]; if (*s++ == '-') { @ arg++; } else break; }@} Several flags can be stacked behind a single minus sign; therefore, we've got to loop through the string, handling them all. @d Interpret the argument... @{{ char c = *s++; while (c) { switch (c) { case 'p': pool_flag = TRUE; break; case 'r': run_flag = FALSE; break; case 'c': print_flag = TRUE; break; case 't': tracing_flag = TRUE; break; case 'd': debug_flag = TRUE; break; case 'w': waiting_time = atoi(argv[++arg]); break; case 's': max_slice = atoi(argv[++arg]); break; default: @< Report unexpected arguments and exit @> } c = *s++; } }@} @d Report unexpected arguments and exit @{ fprintf(stderr, "%s: Unexpected command line arguments.\n" "Usage is: %s [-prctd] [-w time] [-s num] sourcefile [inputfile]\n", command_name, command_name); exit(EXIT_FAILURE); @} \section{File Handling} \label{sec:files} The compiler expects to be passed at least one filename, containing the program to be compiled. If a second filename is passed this is used as the input to the program when it is interpreted. When only one filename is present we assume that the input to the interpreted program will be \verb|stdin|. @d Open program files @{ { FILE *file; int num_files = argc - arg; if (num_files == 0 || num_files > 2) { @< Report unexpected arguments and exit @> } file = fopen(argv[arg],"r"); if (file == NULL) error("Fatal error", "Cannot open file %s.\n",argv[arg]); initialise_lexer(file); if (num_files == 2) { input_fd = open(argv[arg+1],O_RDONLY); if (input_fd == -1) error("Fatal error", "Cannot open file %s.\n",argv[arg+1]); } else if (isatty(input_fd)) @< Disable input buffering @> } @} @d Global variable dec... @{extern int input_fd; @} @d Global variable def... @{int input_fd = 0; /* stdin as default */ @| input_fd @} If the interpreter input is to come from \verb|stdin| then we disable buffering on the stream. This allows us to perform non-blocking reads. Without these, the interpreter would suspend every time the input process checked for input. Unfortunately there doesn't seem to be any portable way of doing this across all Unix systems. @d Disable input buffering @{ disable_buffering(); @} @d Utility prototypes @{extern void disable_buffering();@} \label{fn_disable_buffering} @d Utility functions @{ void disable_buffering() { struct termios term; tcgetattr(0, &term); oldterm = term; term.c_lflag &= ~ICANON; term.c_cc[VMIN] = 0; term.c_cc[VTIME] = waiting_time; tcsetattr(0, TCSANOW, &term); input_buffered = FALSE; atexit(&reset_terminal); } @| disable_buffering @} We introduce a flag to record whether the input is being buffered. This is needed so that we can interpret the return values from calls to \verb|read| appropriately. @d Global variable dec... @{extern int input_buffered; @} @d Global variable def... @{int input_buffered = TRUE; /* buffered by default */ @| input_buffered @} We need to reset the terminal when we have finished. We use the global \verb|oldterm| to remember the state of the terminal before we started tampering with it. @d Global variable def... @{struct termios oldterm; @} \label{fn_reset_terminal} @d Utility prototypes @{extern void reset_terminal(); @} @d Utility functions @{ void reset_terminal() { tcsetattr(0, TCSANOW, &oldterm); } @} \section{Error Reporting} \label{error_reporting} Here is a simple error reporting function. The first argument should contain the type of the error, e.g. "Lexical error", "Syntax error", "Fatal error" etc. The second argument is treated like an argument to printf, i.e. it is a format specification possibly containing specifiers that are replaced by the values of subsequent arguments. If the global variable \verb|line_number| is non-zero then the line number is also printed. The program terminates with a failure error code after the error message has been displayed. \label{fn_error} @d Utility prototypes @{ extern void error(char *type, char *format, ...); @} @d Utility functions @{ void error(char *type, char *format, ...) { int i; va_list arg; va_start(arg,format); fprintf(stderr, "%s: %s", command_name, type); if (line_number) fprintf(stderr, " on line %d.\n", line_number); else fprintf(stderr, ".\n"); for (i = strlen(command_name)+2; i>0; i--) fprintf(stderr, " "); vfprintf(stderr, format, arg); va_end(arg); exit(EXIT_FAILURE); } @| error @} We catch any errors to allow the test scripts to work smoothly. @d Establish signal handlers @{ (void) signal(SIGFPE, leave_fpe); (void) signal(SIGSEGV, leave_segv); @} \label{fn_leave_fpe}\label{fn_leave_segv} @d Utility functions @{ void leave_fpe(int sig) { error("Runtime error", "Floating point exception.\n"); } void leave_segv(int sig) { error("Runtime error", "Segmentation violation.\n"); } @| leave_fpe leave_segv @} \section{The Files} @o code/occam.h @{ #ifndef _OCCAM_H #define _OCCAM_H @< Global variable declarations @> @< Utility prototypes @> #endif @} @d Makefile dependencies @{ occam.c: pool.h symbol_table.h lexer.h parser.h interpreter.h occam.h @} @d Occam object files @{ occam.o @} @o code/occam.c @{ @< Standard \OCCAM\ includes @> #include #include #include #include #include "pool.h" #include "symbol_table.h" #include "lexer.h" #include "parser.h" #include "interpreter.h" #include "occam.h" @< Global variable definitions @> @< Utility functions @> @< The main program @> @} % Following code defines table of functions for preceeding chapter. \newpage \begin{table}[tp] \begin{center} \begin{tabular}{||l|l|l|r||} \hline Function name&Return value type&Argument types&Page No.\\ \hline main&int&int, char *&\pageref{fn_main}\\ disable\_buffering&void&void&\pageref{fn_disable_buffering}\\ reset\_terminal&void&void&\pageref{fn_reset_terminal}\\ error&void&char *, char *, ...&\pageref{fn_error}\\ leave\_fpe&void&int&\pageref{fn_leave_fpe}\\ leave\_segv&void&int&\pageref{fn_leave_segv}\\ \hline \end{tabular} \bigskip This table shows all the functions defined in the preceeding chapter, their argument and return parameter types and the page on which the function is defined. \end{center} % perhaps put typedefs here.... \end{table}