GGSessionLogImpl.mesa
Last edited by Bier on January 26, 1987 4:10:24 pm PST.
Contents: Routines for saving TIP Table atoms in a file to aid in interface evaluation and for playback.
Pier, October 21, 1986 3:08:46 pm PDT
DIRECTORY
Atom, BasicTime, FS, GGError, GGEvent, GGInterfaceTypes, GGBasicTypes, GGParseIn, GGSessionLog, GGUserInput, GGWindow, IO, Rope, SlackProcess;
GGSessionLogImpl: CEDAR PROGRAM
IMPORTS Atom, BasicTime, FS, GGError, GGEvent, GGParseIn, GGUserInput, GGWindow, IO, Rope, SlackProcess
EXPORTS GGSessionLog = BEGIN
CharClass: TYPE = IO.CharClass;
GargoyleData: TYPE = GGInterfaceTypes.GargoyleData;
Point: TYPE = GGBasicTypes.Point;
OpenSessionLog: PUBLIC PROC [fileName: Rope.ROPE, gargoyleData: GargoyleData] = {
this PROC sets up for logging but does not start it. Client must also call SlackProcess.EnableSessionLogging to start logging events.
stream: IO.STREAM;
fullName: Rope.ROPE;
gravExtent: REAL;
IF gargoyleData.debug.logStream#NIL THEN {
GGError.Append[gargoyleData.feedback, "Closing existing script.", oneLiner];
gargoyleData.debug.logStream.Close[];
};
IF Rope.Length[fileName] = 0 THEN GOTO NoName;
[fullName,,] ← FS.ExpandName[fileName, gargoyleData.currentWDir ! FS.Error => GOTO BadName;];
stream ← FS.StreamOpen[fullName, $create ! FS.Error => GOTO FSError];
gargoyleData.debug.logFileName ← fullName;
gargoyleData.debug.logStream ← stream;
GGEvent.InitializeAlignments[NIL, gargoyleData];
GGError.Append[gargoyleData.feedback, Rope.Cat["Opened ", fullName, " for scripting"], oneLiner];
gravExtent ← GGWindow.GetGravityExtent[gargoyleData];
EnterAction[[0,0], LIST[$SetGravityExtent, NEW[REAL ← gravExtent]], FALSE, gargoyleData];
EXITS
NoName => {GGError.Append[gargoyleData.feedback, "Please select a filename for scripting", oneLiner]; GGError.Blink[gargoyleData.feedback];};
BadName => {GGError.Append[gargoyleData.feedback, Rope.Concat["Illegal Name: ", fileName], oneLiner]; GGError.Blink[gargoyleData.feedback];};
FSError => {GGError.Append[gargoyleData.feedback, Rope.Concat["FSError while trying ", fileName], oneLiner]; GGError.Blink[gargoyleData.feedback];};
};
CloseSessionLog: PUBLIC PROC [gargoyleData: GargoyleData] = {
IF gargoyleData.debug.logStream=NIL THEN GOTO NotLogging;
gargoyleData.debug.logStream.Close[];
GGError.Append[gargoyleData.feedback, Rope.Concat["Closed ", gargoyleData.debug.logFileName], oneLiner];
gargoyleData.debug.logStream ← NIL;
gargoyleData.debug.logFileName ← NIL;
EXITS
NotLogging => GGError.Append[gargoyleData.feedback, "Not scripting this session", oneLiner];
};
EnterAction: PUBLIC SlackProcess.LoggingProc = {
LoggingProc: TYPE = PROC [point: Point, action: LIST OF REF ANY, mouseEvent: BOOL, clientData: REF ANY];
gargoyleData: GargoyleData ← NARROW[clientData];
stream: IO.STREAM ← gargoyleData.debug.logStream;
IF stream=NIL THEN {
GGError.AppendHerald[gargoyleData.feedback, "Attempted script entry without open script file", oneLiner];
GGError.Blink[gargoyleData.feedback];
RETURN;
};
IF mouseEvent THEN stream.PutF["* [%g, %g] ", [real[point.x]], [real[point.y]] ];
FOR actionList: LIST OF REF ANY ← action, actionList.rest UNTIL actionList = NIL DO
WITH actionList.first SELECT FROM
atom: ATOM => {
IF atom = $SawStartOp OR atom = $SawSelectAll OR atom = $SawTextFinish THEN LOOP;
stream.PutF["%g", [rope[Atom.GetPName[atom]]]];
};
n: REF INT => stream.PutF["%g", [integer[n^]]];
r: REF REAL => stream.PutF["%g", [real[r^]]];
c: REF CHAR => stream.PutF["'%g", [character[c^]]];
rope: Rope.ROPE => stream.PutF["\"%g\"", [rope[rope]]];
ENDCASE => ERROR;
IF actionList.rest = NIL THEN stream.PutChar[IO.CR]
ELSE stream.PutChar[IO.SP];
ENDLOOP;
};
PlaybackFromFile: PUBLIC PROC [fileName: Rope.ROPE, gargoyleData: GargoyleData] = {
fullName: Rope.ROPE;
success: BOOL;
f: IO.STREAM;
startTime: BasicTime.GMT;
startTimeCard: CARD;
success ← TRUE;
[fullName,,] ← FS.ExpandName[fileName, gargoyleData.currentWDir
! FS.Error => IF error.group = user THEN {
success ← FALSE;
CONTINUE;
}
];
IF NOT success THEN {
GGError.Append[gargoyleData.feedback, Rope.Cat["Could not open ", fileName, " for playback"], oneLiner];
GGError.Blink[gargoyleData.feedback];
RETURN;
};
[f, success] ← OpenExistingFile[fullName, gargoyleData];
IF NOT success THEN {
GGError.Append[gargoyleData.feedback, Rope.Cat["Could not open ", fullName, " for playback"], oneLiner];
GGError.Blink[gargoyleData.feedback];
RETURN;
};
GGEvent.InitializeAlignments[NIL, gargoyleData];
gargoyleData.aborted[playback] ← FALSE; -- just in case there was one from last playback
startTime ← BasicTime.Now[];
startTimeCard ← BasicTime.ToNSTime[startTime];
WHILE success DO
success ← PlayAction[f, gargoyleData];
IF gargoyleData.aborted[playback] THEN {
N.B.: YOU MAY NOT EXECUTE THIS CODE EVEN THOUGH YOU ABORT A PLAYBACK.
REASON: the slack queue. This proc can complete long before anything actually happens because of the queue, so you have to handle aborts at the abort detector. This code only gets executed if you abort while the queue is backed up.
GGError.Append[gargoyleData.feedback, Rope.Cat["Aborted playback of ", fullName], oneLiner];
SlackProcess.FlushQueue[gargoyleData.slackHandle];
gargoyleData.refresh.suppressRefresh ← FALSE; -- in case you killed FastPlayback
gargoyleData.aborted[playback] ← FALSE;
RETURN;
};
ENDLOOP;
GGUserInput.PlayAction[[0,0], LIST[$EndOfSessionLogMessage, fullName, NEW[CARD ← startTimeCard]], FALSE, gargoyleData];
gargoyleData.aborted[playback] ← FALSE;
};
EndOfSessionLogMessage: PUBLIC PROC [event: LIST OF REF ANY, clientData: REF ANY] = {
gargoyleData: GargoyleData ← NARROW[clientData];
logName: Rope.ROPENARROW[event.rest.first];
startTimeCard: CARDNARROW[event.rest.rest.first, REF CARD]^;
startTime: BasicTime.GMT ← BasicTime.FromNSTime[startTimeCard];
endTime: BasicTime.GMT;
totalTime: INT;
endTime ← BasicTime.Now[];
totalTime ← BasicTime.Period[startTime, endTime];
GGError.PutF[gargoyleData.feedback, oneLiner, "Finished playback of %g in time (%r)", [rope[logName]], [integer[totalTime]]];
};
OpenExistingFile: PROC [name: Rope.ROPE, gargoyleData: GargoyleData] RETURNS [f: IO.STREAM, success: BOOL] = {
success ← TRUE;
Two possiblilities
1) File doesn't exist. Print error message.
2) File does exist. File it in. Succeed.
f ← FS.StreamOpen[name
! FS.Error => {
IF error.group = user THEN {
success ← FALSE;
GGError.Append[gargoyleData.feedback, error.explanation, oneLiner];
GGError.Blink[gargoyleData.feedback];
}
ELSE ERROR;
CONTINUE}];
};
AppendAtom: PROC [atom: ATOM, list: LIST OF REF ANY] RETURNS [newList: LIST OF REF ANY] = {
l: LIST OF REF ANY ← list;
IF l = NIL THEN RETURN[LIST[atom]];
UNTIL l.rest = NIL DO l ← l.rest ENDLOOP;
l.rest ← CONS[atom, NIL];
newList ← list;
};
AppendToken: PROC [token: REF ANY, list: LIST OF REF ANY] RETURNS [newList: LIST OF REF ANY] = {
l: LIST OF REF ANY ← list;
IF l = NIL THEN RETURN[LIST[token]];
UNTIL l.rest = NIL DO l ← l.rest ENDLOOP;
l.rest ← CONS[token, NIL];
newList ← list;
};
PlayAction: PROC [f: IO.STREAM, gargoyleData: GargoyleData] RETURNS [success: BOOL] = {
c: CHAR;
mouseEvent: BOOL;
point: Point;
good: BOOL;
token: REF ANY;
action: LIST OF REF ANY;
success ← TRUE;
c ← IO.GetChar[f
!IO.EndOfStream => {success ← FALSE; CONTINUE}];
IF NOT success THEN RETURN;
IF c = '* THEN {
good ← GGParseIn.ReadHorizontalBlank[f];
IF NOT good THEN ERROR;
point ← GGParseIn.ReadPoint[f];
mouseEvent ← TRUE
}
ELSE {
mouseEvent ← FALSE;
IO.Backup[f, c];
};
token ← $Start;
UNTIL token = $EndOfLine DO
[token] ← ReadSpacesAndToken[f];
IF token = $EndOfLine THEN LOOP;
action ← AppendToken[token, action]
ENDLOOP;
GGUserInput.PlayAction[point, action, mouseEvent, gargoyleData];
};
IsDigitOrOp: PROC [c: CHAR] RETURNS [BOOL] = {
RETURN[c IN ['0..'9] OR c = '- OR c = '+];
};
ReadSpacesAndToken: PUBLIC PROC [f: IO.STREAM] RETURNS [token: REF ANY] = {
word: Rope.ROPE;
good: BOOL;
int: INT;
real: REAL;
firstChar: CHAR;
good ← GGParseIn.ReadHorizontalBlank[f];
IF NOT good THEN {
token ← $EndOfLine;
RETURN;
};
firstChar ← IO.PeekChar[f];
SELECT TRUE FROM
firstChar = '" => {
token ← f.GetRopeLiteral[];
};
firstChar = '' => {
[] ← f.GetChar[];
token ← NEW[CHAR ← f.GetChar[]];
};
IsDigitOrOp[firstChar] => {
word ← GGParseIn.ReadBlankAndWord[f];
IF Rope.Find[word, "."] = -1 THEN { -- an integer
int ← IO.GetInt[IO.RIS[word]];
token ← NEW[INT ← int];
}
ELSE {
real ← IO.GetReal[IO.RIS[word]];
token ← NEW[REAL ← real];
};
};
ENDCASE => {
word ← GGParseIn.ReadBlankAndWord[f];
token ← Atom.MakeAtom[word];
};
};
FailedReadSpacesAndToken: PUBLIC PROC [f: IO.STREAM] RETURNS [token: REF ANY] = {
word: Rope.ROPE;
good: BOOL;
int: INT;
real: REAL;
tokenKind: IO.TokenKind;
good ← GGParseIn.ReadHorizontalBlank[f];
IF NOT good THEN {
token ← $EndOfLine;
RETURN;
};
[tokenKind, word, ----] ← IO.GetCedarTokenRope[f];
SELECT tokenKind FROM
tokenDECIMAL => {
int ← IO.GetInt[IO.RIS[word]];
token ← NEW[INT ← int];
};
tokenREAL => {
real ← IO.GetReal[IO.RIS[word]];
token ← NEW[REAL ← real];
};
tokenCHAR => {
token ← NEW[CHAR ← Rope.Fetch[word, 1]];
};
tokenROPE => token ← word;
tokenID => token ← Atom.MakeAtom[word];
ENDCASE => ERROR;
};
ReadLine: PUBLIC PROC [f: IO.STREAM] RETURNS [line: Rope.ROPE] = {
Reads a rope UNTIL <CR> is encountered.
LineBreakProc: SAFE PROC [char: CHAR] RETURNS [IO.CharClass] = CHECKED {
SELECT char FROM
IO.CR =>RETURN [break];
ENDCASE => RETURN [other];
};
end: BOOLFALSE;
[line, ----] ← IO.GetTokenRope[f, LineBreakProc
!IO.EndOfStream => {end ← TRUE; CONTINUE}];
IF end THEN {line ← NIL; RETURN};
};
ReadSpaceAndChar: PUBLIC PROC [f: IO.STREAM] RETURNS [token: REF ANY] = {
c: CHAR;
good: BOOL;
c ← IO.GetChar[f !IO.EndOfStream => ERROR];
c ← IO.GetChar[f !IO.EndOfStream => ERROR];
good ← GGParseIn.ReadHorizontalBlank[f];
IF good THEN ERROR;
token ← NEW[CHAR ← c];
};
END.