CDOut.mesa
Copyright © 1983, 1986 by Xerox Corporation. All rights reserved.
Created by Ch. Jacobi, December 12, 1983 2:02 pm
Some code from Kim Rachmeler
Last Edited by: Christian Jacobi, September 4, 1986 5:08:59 pm PDT
DIRECTORY
BasicTime,
CDIO,
CD,
CDCleanUp,
CDDirectory,
CDEvents,
CDPrivate,
CDProperties,
CDSequencer,
CDValue,
Convert,
FileNames,
FS,
IO,
Process,
Rope,
TerminalIO,
TokenIO;
CDOut: CEDAR PROGRAM --monitored by TokenIO
IMPORTS BasicTime, CD, CDIO, CDCleanUp, CDDirectory, CDEvents, CDProperties, CDSequencer, CDValue, Convert, FileNames, FS, IO, Process, Rope, TerminalIO, TokenIO
EXPORTS CDIO
SHARES CDProperties =
BEGIN
xChipndaleFile: INT = 12121983;
xVersion: INT = 15;
propKeyForKey: REF = NEW[INT];
-- global variables
-- protected through attachment with Attach, Release and TokenIO
attached: BOOLFALSE;
gDesign: CD.Design ← NIL;
gDirectoryNum: INT;
simpleObjectsInTableMax: INT ← 10000;
gCountSimpleObjectsInTable: INT;
gThisKey: REF ATOM ← NIL;
gLastKey: REF ATOM ← NIL;
gDirectoryMark: TokenIO.Mark;
gQuiet, gEmergency: BOOL ← FALSE;
gRemoveAlso: REF ATOMNIL;
dotCnt: INT ← 0; --not exact, it's mainly to pacify user
colCnt: INT ← 0; --not exact, it's mainly to pacify user
FileExists: PROC [name: Rope.ROPE] RETURNS [found: BOOLTRUE] = {
[] ← FS.FileInfo[name: name, remoteCheck: FALSE
! FS.Error => IF error.group#bug THEN {found ← FALSE; CONTINUE}
];
};
GetNameInteractively: PROC [wDir: Rope.ROPENIL] RETURNS [name: Rope.ROPE] = {
--may raise TerminalIO.UserAbort
TerminalIO.WriteRope["file name:"];
IF ~Rope.IsEmpty[wDir] THEN TerminalIO.WriteRopes[" (", wDir, ")"];
name ← TerminalIO.RequestRope[" > "];
name ← CDIO.MakeName[name, "dale", wDir];
IF FileExists[name] THEN {
TerminalIO.WriteRopes["File ", name, " exists already; overwrite?"];
IF ~TerminalIO.Confirm[choice: "overwrite", label: "file exists"] THEN
ERROR TerminalIO.UserAbort;
TerminalIO.WriteRope[" yes\n"];
};
};
WriteRope: PROC [r: Rope.ROPE] = INLINE {
--feedback on terminal
IF ~gQuiet THEN Feedback[gEmergency, r]
};
Feedback: PROC [fork: BOOL, r: Rope.ROPE] = INLINE {
--feedback on terminal
IF fork THEN TRUSTED {
--don't hang the output if viewer wedges
Process.Detach[ FORK TerminalIO.WriteRope[r] ]
}
ELSE TerminalIO.WriteRope[r]
};
WriteLayer: PUBLIC PROC [l: CD.Layer] = {
TokenIO.WriteAtom[CD.LayerKey[l]];
};
WritePos: PUBLIC PROC [p: CD.Position] = {
TokenIO.WriteInt[p.x];
TokenIO.WriteInt[p.y];
};
WriteRect: PUBLIC PROC[r: CD.Rect] = {
TokenIO.WriteInt[r.x1];
TokenIO.WriteInt[r.y1];
TokenIO.WriteInt[r.x2];
TokenIO.WriteInt[r.y2];
};
WriteProperties: PUBLIC PROC [props: CD.PropList] = {
IF ~gEmergency THEN
IF gQuiet THEN Process.Yield[]--should make output slower than redraw
ELSE CDSequencer.CheckAborted[gDesign];
--PropertyLists guarantees properties are not reordered; going through list is ok
FOR l: CD.PropList ← props, l.rest WHILE l#NIL DO
WITH l.first.key SELECT FROM
a: ATOM => {
pp: CDProperties.PropertyProcs = CDProperties.FetchProcs[a];
IF pp#NIL AND pp.internalWrite#NIL THEN {
TokenIO.WriteAtom[$Property];
TokenIO.WritePushFlag[a];
pp.internalWrite[a, l.first.val];
TokenIO.WritePopFlag[];
}
ELSE WITH l.first.val SELECT FROM
r: Rope.ROPE => {
TokenIO.WriteAtom[$DefaultProperty];
TokenIO.WriteAtom[a];
TokenIO.WriteRope[r];
};
at: ATOM => {
TokenIO.WriteAtom[$DefaultProperty];
TokenIO.WriteAtom[a];
TokenIO.WriteAtom[at];
};
ri: REF INT => {
TokenIO.WriteAtom[$DefaultProperty];
TokenIO.WriteAtom[a];
TokenIO.WriteInt[ri^];
};
pl: CDPrivate.LayerRef => {
TokenIO.WriteAtom[$DefaultProperty];
TokenIO.WritePushFlag[a];
TokenIO.WriteAtom[$layer]; -- now comes a property list
WriteLayer[pl.number];
TokenIO.WritePopFlag[];
};
pl: CD.PropList => {
TokenIO.WriteAtom[$DefaultProperty];
TokenIO.WritePushFlag[a];
TokenIO.WriteAtom[$properties]; -- now comes a property list
WriteProperties[pl];
TokenIO.WritePopFlag[];
};
rl: LIST OF Rope.ROPE => {
TokenIO.WriteAtom[$DefaultProperty];
TokenIO.WritePushFlag[a];
TokenIO.WriteAtom[$ropeList]; -- now comes a rope list
FOR l: LIST OF Rope.ROPE ← rl, l.rest WHILE l#NIL DO
TokenIO.WriteRope[l.first];
ENDLOOP;
TokenIO.WritePopFlag[];
};
ENDCASE => NULL;
};
ra: REF ATOM => IF ra^=$MayBeRemoved AND ra#gLastKey THEN gRemoveAlso ← ra;
ENDCASE => NULL;
ENDLOOP;
};
WriteInstance: PUBLIC PROC [ap: CD.Instance] = {
TokenIO.WriteInt[ap.location.x];
TokenIO.WriteInt[ap.location.y];
CDIO.WriteOrientation[ap.orientation];
WriteProperties[ap.properties];
WriteObject[ap.ob];
};
Length: PROC [il: CD.InstanceList] RETURNS [leng: INT𡤀] = INLINE {
FOR list: CD.InstanceList ← il, list.rest WHILE list#NIL DO
leng ← leng+1;
ENDLOOP;
};
WriteInstanceList: PUBLIC PROC [list: CD.InstanceList] = {
TokenIO.WriteInt[Length[list]];
FOR l: CD.InstanceList ← list, l.rest WHILE l#NIL DO
WriteInstance[l.first];
ENDLOOP;
};
WriteObject: PUBLIC PROC [ob: CD.Object] = {
WITH CDProperties.GetObjectProp[from: ob, prop: gThisKey] SELECT FROM
key: REF INT => {
IF key^<0 THEN {TerminalIO.WriteRope["**** circular object dependency\n"]; ERROR};
dotCnt ← dotCnt+1; IF dotCnt>=10 THEN {WriteRope["."]; dotCnt𡤀};
TokenIO.WriteInt[key^];
};
ENDCASE => WriteObjectDefinition[ob]
};
WriteObjectDefinition: PROC [ob: CD.Object] = {
key: REF INT ← NIL;
WriteTableKey: PROC[ob: CD.Object] = INLINE {
gDirectoryNum ← gDirectoryNum+1;
key ← NEW[INT ← -gDirectoryNum]; --a negative key is not yet finished
TokenIO.WriteInt[gDirectoryNum];
CDProperties.PutObjectProp[onto: ob, prop: gThisKey, val: key];
};
colCnt ← colCnt+1; IF colCnt>=10 THEN {WriteRope[":"]; colCnt𡤀};
IF ob.class.internalWrite=NIL THEN {
ob1: CD.Object ← CDDirectory.Expand[ob].new;
IF ob1=NIL THEN ob1 ← CDDirectory.ExpandByDraw[me: ob].new;
IF ob1=NIL THEN {
TokenIO.WritePushFlag[$Unknown];
TokenIO.WritePopFlag[];
WriteRope["*object not written\n"];
}
ELSE {
WriteRope["*"];
WriteObject[ob1];
CDProperties.PutObjectProp[onto: ob, prop: gThisKey, val: CDProperties.GetObjectProp[ob1, gThisKey]];
};
}
ELSE {
--object has internalWrite proc
TokenIO.WritePushFlag[ob.class.objectType];
IF ob.class.inDirectory THEN WriteTableKey[ob]
ELSE IF gCountSimpleObjectsInTable<simpleObjectsInTableMax THEN {
gCountSimpleObjectsInTable ← gCountSimpleObjectsInTable+1;
TokenIO.WriteAtom[$CDIOUseTable];
WriteTableKey[ob]
};
ob.class.internalWrite[ob];
IF ob.class.inDirectory THEN {
TokenIO.WriteRope[CDDirectory.Name[ob]];
};
WriteProperties[ob.properties];
TokenIO.WritePopFlag[];
IF key#NIL THEN key^ ← -key^;--finished: make key positive
};
CDProperties.PutObjectProp[onto: ob, prop: gLastKey, val: NIL];
};
WritePushLevel: PROC [pl: LIST OF CD.PushRec] = {
IF pl=NIL THEN RETURN;
IF pl.rest#NIL THEN WritePushLevel[pl.rest];
TokenIO.WriteAtom[$Push];
IF pl.first.mightReplace#NIL THEN WriteInstance[pl.first.mightReplace]
ELSE TokenIO.WriteAtom[$Nil];
WriteObjectDefinition[pl.first.dummyCell.ob];
WriteRect[pl.first.specific.dIr];
};
EachChildInDir: CDDirectory.EnumerateObjectsProc --PROC [me: Object, x: REF]-- = {
IF me.class.inDirectory OR gCountSimpleObjectsInTable<simpleObjectsInTableMax THEN {
xx: REF ← CDProperties.GetObjectProp[from: me, prop: gThisKey];
IF xx#NIL THEN RETURN; -- it and its children are already written
IF me.class.inDirectory THEN CDDirectory.EnumerateChildObjects[me, EachChildInDir, x];
--if due to funny recursion me is now written out: this will write a reference
WriteObject[me];
};
};
EachDirectoryEntry: CDDirectory.EachEntryAction = {
IF ob.class.inDirectory THEN EachChildInDir[ob, NIL];
};
KillOldProps: PROC [lastAgain: BOOL] = {
RemoveOldProperties: PROC[design: CD.Design, key: REF] = {
--at this place we know that CDCleanUp will fork and will set the priority down
CDCleanUp.RemoveProperties[design, key];
};
temThis: REF ATOM ← gThisKey;
temLast: REF ATOM ← gLastKey;
temAlso: REF ATOM ← gRemoveAlso;
IF temThis#NIL THEN temThis^ ← $MayBeRemoved; --next term to reduce number of processes
IF temLast#NIL AND lastAgain THEN RemoveOldProperties[gDesign, temLast];
IF temAlso#NIL AND temLast#temAlso THEN RemoveOldProperties[gDesign, temAlso];
};
InnerWriteDesign: PROC [] = {
ENABLE UNWIND => KillOldProps[lastAgain: TRUE];
PrepareOutput: PROC = {
--uses globals
WITH CDValue.Fetch[gDesign, propKeyForKey, design] SELECT FROM
ra: REF ATOM => gLastKey ← ra;
ENDCASE => gLastKey ← NEW[ATOM];
gLastKey^ ← $MayBeRemoved;
gThisKey ← NEW[ATOM ← $UsedForIO];
CDValue.Store[gDesign, propKeyForKey, gThisKey];
gDirectoryNum ← 0;
};
NameForOutput: PROC [d: CD.Design] RETURNS [Rope.ROPE] = {
IF Rope.Length[d.name]<=0 OR Rope.Fetch[d.name]='/ OR Rope.Fetch[d.name]='[ THEN RETURN [NIL]
ELSE RETURN [d.name];
};
TimeKey: PROC [] RETURNS [Rope.ROPE] = {
RETURN [Convert.RopeFromTime[from: BasicTime.Now[], end: seconds]];
};
PrepareOutput[];
TokenIO.WriteRope[NameForOutput[gDesign]];
TokenIO.WriteRope[TimeKey[]];
gDirectoryMark ← TokenIO.MarkAndWriteInt[gDirectoryNum]; -- number of entries in directory
gCountSimpleObjectsInTable ← 0;
[] ← CDDirectory.Enumerate[design: gDesign, action: EachDirectoryEntry];
WriteProperties[gDesign.properties^];
WritePushLevel[gDesign.actual];
TokenIO.UpdateMark[mark: gDirectoryMark, value: gDirectoryNum];
TokenIO.WriteAtom[$EndOfDesign];
KillOldProps[lastAgain: FALSE];
};
OpenStream: PROC [design: CD.Design, to: REF, emergency, quiet: BOOL] RETURNS [stream: IO.STREAM, mustClose: BOOLTRUE, name: Rope.ROPENIL] = {
--opens the file but does not do any output to it yet
--makes the messages on failure
wDir: Rope.ROPECDIO.GetWorkingDirectory[design];
IF Rope.IsEmpty[wDir] THEN wDir ← FileNames.CurrentWorkingDirectory[];
WITH to SELECT FROM
s: IO.STREAM => RETURN [stream←s, mustClose←FALSE, name←NIL];
r: Rope.ROPE => name ← r;
ENDCASE => IF to#NIL THEN Feedback[emergency, "bad file parameter in WriteDesign\n"];
IF Rope.IsEmpty[name] THEN
name ←
IF emergency THEN "///temp/ChipNDale/emergency/emergency.dale"
ELSE IF quiet THEN Rope.Cat["///temp/ChipNDale/saved/", design.name]
ELSE GetNameInteractively[wDir];
name ← CDIO.MakeName[name, "dale", wDir];
stream ← FS.StreamOpen[fileName: name, accessOptions: $create, keep: 2, createByteCount: 25600 !
FS.Error => {
mustClose ← FALSE; stream ← NIL;
IF ~quiet THEN
Feedback[emergency, Rope.Cat["file not opened: ", error.explanation, "\n"]];
IF error.group#bug THEN CONTINUE;
}
];
IF stream=NIL AND ~quiet THEN Feedback[emergency, "file not created\n"];
};
Attach: PROC [stream: IO.STREAM, emergency: BOOL, quiet: BOOL] RETURNS [done: BOOLTRUE] = TRUSTED {
--makes messages if failed and no danger of locking
TokenIO.AttachWriter[stream ! TokenIO.Error => {done ← FALSE; CONTINUE}];
IF ~done AND emergency THEN {
FOR i: INT IN [0..5] DO
TokenIO.StopWriting[];
Process.Pause[Process.SecondsToTicks[1]];
TokenIO.StopWriting[];
Process.Pause[Process.SecondsToTicks[2]];
Process.Detach[ FORK TerminalIO.WriteRope["TRIES TO BREAK THE LOCK\n"]];
TokenIO.ReleaseWriter[]; -- DANGEROUS
Process.Pause[Process.SecondsToTicks[1]];
done ← TRUE;
TokenIO.AttachWriter[stream ! TokenIO.Error => {done ← FALSE; CONTINUE}];
IF done THEN RETURN;
ENDLOOP;
};
IF done THEN {gQuiet ← quiet; gEmergency ← emergency; attached ← TRUE}
ELSE IF ~quiet THEN Feedback[emergency, HelpMessage["ChipNDale internal locks are hold"]];
};
Release: PROC [] = {
IF attached THEN TokenIO.ReleaseWriter[];
attached ← FALSE;
};
WriteDesign: PUBLIC PROC [design: CD.Design, to: REF, quiet: BOOL, emergency: BOOL] RETURNS [done: BOOLFALSE] = {
--to is either a IO.STREAM, a Rope.ROPE, or NIL
--if emergency, some locks are ignored, interactive input is skipped; you better
--roll back after an emergency write is done
ENABLE UNWIND => Release[];
mustClose: BOOL FALSE;
binFile: IO.STREAM; fileName: Rope.ROPE; sealMark: TokenIO.Mark;
[binFile, mustClose, fileName] ← OpenStream[design, to, emergency, quiet];
IF binFile=NIL THEN RETURN;
IF ~Attach[binFile, emergency, quiet] THEN RETURN;
[] ← CDEvents.ProcessEvent[ev: beforeOutputEvent, design: design, x: NIL, listenToDont: FALSE];
gDesign ← design;
TokenIO.WriteInt[xChipndaleFile];
TokenIO.WriteInt[xVersion];
sealMark ← TokenIO.MarkAndWriteInt[0]; -- invalid seal; it will be fixed at the end
TokenIO.WriteAtom[design.technology.key];
TokenIO.WriteRope[design.technology.name];
IF CDEvents.ProcessEvent[ev: writeEvent, design: design, x: NIL, listenToDont: TRUE].dont THEN {
WriteRope["output not done\n"];
Release[];
RETURN
};
InnerWriteDesign[];
TokenIO.UpdateMark[sealMark, -1]; -- validate seal
IF mustClose THEN IO.Close[binFile];
WriteRope["\n"];
IF ~emergency AND quiet THEN
TerminalIO.WriteF["%ldesign %g written on file %g%l\n", [rope["i"]], [rope[design.name]], [rope[fileName]], [rope[" "]]]
ELSE
Feedback[emergency, Rope.Cat["design ", design.name, " written on file ", fileName, "\n"]];
Release[];
IF ~quiet THEN {
CDValue.Store[boundTo: design, key: $CDxLastFile, value: fileName];
[] ← CDEvents.ProcessEvent[afterOutputEvent, design];
};
done ← TRUE;
};
HelpMessage: PROC [r: Rope.ROPENIL] RETURNS [msg: Rope.ROPENIL] = {
Write: PROC [r: Rope.ROPE] = {
msg ← Rope.Cat[msg, r, "\n"]
};
Write["****************************************"];
Write[r];
Write["Help for saving a design:"];
Write["wait until the background saving is done and make a copy of it! (but don't rely on background saving alone; background saving is done WITHOUT requiring monitor locks, for obvious reasons.)"];
Write["then, try regular output again, before you proceed to any of the more drastic methods following:\n"];
Write["SHIFT-SHIFT-SWAT (>2 seconds) should save all designs"];
Write["or use a command tool: (the command tool commands require you to bringover the necesarry sources and bcd's first)"];
Write[" ""← CDSequencerImpl.savePeriod ← -1"" "];
Write[" prevents any further un-monitored automatic saving in parallel with your other command tool commands."];
Write[" ""← CDEmergencyHandling.SaveAll[]"" "];
Write[" or"];
Write[" ""← TokenIO.ReleaseWriter[]"" "];
Write[" to release the locks; after this try a regular output command"];
Write["****************************************"];
};
-- Module initialization
writeEvent: CDEvents.EventRegistration = CDEvents.RegisterEventType[$WriteTechnologyPrivate];
beforeOutputEvent: CDEvents.EventRegistration = CDEvents.RegisterEventType[$BeforeOutput];
afterOutputEvent: CDEvents.EventRegistration = CDEvents.RegisterEventType[$AfterOutput];
END.