<> <> <> <> <> <<>> DIRECTORY Ascii USING [NUL, SP, TAB], Convert USING [CardFromRope, RopeFromCard], IO USING [BreakProc, GetTokenRope, STREAM], PFSBackdoor, PFSNames, PFSCanonicalNames, Rope USING [Concat, Substr, Fetch, IsEmpty, Length]; PFSCanonicalNamesImpl: CEDAR PROGRAM IMPORTS Convert, IO, Rope, PFSBackdoor, PFSNames EXPORTS PFSCanonicalNames SHARES PFSNames ~ BEGIN OPEN PFSNames; Error: ERROR ~ CODE; Unparsed: TYPE ~ RECORD [ canonicalUnparsing: ROPE ¬ NIL ]; UnparseName: PUBLIC PROC [name: PATH] RETURNS [fullFName: ROPE] ~ { unparsed: REF Unparsed; serverComponent: BOOL ¬ TRUE; cproc: ComponentProc ~ { fullFName ¬ Rope.Concat[fullFName, UnparseComponent[comp]]; IF serverComponent THEN { IF NOT comp.name.len=0 THEN fullFName ¬ Rope.Concat[fullFName, ":"]; serverComponent ¬ FALSE; } }; sproc: SeparatorProc ~ { IF NOT serverComponent THEN { IF separatorPresent THEN fullFName ¬ Rope.Concat[fullFName, "/"]; } ELSE { IF NOT separatorPresent THEN serverComponent ¬ FALSE; }; }; name ¬ PFSNames.NonNIL[name]; unparsed ¬ WITH name.GetUnparsingHint[] SELECT FROM unp: REF Unparsed => unp, ENDCASE => NIL; IF unparsed#NIL THEN RETURN [unparsed.canonicalUnparsing]; Map[name, cproc, sproc]; }; UnparseComponent: PUBLIC PROC [component: Component] RETURNS [ROPE] ~ { RETURN[Rope.Concat[ component.name.base.Substr[component.name.start, component.name.len], UnparseVersion[component.version]]]; }; UnparseVersion: PUBLIC PROC [version: Version] RETURNS [ROPE] ~ { RETURN [ SELECT version.versionKind FROM none => NIL, lowest => "!L", highest => "!H", all => "!*", numeric => Rope.Concat["!", Convert.RopeFromCard[version.version]], ENDCASE => NIL ]; }; ParseName: PUBLIC PROC [name: ROPE] RETURNS [parsedName: PATH] ~ { <> absolute, directory: BOOL ¬ FALSE; comps: LIST OF Component ¬ NIL; IF name.IsEmpty[] THEN RETURN [NIL]; absolute ¬ name.Fetch[0]='/; IF absolute THEN comps ¬ CONS[ [name~["", 0, 0]], comps ]; directory ¬ name.Fetch[name.Length[]-1]='/; { ENABLE Error => PFSBackdoor.ProduceError[invalidNameSyntax, Rope.Concat[name, " is syntactically invalid as a Unix-style file name"]]; pos: NAT ¬ IF absolute THEN 1 ELSE 0; limit: NAT ¬ name.Length[]; firstComponent: BOOL ¬ TRUE; ScanComponent: PROC [] RETURNS [] ~ { nextComp: Component; [nextComp, pos] ¬ ParseComponent[name, pos]; IF firstComponent AND NOT absolute THEN { len: INT ¬ nextComp.name.len; IF len > 0 THEN IF Rope.Fetch[nextComp.name.base, nextComp.name.start+len-1] = ': THEN { absolute ¬ TRUE; nextComp.name.len ¬ nextComp.name.len-1; }; }; firstComponent ¬ FALSE; comps ¬ CONS[nextComp, comps]; }; ScanSeparator: PROC [] RETURNS [] ~ { IF name.Fetch[pos]#'/ THEN ERROR Error[]; pos ¬ pos+1; }; DO IF pos >= limit THEN EXIT; ScanComponent[]; IF pos >= limit THEN EXIT; ScanSeparator[]; ENDLOOP; }; RETURN[ConstructName[components: comps, absolute: absolute, directory: directory, reverse: TRUE, unparsed: NEW[Unparsed ¬ [name]] ]]; }; ParseComponent: PUBLIC PROC [name: ROPE, first: NAT ¬ 0] RETURNS [component: Component ¬ [], next: NAT] ~ { limit: NAT ¬ name.Length[]; next ¬ first; DO IF next >= limit THEN { component.name ¬ [name, first, next-first]; EXIT; }; SELECT name.Fetch[next] FROM '! => { component.name ¬ [name, first, next-first]; [component.version, next] ¬ ParseVersion[name, next]; EXIT; }; '/ => { component.name ¬ [name, first, next-first]; EXIT; }; ENDCASE => next ¬ next+1; ENDLOOP; }; ParseVersion: PUBLIC PROC [v: ROPE, first: NAT ¬ 0] RETURNS [version: Version ¬ [none], next: NAT] ~ { limit: NAT ¬ v.Length[]; next ¬ first; IF next>=limit THEN RETURN; IF v.Fetch[next]#'! THEN ERROR Error[]; next ¬ next+1; IF next>=limit THEN RETURN; SELECT v.Fetch[next] FROM IN ['0..'9] => { start: NAT ¬ next; next ¬ next+1; WHILE next < limit AND v.Fetch[next] IN ['0..'9] DO next ¬ next+1 ENDLOOP; RETURN [ [numeric, Convert.CardFromRope[v.Substr[start, next-start]]], next ]; }; 'H, 'h => RETURN[ [highest], next+1 ]; 'L, 'l => RETURN[ [lowest], next+1 ]; 'N, 'n => RETURN[ [next], next+1 ]; '* => RETURN[ [all], next+1 ]; ENDCASE => NULL; ERROR Error[]; }; ParseNameFromStream: PUBLIC PROC [s: IO.STREAM] RETURNS [name: PATH] ~ { <> FileNameProc: IO.BreakProc ~ { OPEN Ascii; RETURN [ SELECT char FROM NUL, SP, TAB => sepr, ENDCASE => other ]; }; ropeName: ROPE ¬ IO.GetTokenRope[s, FileNameProc].token; RETURN[ParseName[ropeName]]; }; END.