-- DFParserImpl.Mesa -- last edit December 29, 1982 3:24 pm -- last edit May 22, 1983 1:16 pm, Russ Atkinson -- Pilot 6.0/ Mesa 7.0 DIRECTORY CWF: TYPE USING [WF0, WF1, WF2, WF3, WF4], Date: TYPE USING [StringToPacked], DFSubr: TYPE USING [AppendToUsingSeq, Criterion, DF, DFEntryProcType, DFFileRecord, DFSeq, FreeUsingSeq, InterestingNestedDFProcType, NextDF, StripLongName, UsingEmpty, UsingSeq, ZoneType], IO: TYPE USING[UserAbort], LongString: TYPE USING [AppendChar, AppendString, EquivalentString, InvalidNumber], Stream: TYPE USING [EndOfStream, GetChar, Handle], Subr: TYPE USING [AbortMyself, AllocateString, CopyString, EndsIn, errorflg, FreeString, LongZone, strcpy, SubStrCopy, TTYProcs], Time: TYPE USING [Invalid]; DFParserImpl: PROGRAM IMPORTS CWF, Date, DFSubr, IO, LongString, Stream, Subr, Time EXPORTS DFSubr = { -- MDS usage (encouraged so that we won't run out of Resident VM) -- this is a constant array tokenString: ARRAY Token OF STRING = [ "bad"L, "CameFrom"L, "Directory"L, "Exports"L, "Host"L, "Imports"L, "Include"L, "Of"L, "Public"L, "PublicOnly"L, "ReadOnly"L, "ReleaseAs"L, "Using"L, -- special characters "@"L, "{"L, "}"L, "["L, "]"L, "Comment"L, "~"L, "+"L, "~="L, ">"L, "filename or date"L, "EOF"L, "CR"L]; -- end of MDS (there are also string literals) Token: TYPE = {tBad, -- keywords tCameFrom, tDirectory, tExports, tHost, tImports, tInclude, tOf, tPublic, tPublicOnly, tReadOnly, tReleaseAs, tUsing, -- special characters tAtSign, tOpenCurly, tCloseCurly, tOpenBracket, tCloseBracket, tComment, tTilde, tPlus, tNotEqual, tGreaterThan, tOther, tEOF, tCR}; -- tOther is a catch all that includes filenames -- such as schmidt.df; dates, etc. -- this outer procedure is here to hold state for the inner -- parsing procedures and avoid using any MDS -- making the code reentrant -- if noremoteerrors is true, don't complain if a file doesn't -- appear to have a remote place -- if forceReadonly then make every entry in this DF file be ReadOnly -- if omitNonPublic, then don't parse them into dfseq -- dffilename is for error messages -- if using ~= NIL then restrict parsing to those on using list -- sh may not be a FileStream! -- dfseq does not need to be local! -- justexception is here to kludge over bugs in with -- the spacing on Host and Directory: it is not used with Imports and Includes ParseStream: PUBLIC PROC[sh: Stream.Handle, dfseq: DFSubr.DFSeq, dffilename: LONG STRING, using: DFSubr.UsingSeq, noremoteerrors, forceReadonly, omitNonPublic: BOOL, h: Subr.TTYProcs, interestingNestedDF: DFSubr.InterestingNestedDFProcType, dfEntryProc: DFSubr.DFEntryProcType, ancestor: LONG STRING, nLevel: CARDINAL] = { peekch: CHAR _ ' ; -- peeck character tok: Token _ tBad; -- next token wholecomment: LONG STRING _ NIL; -- memory in longzone streamposn: LONG CARDINAL _ 0; justexception: BOOL _ FALSE; longzone: UNCOUNTED ZONE _ Subr.LongZone[]; -- these strings are allocated dynamically to prevent running out of resident VM -- beware: they are in a longzone tokenstr: LONG STRING _ NIL; -- receives text of next token (tok) commentline: LONG STRING _ NIL; -- used by GetC to accumulate comments, includes trailing CR directory: LONG STRING _ NIL; -- the current directory releasedir: LONG STRING _ NIL; -- the current release directory fullname: LONG STRING _ NIL; -- the whole name shortname: LONG STRING _ NIL; -- the short name tempdir: LONG STRING _ NIL; -- explicit directory on a filename LittleCopyString: PROC[newstr, oldstr: LONG STRING] RETURNS[LONG STRING] = { RETURN[IF dfseq.zoneType ~= huge OR oldstr = NIL OR NOT LongString.EquivalentString[newstr, oldstr] THEN Subr.CopyString[newstr, dfseq.dfzone] ELSE oldstr]; }; CopyHost: PROC[host: LONG STRING] RETURNS[LONG STRING] = { IF dfseq.zoneType = huge THEN { IF LongString.EquivalentString[host, dfseq.indigoHost] THEN RETURN[dfseq.indigoHost]; IF LongString.EquivalentString[host, dfseq.ivyHost] THEN RETURN[dfseq.ivyHost]; }; RETURN[Subr.CopyString[host, dfseq.dfzone]]; }; -- does not get the following token -- parses [host] or -- called only if tok = tOther or tOpenBracket HostAndOrDir: PROC[host, directory: LONG STRING] = { IF tok = tOpenBracket THEN { -- [ host ] GetToken[TRUE]; CkTok[tok, tOther]; Subr.strcpy[host, tokenstr]; GetToken[TRUE]; CkTok[tok, tCloseBracket]; GetToken[TRUE]; }; -- the next string is the directory, possibly with <> CkTok[tok, tOther]; Subr.strcpy[directory, tokenstr]; IF directory.length = 0 THEN { IF NOT noremoteerrors THEN { CWF.WF0["Error - Null directory entry.\n"]; Subr.errorflg _ TRUE; }; RETURN; }; IF directory[directory.length - 1] = '> THEN directory.length _ directory.length -1; IF directory[0] = '< THEN Subr.SubStrCopy[directory, directory, 1]; }; -- does not get the following token ParseFullName: PROC[host, directory, filename: LONG STRING] RETURNS[vers: CARDINAL] = { host.length _ 0; IF tok = tOpenBracket THEN { GetToken[TRUE]; CkTok[tok, tOther]; Subr.strcpy[host, tokenstr]; GetToken[TRUE]; CkTok[tok, tCloseBracket]; GetToken[TRUE]; }; -- tok should now be file!vers CkTok[tok, tOther]; vers _ DFSubr.StripLongName[tokenstr, NIL, directory, filename, FALSE ! LongString.InvalidNumber => { posn: LONG CARDINAL _ IF streamposn = 0 THEN 0 ELSE streamposn - 1; CWF.WF2["Error - invalid number at position %lu in file %s.\n", @posn, dffilename]; SIGNAL Subr.AbortMyself; } ]; }; CkTok: PROC[token: Token, shouldbe: Token] = { IF token ~= shouldbe THEN { posn: LONG CARDINAL _ IF streamposn = 0 THEN 0 ELSE streamposn - 1; CWF.WF4["Error - expecting a %s, found a %s at position %lu in file %s.\n", tokenString[token], tokenString[shouldbe], @posn, dffilename]; }; }; Misplaced: PROC[token: Token] = { posn: LONG CARDINAL _ IF streamposn = 0 THEN 0 ELSE streamposn - 1; CWF.WF3["Error - was not expecting the token '%s' at position %lu in file %s.\n", tokenString[token], @posn, dffilename]; }; SkipCR: PROC = { IF tok = tCR THEN GetToken[TRUE]; }; -- gets the following token, first on the next line GetCR: PROC = { -- forces next GetToken to the beginning of the line -- (and not return a CR) DO IF tok = tCR OR tok = tEOF THEN EXIT; GetToken[FALSE]; ENDLOOP; GetToken[TRUE]; }; ParseDateField: PROC RETURNS[criterion: DFSubr.Criterion, wantdate: LONG CARDINAL] = { sdate: LONG STRING _ NIL; savetok: Token; wantdate _ 0; criterion _ none; GetToken[FALSE]; IF tok = tOf THEN GetToken[FALSE]; IF tok = tCR THEN { GetToken[TRUE]; RETURN; }; savetok _ tBad; sdate _ Subr.AllocateString[100]; {ENABLE UNWIND => {Subr.FreeString[sdate]}; WHILE tok = tOther OR tok = tNotEqual OR tok = tGreaterThan DO LongString.AppendString[sdate, tokenstr]; LongString.AppendChar[sdate, ' ]; savetok _ tok; GetToken[FALSE]; ENDLOOP; -- tok may be tBad or tCR or be one of the following SELECT savetok FROM tNotEqual => criterion _ notequal; tGreaterThan => criterion _ update; tOther => { short: STRING _ [32]; -- must be short for Date.StringToPacked, dammitt! IF sdate.length > 32 THEN sdate.length _ 32; LongString.AppendString[short, sdate]; wantdate _ Date.StringToPacked[short ! Time.Invalid => { CWF.WF3["Error - '%s' is an invalid time at position %lu in %s.\n", sdate, @streamposn, dffilename]; wantdate _ 0; CONTINUE; }]; }; tCR => NULL; -- no date given ENDCASE => CWF.WF3["Error - expecting a date at position %lu in %s, \nfound '%s' instead.\n", @streamposn, dffilename, tokenstr]; }; -- of ENABLE UNWIND Subr.FreeString[sdate]; }; -- this procedure is called ONCE by the outer procedure ParseStreamInternal: PROC = { nExports: CARDINAL; readonly, public, camefrom: BOOL _ FALSE; df: DFSubr.DF; lastdir, lastreleasedir: LONG STRING _ NIL; isatsign, istopmark, ispublicOnly, isnoremoteversion, exportsImports: BOOL; host: LONG STRING _ Subr.AllocateString[40]; -- the current host releaseHost: LONG STRING _ Subr.AllocateString[40]; -- the current release host localhost: LONG STRING _ Subr.AllocateString[40]; localdirectory: LONG STRING _ Subr.AllocateString[100]; localrhost: LONG STRING _ Subr.AllocateString[40]; localrdirectory: LONG STRING _ Subr.AllocateString[100]; {ENABLE UNWIND => { Subr.FreeString[host]; Subr.FreeString[releaseHost]; Subr.FreeString[localhost]; Subr.FreeString[localdirectory]; Subr.FreeString[localrhost]; Subr.FreeString[localrdirectory]; }; nExports _ 0; [] _ GetC[]; -- to set up peekch GetToken[TRUE]; -- first token exportsImports _ FALSE; DO -- tok is from last GetToken[]; should be first on next line IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; justexception _ FALSE; SELECT tok FROM tEOF => EXIT; tHost => { justexception _ TRUE; GetToken[TRUE]; CkTok[tok, tOther]; Subr.strcpy[host, tokenstr]; GetCR[]; -- forces new line }; tDirectory, tPublic, tExports, tReadOnly => { justexception _ TRUE; exportsImports _ public _ readonly _ camefrom _ FALSE; DO SELECT tok FROM tImports => GOTO isImports; tReadOnly => readonly _ TRUE; tExports, tPublic => public _ TRUE; tOpenBracket => EXIT; tDirectory => NULL; ENDCASE => Misplaced[tok]; GetToken[TRUE]; IF tok = tOther OR tok = tEOF OR tok = tOpenBracket THEN EXIT; ENDLOOP; HostAndOrDir[host, directory]; -- parse CameFrom or ReleaseAs GetToken[TRUE]; releaseHost.length _ releasedir.length _ 0; IF tok = tCameFrom OR tok = tReleaseAs THEN { camefrom _ (tok = tCameFrom); GetToken[TRUE]; HostAndOrDir[releaseHost, releasedir]; GetCR[]; -- gets new line & next token }; EXITS isImports => exportsImports _ TRUE; -- "Exports Imports", goto tImports processing }; tImports, tInclude => { -- if exportsImports is true, then this is an Exports Imports innerUsing: DFSubr.UsingSeq _ NIL; vers: CARDINAL; savecomment: LONG STRING _ NIL; -- memory in longzone createtime: LONG CARDINAL; criterion: DFSubr.Criterion; savetok: Token _ tok; skipIt, callIt, publicOk: BOOL _ FALSE; -- (Exports) Imports [host]file!vers Of CameFrom [host] -- Imports (Exports) [host]file!vers Of CameFrom [host] -- Include [host]file!vers Of (CameFrom|ReleaseAs [host]) -- Using [ file list ] localhost.length _ 0; localdirectory.length _ 0; localrhost.length _ 0; localrdirectory.length _ 0; df _ NIL; GetToken[TRUE]; savecomment _ wholecomment; wholecomment _ NIL; IF savetok = tExports THEN { exportsImports _ TRUE; GetToken[TRUE]; -- Imports Exports }; vers _ ParseFullName[localhost, localdirectory, shortname]; skipIt _ FALSE; -- skip it if there is a using list and it does not consume an element -- or if the Imports is not Public -- don't skip it if it is Include IF using ~= NIL THEN skipIt _ NOT Consume[using, shortname] ELSE skipIt _ (omitNonPublic AND NOT exportsImports AND savetok = tImports); IF exportsImports THEN nExports _ nExports + 1; IF NOT skipIt THEN { df _ DFSubr.NextDF[dfseq]; IF df = NIL THEN EXIT; df.host _ CopyHost[localhost]; df.directory _ Subr.CopyString[localdirectory, dfseq.dfzone]; df.shortname _ Subr.CopyString[shortname, dfseq.dfzone]; IF savecomment ~= NIL THEN df.comment _ Subr.CopyString[savecomment, dfseq.dfzone]; }; [criterion, createtime] _ ParseDateField[]; SkipCR[]; camefrom _ FALSE; IF tok = tCameFrom OR tok = tReleaseAs THEN { camefrom _ (tok = tCameFrom); GetToken[TRUE]; HostAndOrDir[localrhost, localrdirectory]; GetToken[TRUE]; }; IF NOT skipIt THEN { IF savetok = tImports THEN df.readonly _ df.publicOnly _ df.atsign _ TRUE ELSE -- tInclude -- df.atsign _ TRUE; df.version _ vers; df.public _ exportsImports; -- from Exports Imports df.criterion _ criterion; df.createtime _ createtime; df.releaseDirectory _ IF localrdirectory.length > 0 THEN Subr.CopyString[localrdirectory, dfseq.dfzone] ELSE NIL; df.releaseHost _ IF localrhost.length > 0 THEN CopyHost[localrhost] ELSE NIL; df.cameFrom _ camefrom; }; IF tok = tUsing AND savetok = tImports THEN { GetToken[TRUE]; CkTok[tok, tOpenBracket]; DO GetToken[TRUE]; IF tok = tCloseBracket OR tok = tEOF THEN EXIT; CkTok[tok, tOther]; innerUsing _ DFSubr.AppendToUsingSeq[innerUsing, tokenstr, dfseq.dfzone]; ENDLOOP; IF NOT skipIt THEN df.using _ innerUsing; GetCR[]; }; IF savecomment ~= NIL THEN Subr.FreeString[savecomment, longzone]; savecomment _ NIL; -- first call about this entry IF NOT skipIt AND dfEntryProc ~= NIL THEN dfEntryProc[dfEntry: df]; -- call the user about this DF file -- call if -- 1) the using list we are driven by is not exhausted and -- a) there is an inner using list so we must check the intersection -- b) if we are looking at an Imports w/o Using list then this entry is Public, -- c) if this is an Included DF file -- 2) or if the using list is exhausted, the DF file was on the using list; -- 3) or if there is no using list and -- a) the DF file is Public -- b) if PublicOnly is false -- c) if the df file is an Includes publicOk _ exportsImports OR NOT omitNonPublic; callIt _ interestingNestedDF ~= NIL; IF using ~= NIL THEN callIt _ callIt AND ((NOT DFSubr.UsingEmpty[using] AND (savetok = tInclude OR publicOk OR innerUsing ~= NIL)) OR NOT skipIt) ELSE callIt _ callIt AND (publicOk OR savetok = tInclude); IF callIt THEN interestingNestedDF[host: localhost, directory: localdirectory, shortname: shortname, ancestor: ancestor, immediateParent: dffilename, nLevel: nLevel, version: vers, createtime: createtime, driverUsingSeq: using, innerUsingSeq: innerUsing, dfEntry: df, entryIsReadonly: (savetok = tImports) OR forceReadonly, publicOnly: (savetok = tImports) OR omitNonPublic, criterion: criterion]; IF skipIt THEN DFSubr.FreeUsingSeq[innerUsing]; exportsImports _ public _ FALSE; -- reset for next line }; tOther, tAtSign, tPlus, tTilde => { criterion: DFSubr.Criterion; createtime: LONG CARDINAL; skipIt, callIt, publicOk, isNewOnly: BOOL; vers: CARDINAL; -- +, ~, @ { PublicOnly } Filename!vers createdate df _ NIL; skipIt _ FALSE; -- if no using list and this is exports only, then skip it IF omitNonPublic AND NOT public AND using = NIL THEN skipIt _ TRUE; IF public THEN nExports _ nExports + 1; isatsign _ istopmark _ ispublicOnly _ isNewOnly _ FALSE; DO SELECT tok FROM tAtSign => isatsign _ TRUE; tOpenCurly => NULL; tPublicOnly => ispublicOnly _ TRUE; tCloseCurly => NULL; tPlus => istopmark _ TRUE; tTilde => isNewOnly _ TRUE; tOther, tEOF => EXIT; ENDCASE => Misplaced[tok]; GetToken[TRUE]; ENDLOOP; IF tok = tEOF THEN LOOP; CkTok[tok, tOther]; isnoremoteversion _ FALSE; Subr.strcpy[fullname, tokenstr]; IF tokenstr[0] ~= '< AND directory.length = 0 THEN { IF NOT noremoteerrors THEN { Subr.errorflg _ TRUE; CWF.WF1[ "Error - directory not specified for '%s'.\n", tokenstr]; }; isnoremoteversion _ TRUE; }; -- fullname looks like model>junk.mesa!3 or junk.mesa!3 vers _ DFSubr.StripLongName[fullname, NIL, tempdir, shortname, FALSE ! LongString.InvalidNumber => { posn: LONG CARDINAL _ IF streamposn = 0 THEN 0 ELSE streamposn - 1; CWF.WF2["Error - invalid number at position %lu in file %s.\n", @posn, dffilename]; SIGNAL Subr.AbortMyself; } ]; -- skip it if not on using list -- we will not skip DF files that are referenced in included DF files (omitNonPublic = FALSE), -- we will skip DF files that are referenced in imported DF files (omitNonPublic = TRUE) IF using ~= NIL THEN skipIt _ NOT Consume[using, shortname]; -- skip it if there is a using list and it does not consume an element IF NOT skipIt THEN { df _ DFSubr.NextDF[dfseq]; IF df = NIL THEN EXIT; -- get previous comment saved IF wholecomment ~= NIL THEN df.comment _ Subr.CopyString[wholecomment, dfseq.dfzone]; }; -- free previous comment at this point -- any later and the parsedatefield/skipCR will loose a line IF wholecomment ~= NIL THEN Subr.FreeString[wholecomment, longzone]; wholecomment _ NIL; [criterion, createtime] _ ParseDateField[]; SkipCR[]; -- if we are looking at a CR, get next tok IF NOT skipIt THEN { lastdir _ df.directory _ IF isnoremoteversion THEN NIL ELSE IF tempdir.length = 0 THEN LittleCopyString[directory, lastdir] ELSE LittleCopyString[tempdir, lastdir]; lastreleasedir _ df.releaseDirectory _ IF releasedir.length > 0 THEN LittleCopyString[releasedir, lastreleasedir] ELSE NIL; df.releaseHost _ IF releaseHost.length > 0 THEN CopyHost[releaseHost] ELSE NIL; df.host _ CopyHost[host]; df.shortname _ Subr.CopyString[shortname, dfseq.dfzone]; df.version _ vers; df.atsign _ isatsign; df.topmark _ istopmark; df.newOnly _ isNewOnly; df.readonly _ forceReadonly OR readonly; df.public _ public; df.publicOnly _ ispublicOnly; df.cameFrom _ camefrom; df.createtime _ createtime; df.criterion _ criterion; }; -- first call about this entry IF NOT skipIt AND dfEntryProc ~= NIL THEN dfEntryProc[dfEntry: df]; -- call the user about this DF file publicOk _ public OR NOT omitNonPublic; callIt _ isatsign AND interestingNestedDF ~= NIL AND Subr.EndsIn[shortname, ".df"]; IF using ~= NIL THEN callIt _ callIt AND ((NOT DFSubr.UsingEmpty[using] AND publicOk OR NOT readonly) OR NOT skipIt) ELSE callIt _ callIt AND (publicOk OR NOT readonly); IF callIt THEN interestingNestedDF[host: host, directory: directory, shortname: shortname, ancestor: ancestor, immediateParent: dffilename, nLevel: nLevel, version: vers, createtime: createtime, driverUsingSeq: using, innerUsingSeq: NIL, dfEntry: df, entryIsReadonly: readonly OR forceReadonly, publicOnly: ispublicOnly OR omitNonPublic, criterion: criterion]; }; ENDCASE => { Misplaced[tok]; GetToken[TRUE]; }; ENDLOOP; IF wholecomment ~= NIL THEN { IF dfseq.trailingcomment = NIL THEN dfseq.trailingcomment _ Subr.CopyString[wholecomment, dfseq.dfzone]; Subr.FreeString[wholecomment, longzone]; }; }; -- of ENABLE UNWIND Subr.FreeString[host]; Subr.FreeString[releaseHost]; Subr.FreeString[localhost]; Subr.FreeString[localdirectory]; Subr.FreeString[localrhost]; Subr.FreeString[localrdirectory]; -- don't give warning if no exports, since it may be nested Includes -- IF omitNonPublic AND nExports = 0 THEN -- CWF.WF1["Warning - %s is analyzed Exports Only, but it has no Exports!\n", dffilename]; }; -- end of ParseStreamInternal -- returns results in tok and tokenstr GetToken: PROC[ignoreLeadingCR: BOOL] = { ch: CHAR; tok _ tBad; tokenstr.length _ 0; WHILE peekch = ' OR peekch = '\t OR peekch = ', OR peekch = '/ OR peekch = '- -- questionable? OR (ignoreLeadingCR AND peekch = '\n) DO [] _ GetC[]; ENDLOOP; IF peekch IN ['A .. 'Z] OR peekch IN ['a .. 'z] OR peekch IN ['0 .. '9] OR peekch = '< THEN { tok _ tOther; DO ch _ GetC[]; IF tokenstr.length >= tokenstr.maxlength THEN { CWF.WF1["String '%s' is too long.\n", tokenstr]; RETURN; }; tokenstr[tokenstr.length] _ ch; tokenstr.length _ tokenstr.length + 1; -- not ">", "-" as they are part of file names IF peekch = 0C OR peekch = '[ OR peekch = '] OR peekch = ' OR peekch = '\t OR peekch = '\n OR peekch = ', OR peekch = '+ OR peekch = '@ OR peekch = '{ OR peekch = '} OR peekch = '/ OR peekch = '~ THEN EXIT; ENDLOOP; --now see if reserved or identifier or other IF tokenstr[0] = '< OR tokenstr.length > 10 THEN RETURN; -- claim: this is as good as hashing!! SELECT tokenstr.length FROM 2 => IF LongString.EquivalentString[tokenstr, "of"] THEN tok _ tOf; 4 => IF LongString.EquivalentString[tokenstr, "host"] THEN tok _ tHost; 5 => IF LongString.EquivalentString[tokenstr, "using"] THEN tok _ tUsing; 6 => IF LongString.EquivalentString[tokenstr, "public"] THEN tok _ tPublic; 7 => { IF LongString.EquivalentString[tokenstr, "exports"] THEN tok _ tExports; IF LongString.EquivalentString[tokenstr, "imports"] THEN tok _ tImports; IF LongString.EquivalentString[tokenstr, "include"] THEN tok _ tInclude; }; 8 => { IF LongString.EquivalentString[tokenstr, "includes"] THEN tok _ tInclude; IF LongString.EquivalentString[tokenstr, "camefrom"] THEN tok _ tCameFrom; IF LongString.EquivalentString[tokenstr, "readonly"] THEN tok _ tReadOnly; }; 9 => { IF LongString.EquivalentString[tokenstr, "directory"] THEN tok _ tDirectory; IF LongString.EquivalentString[tokenstr, "releaseas"] THEN tok _ tReleaseAs; }; 10 => IF LongString.EquivalentString[tokenstr, "publiconly"] THEN tok _ tPublicOnly; -- don't change # 10 without looking at IF test above ENDCASE; RETURN; }; ch _ GetC[]; LongString.AppendChar[tokenstr, ch]; -- not alphanumeric, look for magic characters SELECT ch FROM -- one char delimiters '\n => tok _ tCR; '+ => tok _ tPlus; '@ => tok _ tAtSign; '{ => tok _ tOpenCurly; '} => tok _ tCloseCurly; '[ => tok _ tOpenBracket; '] => tok _ tCloseBracket; '# => tok _ tNotEqual; '> => tok _ tGreaterThan; 0C => tok _ tEOF; -- two chars '~ => { IF peekch = '= THEN { [] _ GetC[]; tok _ tNotEqual; } ELSE tok _ tTilde; }; ENDCASE => tok _ tOther; }; ReadC: PROC = { peekch _ Stream.GetChar[sh ! Stream.EndOfStream => { peekch _ 0C; CONTINUE; } ]; streamposn _ streamposn + 1; }; GetC: PROC RETURNS[ch: CHAR] = { ch _ peekch; IF ch = 0C THEN RETURN; -- STP only signals EndOfStream once, then hangs ReadC[]; DO -- strips leading blanks from lines IF ch = '\n AND peekch = ' THEN { WHILE peekch = ' DO ReadC[]; ENDLOOP; -- note that ch remains = \n }; IF (ch = '/ AND peekch = '/) OR (ch = '- AND peekch = '-) OR (ch = '\n AND peekch = '\n AND NOT justexception) THEN { -- handle comment len: CARDINAL; savecomment: LONG STRING; commentline.length _ 0; LongString.AppendChar[commentline, ch]; IF peekch ~= '\n THEN { -- get rest of comment line DO LongString.AppendChar[commentline, peekch]; ReadC[]; IF peekch = '\n OR peekch = 0C THEN EXIT; ENDLOOP; LongString.AppendChar[commentline, peekch]; }; ch _ peekch; IF ch ~= 0C THEN ReadC[]; len _ commentline.length; IF wholecomment ~= NIL THEN len _ len + wholecomment.length; savecomment _ wholecomment; wholecomment _ Subr.AllocateString[len, longzone]; IF savecomment ~= NIL THEN { Subr.strcpy[wholecomment, savecomment]; Subr.FreeString[savecomment, longzone]; }; LongString.AppendString[wholecomment, commentline]; -- CWF.WF1["Comment !%s!\n", wholecomment]; } ELSE EXIT; ENDLOOP; }; Cleanup: PROC = { Subr.FreeString[tempdir, longzone]; Subr.FreeString[shortname, longzone]; Subr.FreeString[fullname, longzone]; Subr.FreeString[releasedir, longzone]; Subr.FreeString[directory, longzone]; Subr.FreeString[tokenstr, longzone]; Subr.FreeString[commentline, longzone]; }; -- these strings are allocated here to avoid running out of resident VM commentline _ Subr.AllocateString[600, longzone]; tokenstr _ Subr.AllocateString[100, longzone]; directory _ Subr.AllocateString[100, longzone]; releasedir _ Subr.AllocateString[100, longzone]; fullname _ Subr.AllocateString[100, longzone]; shortname _ Subr.AllocateString[100, longzone]; tempdir _ Subr.AllocateString[100, longzone]; ParseStreamInternal[ ! UNWIND => Cleanup[]]; Cleanup[]; }; Consume: PROC[usingseq: DFSubr.UsingSeq, shortname: LONG STRING] RETURNS[consumed: BOOL] = { FOR i: CARDINAL IN [0 .. usingseq.size) DO IF usingseq[i] = NIL THEN LOOP; IF LongString.EquivalentString[usingseq[i], shortname] THEN { Subr.FreeString[usingseq[i], usingseq.zone]; usingseq[i] _ NIL; RETURN[TRUE]; }; ENDLOOP; RETURN[FALSE]; }; }.