CDSatellitesImpl.mesa
Copyright © 1985, 1986, 1987 by Xerox Corporation. All rights reserved.
Written by: Christian Jacobi, August 29, 1986 9:55:02 pm PDT
This module is descended from the early implementation of satellites by Monier. It has been
rewritten a number of times since then by Sindhu and Serlet to find an implementation that
is comfortable to use both from programs and interactively. This last version is a complete
rewrite by Jacobi, one hopes for the last time!
Old version by: Pradeep Sindhu December 20, 1985 1:25:44 pm PST
Old version by: Pradeep Sindhu March 26, 1986 3:21:40 pm PST
Old version by: Bertrand Serlet August 17, 1986 0:49:00 am PDT
Old version by: Christian Jacobi, July 15, 1986 1:59:30 pm PDT
Old version by: Jean-Marc Frailong July 28, 1986 6:45:49 pm PDT
Last edited by: Christian Jacobi, June 2, 1987 6:09:49 pm PDT
Clients of this interface should be aware of the following set of invariants maintained for satellites, so they may avoid violating them inadvertently (the description below uses the notion of a world, which is a list of CD.Instance belonging to a cell or to the top level of the design):
The strong invariant
I1) A master must be a non-text CD.Instance with a non-nil groupIDProp.
I2) A satellite must be a text CD.Instance with a non-nil groupIDProp.
I3) Every satellite in a given world must have a corresponding master. That is, if n is the value of its groupIDProp (its groupID), then there must be a master in world that also has n as its groupID.
I4) A world may have at most one master with a given groupID.
I5) Each master must have a satellitesProp property that points to a list instances that are the master's satellites or, if the master is an object and has no satellites, a $none property value.
The weak invariant
hold I1, hold I2, dont care about I3, hold I4, dont care about I5
The almost invariant
A4) A design may have at most one master with a given groupID.
the almost invariant helps to remember stallite assiciations when a design is edited; however, the implementation tries but does not manage to keep it reliable.
DIRECTORY
Ascii, CD, CDCells, CDDirectory, CDEvents, CDInstances, CDOps, CDPrivate, CDProperties, CDSatellites, CDTexts, Rope;
CDSatellitesImpl:
CEDAR
MONITOR
IMPORTS Ascii, CD, CDCells, CDDirectory, CDEvents, CDInstances, CDOps, CDPrivate, CDProperties, CDTexts, Rope
EXPORTS CDSatellites =
BEGIN
OPEN CDSatellites;
ROPE: TYPE = Rope.ROPE;
satellitesProp:
REF
ATOM ~
NEW[
ATOM←$CDSatellitesSatList];
This property hangs on a master.
Its value is the list of the master's satellites or the
atom $none if the master is an object or a design.
maxGroupIdProp:
ATOM ~ $CDSatellitesMax;
This property hangs on a design.
Its value is the highest numbered satellite group in the design.
It is valid only while the design is saved on file.
iGroupIdProp:
ATOM ~ $CDSatellitesGroupId;
This property hangs on each instance that is in a satellite group.
Its value is the group's groupId. The group's groupId is read-only
and can be reused.
oGroupIdProp:
ATOM ~ $CDSatellitesOGroup;
This property hangs on objects and designs
Its value is the group's groupId. The group's groupId is read-only
and can be reused..
noGroup: INT = -1;
maxGroupId: INT ← noGroup+1;
GetSatellites:
PUBLIC
PROC [from:
REF]
RETURNS [sats:
CD.InstanceList←
NIL] = {
--external, does not assume but check invariants for objects and designs
--assumes invariants for instances
WITH from
SELECT
FROM
d: CD.Design => RETURN [EnforceInvariants[CDOps.RealTopCell[d]]]; --not cached
ob: CD.Object => IF CDCells.IsDummyCell[ob] THEN RETURN [EnforceInvariants[ob]];
ENDCASE => NULL;
WITH CDProperties.GetProp[from, satellitesProp]
SELECT
FROM
s: CD.InstanceList => RETURN [s];
a: ATOM => IF a=$none THEN RETURN [NIL];
ENDCASE => NULL;
WITH from
SELECT
FROM
ob: CD.Object => IF CDCells.IsCell[ob] THEN RETURN [EnforceInvariants[ob]];
i: CD.Instance => RETURN [NIL];
ENDCASE => ERROR;
};
InternalGetSatellites:
PROC [x:
REF]
RETURNS [
CD.InstanceList] =
INLINE {
WITH CDProperties.GetProp[x, satellitesProp]
SELECT
FROM
list: CD.InstanceList => RETURN [list];
ENDCASE => RETURN [NIL];
};
InternalPutSatellites:
PROC [x:
REF, sats:
REF] =
INLINE {
WITH x
SELECT
FROM
i: CD.Instance => CDProperties.PutInstanceProp[i, satellitesProp, sats];
ENDCASE => CDProperties.PutProp[x, satellitesProp, IF sats=NIL THEN $none ELSE sats];
};
EnforceInvariants:
ENTRY
PROC [cell:
CD.Object]
RETURNS [oSats:
CD.InstanceList←
NIL] = {
--This is exagerated locking...
-- We could have a list of currently active objects and wait until our object is
-- removed from this list; however, that might be exagerated coding in our nearly
-- sequential DA world
ENABLE UNWIND => NULL;
Node: TYPE = REF NodeRec;
NodeRec:
TYPE =
RECORD [
id: INT ← -1,
list: CD.InstanceList ← NIL];
Table: TYPE = REF TableRec;
TableRec:
TYPE =
RECORD [
elements: SEQUENCE size: NAT OF LIST OF Node
];
masterTable: Table ← NEW[TableRec[67]];
iSats: CD.InstanceList;
oldObId: INT;
newObId: REF ← INewId[];
Hash:
PROC [i:
INT]
RETURNS [
INT] =
INLINE {
RETURN[ABS[i] MOD masterTable.size]
};
StoreMaster:
PROC [inst:
CD.Instance] = {
id: INT ← IGroupId[inst];
hash: INT ← Hash[id];
FOR nl:
LIST
OF Node ← masterTable[hash], nl.rest
WHILE nl#
NIL
DO
IF nl.first.id=id THEN {nl.first.list ← CONS[inst, nl.first.list]; RETURN};
ENDLOOP;
masterTable[hash] ← CONS[NEW[NodeRec ← [id: id, list: LIST[inst]]], masterTable[hash]];
};
FetchMaster:
PROC [id:
INT]
RETURNS [
CD.InstanceList] = {
hash: INT ← Hash[id];
FOR nl:
LIST
OF Node ← masterTable[hash], nl.rest
WHILE nl#
NIL
DO
IF nl.first.id=id THEN RETURN [nl.first.list];
ENDLOOP;
RETURN [NIL];
};
TrustedMakeInstSat:
PROC [master:
CD.Instance, satellite:
CD.Instance] = {
Internal proc! assumes invariant
sats: CD.InstanceList ← NIL;
WITH InternalGetSatellites[master]
SELECT
FROM
sats:
CD.InstanceList => {
IF CDInstances.Member[satellite, sats] THEN ERROR;
sats.rest ← CONS[satellite, sats.rest];
RETURN
};
ENDCASE => sats ← LIST[satellite];
CDProperties.PutInstanceProp[master, satellitesProp, sats];
};
EachInst: CDCells.InstEnumerator = {
WITH inst.ob.specific
SELECT
FROM
tp: CDTexts.TextSpecific =>
IF IGroupId[inst]#noGroup THEN iSats ← CONS[inst, iSats];
ENDCASE =>
IF IGroupId[inst]#noGroup
THEN {
StoreMaster[inst];
CDProperties.PutInstanceProp[inst, satellitesProp, NIL];
}
};
paranoid: BOOL ← CDCells.IsDummyCell[cell];
oldObId ← OGroupId[cell];
CDProperties.PutObjectProp[cell, oGroupIdProp, newObId];
--Make masterTable and iSats; at the same time strip masters of their satellite lists
[] ← CDCells.EnumerateInstances[cell, EachInst];
--For each satellite find its closest master and put it on that master's satellite list
FOR sl:
CD.InstanceList ← iSats, sl.rest
WHILE sl#
NIL
DO
Closer:
PROC [newMaster:
CD.Instance]
RETURNS [
BOOL] =
INLINE {
accesses global master
mRect, newMRect, sRect: CD.Rect;
IF master=NIL THEN RETURN [TRUE];
mRect ← CDInstances.InstRectO[master];
newMRect ← CDInstances.InstRectO[newMaster];
sRect ← CDInstances.InstRectO[sl.first];
RETURN [RectMinDist[newMRect, sRect] < RectMinDist[mRect, sRect]];
};
master: CD.Instance ← NIL;
satGId: INT ← IGroupId[sl.first];
FOR ml:
CD.InstanceList ← FetchMaster[satGId], ml.rest
WHILE ml#
NIL
DO
IF satGId#IGroupId[ml.first] THEN ERROR;
IF Closer[ml.first] THEN master ← ml.first;
ENDLOOP;
IF master#NIL THEN --InstSat-- TrustedMakeInstSat[master, sl.first]
ELSE
IF satGId=oldObId
THEN
--ObjectSat-- {
CDProperties.PutInstanceProp[sl.first, iGroupIdProp, newObId];
oSats ← CONS[sl.first, oSats];
}
ELSE --not a satellite-- CDProperties.PutInstanceProp[sl.first, iGroupIdProp, NIL]
ENDLOOP;
Now go through the masterTable and renumber groups with identical group Id's
FOR i:
INT
IN [0..masterTable.size)
DO
FOR nl:
LIST
OF Node ← masterTable[i], nl.rest
WHILE nl#
NIL
DO
FOR ml:
CD.InstanceList ← nl.first.list.rest, ml.rest
WHILE ml#
NIL
DO
id: REF ← INewId[];
CDProperties.PutInstanceProp[ml.first, iGroupIdProp, id];
FOR sl:
CD.InstanceList ← InternalGetSatellites[ml.first], sl.rest
WHILE sl#
NIL
DO
CDProperties.PutInstanceProp[sl.first, iGroupIdProp, id];
ENDLOOP;
ENDLOOP;
ENDLOOP;
ENDLOOP;
InternalPutSatellites[cell, oSats];
};
GetSatelliteRopes:
PUBLIC
PROC [from:
REF, filter:
PROC [
CD.Instance]
RETURNS [
BOOL]]
RETURNS [ropes:
LIST
OF
ROPE ←
NIL] = {
FOR list:
CD.InstanceList ← GetSatellites[from], list.rest
WHILE list#
NIL
DO
IF filter=
NIL
OR filter[list.first]
THEN {
rope: ROPE ← NARROW[list.first.ob.specific, CDTexts.TextSpecific].text;
ropes ← CONS[rope, ropes];
}
ENDLOOP;
};
StandardFilter:
PUBLIC PROC [inst:
CD.Instance]
RETURNS [
BOOL←
FALSE] = {
WITH inst.ob.specific
SELECT
FROM
tp: CDTexts.TextSpecific =>
IF
CD.LayerTechnology[inst.ob.layer]=
NIL
THEN
RETURN [NOT IsItalic[tp.cdFont.supposedName]];
ENDCASE => NULL;
};
Associate:
PUBLIC
PROC [master:
REF, text:
CD.Instance] = {
WITH master
SELECT
FROM
d: CD.Design => master ← CDOps.RealTopCell[d];
ENDCASE => NULL;
IF text=
NIL
THEN {
WITH master
SELECT
FROM
o: CD.Object => OPutGroup[o, NIL];
i: CD.Instance => IPutGroup[i, NIL];
ENDCASE => ERROR;
}
ELSE {
id: REF ← NIL;
IF ~CDTexts.IsText[text.ob] THEN ERROR;
WITH master
SELECT
FROM
o:
CD.Object => {
id ← CDProperties.GetObjectProp[o, oGroupIdProp];
IF id=NIL THEN OPutGroup[o, id←XNewId[]];
IPutGroup[text, id];
};
i:
CD.Instance =>
IF ~CDTexts.IsText[i.ob]
THEN {
id ← CDProperties.GetInstanceProp[i, iGroupIdProp];
IF id=NIL THEN IPutGroup[i, id←XNewId[]];
IPutGroup[text, id];
} ELSE ERROR;
ENDCASE => IF master#NIL THEN ERROR;
IPutGroup[text, id];
};
};
IsAssociated:
PUBLIC
PROC [any:
REF, inst:
CD.Instance]
RETURNS [
BOOL] = {
id: INT ← IGroupId[inst];
IF id=noGroup THEN RETURN [any=NIL];
RETURN [
WITH any
SELECT
FROM
d: CD.Design => id=OGroupId[CDOps.RealTopCell[d]],
o: CD.Object => id=OGroupId[o],
i: CD.Instance => id=IGroupId[i],
ENDCASE => FALSE
]
};
GetMaster:
PUBLIC
PROC [cell:
REF, text:
CD.Instance]
RETURNS [master:
REF ←
NIL] = {
id: INT;
CheckInst: CDCells.InstEnumerator = {
IF id=IGroupId[inst]
AND ~CDTexts.IsText[inst.ob]
THEN {
master ← inst; quit←TRUE
};
};
IF ~CDTexts.IsText[text.ob] THEN ERROR;
[] ← GetSatellites[cell]; --enforces invariants
id ← IGroupId[text];
IF id=noGroup THEN RETURN [NIL];
WITH cell
SELECT
FROM
ob:
CD.Object => {
IF ~CDCells.IsCell[ob] THEN ERROR;
IF id=OGroupId[ob] THEN RETURN [ob];
[] ← CDCells.EnumerateInstances[ob, CheckInst];
};
d:
CD.Design => {
ob: CD.Object ← CDOps.RealTopCell[d];
IF id=OGroupId[ob] THEN RETURN [d];
[] ← CDCells.EnumerateInstances[ob, CheckInst];
};
ENDCASE => NULL;
};
OPutGroup:
PROC [ob:
CD.Object, x:
REF] =
INLINE {
CDProperties.PutObjectProp[ob, oGroupIdProp, x]
};
IPutGroup:
PROC [i:
CD.Instance, x:
REF] =
INLINE {
CDProperties.PutInstanceProp[i, iGroupIdProp, x]
};
OGroupId:
PROC [ob:
CD.Object]
RETURNS [
INT] = {
WITH CDProperties.GetObjectProp[ob, oGroupIdProp]
SELECT
FROM
ri: REF INT => RETURN [ri^];
ENDCASE => RETURN [noGroup];
};
IGroupId:
PROC [i:
CD.Instance]
RETURNS [
INT←noGroup] = {
WITH CDProperties.GetInstanceProp[i, iGroupIdProp]
SELECT
FROM
ri: REF INT => RETURN [ri^];
ENDCASE => NULL;
};
XNewId:
ENTRY
PROC []
RETURNS [
REF] = {
ENABLE UNWIND => NULL;
RETURN [INewId[]];
};
INewId:
INTERNAL PROC []
RETURNS [
REF] = {
IF maxGroupId=LAST[INT] THEN maxGroupId ← noGroup+1;
RETURN [NEW[INT ← maxGroupId ← maxGroupId+1]]
};
BeforeOutput: CDEvents.EventProc = {
IF design#NIL THEN CDProperties.PutProp[design, maxGroupIdProp, NEW[INT←maxGroupId]]
};
AfterInput: CDEvents.EventProc = {
[] ← CheckMaxGroupId[design];
};
Convert:
PROC [design:
CD.Design] = {
ConvertCell: CDDirectory.EachEntryAction = {
IF CDCells.IsCell[ob]
THEN {
DoInst: CDCells.InstEnumerator = {
IF CDTexts.IsText[inst.ob]
THEN
IF CDProperties.GetProp[inst, $CDSatellitesGroupId]=
NIL
THEN
Associate[ob, inst]
};
[] ← CDCells.EnumerateInstances[ob, DoInst];
};
};
CDProperties.PutProp[design, $CDSatellitesMaxGroupId, NIL];
[] ← CDDirectory.Enumerate[design, ConvertCell];
FOR pl:
LIST
OF
CD.PushRec ← design^.actual, pl.rest
WHILE pl#
NIL
DO
[] ← ConvertCell[NIL, pl.first.dummyCell.ob]
ENDLOOP
};
CheckMaxGroupId:
PROC [design:
CD.Design]
RETURNS [
BOOL←
FALSE] = {
MaxGroupId:
PROC [design:
CD.Design]
RETURNS [n:
INT ← -1] = {
IF design#
NIL
THEN
WITH CDProperties.GetProp[design, maxGroupIdProp]
SELECT
FROM
i: REF INT => n ← i^;
ENDCASE => NULL;
};
IF design#
NIL
THEN {
n: INT ← MaxGroupId[design];
IF n<LAST[INT]-10000 THEN maxGroupId ← MAX[maxGroupId, n];
IF CDProperties.GetProp[design, $CDSatellitesMaxGroupId]#NIL THEN Convert[design];
};
};
--general utilities
RectMinDist:
PROC [rectA, rectB:
CD.Rect]
RETURNS [
INT] =
INLINE {
IntervalMinDist:
PROC [i1min, i1max, i2min, i2max:
INT]
RETURNS [
INT] =
INLINE {
IF i1max<i2min THEN RETURN [i2min-i1max];
IF i1min>i2max THEN RETURN [i1min-i2max];
RETURN [0];
};
xMinDist: INT ~ IntervalMinDist[rectA.x1, rectA.x2, rectB.x1, rectB.x2];
yMinDist: INT ~ IntervalMinDist[rectA.y1, rectA.y2, rectB.y1, rectB.y2];
RETURN [xMinDist+yMinDist]
};
IsItalic:
PROC [fontName:
ROPE]
RETURNS [
BOOL←
FALSE] = {
pressFontLeng: INT = 17; pressFontName: ROPE = "Xerox/PressFonts/";
tiogaFontLeng: INT = 17; tiogaFontName: ROPE = "Xerox/TiogaFonts/";
leng: INT ← Rope.Length[fontName];
IF leng>tiogaFontLeng
THEN
IF Rope.Equal[Rope.Substr[fontName, 0, tiogaFontLeng], tiogaFontName,
FALSE]
THEN
RETURN [Ascii.Upper[Rope.Fetch[fontName, leng-1]]='I];
IF leng>pressFontLeng
THEN
IF Rope.Equal[Rope.Substr[fontName, 0, pressFontLeng], pressFontName,
FALSE]
THEN
RETURN [Ascii.Upper[Rope.Fetch[fontName, leng-2]]='I]
};
[] ← CDProperties.RegisterProperty[satellitesProp, $CDSatellites];
[] ← CDProperties.RegisterProperty[maxGroupIdProp, $CDSatellites];
[] ← CDProperties.RegisterProperty[oGroupIdProp, $CDSatellites];
[] ← CDProperties.RegisterProperty[iGroupIdProp, $CDSatellites];
CDProperties.InstallProcs[satellitesProp, [makeCopy: CDProperties.CopyVal, autoRem: TRUE]];
CDProperties.InstallProcs[maxGroupIdProp, [makeCopy: CDProperties.CopyVal]];
CDProperties.InstallProcs[oGroupIdProp, [makeCopy: CDProperties.CopyVal, autoRem: FALSE]];
CDProperties.InstallProcs[iGroupIdProp, [makeCopy: CDProperties.CopyVal, autoRem: FALSE]];
CDEvents.RegisterEventProc[$BeforeOutput, BeforeOutput];
CDEvents.RegisterEventProc[$AfterInput, AfterInput];
[] ← CDPrivate.EnumDesigns[CheckMaxGroupId];
END.