TextNodeImpl.mesa
Copyright Ó 1985, 1986, 1987, 1988, 1991, 1992 by Xerox Corporation. All rights reserved.
written by Bill Paxton, March 1981
last edit by Bill Paxton, August 11, 1982 9:51 am
Rick Beach, March 27, 1985 10:13:12 am PST
Michael Plass, September 24, 1991 2:21 pm PDT
Doug Terry, August 9, 1988 3:28:42 pm PDT
Don Baker, June 3, 1988 4:24:24 pm PDT
Bier, January 6, 1989 5:14:03 pm PST
Doug Wyatt, July 21, 1992 4:37 pm PDT
This is a modified version (by Doug Terry) of TextNodeImpl.mesa that performs transformations on "active" nodes as they are requested. Active nodes as those with the "Active" property; the value associated with this property identifies a particular transformation. Transformations must be registered using TextNodeRegistry.
Note: All of the public procedures in this module assume that any nodes passed as inputs are regular text nodes (i.e. do not need to be transformed) and ensure that any nodes returned as results are regular text nodes. Strange behavior might result if the transformation of an active node yields another active node.
DIRECTORY
Atom USING [MakeAtom],
NodeProps USING [GetProp, nameActive],
RefTab USING [Create, Delete, EachPairAction, Fetch, Pairs, Ref, Store],
Rope USING [ROPE, Size],
TextNode,
TextNodeRegistry USING [NotifySet, NotifyProc, ProcSet],
Tioga;
TextNodeImpl: CEDAR PROGRAM
IMPORTS Atom, NodeProps, RefTab, Rope
EXPORTS TextNode, TextNodeRegistry
= BEGIN
Node: TYPE ~ Tioga.Node;
Location: TYPE ~ Tioga.Location;
ROPE: TYPE ~ Rope.ROPE;
TextNodeRegistryImpl
ProcSet: TYPE ~ TextNodeRegistry.ProcSet;
NotifySet: TYPE ~ TextNodeRegistry.NotifySet;
NotifyProc: TYPE ~ TextNodeRegistry.NotifyProc;
registrationTable: RefTab.Ref = RefTab.Create[];
notificationTable: RefTab.Ref = RefTab.Create[];
Register: PUBLIC PROC [activity: ATOM, procs: ProcSet] RETURNS [] ~ {
The given TransformProc will hereafter be applied to all nodes having an active property with value=activity. Notification procedures are invoked accordingly.
NotifyRegistration: RefTab.EachPairAction = {
[key: RefTab.Key, val: RefTab.Val] RETURNS [quit: BOOLFALSE]
IF val#NIL THEN {
notifier: NotifyProc = NARROW[val, NotifySet].registrationNotify;
IF notifier#NIL THEN notifier[key, activity, procs.activityOn];
};
};
[] ¬ RefTab.Store[registrationTable, activity, procs];
[] ¬ RefTab.Pairs[notificationTable, NotifyRegistration];
};
UnRegister: PUBLIC PROC [activity: ATOM] RETURNS [] ~ {
Removes the procedures associated with the given activity. Notification procedures are invoked accordingly.
on: BOOLEAN = IsActivityOn[activity];
NotifyUnRegistration: RefTab.EachPairAction = {
[key: RefTab.Key, val: RefTab.Val] RETURNS [quit: BOOLFALSE]
IF val#NIL THEN {
notifier: NotifyProc = NARROW[val, NotifySet].unRegistrationNotify;
IF notifier#NIL THEN notifier[key, activity, on];
};
};
[] ¬ RefTab.Pairs[notificationTable, NotifyUnRegistration];
[] ¬ RefTab.Delete[registrationTable, activity];
};
GetProcs: PUBLIC PROC [activity: ATOM] RETURNS [procs: ProcSet] ~ {
Return the procedures associated with the given activity.
RETURN[NARROW[RefTab.Fetch[registrationTable, activity].val]];
};
ActivityOn: PUBLIC PROC [activity: ATOM, on: BOOL ¬ TRUE] RETURNS [wasOn: BOOL] ~ {
Determines whether or not the registered transformations should be currently applied; if activity=NIL then activity is turned on/off for all registered clients. Registered clients are informed via callbacks when this switch is toggled so they can take appropriate action.
SetActivity: RefTab.EachPairAction = {
[key: RefTab.Key, val: RefTab.Val] RETURNS [quit: BOOLFALSE]
enumeratedActivity: ATOM = NARROW[key];
procs: ProcSet = NARROW[val];
NotifyActivityOn: RefTab.EachPairAction = {
[key: RefTab.Key, val: RefTab.Val] RETURNS [quit: BOOLFALSE]
notifier: NotifyProc = NARROW[val, NotifySet].activityOnNotify;
IF notifier#NIL THEN notifier[key, enumeratedActivity, on];
};
IF procs#NIL THEN {
wasOn ¬ procs.activityOn;
procs.activityOn ¬ on;
IF wasOn#on THEN {
IF procs.activityProc#NIL THEN
procs.activityProc[on, procs.activityProcData];
[] ¬ RefTab.Pairs[notificationTable, NotifyActivityOn];
};
};
};
wasOn ¬ FALSE;
IF activity#NIL
THEN [] ¬ SetActivity[activity, RefTab.Fetch[registrationTable, activity].val]
ELSE [] ¬ RefTab.Pairs[registrationTable, SetActivity];
};
IsActivityOn: PUBLIC PROC [activity: ATOM] RETURNS [isOn: BOOLEAN] ~ {
Returns true if the activity specified is on.
RETURN[NARROW[RefTab.Fetch[registrationTable, activity].val, ProcSet].activityOn];
};
NotificationRegister: PUBLIC PROC [handle: REF ANY, notifyProcs: NotifySet] RETURNS [] ~ {
Registers a set of notification procedures which are called both in the event of a change in activity registration or status *and* during this call, notification will be given of the "registration" of those activities which are already registered.
notifier: NotifyProc;
NotifyAllRegister: RefTab.EachPairAction = {
[key: RefTab.Key, val: RefTab.Val] RETURNS [quit: BOOLFALSE]
IF val#NIL THEN notifier[handle, NARROW[key], NARROW[val, ProcSet].activityOn];
};
[] ¬ RefTab.Store[notificationTable, handle, notifyProcs];
IF notifyProcs#NIL THEN {
notifier ¬ notifyProcs.registrationNotify;
IF notifier#NIL THEN [] ¬ RefTab.Pairs[registrationTable, NotifyAllRegister];
};
};
NotificationUnRegister: PUBLIC PROC [handle: REF ANY, doNotify: BOOLEAN ¬ FALSE] RETURNS [] ~ {
Unregisters a set of notification procedures. If doNotify is set, notification of "unregistration" of registered activities will be made--during this call.
notifier: NotifyProc;
NotifyAllUnRegister: RefTab.EachPairAction = {
[key: RefTab.Key, val: RefTab.Val] RETURNS [quit: BOOLFALSE]
IF val#NIL THEN notifier[handle, NARROW[key], NARROW[val, ProcSet].activityOn];
};
notifyProcs: NotifySet = NARROW[RefTab.Fetch[notificationTable, handle].val];
[] ¬ RefTab.Delete[notificationTable, handle];
IF doNotify AND notifyProcs#NIL THEN {
notifier ¬ notifyProcs.unRegistrationNotify;
IF notifier#NIL THEN [] ¬ RefTab.Pairs[registrationTable, NotifyAllUnRegister];
};
};
DoTransform: PUBLIC PROC [activity: ATOM, node: Node, parent: Node ¬ NIL, wantFirst: BOOL ¬ TRUE] RETURNS [new: Node] ~ {
Force the TransformProc associated with the given activity to be applied to the node. (This is intended primarily for use by TextNodeImpl.)
procs: ProcSet = NARROW[RefTab.Fetch[registrationTable, activity].val];
new ¬ IF procs=NIL OR NOT procs.activityOn OR procs.transformProc=NIL THEN node ELSE procs.transformProc[node, parent, wantFirst, procs.transformProcData];
};
GetSize: PUBLIC PROC [activity: ATOM, node: Node] RETURNS [size: INT] ~ {
Computes or estimates the size that the given node will be AFTER it is transformed by the specified activity. (This is intended primarily for use by TextNodeImpl.)
procs: ProcSet = NARROW[RefTab.Fetch[registrationTable, activity].val];
size ¬ IF procs=NIL OR NOT procs.activityOn OR procs.sizeProc=NIL THEN Rope.Size[node.rope] ELSE procs.sizeProc[node, procs.sizeProcData];
};
Routines to check for and generate registered activities
activeAtom: ATOM ~ NodeProps.nameActive;
PTransform: PROC [node: Node, wantFirst: BOOL] RETURNS [Node] ~ {
WITH NodeProps.GetProp[node, activeAtom] SELECT FROM
activity: ROPE => RETURN[DoTransform[activity: Atom.MakeAtom[activity],
node: node, parent: node.parent, wantFirst: wantFirst]];
ENDCASE => RETURN[node];
};
PSize: PROC [node: Node] RETURNS [INT] ~ {
WITH NodeProps.GetProp[node, activeAtom] SELECT FROM
activity: ROPE => RETURN[GetSize[activity: Atom.MakeAtom[activity], node: node]];
ENDCASE => RETURN[Rope.Size[node.rope]];
};
Transform: PROC [node: Node, wantFirst: BOOL ¬ TRUE] RETURNS [Node] ~ INLINE {
RETURN[IF node#NIL AND node.hasActive THEN PTransform[node, wantFirst] ELSE node];
};
Size: PROC [node: Node] RETURNS [INT] ~ INLINE {
RETURN[IF node.hasActive THEN PSize[node] ELSE Rope.Size[node.rope]];
};
The rest of TextNodeImpl
NewTextNode: PUBLIC PROC RETURNS [Node] = {
RETURN[NEW[Tioga.NodeRep ¬ []]];
};
XParent: PROC [n: Node] RETURNS [Node] ~ INLINE { RETURN[n.parent] };
XNext: PROC [n: Node] RETURNS [Node] ~ INLINE { RETURN[n.next] };
XChild: PROC [n: Node] RETURNS [Node] ~ INLINE { RETURN[n.child] };
XRoot: PROC [n: Node] RETURNS [root: Node ¬ NIL] ~ {
FOR x: Node ¬ n, XParent[x] UNTIL x=NIL DO root ¬ x ENDLOOP;
};
XFirstSibling: PROC [n: Node] RETURNS [Node] ~ {
p: Node ~ XParent[n]; RETURN[IF p=NIL THEN n ELSE XChild[p]];
};
XPrevious: PROC [n: Node] RETURNS [prev: Node ¬ NIL] ~ {
FOR x: Node ¬ XFirstSibling[n], XNext[x] UNTIL x=n DO prev ¬ x ENDLOOP;
};
XForward: PROC [n: Node] RETURNS [nx: Node, deltaLevel: INTEGER] ~ {
child: Node ~ XChild[n];
IF child#NIL
THEN RETURN[child, 1] -- descend in the tree
ELSE { -- move to sibling or up* then sibling
x: Node ¬ n;
FOR delta: INTEGER ¬ 0, delta-1 DO
next: Node ~ XNext[x];
IF next#NIL THEN RETURN[next, delta]; -- the sibling
IF (x ¬ XParent[x])=NIL THEN RETURN[NIL, delta]; -- the parent
ENDLOOP;
};
};
Parent: PUBLIC PROC [n: Node] RETURNS [Node] ~ {
RETURN[IF n=NIL THEN NIL ELSE Transform[node: XParent[n], wantFirst: FALSE]];
};
Root: PUBLIC PROC [n: Node] RETURNS [Node] ~ {
RETURN[IF n=NIL THEN NIL ELSE Transform[node: XRoot[n], wantFirst: TRUE]];
};
Level: PUBLIC PROC [n: Node] RETURNS [level: INTEGER ¬ 0] ~ {
p: Node ¬ IF n=NIL THEN NIL ELSE XParent[n];
UNTIL p=NIL DO level ¬ level+1; p ¬ XParent[p] ENDLOOP;
};
Next: PUBLIC PROC [n: Node] RETURNS [Node] ~ {
RETURN[IF n=NIL THEN NIL ELSE Transform[node: XNext[n], wantFirst: TRUE]];
};
Previous: PUBLIC PROC [n: Node, parent: Node ¬ NIL] RETURNS [nx: Node] ~ {
RETURN[IF n=NIL THEN NIL ELSE Transform[node: XPrevious[n], wantFirst: FALSE]];
};
FirstChild: PUBLIC PROC [n: Node] RETURNS [Node] ~ {
RETURN[IF n=NIL THEN NIL ELSE Transform[node: XChild[n], wantFirst: TRUE]];
};
Forward: PUBLIC PROC [node: Node] RETURNS [nx: Node, levelDelta: INTEGER] ~ {
IF node=NIL THEN RETURN[NIL, 0];
[nx, levelDelta] ¬ XForward[node];
nx ¬ Transform[node: nx, wantFirst: TRUE];
};
StepForward: PUBLIC PROC [node: Node] RETURNS [Node] ~ {
RETURN[Forward[node].nx];
};
Backward: PUBLIC PROC [node: Node, parent: Node ¬ NIL]
RETURNS [back, backparent: Node, levelDelta: INTEGER] = {
returns preceding node in standard tree walk order
child, child2: Node;
IF node=NIL OR (parent ¬ XParent[node])=NIL THEN RETURN [NIL, NIL, 0];
IF (child ¬ parent.child) = node THEN RETURN [Transform[node: parent, wantFirst: FALSE], Parent[parent], -1];
DO IF child.next=NIL THEN ERROR; -- incorrect value supplied for parent
IF (child2 ¬ child.next)=node THEN EXIT;
child ¬ child2;
ENDLOOP;
levelDelta ¬ 0;
child ¬ Transform[node: child, wantFirst: FALSE];
DO IF (child2 ¬ LastChild[child]) = NIL THEN RETURN [child, parent, levelDelta];
levelDelta ¬ levelDelta+1;
parent ¬ child; child ¬ child2;
ENDLOOP;
};
StepBackward: PUBLIC PROC [node: Node, parent: Node ¬ NIL] RETURNS [Node] ~ {
RETURN[Backward[node, parent].back];
};
ForwardClipped: PUBLIC PROC [node: Node, maxLevel: INTEGER,
nodeLevel: INTEGER ¬ 0]
RETURNS
[nx: Node, nxLevel: INTEGER] = {
like Forward, but limits how deep will go in tree
if pass nodeLevel=0, correct value will be computed
nxLevel = Level[nx] <= MAX[maxLevel, Level[node]]
child: Node;
IF node=NIL THEN RETURN [NIL, 0];
IF nodeLevel <= 0 THEN nodeLevel ¬ Level[node];
IF nodeLevel < maxLevel AND (child ¬ node.child) # NIL THEN
RETURN [Transform[node: child, wantFirst: TRUE], nodeLevel+1]; -- return the child
DO -- move to next node, sibling or up* then sibling
IF node.next#NIL THEN RETURN [Transform[node: node.next, wantFirst: TRUE], nodeLevel]; -- return the sibling
IF (node ¬ node.parent) = NIL THEN RETURN [NIL, 0]; -- go to the parent
nodeLevel ¬ nodeLevel-1;
ENDLOOP;
};
BackwardClipped: PUBLIC PROC [node: Node, maxLevel: INTEGER,
parent: Node ¬ NIL, nodeLevel: INTEGER ¬ 0]
RETURNS
[back, backparent: Node, backLevel: INTEGER] = {
like Backward, but limits how deep will go in tree
backLevel = Level[back] <= MAX[maxLevel, Level[node]]
child, child2: Node;
IF node=NIL OR (parent ¬ XParent[node])=NIL THEN RETURN [NIL, NIL, 0];
IF nodeLevel <= 0 THEN nodeLevel ¬ Level[node];
IF (child ¬ parent.child) = node THEN RETURN [Transform[node: parent, wantFirst: FALSE], Parent[parent], nodeLevel-1];
DO -- search for sibling just before node
IF child.next=NIL THEN ERROR; -- incorrect value supplied for parent
IF (child2 ¬ child.next)=node THEN EXIT;
child ¬ child2;
ENDLOOP;
child ¬ Transform[node: child, wantFirst: FALSE];
DO -- go deeper in tree until reach maxLevel
IF nodeLevel >= maxLevel OR (child2 ¬ LastChild[child]) = NIL THEN
RETURN [child, parent, nodeLevel];
nodeLevel ¬ nodeLevel+1;
parent ¬ child;
child ¬ child2;
ENDLOOP;
};
XLocRelative: PROC [location: Location, count: INT,
break: NAT ¬ 1, skipCommentNodes: BOOL ¬ FALSE] RETURNS [Location] ~ {
rem: INT ¬ MAX[count, 0];
last: Location ¬ [NIL, 0];
FOR node: Node ← location.node, XForward[node].nx UNTIL node=NIL DO
IF NOT (skipCommentNodes AND node.comment) THEN {
size: INT ~ Size[node];
start: INT ~ IF node=location.node THEN MIN[MAX[location.where, 0], size] ELSE 0;
len: INT ~ size-start;
IF rem<=len THEN RETURN[[node, start+rem]];
rem ¬ rem-MIN[len+break, rem];
last ¬ [node, size];
};
ENDLOOP;
RETURN[last];
};
LocRelative: PUBLIC PROC [location: Location, count: INT,
break: NAT ¬ 1, skipCommentNodes: BOOL ¬ FALSE] RETURNS [Location] ~ {
loc: Location ~ XLocRelative[location, count, break, skipCommentNodes];
RETURN[[Transform[node: loc.node, wantFirst: TRUE], loc.where]];
};
LocWithin: PUBLIC PROC [n: Node, count: INT,
break: NAT ¬ 1, skipCommentNodes: BOOL ¬ FALSE] RETURNS [Location] ~ {
RETURN[LocRelative[[n, 0], count, break, skipCommentNodes]];
};
BadArgs: PUBLIC ERROR ~ CODE;
LocOffset: PUBLIC PROC [loc1, loc2: Location,
break: NAT ¬ 1, skipCommentNodes: BOOL ¬ FALSE] RETURNS [count: INT ¬ 0] ~ {
FOR node: Node ← loc1.node, XForward[node].nx UNTIL node=NIL DO
IF NOT (skipCommentNodes AND node.comment) THEN {
size: INT ~ Size[node];
i0: INT ~ IF node=loc1.node THEN MIN[MAX[loc1.where, 0], size] ELSE 0;
i1: INT ~ IF node=loc2.node THEN MIN[MAX[loc2.where, 0], size] ELSE size+break;
count ¬ count+(i1-i0);
};
IF node=loc2.node THEN RETURN;
ENDLOOP;
IF loc2.node#NIL THEN ERROR BadArgs;
};
LocNumber: PUBLIC PROC [at: Location,
break: NAT ¬ 1, skipCommentNodes: BOOL ¬ FALSE] RETURNS [count: INT] ~ {
RETURN[LocOffset[[Root[at.node], 0], at, break, skipCommentNodes]];
};
FirstSibling: PUBLIC PROC [n: Node] RETURNS [Node] = {
RETURN[FirstChild[Parent[n]]];
};
LastSibling: PUBLIC PROC [n: Node] RETURNS [Node] = {
IF n=NIL THEN RETURN [NIL];
UNTIL n.next=NIL DO n ¬ n.next ENDLOOP;
RETURN[Transform[node: n, wantFirst: FALSE]];
};
LastChild: PUBLIC PROC [n: Node] RETURNS [Node] = {
RETURN[LastSibling[FirstChild[n]]];
};
LastWithin: PUBLIC PROC [n: Node] RETURNS [Node] = {
nxt: Node;
IF n=NIL THEN RETURN [NIL];
IF (nxt ¬ n.child)=NIL THEN RETURN [n];
n ¬ nxt;
DO -- keep going to child of last sibling
IF n.next=NIL THEN {
IF (nxt ¬ n.child)=NIL THEN RETURN [Transform[node: n, wantFirst: FALSE]];
n ¬ nxt
}
ELSE n ¬ n.next;
ENDLOOP;
};
LastLocWithin: PUBLIC PROC [n: Node] RETURNS [Location] = {
last: Node ¬ LastWithin[n];
where: INT ¬ IF last # NIL THEN Rope.Size[last.rope] ELSE Tioga.NodeItself;
RETURN [[last, where]];
};
NthChild: PUBLIC PROC [n: Node, location: INT ¬ 0] RETURNS [child: Node] = {
note: NthChild[n, 0]==FirstChild[n]; NthChild[n, k+1]==Next[NthChild[n, k]]
IF n=NIL OR (child ¬ n.child)=NIL THEN RETURN;
DO IF location=0 THEN RETURN [Transform[node: child, wantFirst: TRUE]];
child ¬ Transform[node: child, wantFirst: TRUE];
IF child.next=NIL THEN RETURN [NIL];
child ¬ child.next;
location ¬ location-1;
ENDLOOP;
};
NthSibling: PUBLIC PROC [n: Node, cnt: INT ¬ 0] RETURNS [Node] = {
note: NthSibling[n, 0]==n; NthSibling[n, k+1]==Next[NthSibling[n, k]]
IF n=NIL THEN RETURN [NIL];
DO IF cnt=0 THEN RETURN [Transform[node: n, wantFirst: TRUE]];
n ¬ Transform[node: n, wantFirst: TRUE];
IF n.next=NIL THEN RETURN [NIL];
n ¬ n.next;
cnt ¬ cnt-1;
ENDLOOP;
};
CountChildren: PUBLIC PROC [n: Node] RETURNS [count: INT ¬ 0] = {
child: Node;
IF (child ¬ FirstChild[n])=NIL THEN RETURN;
DO count ¬ count+1;
child ¬ Transform[node: child, wantFirst: TRUE];
IF child.next=NIL THEN RETURN;
child ¬ child.next;
ENDLOOP;
};
CountFollowing: PUBLIC PROC [n: Node] RETURNS [count: INT ¬ 0] = {
IF n=NIL THEN RETURN;
UNTIL n.next=NIL DO
n ¬ n.next;
count ¬ count+1;
n ¬ Transform[node: n, wantFirst: TRUE];
ENDLOOP;
};
CountToParent: PUBLIC PROC [n: Node] RETURNS [count: INT ¬ 0, parent: Node] = {
IF n=NIL THEN RETURN;
UNTIL n.next=NIL DO
n ¬ n.next;
count ¬ count+1;
n ¬ Transform[node: n, wantFirst: TRUE];
ENDLOOP;
parent ¬ n.next;
parent ¬ Transform[node: parent, wantFirst: FALSE];
};
CountToChild: PUBLIC PROC [parent, child: Node] RETURNS [count: INT ¬ 0] = {
note: CountToChild[parent, FirstChild[parent]]==0
n: Node;
IF parent=NIL OR child=NIL THEN RETURN;
n ¬ Transform[node: parent.child, wantFirst: TRUE];
DO SELECT n FROM
child => RETURN [count];
NIL => RETURN [TextNode.MaxLen];
ENDCASE;
n ¬ Next[n];
count ¬ count+1;
ENDLOOP;
};
EndPos: PUBLIC PROC [n: Node] RETURNS [INT] = {
IF n=NIL THEN RETURN [0];
RETURN [MAX[Rope.Size[n.rope], 1]-1];
};
DestroyTree: PUBLIC PROC [root: Node] ~ {
parent: Node ~ Parent[root];
next: Node ¬ root;
UNTIL next=parent DO -- go through the tree zapping REF's
node: Node ~ next;
IF node.child#NIL THEN { next ¬ node.child; node.child ¬ NIL }
ELSE {
next ¬ IF node.next#NIL THEN node.next ELSE node.parent;
node.deleted ¬ TRUE;
node.parent ¬ NIL;
node.next ¬ NIL;
node.rope ¬ NIL;
node.runs ¬ NIL;
node.charSets ¬ NIL;
node.charProps ¬ NIL;
node.nodeProps ¬ NIL;
};
ENDLOOP;
};
END.