<> <> <> <<>> DIRECTORY ImmutableTioga, Rope, Rosary, TextLooks; ImmutableTiogaImpl: CEDAR PROGRAM IMPORTS Rope, Rosary, TextLooks EXPORTS ImmutableTioga ~ BEGIN OPEN ImmutableTioga; NChildren: PUBLIC PROC [node: Node] RETURNS [INT] ~ { children: ROSARY ~ node.children; RETURN [IF children=NIL THEN 0 ELSE children.size]; }; NthChild: PUBLIC PROC [node: Node, i: INT] RETURNS [Node] ~ { WITH Rosary.Fetch[node.children, i] SELECT FROM child: Node => RETURN[child]; ENDCASE => RETURN[NIL]; }; Index: TYPE ~ REF IndexRep; IndexRep: TYPE ~ RECORD [depth: NAT, seq: SEQUENCE size: NAT OF INT]; Path: TYPE ~ REF PathRep; PathRep: TYPE ~ RECORD [depth: NAT, seq: SEQUENCE size: NAT OF Node]; IndexedNode: PROC [root: Node, index: Index] RETURNS [Node] ~ { node: Node _ root; FOR k: NAT IN [0..index.depth) DO node _ NthChild[node, index[k]]; ENDLOOP; RETURN[node]; }; SetPath: PROC [root: Node, index: Index, path: Path] ~ { depth: NAT ~ index.depth; node: Node _ root; FOR k: NAT IN [0..depth) DO path[k] _ node; node _ NthChild[node, index[k]]; ENDLOOP; path[depth] _ node; path.depth _ depth+1; }; <<>> PropListGet: PUBLIC PROC [propList: PropList, name: ATOM] RETURNS [value: REF] ~ { FOR list: PropList _ propList, list.rest UNTIL list=NIL DO prop: Prop ~ list.first; IF prop.name=name THEN RETURN[prop.value]; ENDLOOP; RETURN[NIL]; }; PropListPut: PUBLIC PROC [propList: PropList, name: ATOM, value: REF] RETURNS [PropList] ~ { IF PropListGet[propList, name]=value THEN RETURN [propList] ELSE { rem: PropList ~ PropListRem[propList, name]; IF value=NIL THEN RETURN [rem] ELSE RETURN [CONS[NEW[PropRep _ [name, value]], rem]]; }; }; PropListRem: PROC [propList: PropList, name: ATOM] RETURNS [PropList] ~ { IF propList=NIL THEN RETURN [NIL] ELSE IF propList.first.name=name THEN RETURN [propList.rest] ELSE { remRest: PropList ~ PropListRem[propList.rest, name]; IF propList.rest=remRest THEN RETURN [propList] ELSE RETURN [CONS[propList.first, remRest]]; }; }; aComment: ATOM ~ $Comment; aFormat: ATOM ~ $Format; aStyleDef: ATOM ~ $StyleDef; aPrefix: ATOM ~ $Prefix; aPostfix: ATOM ~ $Postfix; aArtwork: ATOM ~ $Artwork; refBool: ARRAY BOOL OF REF BOOL ~ [FALSE: NEW[BOOL _ FALSE], TRUE: NEW[BOOL _ TRUE]]; PropsGet: PUBLIC PROC [props: Props, name: ATOM] RETURNS [value: REF] ~ { SELECT name FROM aComment => value _ refBool[props.comment]; aFormat => value _ props.formatName; ENDCASE => value _ PropListGet[props.propList, name]; }; PropsPut: PUBLIC PROC [props: Props, name: ATOM, value: REF] RETURNS [Props] ~ { propList: PropList _ props.propList; format: ATOM _ props.formatName; comment: BOOL _ props.comment; SELECT name FROM aComment => comment _ WITH value SELECT FROM x: REF BOOL => x^, ENDCASE => FALSE; aFormat => format _ WITH value SELECT FROM x: ATOM => x, ENDCASE => NIL; ENDCASE => propList _ PropListPut[props.propList, name, value]; IF propList#props.propList OR format#props.formatName OR comment#props.comment THEN { new: Props ~ NEW [PropsRep _ [propList: propList, formatName: format, comment: comment, hasStyleDef: props.hasStyleDef, hasPrefix: props.hasPrefix, hasPostfix: props.hasPostfix, hasArtwork: props.hasArtwork]]; IF new.propList#props.propList THEN { hasValue: BOOL ~ (value#NIL); SELECT name FROM aStyleDef => new.hasStyleDef _ hasValue; aPrefix => new.hasPrefix _ hasValue; aPostfix => new.hasPostfix _ hasValue; aArtwork => new.hasArtwork _ hasValue; ENDCASE; }; RETURN[new]; } ELSE RETURN [props]; -- nothing changed }; NewText: PUBLIC PROC [props: Props _ NIL] RETURNS [Text] ~ { IF props=NIL THEN props _ NEW [PropsRep _ []]; RETURN [NEW[TextRep _ [rope: NIL, runs: NIL, charSets: NIL, charProps: NIL, props: props]]]; }; TextFromRope: PUBLIC PROC [rope: ROPE, looks: Looks _ noLooks] RETURNS [Text] ~ { text: Text ~ NewText[]; text.rope _ rope; text.runs _ TextLooks.CreateRun[Rope.Size[rope], looks]; RETURN [text]; }; Size: PUBLIC PROC [text: Text] RETURNS [INT] ~ { RETURN[IF text=NIL THEN 0 ELSE Rope.InlineSize[text.rope]]; }; FetchChar: PUBLIC PROC [text: Text, index: INT] RETURNS [char: XChar _ [0, 0]] ~ { char.code _ ORD[Rope.Fetch[text.rope, index]]; IF text.charSets#NIL THEN WITH Rosary.Fetch[text.charSets, index] SELECT FROM refCharSet: REF CharSet => char.set _ refCharSet^; ENDCASE; }; FetchLooks: PUBLIC PROC [text: Text, index: INT] RETURNS [Looks] = { RETURN [TextLooks.FetchLooks[text.runs, index]]; }; Fetch: PUBLIC PROC [text: Text, index: INT] RETURNS [char: XChar, looks: Looks] ~ { char _ FetchChar[text, index]; looks _ FetchLooks[text, index]; }; GetFormat: PUBLIC PROC [text: Text] RETURNS [ATOM] ~ { RETURN[text.props.formatName]; }; GetComment: PUBLIC PROC [text: Text] RETURNS [BOOL] ~ { RETURN[text.props.comment]; }; GetProp: PUBLIC PROC [text: Text, name: ATOM] RETURNS [REF] ~ { RETURN[PropsGet[text.props, name]]; }; GetCharProp: PUBLIC PROC [text: Text, index: INT, name: ATOM] RETURNS [REF] ~ { RETURN[PropListGet[GetCharPropList[text, index], name]]; }; GetCharPropList: PUBLIC PROC [text: Text, index: INT] RETURNS [PropList] ~ { RETURN[NARROW[Rosary.Fetch[text.charProps, index]]]; }; rosaryOfNil: ROSARY ~ Rosary.FromItem[NIL, maxLen]; RosaryReplace: PROC [dest: ROSARY, destSize: INT, destStart: INT, destLen: INT, source: ROSARY, sourceStart: INT, sourceLen: INT] RETURNS [rosary: ROSARY] ~ { notNil: Rosary.RunActionType ~ { quit _ item#NIL }; d: ROSARY ~ IF dest=NIL THEN rosaryOfNil ELSE dest; d0: INT ~ 0; d1: INT ~ destStart; d2: INT ~ d1+destLen; d3: INT ~ destSize; s: ROSARY ~ IF source=NIL THEN rosaryOfNil ELSE source; s0: INT ~ sourceStart; s1: INT ~ s0+sourceLen; rosary _ Rosary.CatSegments[[d, d0, d1-d0], [s, s0, s1-s0], [d, d2, d3-d2]]; IF NOT Rosary.MapRuns[[rosary], notNil] THEN rosary _ NIL; }; RosaryMapRuns: PROC [rosary: ROSARY, start, len: INT, action: Rosary.RunActionType] ~ { IF rosary=NIL THEN [] _ action[NIL, len] ELSE [] _ Rosary.MapRuns[[rosary, start, len], action]; }; DeleteText: PUBLIC PROC [dest: Text, destStart: INT _ 0, destLen: INT _ maxLen] RETURNS [Text] ~ { destSize: INT ~ Size[dest]; destStart _ MIN[MAX[0, destStart], destSize]; destLen _ MIN[MAX[0, destLen], destSize-destStart]; IF destLen=0 THEN RETURN[dest] -- delete nothing ELSE { new: Text ~ NEW[TextRep _ [rope: NIL, runs: NIL, charSets: NIL, charProps: NIL, props: NIL]]; newSize: INT ~ destSize-destLen; IF newSize#0 THEN { SELECT TRUE FROM (destStart=0) => { -- delete from start new.rope _ Rope.Substr[dest.rope, destLen, newSize]; IF dest.runs#NIL THEN new.runs _ TextLooks.Substr[dest.runs, destLen, newSize]; }; (destStart+destLen=destSize) => { -- delete from end new.rope _ Rope.Substr[dest.rope, 0, newSize]; IF dest.runs#NIL THEN new.runs _ TextLooks.Substr[dest.runs, 0, newSize]; }; ENDCASE => { new.rope _ Rope.Replace[base: dest.rope, start: destStart, len: destLen, with: NIL]; IF dest.runs#NIL THEN new.runs _ TextLooks.Replace[base: dest.runs, start: destStart, len: destLen, replace: NIL, baseSize: destSize, repSize: 0]; }; IF dest.charSets#NIL THEN new.charSets _ RosaryReplace[dest: dest.charSets, destSize: destSize, destStart: destStart, destLen: destLen, source: NIL, sourceStart: 0, sourceLen: 0]; IF dest.charProps#NIL THEN new.charProps _ RosaryReplace[dest: dest.charProps, destSize: destSize, destStart: destStart, destLen: destLen, source: NIL, sourceStart: 0, sourceLen: 0]; }; new.props _ dest.props; RETURN[new]; }; }; ReplaceText: PUBLIC PROC [dest: Text, destStart: INT _ 0, destLen: INT _ maxLen, source: Text _ NIL, sourceStart: INT _ 0, sourceLen: INT _ maxLen] RETURNS [Text] ~ { sourceSize: INT ~ Size[source]; sourceStart _ MIN[MAX[0, sourceStart], sourceSize]; sourceLen _ MIN[MAX[0, sourceLen], sourceSize-sourceStart]; IF sourceLen=0 THEN RETURN DeleteText[dest, destStart, destLen] ELSE { new: Text ~ NEW[TextRep _ [rope: NIL, runs: NIL, charSets: NIL, charProps: NIL, props: NIL]]; replaceRope: ROPE ~ Rope.Substr[source.rope, sourceStart, sourceLen]; replaceRuns: Runs ~ TextLooks.Substr[source.runs, sourceStart, sourceLen]; destSize: INT ~ Size[dest]; destStart _ MIN[MAX[0, destStart], destSize]; destLen _ MIN[MAX[0, destLen], destSize-destStart]; SELECT TRUE FROM (destLen=0 AND destStart=0) => { -- insert at start new.rope _ Rope.Concat[replaceRope, dest.rope]; new.runs _ TextLooks.Concat[replaceRuns, dest.runs, sourceLen, destSize]; }; (destLen=0 AND destStart=destSize) => { -- insert at end new.rope _ Rope.Concat[dest.rope, replaceRope]; new.runs _ TextLooks.Concat[dest.runs, replaceRuns, destSize, sourceLen]; }; ENDCASE => { new.rope _ Rope.Replace[base: dest.rope, start: destStart, len: destLen, with: replaceRope]; new.runs _ TextLooks.Replace[base: dest.runs, start: destStart, len: destLen, replace: replaceRuns, baseSize: destSize, repSize: sourceLen]; }; IF dest.charSets#NIL OR source.charSets#NIL THEN new.charSets _ RosaryReplace[dest: dest.charSets, destSize: destSize, destStart: destStart, destLen: destLen, source: source.charSets, sourceStart: sourceStart, sourceLen: sourceLen]; IF dest.charProps#NIL OR source.charProps#NIL THEN new.charProps _ RosaryReplace[dest: dest.charProps, destSize: destSize, destStart: destStart, destLen: destLen, source: source.charProps, sourceStart: sourceStart, sourceLen: sourceLen]; new.props _ dest.props; RETURN[new]; }; }; CharSetsReplaceByRun: PROC [destCharSets: ROSARY, destSize: INT, destStart: INT, destLen: INT, charSet: CharSet, runSize: INT] RETURNS [ROSARY] ~ { sourceCharSets: ROSARY _ NIL; IF charSet#0 THEN { item: REF CharSet _ NIL; IF destCharSets#NIL THEN { <> Try: TYPE ~ {before, after}; tries: ARRAY Try OF INT _ [before: destStart-1, after: destStart+destLen]; FOR try: Try IN Try WHILE item=NIL DO i: INT ~ tries[try]; IF i IN [0..destSize) THEN WITH Rosary.Fetch[destCharSets, i] SELECT FROM destItem: REF CharSet => IF destItem^=charSet THEN item _ destItem; ENDCASE; ENDLOOP; }; IF item=NIL THEN item _ NEW[CharSet _ charSet]; sourceCharSets _ Rosary.FromItem[item, runSize]; }; RETURN[RosaryReplace[dest: destCharSets, destSize: destSize, destStart: destStart, destLen: destLen, source: sourceCharSets, sourceStart: 0, sourceLen: runSize]]; }; ReplaceByRope: PUBLIC PROC [dest: Text, destStart: INT, destLen: INT, rope: ROPE, inherit: BOOL, looks: Looks, charSet: CharSet] RETURNS [Text] ~ { ropeSize: INT ~ Rope.Size[rope]; IF ropeSize=0 THEN RETURN DeleteText[dest, destStart, destLen] ELSE { new: Text ~ NEW[TextRep _ [rope: NIL, runs: NIL, charSets: NIL, charProps: NIL, props: NIL]]; destSize: INT ~ Size[dest]; destStart _ MIN[MAX[0, destStart], destSize]; destLen _ MIN[MAX[0, destLen], destSize-destStart]; new.rope _ Rope.Replace[base: dest.rope, start: destStart, len: destLen, with: rope]; new.runs _ TextLooks.ReplaceByRun[dest: dest.runs, start: destStart, len: destLen, runLen: ropeSize, destSize: destSize, inherit: inherit, looks: looks]; IF dest.charSets#NIL OR charSet#0 THEN new.charSets _ CharSetsReplaceByRun[destCharSets: dest.charSets, destSize: destSize, destStart: destStart, destLen: destLen, charSet: charSet, runSize: ropeSize]; IF dest.charProps#NIL THEN new.charProps _ RosaryReplace[dest: dest.charProps, destSize: destSize, destStart: destStart, destLen: destLen, source: NIL, sourceStart: 0, sourceLen: ropeSize]; new.props _ dest.props; RETURN[new]; }; }; SetProp: PUBLIC PROC [text: Text, name: ATOM, value: REF] RETURNS [Text] ~ { props: Props ~ PropsPut[text.props, name, value]; IF text.props=props THEN RETURN [text] ELSE RETURN [NEW[TextRep _ [rope: text.rope, runs: text.runs, charSets: text.charSets, charProps: text.charProps, props: props]]]; }; SetComment: PUBLIC PROC [text: Text, comment: BOOL] RETURNS [Text] ~ { IF text.props.comment=comment THEN RETURN [text] ELSE RETURN SetProp[text, aComment, refBool[comment]]; }; SetFormat: PUBLIC PROC [text: Text, formatName: ATOM] RETURNS [Text] ~ { IF text.props.formatName=formatName THEN RETURN [text] ELSE RETURN SetProp[text, aFormat, formatName]; }; SetCharProp: PUBLIC PROC [text: Text, start: INT _ 0, len: INT _ maxLen, name: ATOM, value: REF] RETURNS [Text] ~ { size: INT ~ Size[text]; start _ MIN[MAX[0, start], size]; len _ MIN[MAX[0, len], size-start]; IF len=0 THEN RETURN [text] ELSE { p: PROC[q: PROC[REF, INT]] ~ { action: Rosary.RunActionType ~ { q[PropListPut[NARROW[item], name, value], repeat] }; RosaryMapRuns[text.charProps, start, len, action]; }; replaceCharProps: ROSARY ~ Rosary.FromProcProc[p]; newCharProps: ROSARY ~ RosaryReplace[dest: text.charProps, destSize: size, destStart: start, destLen: len, source: replaceCharProps, sourceStart: 0, sourceLen: len]; RETURN [NEW[TextRep _ [rope: text.rope, runs: text.runs, charSets: text.charSets, charProps: newCharProps, props: text.props]]]; }; }; SetCharPropList: PUBLIC PROC [text: Text, start: INT _ 0, len: INT _ maxLen, propList: PropList] RETURNS [Text] ~ { size: INT ~ Size[text]; start _ MIN[MAX[0, start], size]; len _ MIN[MAX[0, len], size-start]; IF len=0 THEN RETURN [text] ELSE { replaceCharProps: ROSARY ~ Rosary.FromItem[propList, len]; newCharProps: ROSARY ~ RosaryReplace[dest: text.charProps, destSize: size, destStart: start, destLen: len, source: replaceCharProps, sourceStart: 0, sourceLen: len]; RETURN [NEW[TextRep _ [rope: text.rope, runs: text.runs, charSets: text.charSets, charProps: newCharProps, props: text.props]]]; }; }; ChangeLooks: PUBLIC PROC [text: Text, start: INT _ 0, len: INT _ maxLen, remove, add: Looks] RETURNS [Text] ~ { size: INT ~ Size[text]; start _ MIN[MAX[0, start], size]; len _ MIN[MAX[0, len], size-start]; IF len=0 THEN RETURN [text] ELSE { oldRuns: Runs ~ text.runs; newRuns: Runs ~ TextLooks.ChangeLooks[oldRuns, size, remove, add, start, len]; IF newRuns=oldRuns THEN RETURN [text] -- no change ELSE RETURN [NEW[TextRep _ [rope: text.rope, runs: newRuns, charSets: text.charSets, charProps: text.charProps, props: text.props]]]; }; }; END.