/* YREF.Y: YREF cross reference utility, V1.1 5-1-91 AG This is a sample Yacc program which produces complete cross reference listings of Yacc source (.y) files. It is based on the same grammar which has actually been used to implement Yacc itself, and thus might also be useful for other utilities which have to parse Yacc sources. The lexical analyzer for this program can be found in YREFLEX.L. The units YRefTools and YRefTables supply the additional routines and data structures used by the program. To compile the program, issue the commands: yacc yref lex yreflex tpc /m yref The YREF program reads and parses a Yacc source file and produces a listing of the source file (with linenumbers), followed by a cross reference table of all (literal and nonterminal) identifiers used in the grammar, in alphabetical order. For each symbol, YREF lists the type of the symbol (if type tags are used), and the corresponding line numbers where the symbol occurs. Line numbers marked with an asterisk denote places where the corresponding symbol is "defined" (the lines where a terminal is introduced in a %token definition, or a nonterminal appears on the left-hand side of a rule). Symbols which do not have at least one defining position are listed as "undefined symbols" at the end of the list; this is useful to check Yacc grammars for "completeness." YREF does not handle syntactic errors in the source file; it is assumed that the input file is a syntactically correct Yacc program. If a syntax error is encountered, YREF simply gives an error message indicating the offending position, and exits with return code 1. The command line syntax is as follows: YREF yacc-file[.Y] [output-file[.REF]] Default extensions .Y for the input, and .REF for the output file are supplied automatically. If the output file name is ommitted, it defaults to the name of the input file with new suffix .REF. */ %{ {$I-} uses YaccLib, LexLib, Dos, YRefTools, YRefTables; procedure yyerror ( msg : String ); begin writeln(msg , ' in line ', yylineno, ' at or near `', yytext, ''''); end(*yyerror*); var tag : Integer; (* type tag *) symlineno : Integer; (* line number of last identifier *) procedure scan ( var c : Char ); forward; (* scan for nonempty character, skipping comments *) procedure skip ( delim : String ); forward; (* skip up to next occurrence of delim *) procedure search ( delim : String ); forward; (* like skip, but retain found delimiter, and handle embedded strings *) %} /* Tokens of the Yacc language: Note the use of C_ID to distinguish identifiers which start a new rule. This is necessary because the parser is limited to one-symbol lookahead and hence could not determine whether an identifier is followed by a colon, starting a new rule, or is simply just another nonterminal or token identifier in the right-hand of a rule. Thus the lexical analyzer performs the necessary lookahead, and returns C_ID if the identifier is followed by a colon (skipping blanks and comments), and ID otherwise. */ %token ID /* identifiers: {letter}{letter_or_digit}* */ C_ID /* identifier which forms left side of rule, i.e. is followed by a colon */ LITERAL /* literals (strings enclosed in single or double quotes) */ NUMBER /* nonnegative integers: {digit}+ */ PTOKEN PLEFT PRIGHT PNONASSOC PTYPE PSTART PPREC /* reserved words: PTOKEN=%token, etc. */ PP /* source sections separator %% */ LCURL /* curly braces: %{ and %} */ RCURL ',' ':' ';' '|' '{' '}' '<' '>' '=' /* literals */ %start grammar %% grammar : defs PP rules aux_procs ; aux_procs : /* empty: aux_procs is optional */ | PP { yyaccept; } ; defs : /* empty */ | defs def ; def : PSTART ID | LCURL { search('%}'); } RCURL | PTOKEN tag token_list | PLEFT tag token_list | PRIGHT tag token_list | PNONASSOC tag token_list | PTYPE tag nonterm_list | PTYPE tag ; tag : /* empty: type tag is optional */ { tag := 0; } | '<' ID '>' { tag := $2; } ; token_list : token_num | token_list token_num | token_list ',' token_num ; token_num : LITERAL opt_num | ID { add_ref($1, symlineno, true); set_type($1, tag); } opt_num ; opt_num : /* empty */ | NUMBER ; nonterm_list : nonterm | nonterm_list nonterm | nonterm_list ',' nonterm ; nonterm : ID { add_ref($1, symlineno, false); set_type($1, tag); } ; rules : rule1 | LCURL { search('%}'); } RCURL rule1 /* rules section may be prefixed with `local' Turbo Pascal declarations */ | rules rule ; rule1 : C_ID { add_ref($1, symlineno, true); } ':' body prec ; rule : rule1 | '|' body prec ; body : /* empty */ | body LITERAL | body ID { add_ref($2, symlineno, false); } | body action ; action : '{' { search('}'); } '}' | '=' { skip(';'); } /* old language feature; code must be single statement ending with `;' */ ; prec : /* empty */ | PPREC LITERAL opt_action | PPREC ID { add_ref($2, symlineno, false); } opt_action | prec ';' ; opt_action : /* empty */ | action ; %% procedure scan ( var c : Char ); label next; const tab = #9; nl = #10; begin next: c := get_char; case c of ' ', tab, nl: goto next; '/': begin c := get_char; if c='*' then begin skip('*/'); goto next; end else begin unget_char(c); unget_char('/'); c := '/'; end; end; else unget_char(c); end; end(*scan*); procedure skip ( delim : String ); var i, j : Integer; c : Char; begin i := 1; while i<=length(delim) do begin c := get_char; if c=delim[i] then inc(i) else if c<>#0 then begin for j := i-1 downto 2 do unget_char(delim[j]); i := 1; end else exit; end; end(*skip*); procedure search ( delim : String ); var i, j : Integer; c : Char; begin i := 1; while i<=length(delim) do begin c := get_char; if c=delim[i] then inc(i) else if c<>#0 then begin for j := i-1 downto 2 do unget_char(delim[j]); i := 1; if c='''' then (* skip string *) skip(''''); end else exit; end; for i := length(delim) downto 1 do unget_char(delim[i]); end(*search*); (* Lexical analyzer: *) {$I YRefLex} (* Main program: *) function addExt ( filename, ext : String ) : String; (* add default extension to filename *) var d : DirStr; n : NameStr; e : ExtStr; begin fsplit(filename, d, n, e); if e='' then e := '.'+ext; addExt := d+n+e; end(*addExt*); function root ( filename : String ) : String; (* return filename with extension stripped off *) var d : DirStr; n : NameStr; e : ExtStr; begin fsplit(filename, d, n, e); root := d+n; end(*root*); var result, lineno : Integer; yfile, reffile, line : String; begin (* sign-on: *) writeln('YREF Version 1.0 [Mar 91], Copyright (c) 1991 by Albert Graef'); (* parse command line: *) case paramCount of 1 : begin yfile := addExt(paramStr(1), 'y'); reffile := root(paramStr(1))+'.ref'; end; 2 : begin yfile := addExt(paramStr(1), 'y'); reffile := addExt(paramStr(2), 'ref'); end; else begin writeln('Usage: YREF yacc-file[.Y] [output-file[.REF]]'); halt(0); end; end; (* open files: *) assign(yyinput, yfile); assign(yyoutput, reffile); reset(yyinput); if ioresult<>0 then begin writeln('cannot open file '+yfile); halt(1); end; rewrite(yyoutput); if ioresult<>0 then begin writeln('cannot open file '+reffile); halt(1); end; (* produce numbered listing: *) lineno := 1; while not eof(yyinput) do begin readln(yyinput, line); writeln(yyoutput, lineno:5, ': ', line); inc(lineno); end; close(yyinput); reset(yyinput); (* parse: *) result := yyparse; (* produce cross reference listing *) if result=0 then ref_list; (* close files: *) close(yyinput); close(yyoutput); if result>0 then erase(yyoutput); (* terminate: *) if (result=0) and (n_undef>0) then writeln(n_undef, ' undefined symbol(s)'); halt(result); end.