DIRECTORY Ascii USING [Digit, Letter], Basics USING [MoveBytes, RawBytes, UnsafeBlock], BasicTime USING [GetClockPulses, GMT, Now, Pulses, PulsesToSeconds], CedarProcess, Commander USING [CommandProc, Handle, Register], CommanderOps USING [DoCommand, Failed, ParseToList], DFUtilities USING [DirectoryItem, FileItem, FileSyntaxError, Filter, ImportsItem, IncludeItem, ParseFromFile, ParseFromStream, ProcessItemProc, SearchUsingList, SortUsingList], FileNames USING [ResolveRelativePath, StripVersionNumber], IO, PFS USING [Close, EnumerateForNames, Error, GetInfo, NameProc, Open, OpenFile, PATH, PathFromRope, RopeFromPath, StreamFromOpenFile, StreamOpen, tDirectory], PFSNames USING [ExpandName, SetVersionNumber, ShortName, ShortNameRope], Process USING [CheckForAbort, Pause, MsecToTicks, Yield], Rope, SymTab, VersionMap USING [Map, MapList, Range, RangeList, RangeToEntry, ShortNameToRanges], VersionMapDefaults USING [GetMapList], VM USING [AddressForPageNumber, Allocate, bytesPerPage, Free, Interval]; QFind: CEDAR MONITOR LOCKS ml USING ml: Lock IMPORTS Ascii, Basics, BasicTime, CedarProcess, Commander, CommanderOps, DFUtilities, FileNames, IO, PFS, PFSNames, Process, Rope, SymTab, VersionMap, VersionMapDefaults, VM = BEGIN STREAM: TYPE = IO.STREAM; ROPE: TYPE = Rope.ROPE; PATH: TYPE = PFS.PATH; Lock: TYPE ~ REF MONITORLOCK; overlap: CARDINAL = 154; -- keep this many chars from previous buffer so we can try to back up to beginning of line when we find a match (must be divisible by bytesPerWord) bufferPages: NAT = 40; bufferSize: CARDINAL = bufferPages*VM.bytesPerPage; Buffer: TYPE = RECORD [PACKED SEQUENCE COMPUTED CARDINAL OF CHAR]; Map: TYPE = PACKED ARRAY CHAR OF CHAR; OpenFile: PROC [name: PATH] RETURNS [st: STREAM ¬ NIL, isDir: BOOL ¬ FALSE] = { of: PFS.OpenFile; of ¬ PFS.Open[name ! PFS.Error => IF error.group # bug THEN CONTINUE]; IF of = NIL THEN RETURN; IF PFS.GetInfo[of].fileType = PFS.tDirectory THEN { PFS.Close[of]; isDir ¬ TRUE; RETURN }; st ¬ PFS.StreamFromOpenFile[of ! PFS.Error => IF error.group # bug THEN CONTINUE]; }; NonAlpha: PROC [char: CHAR] RETURNS [BOOL] ~ { RETURN [NOT (Ascii.Letter[char] OR Ascii.Digit[char])] }; QFindCmd: Commander.CommandProc ~ { log: IO.STREAM ¬ cmd.err; out: IO.STREAM ¬ cmd.out; inputList: LIST OF ROPE; buffer: LONG POINTER TO Buffer; vmInt: VM.Interval; qual, key, file: ROPE; bm, bmq: BMhandle; startTime: BasicTime.Pulses ¬ BasicTime.GetClockPulses[]; matches, files: INT ¬ 0; map: REF Map; caseless: BOOL ¬ TRUE; tkey, tqual: REF TEXT; shortNames: BOOL ¬ FALSE; listNamesOnly: BOOL ¬ FALSE; openFiles: BOOL ¬ FALSE; filePos: BOOL ¬ FALSE; matchWordsOnly: BOOL ¬ FALSE; useVersionMap: BOOL ¬ FALSE; textOnly: BOOL ¬ FALSE; ignoreRemainingSwitches: BOOL ¬ FALSE; modular: BOOL ¬ FALSE; roots: LIST OF PATH ¬ NIL; fc: FileConsumer ¬ NIL; AddDFNames: PROC [key: ROPE, in: LIST OF ROPE] RETURNS [out: LIST OF ROPE] ~ { ProcessItem: DFUtilities.ProcessItemProc ~ { WITH item SELECT FROM f: REF DFUtilities.FileItem => l ¬ l.rest ¬ LIST[FileNames.StripVersionNumber[f.name]]; ENDCASE; }; stream: STREAM; l: LIST OF ROPE ¬ in; out ¬ in; stream ¬ PFS.StreamOpen[PFS.PathFromRope[FileNames.ResolveRelativePath[key]] ! PFS.Error => { IF error.group # bug THEN { IO.PutF1[cmd.out, "** %g\n", [rope[error.explanation]] ]; GOTO bad; }; }]; IF in = NIL THEN RETURN; WHILE l.rest # NIL DO l ¬ l.rest ENDLOOP; DFUtilities.ParseFromStream[stream, ProcessItem, [FALSE, source, all, all]]; IO.Close[stream]; EXITS bad => NULL; }; SearchFile: PFS.NameProc = { RETURN ConsumeFile[fc, name] }; inputList ¬ CommanderOps.ParseToList[cmd: cmd ! CommanderOps.Failed => { log.PutRope["invalid input format\n"]; GO TO quit }].list; IF inputList = NIL THEN RETURN[NIL, usage]; map ¬ NEW [Map]; FOR c: CHAR IN CHAR DO map[c] ¬ IF c IN ['a..'z] THEN c - 'a + 'A ELSE c; ENDLOOP; DO key ¬ inputList.first; inputList ¬ inputList.rest; IF Rope.Size[key] > 1 AND Rope.Fetch[key, 0] = '- AND NOT ignoreRemainingSwitches THEN { IF Rope.Equal[key, "-d", FALSE] THEN { key ¬ inputList.first; inputList ¬ inputList.rest; inputList ¬ AddDFNames[key, inputList]; } ELSE { FOR i: INT IN [1..Rope.Size[key]) DO SELECT Rope.Fetch[key, i] FROM 'c, 'C => FOR c: CHAR IN ['a..'z] DO map[c] ¬ c ENDLOOP; 'f, 'F => listNamesOnly ¬ TRUE; 'o, 'O => openFiles ¬ TRUE; 'm, 'M => useVersionMap ¬ TRUE; 'p, 'P => filePos ¬ TRUE; 'q, 'Q => ignoreRemainingSwitches ¬ TRUE; 'r, 'R => { modular ¬ TRUE; roots ¬ CONS[PFS.PathFromRope[inputList.first], roots]; inputList ¬ inputList.rest }; 's, 'S => shortNames ¬ TRUE; 't, 'T => textOnly ¬ TRUE; 'w, 'W => matchWordsOnly ¬ TRUE; ENDCASE => { log.PutF1["invalid switch: $g", [character[Rope.Fetch[key, i]]]]; GO TO quit}; ENDLOOP; }; LOOP; }; EXIT; ENDLOOP; IF modular THEN { dotPos: INT ~ key.Find["."]; IF dotPos<0 THEN CommanderOps.Failed["No dot in key"]; qual ¬ key.Substr[len: dotPos]; key ¬ key.Substr[start: dotPos+1] }; tkey ¬ Rope.ToRefText[key]; FOR i: CARDINAL IN [0..tkey.length) DO tkey[i] ¬ map[tkey[i]]; ENDLOOP; bm ¬ MakeFailureFunctions[tkey]; IF modular THEN { tqual ¬ Rope.ToRefText[qual]; FOR i: CARDINAL IN [0..tqual.length) DO tqual[i] ¬ map[tqual[i]]; ENDLOOP; bmq ¬ MakeFailureFunctions[tqual] }; vmInt ¬ VM.Allocate[bufferPages]; buffer ¬ LOOPHOLE[VM.AddressForPageNumber[vmInt.page]]; IF useVersionMap THEN { mapList: VersionMap.MapList ~ VersionMapDefaults.GetMapList[$Source]; head: LIST OF ROPE ~ LIST[NIL]; last: LIST OF ROPE ¬ head; FOR each: LIST OF ROPE ¬ inputList, each.rest UNTIL each=NIL DO pattern: ROPE ~ IF Rope.Match["*.*", each.first] THEN each.first ELSE Rope.Concat[each.first, ".mesa"]; rangeList: VersionMap.RangeList ~ VersionMap.ShortNameToRanges[mapList, pattern]; FOR r: VersionMap.RangeList ¬ rangeList, r.rest UNTIL r=NIL DO range: VersionMap.Range ¬ r.first; WHILE range.len # 0 DO fullFName: ROPE; [name: fullFName, next: range] ¬ VersionMap.RangeToEntry[range]; last ¬ last.rest ¬ LIST[PFSNames.ShortNameRope[PFS.PathFromRope[fullFName]]]; ENDLOOP; ENDLOOP; ENDLOOP; inputList ¬ head.rest; }; fc ¬ NEW[FileConsumerPrivate ¬ [NEW[MONITORLOCK ¬ []], bm, bmq, buffer, cmd, map, shortNames, listNamesOnly, openFiles, matchWordsOnly, filePos, textOnly, modular]]; IF roots # NIL THEN GenerateRoots[fc, roots, qual, key]; WHILE inputList # NIL DO ENABLE UNWIND => TRUSTED { --give buffer back VM.Free[vmInt]}; pattern: PATH ¬ DefaultToHighestGeneration[inputList.first]; file ¬ inputList.first; inputList ¬ inputList.rest; PFS.EnumerateForNames[pattern, SearchFile ! PFS.Error => IF error.group # bug THEN { out.PutF1["** %g\n", [rope[error.explanation]] ]; LOOP}; ]; IF fc.abort THEN EXIT; ENDLOOP; TRUSTED { VM.Free[vmInt] }; IF fc.abort THEN out.PutRope["\nABORTED\n"]; RETURN[ IF fc.abort THEN $aborted ELSE IF fc.matches = 0 THEN $Failure ELSE NIL, IF listNamesOnly THEN NIL ELSE IO.PutFLR[ "%g%g files, %g matches, %g seconds", LIST[ [rope[IF modular THEN IO.PutFR["%g Suites found, %g DFs tested, %g DFs searched, ", [integer[fc.dfs[suite]]], [integer[fc.dfs[test]]], [integer[fc.dfs[search]]] ] ELSE ""]], [integer[fc.files]], [integer[fc.matches]], [real[BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[] - startTime]]]]] ]; EXITS quit => RETURN[$Failure, NIL]; }; FileConsumer: TYPE ~ REF FileConsumerPrivate; FileConsumerPrivate: TYPE ~ RECORD [ searchLock: Lock, bm, bmq: BMhandle, buffer: LONG POINTER TO Buffer, cmd: Commander.Handle, map: REF Map, shortNames, listNamesOnly, openFiles, matchWordsOnly, filePos, textOnly, modular: BOOL, abort: BOOL ¬ FALSE, files, matches: INT ¬ 0, dfs: ARRAY DfRole OF INT ¬ ALL[0] ]; DfRole: TYPE ~ {suite, test, search}; ConsumeFile: PROC [fc: FileConsumer, fullFName: PATH] RETURNS [continue: BOOL ¬ TRUE] ~ { OPEN fc; worthy: BOOL ¬ TRUE; stream: STREAM; isDir: BOOL ¬ FALSE; vMatches, fMatches: INT ¬ 0; OneSearch: ENTRY PROC [ml: Lock] ~ { ENABLE UNWIND => NULL; localAbort: BOOL ¬ FALSE; IF isDir THEN { cmd.out.PutF1["%g is a directory\n", [rope[PFS.RopeFromPath[fullFName]]]]; Process.CheckForAbort[]; -- may raise ABORTED caught above continue ¬ TRUE; RETURN}; IF stream = NIL THEN { cmd.out.PutF1["%g cannot be opened\n", [rope[PFS.RopeFromPath[fullFName]]]]; Process.CheckForAbort[]; -- may raise ABORTED caught above continue ¬ TRUE; RETURN}; files ¬ files + 1; NULL; { ENABLE UNWIND => stream.Close[]; IF modular THEN { [localAbort, vMatches] ¬ BoyerMooreSearch[fullFName, stream, bmq, buffer, cmd, map, shortNames, listNamesOnly, openFiles, matchWordsOnly, filePos, textOnly, TRUE]; IF localAbort OR vMatches=0 THEN worthy ¬ FALSE ELSE stream.SetIndex[0]; }; IF worthy THEN [localAbort, fMatches] ¬ BoyerMooreSearch[fullFName, stream, bm, buffer, cmd, map, shortNames, listNamesOnly, openFiles, matchWordsOnly, filePos, textOnly, FALSE]; }; IF localAbort THEN abort ¬ TRUE; stream.Close[]; matches ¬ matches + fMatches; continue ¬ NOT abort; RETURN}; [stream, isDir] ¬ OpenFile[fullFName]; OneSearch[searchLock]; RETURN}; DefaultToHighestGeneration: PROC [filePattern: ROPE] RETURNS [path: PATH] = { path ¬ PFS.PathFromRope[filePattern]; IF PFSNames.ShortName[path].version.versionKind # numeric THEN path ¬ PFSNames.SetVersionNumber[path, [highest]]; }; Delta1: TYPE = ARRAY CHAR OF CARDINAL; Delta2: TYPE = RECORD [SEQUENCE COMPUTED CARDINAL OF CARDINAL]; BMhandle: TYPE = REF BMdata; BMdata: TYPE = RECORD [ pattern: REF TEXT, delta1: REF Delta1, delta2: REF Delta2]; MakeFailureFunctions: PROCEDURE [key: REF TEXT] RETURNS [BMhandle] = TRUSTED { c: CARDINAL; length: CARDINAL = key.length; t: CARDINAL ¬ length; delta1: REF Delta1 = NEW[Delta1]; delta2: REF Delta2 = NEW[Delta2[length]]; aux: REF Delta2 ¬ NEW[Delta2[length]]; FOR ch: CHAR IN CHAR DO delta1[ch] ¬ length+1 ENDLOOP; FOR c IN [0..length) DO delta2[c] ¬ length + (delta1[key[c]] ¬ length - c); ENDLOOP; FOR c DECREASING IN [0..length) DO aux[c] ¬ t; WHILE t < length AND key[c] # key[t] DO t ¬ aux[t]; ENDLOOP; t ¬ t - 1; delta2[t] ¬ MIN[delta2[t], length - (c-1)]; ENDLOOP; FOR c IN [0..t] DO delta2[c] ¬ MIN[delta2[c], length+1+t-c] ENDLOOP; RETURN [NEW[BMdata ¬ [pattern: key, delta1: delta1, delta2: delta2]]]}; BoyerMooreSearch: PROC [ file: PATH, stream: STREAM, bm: BMhandle, buffer: LONG POINTER TO Buffer, cmd: Commander.Handle, map: REF Map, shortNames: BOOL ¬ FALSE, listNamesOnly: BOOL ¬ FALSE, openFiles: BOOL ¬ FALSE, matchWordsOnly: BOOL ¬ FALSE, filePos: BOOL ¬ FALSE, textOnly: BOOL ¬ FALSE, noOutput: BOOL ¬ FALSE] RETURNS [abort: BOOLEAN ¬ FALSE, matches: LONG CARDINAL ¬ 0] ~ TRUSTED { ENABLE ABORTED => GO TO aborted; checkAbort: PROC RETURNS [a: BOOL ¬ FALSE] = TRUSTED { Process.CheckForAbort[!ABORTED => {a ¬ TRUE; CONTINUE}]}; firstMatch: BOOL ¬ TRUE; delta1: REF Delta1 = bm.delta1; delta2: REF Delta2 = bm.delta2; pattern: REF TEXT = bm.pattern; length: INT = pattern.length; stopIndexPlusOne: INT ¬ bufferSize-overlap; block: Basics.UnsafeBlock ¬ [LOOPHOLE[buffer], 0, stopIndexPlusOne - 0]; index: INT ¬ length; bytes: INT ¬ 0; winning: INT ¬ 0; -- if # 0, we're in the middle of printing a match, and are willing to scan this many more chars looking for end of line BEGIN -- for ENABLE purposes ENABLE ABORTED => IF stream # NIL THEN stream.Close[]; IF stream = NIL THEN { cmd.out.PutF1["%g cannot be opened\n", [rope[PFS.RopeFromPath[file]]]]; Process.CheckForAbort[]; -- may raise ABORTED caught above RETURN[FALSE, 0]}; DO UNTIL index <= block.startIndex DO -- get enough characters to look at got: INT; IF IO.EndOf[stream] OR abort OR (abort ¬ checkAbort[]) THEN {IF winning # 0 THEN cmd.out.PutRope["\n"]; stream.Close[]; RETURN}; IF block.startIndex = stopIndexPlusOne THEN { Basics.MoveBytes[ dstBase: block.base, dstStart: 0, srcBase: block.base, srcStart: (stopIndexPlusOne-overlap), count: overlap ]; index ¬ index - (stopIndexPlusOne-overlap); block.startIndex ¬ overlap; stopIndexPlusOne ¬ bufferSize}; block.count ¬ stopIndexPlusOne - block.startIndex; got ¬ stream.UnsafeGetBlock[block]; block.startIndex ¬ block.startIndex + got; bytes ¬ bytes + got; IF winning # 0 THEN [index, winning] ¬ ReportMatch[cmd.out, block, index, winning, 0, textOnly]; ENDLOOP; FOR keyIndex: CARDINAL DECREASING IN [0..length) DO IF map[buffer[index ¬ index-1]] # pattern[keyIndex] THEN { index ¬ index + MAX[delta1[map[buffer[index]]], delta2[keyIndex]]; EXIT}; REPEAT FINISHED => { -- found a match IF NOT matchWordsOnly OR ((index = 0 OR NonAlpha[map[buffer[index-1]]]) AND (index+length >= block.startIndex+block.count OR NonAlpha[map[buffer[index+length]]])) THEN { matches ¬ matches + 1; IF noOutput THEN RETURN; IF firstMatch THEN { firstMatch ¬ FALSE; IF listNamesOnly THEN { IO.PutF1[cmd.out, "%g", IO.rope[IF shortNames THEN PFSNames.ShortNameRope[file] ELSE PFS.RopeFromPath[file]]]; IF filePos THEN IO.PutF1[cmd.out, "|%g", IO.int[bytes+index-block.startIndex]]; IO.PutChar[cmd.out, '\n]; RETURN; }; IF openFiles THEN { IO.Close[stream]; [] ¬ CommanderOps.DoCommand[IO.PutFR["Open \"%g|%g\"", [rope[PFS.RopeFromPath[file]]], [integer[bytes+index-block.startIndex]]], cmd]; RETURN; }; IF NOT textOnly THEN { IF shortNames THEN IO.PutF[cmd.out, "\n%l***\t%g%l\n", [rope["b"]], [rope[PFSNames.ShortNameRope[file]]], [rope["B"]]] ELSE IO.PutF[cmd.out, "%l%g%l\n", [rope["b"]], [rope[PFS.RopeFromPath[file]]], [rope["B"]]]; }; }; [index, winning] ¬ ReportMatch[ report: cmd.out, block: block, at: index --+length-1--, limit: 0, total: bytes+index --+length-1-- -block.startIndex, textOnly: textOnly]; } ELSE {index ¬ index + length}; index ¬ (IF abort ¬ checkAbort[] THEN block.startIndex ELSE index) + 2; }; ENDLOOP; ENDLOOP; END; -- of nested ABORTED catchphrase EXITS aborted => {RETURN [TRUE, matches]}; }; ReportMatch: PROC [ report: STREAM, block: Basics.UnsafeBlock, at, limit: INT, total: INT ¬ 0, textOnly: BOOL] RETURNS [lastUsed, waitingForCR: CARDINAL] = TRUSTED { BytePointer: UNSAFE PROC [ptr: POINTER] RETURNS [POINTER TO Basics.RawBytes] ~ INLINE { RETURN [LOOPHOLE[ptr]]; }; extend: INT = IF limit = 0 THEN overlap ELSE limit; backTo: INT = MAX[at, extend] - extend; forwardTo: INT = MIN[at+extend, block.startIndex]; IF limit = 0 THEN { IF NOT textOnly THEN report.PutF1["%5g: ", [cardinal[total]]]; FOR cr: INT DECREASING IN [backTo..at) DO c: CHAR = VAL[BytePointer[block.base][cr]]; SELECT c FROM '\l, '\r, '\n => { report.UnsafePutBlock[[block.base, cr+1, at - cr-1]]; EXIT; }; ENDCASE; REPEAT FINISHED => { IF total > extend THEN report.PutRope["..."]; report.UnsafePutBlock[[block.base, backTo, at - backTo]]}; ENDLOOP; }; FOR cr: INT IN [at..forwardTo) DO c: CHAR = VAL[BytePointer[block.base][cr]]; SELECT c FROM '\l, '\r, '\n => { report.UnsafePutBlock[[block.base, at, cr+1 - at]]; RETURN [cr, 0]; }; ENDCASE; ENDLOOP; report.UnsafePutBlock[[block.base, at, forwardTo - at]]; IF (waitingForCR ¬ at+extend - forwardTo) = 0 THEN report.PutRope["...\n"]; lastUsed ¬ forwardTo - 1; }; CheckDFData: TYPE = REF CheckDFDataRec; CheckDFDataRec: TYPE = RECORD [dfName: PATH, state: State]; CheckFileData: TYPE = REF CheckFileDataRec; CheckFileDataRec: TYPE = RECORD [fileName: PATH, state: State]; State: TYPE = REF StateRec; -- modification of state must always be monitored StateRec: TYPE = RECORD [ dfSeen: SymTab.Ref, -- SymTab [DF full Name -> NIL]: list of all DF full names already seen (or being seen) moduleName, functionName: ROPE, fc: FileConsumer, pool: ProcessPool, matches: INT ¬ 0 ]; GenerateRoots: PROC [fc: FileConsumer, roots: LIST OF PATH, moduleName, functionName: ROPE] = { Abort: PROC ~ {fc.abort ¬ TRUE; fc.cmd.err.PutRope["\n ABORTED seen!\n"]}; time: BasicTime.GMT ¬ BasicTime.Now[]; state: State = NEW [StateRec ¬ [ moduleName: moduleName, functionName: functionName, dfSeen: SymTab.Create[case: FALSE], fc: fc, pool: CreateProcessPool[Abort] ]]; FOR rootList: LIST OF PATH ¬ roots, rootList.rest WHILE rootList#NIL DO [] ¬ ForkInPool[state.pool, CheckDF, NEW [CheckDFDataRec ¬ [dfName: rootList.first, state: state]], [priority: background, usePriority: TRUE]]; ENDLOOP; JoinPool[state.pool]; RETURN}; IncDfs: ENTRY PROC [ml: Lock, fc: FileConsumer, role: DfRole] ~ { ENABLE UNWIND => NULL; fc.dfs[role] ¬ fc.dfs[role]+1; RETURN}; CheckDF: PROC [data: REF] RETURNS [results: REF ¬ NIL] --CedarProcess.ForkableProc-- = { ScanDf: PROC [dfName: ROPE] ~ { IncDfs[fc.searchLock, fc, test]; todo ¬ CONS[dfName, todo]; DFUtilities.ParseFromFile[dfName, ProcessItem1 ! PFS.Error => {state.fc.cmd.out.PutF1["Cannot open: %g\n", [rope[dfName]]]; GOTO Fails}; DFUtilities.FileSyntaxError => {state.fc.cmd.out.PutF1["Cannot parse: %g\n", [rope[dfName]]]; GOTO Fails}]; EXITS Fails => {}; }; ProcessItem1: PROC [item: REF ANY] RETURNS [stop: BOOL ¬ FALSE] --DFUtilities.ProcessItemProc-- = { IF fc.abort THEN RETURN [TRUE]; WITH item SELECT FROM directory: REF DFUtilities.DirectoryItem => {}; file: REF DFUtilities.FileItem => IF Rope.Match[importeePattern, file.name, FALSE] THEN defining ¬ TRUE; import: REF DFUtilities.ImportsItem => { DFUtilities.SortUsingList[import.list, TRUE]; IF import.form=list AND DFUtilities.SearchUsingList[importeeName, import.list].found THEN importing ¬ TRUE; IF NOT import.exported THEN NULL ELSE IF SuiteTop[import.path1] THEN { new: CheckDFData = NEW [CheckDFDataRec ¬ [dfName: PFS.PathFromRope[import.path1], state: state]]; [] ¬ ForkInPool[state.pool, CheckDF, new]; } ELSE ScanDf[import.path1]; }; include: REF DFUtilities.IncludeItem => { IF SuiteTop[include.path1] THEN { new: CheckDFData = NEW [CheckDFDataRec ¬ [dfName: PFS.PathFromRope[include.path1], state: state]]; [] ¬ ForkInPool[state.pool, CheckDF, new]; } ELSE ScanDf[include.path1]}; ENDCASE => ERROR; }; processes: LIST OF CedarProcess.Process ¬ NIL; checkDFData: CheckDFData = NARROW [data]; dfName: ROPE = PFS.RopeFromPath[checkDFData.dfName]; state: State = checkDFData.state; fc: FileConsumer ~ state.fc; importing, defining: BOOL ¬ FALSE; importeeName: ROPE = Rope.Concat[state.moduleName, ".mob"]; importeePattern: ROPE = Rope.Concat[state.moduleName, ".mob*"]; todo: LIST OF ROPE ¬ NIL; IF fc.abort THEN RETURN; IF SymTab.Fetch[state.dfSeen, dfName].found THEN RETURN; [] ¬ SymTab.Store[state.dfSeen, dfName, NIL]; IncDfs[fc.searchLock, fc, suite]; ScanDf[dfName]; IF importing OR defining THEN { FOR dolist: LIST OF ROPE ¬ todo, dolist.rest WHILE dolist#NIL DO currentDirectory: PATH ¬ PFS.PathFromRope["?no directory given?"]; ProcessItem2: PROC [item: REF ANY] RETURNS [stop: BOOL ¬ FALSE] --DFUtilities.ProcessItemProc-- = { IF fc.abort THEN RETURN [TRUE]; WITH item SELECT FROM directory: REF DFUtilities.DirectoryItem => currentDirectory ¬ PFS.PathFromRope[directory.path1]; file: REF DFUtilities.FileItem => IF Rope.Match["*.mesa!*", file.name, FALSE] THEN { new: CheckFileData = NEW [CheckFileDataRec ¬ [fileName: PFSNames.ExpandName[PFS.PathFromRope[file.name], currentDirectory], state: state]]; [] ¬ ForkInPool[state.pool, CheckFile, new]; }; import: REF DFUtilities.ImportsItem => {}; include: REF DFUtilities.IncludeItem => {}; ENDCASE => ERROR; }; IncDfs[fc.searchLock, fc, search]; DFUtilities.ParseFromFile[dolist.first, ProcessItem2]; ENDLOOP; }; RETURN}; suite: ROPE ~ "-Suite.df"; suiteLen: INT ~ Rope.Length[suite]; SuiteTop: PROC[name: ROPE] RETURNS [BOOL] ~ { shortNameRope: ROPE ¬ PFSNames.ShortNameRope[PFS.PathFromRope[name]]; suitePos: INT ¬ Rope.Find[shortNameRope, suite, 0, FALSE]; RETURN[(suitePos > 0) AND ((Rope.Length[shortNameRope] - suiteLen) = suitePos)]; }; CheckFile: CedarProcess.ForkableProc = { checkFileData: CheckFileData = NARROW [data]; IF checkFileData.state.fc.abort THEN RETURN; [] ¬ ConsumeFile[checkFileData.state.fc, checkFileData.fileName]; }; ProcessPool: TYPE = REF ProcessPoolRec; ProcessPoolRec: TYPE = RECORD [ lock: Lock, resultAction: ResultActionProc ¬ NIL, WhenAbort: PROC, processes: SEQUENCE maxProcesses: NAT OF CedarProcess.Process ]; ResultActionProc: TYPE = PROC [REF]; -- applied to results of actions, only if those results are NON-NIL CreateProcessPool: PROC [WhenAbort: PROC, maxProcesses: NAT ¬ 4, resultAction: ResultActionProc ¬ NIL] RETURNS [pool: ProcessPool] = { pool ¬ NEW [ProcessPoolRec[maxProcesses]]; pool.lock ¬ NEW[MONITORLOCK ¬ []]; TRUSTED { pool.WhenAbort ¬ WhenAbort }; TRUSTED { pool.resultAction ¬ resultAction }; }; ForkInPool: PROC [pool: ProcessPool, action: CedarProcess.ForkableProc, data: REF ¬ NIL, options: CedarProcess.ForkOptions ¬ CedarProcess.DefaultForkOptions] RETURNS [forked: BOOL ¬ FALSE] = { ENABLE ABORTED => {pool.WhenAbort[]; CONTINUE}; IF TryToForkInPool[pool.lock, pool, action, data, options] THEN RETURN[TRUE]; Process.Yield[]; IF TryToForkInPool[pool.lock, pool, action, data, options] THEN RETURN[TRUE]; BEGIN results: REF = action[data]; IF results#NIL AND pool.resultAction#NIL THEN pool.resultAction[results]; RETURN [FALSE]; END; }; TryToForkInPool: ENTRY PROC [ml: Lock, pool: ProcessPool, action: CedarProcess.ForkableProc, data: REF, options: CedarProcess.ForkOptions] RETURNS [forked: BOOL] = { ENABLE UNWIND => NULL; LaunderPool[pool]; FOR i: NAT IN [0 .. pool.maxProcesses) DO IF pool[i]=NIL THEN {pool[i] ¬ CedarProcess.Fork[action, data, options]; RETURN [TRUE]}; ENDLOOP; RETURN [FALSE]}; JoinPool: PROC [pool: ProcessPool] = { DO ENABLE ABORTED => {pool.WhenAbort[]; CONTINUE}; IF IsPoolDone[pool.lock, pool] THEN EXIT; Process.Pause[Process.MsecToTicks[1000]]; ENDLOOP; }; IsPoolDone: ENTRY PROC [ml: Lock, pool: ProcessPool] RETURNS [done: BOOL ¬ FALSE] = { ENABLE UNWIND => NULL; LaunderPool[pool]; FOR i: NAT IN [0 .. pool.maxProcesses) DO IF pool[i]#NIL THEN RETURN [FALSE] ENDLOOP; RETURN[TRUE]}; LaunderPool: INTERNAL PROC [pool: ProcessPool] = { FOR i: NAT IN [0 .. pool.maxProcesses) DO IF pool[i]=NIL THEN LOOP; IF pool[i].status=aborted THEN {pool[i] ¬ NIL; LOOP}; IF pool[i].status=done THEN { IF pool[i].results#NIL AND pool.resultAction#NIL THEN pool.resultAction[pool[i].results]; pool[i] ¬ NIL; }; ENDLOOP; }; usage: ROPE ~" \tSearch list of files for occurrences of a string token. \tUsage: QFind [option(s)] \tOptions: \t\t-c\t\t\t\tbe case sensitive \t\t-f\t\t\t\tlist file names only \t\t-o\t\t\t\topen files successfully searched \t\t-d \tcheck files in DF's directory clause \t\t-m\t\t\tuse source version maps to expand names \t\t-p\t\t\t\tprint file position of first match \t\t-q\t\t\t\t(last option) if search key starts with dash \t\t-s\t\t\t\tprint short names of files \t\t-t\t\t\t\ttext only (don't give positions or file names) \t\t-w\t\t\tmatch words only"; Commander.Register["QFind", QFindCmd, usage]; END. QFind.mesa Copyright Σ 1985, 1987, 1988, 1989, 1990, 1991, 1992 by Xerox Corporation. All rights reserved. Sweet December 5, 1985 10:52:28 am PST Eric Nickell, March 25, 1987 10:58:52 pm PST Bloomenthal, December 12, 1990 9:20 pm PST Michael Plass, February 20, 1992 1:35 pm PST Tim Diebert: March 29, 1989 1:27:35 pm PST Russ Atkinson (RRA) June 21, 1989 8:14:02 pm PDT Wes Irish, October 4, 1989 10:11:25 am PDT Willie-s, June 16, 1992 6:02 pm PDT Last tweaked by Mike Spreitzer February 7, 1992 5:45 pm PST ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- Boyer-Moore parsing: construct tables of offsets for when match fails ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- See Knuth, Morris, Pratt, "Fast Pattern...", SIAM J. Comp, June 1977 1/12/84:RSS Above algorithm (dd') fails for patterns in which all characters are identical. Changed implementation to calculate dd instead of dd' in p342 algorithm. block points to remaining empty buffer beyond file contents currently there ReportMatch ignores the count field of block initial match as opposed to later search for closing CR ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- DF enumeration and clipping ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- we assume no perverse names for df files ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- Limited Processing ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- resultAction is only called for processes which are done and when results are not NIL No more process possible in the pool, we do not fork! ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- Command Line Registration ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ΚΛ•NewlineDelimiter –(cedarcode) style˜codešœ ™ Kšœ ΟeœU™`KšΟy&™&Kšž,™,K™*K™,K™*K™0K™*K™#K™;K™šΟk ˜ KšœŸœ˜KšœŸœ$˜0Kšœ ŸœŸœ ˜DKšœ ˜ Kšœ Ÿœ!˜0Kšœ Ÿœ"˜4Kšœ ŸœŸ˜°Kšœ Ÿœ+˜:KšŸœ˜KšŸœŸœFŸœJ˜Kšœ Ÿœ:˜HKšœŸœ,˜9Kšœ˜K˜Kšœ ŸœC˜SKšœŸœ˜&KšŸœŸœ@˜H——K˜š ΠlnœŸœŸœŸœŸœ ˜,KšŸœZŸœŸœCŸ˜­—šœŸ˜K˜KšŸœŸœŸœŸœ˜KšŸœŸœŸœ˜KšŸœŸœŸœŸœ˜K˜KšœŸœŸœŸ œ˜K˜Kšœ ŸœΟc“˜¬Kšœ Ÿœ˜Kšœ ŸœŸœ˜3KšœŸœŸœŸœŸœŸœŸœŸœ˜BKš œŸœŸœŸœŸœŸœŸœ˜&K˜šΟnœŸœŸœŸœŸœŸœ ŸœŸœ˜OKšœŸœ ˜Kš œœŸœ Ÿœ ŸœŸœŸœ˜FKšŸœŸœŸœŸœ˜KšŸœŸœŸœ ŸœŸœŸœŸœ˜ZšœŸœ˜Kš œŸœ ŸœŸœŸœ˜3—Kšœ˜K˜—•StartOfExpansionL -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š ’œŸœŸœŸœŸœ˜.KšŸœŸœŸœ˜6Kšœ˜K˜—š’œ˜#KšœŸœŸœ ˜KšœŸœŸœ ˜Kšœ ŸœŸœŸœ˜KšœŸœŸœŸœ˜KšœŸœ ˜KšœŸœ˜K˜K˜9KšœŸœ˜KšœŸœ˜ Kšœ ŸœŸœ˜Kšœ ŸœŸœ˜Kšœ ŸœŸœ˜KšœŸœŸœ˜Kšœ ŸœŸœ˜Kšœ ŸœŸœ˜KšœŸœŸœ˜KšœŸœŸœ˜Kšœ ŸœŸœ˜KšœŸœŸœ˜&Kšœ ŸœŸœ˜Kš œŸœŸœŸœŸœ˜KšœŸœ˜K˜š’ œŸœŸœŸœŸœŸœŸœŸœŸœŸœ˜NšΠbn œ!˜,šŸœŸœŸ˜KšœŸœ&Ÿœ'˜WKšŸœ˜—K˜—KšœŸœ˜KšœŸœŸœŸœ˜K˜ šœ Ÿœ Ÿœ3˜NšŸœ ˜šŸœŸœ˜KšŸœ7˜9KšŸœ˜ Kšœ˜—Kšœ˜——KšŸœŸœŸœŸœ˜KšŸœ ŸœŸœ Ÿœ˜)Kšœ2Ÿœ˜LKšŸœ˜šŸ˜KšœŸœ˜ —K˜—š£ œŸœ ˜KšŸœ˜K˜—KšœpŸœŸœ˜ƒKš Ÿœ ŸœŸœŸœŸœ ˜+KšœŸœ˜š ŸœŸœŸœŸœŸ˜Kš œ ŸœŸœ Ÿœ Ÿœ˜2KšŸœ˜—šŸœ˜Kšœ2˜2š ŸœŸœŸœŸœŸœ˜XšŸœŸœ˜šŸœ˜Kšœ˜Kšœ˜K˜'Kšœ˜—šŸœ˜šŸœŸœŸœŸ˜$šŸœŸ˜Kš œ ŸœŸœŸœ Ÿœ Ÿœ˜8KšœŸœ˜KšœŸœ˜KšœŸœ˜KšœŸœ˜Kšœ$Ÿœ˜)šœ ˜ Kšœ Ÿœ˜Kšœ ŸœŸœ'˜8K˜K˜—KšœŸœ˜KšœŸœ˜KšœŸœ˜ šŸœ˜ KšœBŸœŸœ˜N——KšŸœ˜—Kšœ˜——KšŸœ˜Kšœ˜—KšŸœ˜KšŸœ˜—šŸœ Ÿœ˜KšœŸœ˜KšŸœ Ÿœ&˜6K˜K˜!K˜—Kšœ˜šŸœŸœŸœŸ˜&K˜KšŸœ˜—Kšœ ˜ šŸœ Ÿœ˜K˜šŸœŸœŸœŸ˜'K˜KšŸœ˜—K˜!K˜—KšœŸœ˜!Kšœ ŸœŸœ#˜7šŸœŸœ˜KšœE˜EKš œŸœŸœŸœŸœŸœ˜KšœŸœŸœŸœ˜š ŸœŸœŸœŸœŸœŸœŸ˜?Kš œ ŸœŸœŸœ Ÿœ"˜gKšœQ˜QšŸœ-ŸœŸœŸ˜>K˜"šŸœŸ˜Kšœ Ÿœ˜K˜@KšœŸœŸœ˜MKšŸœ˜—KšŸœ˜—KšŸœ˜—K˜Kšœ˜—KšœŸœŸœŸ œv˜₯KšŸœ ŸœŸœ%˜8šŸœ ŸœŸ˜šŸœ˜šŸœŸœ‘˜&KšŸœ˜——Kšœ Ÿœ/˜š ŸœŸœŸ œŸœŸ˜)KšœŸœŸœ˜+šŸœŸ˜ ˜K˜5KšŸœ˜Kšœ˜—KšŸœ˜—šŸœŸœ˜KšŸœŸœ˜-K˜:—KšŸœ˜—Kšœ˜—šŸœŸœŸœŸ˜!KšœŸœŸœ˜+šŸœŸ˜ ˜K˜3KšŸœ ˜K˜—KšŸœ˜—KšŸœ˜—K˜8KšŸœ,Ÿœ˜KK˜K˜K˜—KšœE™EK™KšœE™EKšœ ŸœŸœ˜'KšœŸœŸœ Ÿœ˜;K˜KšœŸœŸœ˜+šœŸœŸœ Ÿœ˜?K˜—KšœŸœŸœ ‘1˜Mšœ ŸœŸœ˜Kšœ‘W˜kKšœŸœ˜ K˜Kšœ˜Kšœ Ÿœ˜Kšœ˜K˜—š ’ œŸœŸœŸœŸœŸœ˜_Kš’œŸœŸœ,˜JKšœŸœ˜&šœŸœ˜ Kšœ4˜4KšœŸœ˜#K˜Kšœ’œ˜Kšœ˜—š Ÿœ ŸœŸœŸœŸœ ŸœŸ˜GKšœ%Ÿœ`Ÿœ˜KšŸœ˜—Kšœ˜KšŸœ˜K˜—š’œŸœŸœ/˜AKšŸœŸœŸœ˜K˜KšŸœ˜K˜—š’œŸœŸœŸœ ŸœŸœ‘œ˜Xš’œŸœ Ÿœ˜Kšœ ˜ KšœŸœ˜˜1KšŸœHŸœ ˜XKšœ^Ÿœ ˜k—KšŸœ˜K˜—š’ œŸœŸœŸœŸœŸœŸœ‘œ˜cKšŸœ ŸœŸœŸœ˜šŸœŸœŸ˜Kšœ Ÿœ"˜0šœŸœ˜$KšŸœ(ŸœŸœ Ÿœ˜F—šœŸœ˜*Kšœ'Ÿœ˜-KšŸœŸœ>Ÿœ Ÿœ˜kKšŸœŸœŸœŸ˜ šŸœŸœŸœ˜%KšœŸœŸœ,˜aK˜*K˜—KšŸœ˜K˜—šœ Ÿœ˜)šŸœŸœ˜!KšœŸœŸœ-˜bK˜*K˜—KšŸœ˜—KšŸœ Ÿœ˜—K˜—Kšœ ŸœŸœŸœ˜.KšœŸœ˜)KšœŸœŸœ"˜4Kšœ!˜!K˜KšœŸœŸœ˜"KšœŸœ)˜;KšœŸœ*˜?Kš œŸœŸœŸœŸœ˜KšŸœ ŸœŸœ˜KšŸœ*ŸœŸœ˜8Kšœ(Ÿœ˜-K˜!K˜šŸœ Ÿœ Ÿœ˜š Ÿœ ŸœŸœŸœŸœŸœŸ˜@KšœŸœŸœ&˜Bš’ œŸœŸœŸœŸœŸœŸœ‘œ˜cKšŸœ ŸœŸœŸœ˜šŸœŸœŸ˜Kšœ Ÿœ1Ÿœ˜ašœŸœŸœ#Ÿœ˜NšŸœ˜KšœŸœ4Ÿœ<˜‹K˜,K˜——KšœŸœ ˜+Kšœ Ÿœ ˜,KšŸœ Ÿœ˜—K˜—Kšœ"˜"K˜6KšŸœ˜—Kšœ˜—KšŸœ˜K˜—KšœŸœ˜šœ Ÿœ˜#K˜—š ’œŸœŸœŸœŸœ˜-K™(KšœŸœŸœ˜EKšœ Ÿœ&Ÿœ˜:KšŸœŸœ7˜PK˜K˜—š’ œ˜(KšœŸœ˜-KšŸœŸœŸœ˜,K˜AK˜K˜—KšœE™EK™KšœE™EK™Kšœ ŸœŸœ˜'šœŸœŸœ˜K˜ Kšœ!Ÿœ˜%Kš’ œŸœ˜Kšœ ŸœŸœŸœ˜=K˜K˜—šœŸœŸœŸœ‘C˜hK˜—š’œŸœ’ œŸœŸœ'ŸœŸœ˜†KšœŸœ ˜*Kšœ ŸœŸ œ˜"KšŸœ ˜'KšŸœ&˜-K˜K˜—KšœU™Uš’ œŸœ>ŸœŸœGŸœ ŸœŸœ˜ΐKšŸœŸœŸœ˜/KšŸœ9ŸœŸœŸœ˜MK˜KšŸœ9ŸœŸœŸœ˜MKšœ5™5šŸ˜Kšœ Ÿœ˜Kš Ÿœ ŸœŸœŸœŸœ˜IKšŸœŸœ˜KšŸœ˜—K˜K˜—š ’œŸœŸœHŸœ%Ÿœ Ÿœ˜₯KšŸœŸœŸœ˜Kšœ˜šŸœŸœŸœŸ˜)Kš Ÿœ ŸœŸœ6ŸœŸœ˜XKšŸœ˜—KšŸœŸœ˜K˜—š’œŸœ˜&šŸ˜KšŸœŸœŸœ˜/KšŸœŸœŸœ˜)Kšœ)˜)KšŸœ˜—K˜K˜—š ’ œŸœŸœŸœŸœŸœ˜UKšŸœŸœŸœ˜Kšœ˜šŸœŸœŸœŸ˜)Kš Ÿœ ŸœŸœŸœŸœ˜"KšŸœ˜—KšŸœŸœ˜K˜—š’ œŸœŸœ˜2šŸœŸœŸœŸ˜)KšŸœ ŸœŸœŸœ˜KšŸœŸœ ŸœŸœ˜5šŸœŸœ˜Kš ŸœŸœŸœŸœŸœ$˜YKšœ Ÿœ˜Kšœ˜—KšŸœ˜—K˜K˜—K™KšœE™EK™KšœE™EK™KšœŸœΩ˜δK˜Kšœ-˜-K˜—KšŸœ˜K˜K˜—…—X }ρ