MODULE -->Grammar; (* This is an example of a rudimentary main module for use with COCO/R. It assumes the FileIO/Storage I/O libraries (as supplied with this project) are available. The auxiliary modules S (scanner) and P (parser) are assumed to have been constructed with COCO/R compiler generator. *) FROM -->Scanner IMPORT lst, src, errors, Error, CharAt; FROM -->Parser IMPORT Parse, Successful, GetUnit, InInst, InstBegin, InstAddActual, InstEnd, InstNameGet, InstActualGet, UnitNameGet, UnitIsGeneric, UnitIsDefModule, RefineSeen, RefineIsDef, RefineInst, RefineGen, RefineActualCount, RefineActualGet, SetTwoPhase; IMPORT Strings, Storage, SYSTEM, FileIO, SymTab, QbeGen, AST, Lower, Check; (* and any others needed *) TYPE INT32 = FileIO.INT32 (* 32 bit integers needed *); GName = SymTab.Name; GPath = ARRAY [0 .. 255] OF CHAR; MODULE ListHandler; (* ------------------- Source Listing and Error handler -------------- *) FROM FileIO IMPORT CR, LF, EOF, File, StdOut, WriteString, Write, WriteLn, WriteInt, Long0; FROM Storage IMPORT ALLOCATE; FROM SYSTEM IMPORT TSIZE; IMPORT lst, CharAt, errors, INT32; EXPORT StoreError, PrintListing, PrintConsoleErrors, ResetErrors; TYPE Err = POINTER TO ErrDesc; ErrDesc = RECORD nr, line, col: INTEGER; next: Err END; CONST tab = CHR(9); VAR firstErr, lastErr: Err; Extra: INTEGER; PROCEDURE StoreError (nr, line, col: INTEGER; pos: INT32); (* Store an error message for later printing *) VAR nextErr: Err; BEGIN ALLOCATE(nextErr, TSIZE(ErrDesc)); nextErr^.nr := nr; nextErr^.line := line; nextErr^.col := col; nextErr^.next := NIL; IF firstErr = NIL THEN firstErr := nextErr ELSE lastErr^.next := nextErr END; lastErr := nextErr; INC(errors) END StoreError; PROCEDURE GetLine (VAR pos: INT32; VAR line: ARRAY OF CHAR; VAR eof: BOOLEAN); (* Read a source line. Return empty line if eof *) VAR ch: CHAR; i: CARDINAL; BEGIN i := 0; eof := FALSE; ch := CharAt(pos); INC(pos); WHILE (ch # CR) AND (ch # LF) AND (ch # EOF) DO (* bound-check: a line longer than the caller's buffer must truncate, not overflow it (stack corruption / segfault) *) IF i < HIGH(line) THEN line[i] := ch; INC(i) END; ch := CharAt(pos); INC(pos); END; eof := (i = 0) AND (ch = EOF); line[i] := CHR(0); IF ch = CR THEN (* check for MsDos *) ch := CharAt(pos); IF ch = LF THEN INC(pos); Extra := 0 END END END GetLine; PROCEDURE ErrText (nr: INTEGER; f: File); (* Error message text for nr on file f (listing and console share it) *) PROCEDURE Msg (s: ARRAY OF CHAR); BEGIN WriteString(f, s) END Msg; BEGIN CASE nr OF -->Errors (* add customized cases here *) | 200: Msg("duplicate identifier") | 201: Msg("undeclared identifier") | 202: Msg("module name mismatch") | 210: Msg("incompatible assignment") | 211: Msg("arithmetic operand must be numeric") | 212: Msg("boolean operand required") | 213: Msg("incompatible comparison") | 214: Msg("BOOLEAN condition required") | 215: Msg("not a RECORD type") | 216: Msg("unknown field") | 217: Msg("not an ARRAY type") | 218: Msg("array index must be integer") | 219: Msg("not a POINTER type") | 220: Msg("FOR needs integer variable and bounds") | 221: Msg("not a type name") | 222: Msg("set operand mismatch") | 223: Msg("cyclical type definition") | 224: Msg("ordinal type required") | 230: Msg("not supported yet") | 231: Msg("opaque type outside definition") | 232: Msg("bad RETURN") | 233: Msg("invalid call") | 234: Msg("invalid UTF-8 in U-literal") | 235: Msg("implementation does not match definition") | 236: Msg("bad variant record") ELSE Msg("Error: "); WriteInt(f, nr, 0); END END ErrText; PROCEDURE PrintErr (line: ARRAY OF CHAR; nr, col: INTEGER); (* Print an error message *) PROCEDURE Pointer; VAR i: INTEGER; BEGIN WriteString(lst, "***** "); i := 0; WHILE (i < col + Extra - 2) AND (i <= VAL(INTEGER, HIGH(line))) AND (line[i] # CHR(0)) DO IF line[i] = tab THEN Write(lst, tab) ELSE Write(lst, ' ') END; INC(i) END; WriteString(lst, "^ ") END Pointer; BEGIN Pointer; ErrText(nr, lst); WriteLn(lst) END PrintErr; PROCEDURE PrintListing; (* Print a source listing with error messages *) VAR nextErr: Err; eof, done: BOOLEAN; lnr, errC: INTEGER; srcPos: INT32; line: ARRAY [0 .. 4095] OF CHAR; (* long lines (e.g. big enums) *) BEGIN WriteString(lst, "Listing:"); WriteLn(lst); WriteLn(lst); srcPos := 0; nextErr := firstErr; GetLine(srcPos, line, eof); lnr := 1; errC := 0; WHILE NOT eof DO WriteInt(lst, lnr, 5); WriteString(lst, " "); WriteString(lst, line); WriteLn(lst); done := FALSE; WHILE (nextErr # NIL) AND NOT done DO IF nextErr^.line = lnr THEN PrintErr(line, nextErr^.nr, nextErr^.col); INC(errC); nextErr := nextErr^.next ELSE done := TRUE END END; GetLine(srcPos, line, eof); INC(lnr); END; IF nextErr # NIL THEN WriteInt(lst, lnr, 5); WriteLn(lst); WHILE nextErr # NIL DO PrintErr(line, nextErr^.nr, nextErr^.col); INC(errC); nextErr := nextErr^.next END END; WriteLn(lst); WriteInt(lst, errC, 5); WriteString(lst, " error"); IF errC # 1 THEN Write(lst, 's') END; WriteLn(lst); WriteLn(lst); WriteLn(lst); END PrintListing; PROCEDURE PrintConsoleErrors (fname: ARRAY OF CHAR); (* "file:line:col: message" errors on StdOut, for editor integration. Columns are 1-based token starts, matching the listing carets (NOT the col-1 of CRQ.FRM). *) PROCEDURE WriteNum (n: INTEGER); (* FileIO.WriteInt pads a leading blank; editors want none. *) VAR buf: ARRAY [0 .. 15] OF CHAR; i: CARDINAL; BEGIN IF n < 0 THEN Write(StdOut, "-"); n := -n END; i := 0; IF n = 0 THEN buf[0] := "0"; i := 1 END; WHILE (n > 0) AND (i <= HIGH(buf)) DO buf[i] := CHR(ORD("0") + VAL(CARDINAL, n MOD 10)); n := n DIV 10; INC(i) END; WHILE i > 0 DO DEC(i); Write(StdOut, buf[i]) END END WriteNum; VAR e: Err; BEGIN e := firstErr; WHILE e # NIL DO WriteString(StdOut, fname); Write(StdOut, ":"); WriteNum(e^.line); Write(StdOut, ":"); WriteNum(e^.col); WriteString(StdOut, ": "); ErrText(e^.nr, StdOut); WriteLn(StdOut); e := e^.next END END PrintConsoleErrors; PROCEDURE ResetErrors; (* Fresh error list per source file in a session. *) BEGIN firstErr := NIL END ResetErrors; BEGIN firstErr := NIL; Extra := 1; END ListHandler; (* --------------------------- main module ------------------------------- *) PROCEDURE ChangeExtension (oldName, Ext: ARRAY OF CHAR; VAR newName: ARRAY OF CHAR); (* Constructs newName as complete file name by appending ext to oldName Examples: (assume ext = "EXT") old.any ==> old.EXT old ==> old.EXT This is not a file renaming facility, merely a string manipulation routine. *) VAR i, l: CARDINAL; BEGIN Strings.Assign(oldName, newName); i := LENGTH(oldName); l := i; WHILE (i > 0) AND (oldName[i -1] # '.') AND (oldName[i -1] # '\') AND (oldName[i -1] # '/') DO DEC(i) END; IF (i > 0) AND (oldName[i-1] = '.') THEN Strings.Delete(newName, i - 1, l + 1 - i) END; IF Ext[0] = '.' THEN Strings.Delete(Ext, 0, 1) END; Strings.Append(".", newName); Strings.Append(Ext, newName) END ChangeExtension; PROCEDURE BaseName (path: ARRAY OF CHAR; VAR name: ARRAY OF CHAR); (* Basename of a path without its extension (program image name). *) VAR i, j, k: CARDINAL; BEGIN i := 0; j := 0; WHILE (i <= HIGH(path)) AND (path[i] # CHR(0)) DO IF (path[i] = '/') OR (path[i] = '\') THEN j := i + 1 END; INC(i) END; k := j; WHILE (k < i) AND (path[k] # '.') DO INC(k) END; i := 0; WHILE j < k DO name[i] := path[j]; INC(i); INC(j) END; name[i] := CHR(0) END BaseName; VAR sourceName, listName, progName: ARRAY [0 .. 255] OF CHAR; files: ARRAY [0 .. 127] OF ARRAY [0 .. 255] OF CHAR; nFiles: CARDINAL; f: CARDINAL; bad: BOOLEAN; doCheck: BOOLEAN; (* run the resolve/check pass (Check.Resolve) *) doTwoPhase: BOOLEAN; (* probe the two-phase body skipper (ProbeBody) *) (* generics (Increment 2): module name -> source path, def and impl *) genDefNm: ARRAY [0 .. 31] OF ARRAY [0 .. 63] OF CHAR; genDefPth: ARRAY [0 .. 31] OF ARRAY [0 .. 255] OF CHAR; nGenDef: CARDINAL; genImpNm: ARRAY [0 .. 31] OF ARRAY [0 .. 63] OF CHAR; genImpPth: ARRAY [0 .. 31] OF ARRAY [0 .. 255] OF CHAR; nGenImp: CARDINAL; uname, iname, gname: GName; gpath: GPath; isDef: BOOLEAN; PROCEDURE EqNm (a, b: ARRAY OF CHAR): BOOLEAN; VAR i: CARDINAL; BEGIN i := 0; WHILE (i <= HIGH(a)) AND (i <= HIGH(b)) DO IF a[i] # b[i] THEN RETURN FALSE END; IF a[i] = CHR(0) THEN RETURN TRUE END; INC(i) END; RETURN FALSE END EqNm; PROCEDURE NoteGen (name: ARRAY OF CHAR; isD: BOOLEAN; path: ARRAY OF CHAR); VAR i: CARDINAL; BEGIN IF isD THEN IF nGenDef <= HIGH(genDefNm) THEN i := 0; WHILE (i <= HIGH(genDefNm[nGenDef])) AND (name[i] # CHR(0)) DO genDefNm[nGenDef][i] := name[i]; INC(i) END; genDefNm[nGenDef][i] := CHR(0); i := 0; WHILE (i <= HIGH(genDefPth[nGenDef])) AND (path[i] # CHR(0)) DO genDefPth[nGenDef][i] := path[i]; INC(i) END; genDefPth[nGenDef][i] := CHR(0); INC(nGenDef) END ELSE IF nGenImp <= HIGH(genImpNm) THEN i := 0; WHILE (i <= HIGH(genImpNm[nGenImp])) AND (name[i] # CHR(0)) DO genImpNm[nGenImp][i] := name[i]; INC(i) END; genImpNm[nGenImp][i] := CHR(0); i := 0; WHILE (i <= HIGH(genImpPth[nGenImp])) AND (path[i] # CHR(0)) DO genImpPth[nGenImp][i] := path[i]; INC(i) END; genImpPth[nGenImp][i] := CHR(0); INC(nGenImp) END END END NoteGen; PROCEDURE FindGenIdx (name: ARRAY OF CHAR; isD: BOOLEAN): INTEGER; (* Index of the generic module's file in the registry, or -1. *) VAR i: CARDINAL; BEGIN i := 0; IF isD THEN WHILE i < nGenDef DO IF EqNm(genDefNm[i], name) THEN RETURN VAL(INTEGER, i) END; INC(i) END ELSE WHILE i < nGenImp DO IF EqNm(genImpNm[i], name) THEN RETURN VAL(INTEGER, i) END; INC(i) END END; RETURN -1 END FindGenIdx; PROCEDURE DoInst; (* Re-parse the generic's source with the instance name and the actuals bound, then lower the resulting unit. *) VAR j: CARDINAL; gi: INTEGER; saved: FileIO.File; BEGIN RefineInst(iname); RefineGen(gname); isDef := RefineIsDef(); gi := FindGenIdx(gname, isDef); IF gi < 0 THEN FileIO.WriteString(FileIO.StdOut, "generic not found: "); FileIO.WriteString(FileIO.StdOut, gname); FileIO.WriteLn(FileIO.StdOut); RETURN END; IF isDef THEN j := 0; WHILE (j <= HIGH(gpath)) AND (genDefPth[gi][j] # CHR(0)) DO gpath[j] := genDefPth[gi][j]; INC(j) END; gpath[j] := CHR(0) ELSE j := 0; WHILE (j <= HIGH(gpath)) AND (genImpPth[gi][j] # CHR(0)) DO gpath[j] := genImpPth[gi][j]; INC(j) END; gpath[j] := CHR(0) END; saved := src; FileIO.Open(src, gpath, FALSE); IF NOT FileIO.Okay THEN src := saved; RETURN END; ResetErrors; InstBegin(iname); j := 0; WHILE j < RefineActualCount() DO InstAddActual(RefineActualGet(j)); INC(j) END; AST.Init; Lower.BeginUnit; Check.BeginUnit; SetTwoPhase(doTwoPhase); Parse; InstEnd; IF doCheck THEN Check.Resolve(GetUnit()) END; Lower.LowerUnitNow(GetUnit(), FALSE); IF (src # FileIO.StdIn) & (src # FileIO.StdOut) THEN FileIO.Close(src) END; src := saved END DoInst; BEGIN (* collect the session's source files (program module last) *) nFiles := 0; doCheck := TRUE; doTwoPhase := FALSE; LOOP FileIO.NextParameter(sourceName); IF (sourceName[0] = CHR(0)) OR (nFiles > HIGH(files)) THEN EXIT END; IF EqNm(sourceName, "-noresolve") THEN doCheck := FALSE ELSIF EqNm(sourceName, "-twophase") THEN doTwoPhase := TRUE ELSE Strings.Assign(sourceName, files[nFiles]); INC(nFiles) END END; IF nFiles = 0 THEN FileIO.WriteString(FileIO.StdOut, "No input file specified"); HALT END; BaseName(files[nFiles - 1], progName); SymTab.Init; QbeGen.OpenModule(progName); Lower.BeginSession(progName); Check.BeginSession(progName); Error := StoreError; (* parse each unit into the shared session *) bad := FALSE; f := 0; WHILE f < nFiles DO Strings.Assign(files[f], sourceName); FileIO.Open(src, sourceName, FALSE); IF NOT FileIO.Okay THEN FileIO.WriteString(FileIO.StdOut, "Could not open input file"); FileIO.WriteLn(FileIO.StdOut); HALT END; ChangeExtension(sourceName, ".LST", listName); FileIO.Open(lst, listName, TRUE); IF NOT FileIO.Okay THEN lst := FileIO.StdOut END; ResetErrors; FileIO.WriteString(FileIO.StdOut, "Parsing"); FileIO.WriteLn(FileIO.StdOut); AST.Init; Lower.BeginUnit; Check.BeginUnit; SetTwoPhase(doTwoPhase); Parse; UnitNameGet(uname); IF doCheck THEN Check.Resolve(GetUnit()) END; IF RefineSeen() THEN DoInst ELSIF UnitIsGeneric() THEN (* record the generic's source for later instantiation; do not emit the abstract template *) NoteGen(uname, UnitIsDefModule(), sourceName) ELSE Lower.LowerUnitNow(GetUnit(), f = nFiles - 1) END; PrintListing; IF lst # FileIO.StdOut THEN FileIO.Close(lst) END; IF NOT Successful() THEN PrintConsoleErrors(sourceName); bad := TRUE END; (* Release the source handle before the next file: the host FileIO caps open files at BitSetSize (16), and a session may compile more sources than that. *) IF (src # FileIO.StdIn) & (src # FileIO.StdOut) THEN FileIO.Close(src) END; INC(f) END; Lower.EndSession; Check.EndSession; IF bad OR NOT SymTab.HaveProgram() THEN FileIO.WriteString(FileIO.StdOut, "Incorrect source") ELSE FileIO.WriteString(FileIO.StdOut, "Parsed correctly") END END -->Grammar.