-- DFParserImpl.Mesa, last edit December 29, 1982 3:24 pm -- 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], Stream: TYPE USING [EndOfStream, GetChar, Handle], String: TYPE USING [AppendChar, InvalidNumber], 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, String, 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 <dir>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]<directory> or <directory> -- 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 <directory>file!vers CkTok[tok, tOther]; vers ← DFSubr.StripLongName[tokenstr, NIL, directory, filename, FALSE ! String.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: STRING ← [100]; savetok: Token; wantdate ← 0; criterion ← none; GetToken[FALSE]; IF tok = tOf THEN GetToken[FALSE]; IF tok = tCR THEN { GetToken[TRUE]; RETURN; }; savetok ← tBad; WHILE tok = tOther OR tok = tNotEqual OR tok = tGreaterThan DO LongString.AppendString[sdate, tokenstr]; String.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 => { wantdate ← Date.StringToPacked[sdate ! 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]; }; -- this procedure is called ONCE by the outer procedure ParseStreamInternal: PROC = { host: STRING ← [30]; -- the current host releaseHost: STRING ← [30]; -- the current release host nExports: CARDINAL; readonly, public, camefrom: BOOL ← FALSE; df: DFSubr.DF; lastdir, lastreleasedir: LONG STRING ← NIL; isatsign, istopmark, ispublicOnly, isnoremoteversion, exportsImports: BOOL; 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; localhost: STRING ← [30]; localdirectory: STRING ← [100]; localrhost: STRING ← [30]; localrdirectory: STRING ← [100]; skipIt, callIt, publicOk: BOOL ← FALSE; -- (Exports) Imports [host]<path>file!vers Of <date> CameFrom [host]<path> -- Imports (Exports) [host]<path>file!vers Of <date> CameFrom [host]<path> -- Include [host]<path>file!vers Of <date> (CameFrom|ReleaseAs [host]<path>) -- Using [ file list ] 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 } <Directory>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 <schmidt>model>junk.mesa!3 or junk.mesa!3 vers ← DFSubr.StripLongName[fullname, NIL, tempdir, shortname, FALSE ! String.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]; }; -- 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]; }; }.