SessionLogImpl.mesa
Copyright Ó 1986, 1987 by Xerox Corporation. All rights reserved.
Last edited by Bier on November 4, 1987 4:39:10 pm PST
Contents: Routines recording and playing back a record of an interactive session. SessionLog is intended to be used with SlackProcess in systems built according to the new user interface architecture (see SlackProcessDoc and AtomButtonsDoc).
Pier, October 28, 1987 11:39:17 am PST
Bier, March 13, 1991 5:26 pm PST
DIRECTORY
Atom, Convert, Imager, IO, MessageWindow, RefText, Rope, SessionLog, SlackProcess, Vector2;
SessionLogImpl: CEDAR PROGRAM
IMPORTS Atom, Convert, IO, MessageWindow, RefText, Rope
EXPORTS SessionLog = BEGIN
SyntaxError: SIGNAL [position: NAT, wasThere: Rope.ROPE, notThere: Rope.ROPE] = CODE;
Recording
Complain: PROC [r: Rope.ROPE] = {
MessageWindow.Append[message: r, clearFirst: TRUE];
MessageWindow.Blink[];
};
recentEnterActionError: BOOLFALSE;
EnterAction: PUBLIC PROC [f: IO.STREAM, action: LIST OF REF ANY] = {
ioError: Rope.ROPE;
BEGIN
ENABLE IO.Error => {ioError ← DescribeIOError[ec]; GOTO IOError};
FOR actionList: LIST OF REF ANY ← action, actionList.rest UNTIL actionList = NIL DO
IF actionList.first=NIL THEN f.PutF["\"\""] -- assume its an empty rope
ELSE WITH actionList.first SELECT FROM
atom: ATOM => f.PutF["%g", [rope[Atom.GetPName[atom]]]];
n: REF INT => f.PutF["%g", [integer[n^]]];
r: REF REAL => f.PutF["%g", [real[r^]]];
c: REF CHAR => f.PutF["'%g", [character[c^]]];
cd: REF CARD => f.PutF["%g", [cardinal[cd^]]];
rope: Rope.ROPE => f.PutF["\"%g\"", [rope[rope]]];
refText: REF TEXT => f.PutF["\"%g\"", [text[refText]]];
refPoint: REF Vector2.VEC => f.PutF["[%g, %g]", [real[refPoint.x]], [real[refPoint.y]]];
refRect: REF Imager.Rectangle => f.PutF["[%g %g %g %g]", [real[refRect.x]], [real[refRect.y]], [real[refRect.w]], [real[refRect.h]] ];
ENDCASE; -- shouldn't happen, but don't do anything bad
IF actionList.rest = NIL THEN f.PutChar['\n]
ELSE f.PutChar[IO.SP];
ENDLOOP;
recentEnterActionError ← FALSE;
EXITS
IOError => {
IF NOT recentEnterActionError THEN
Complain[Rope.Cat["IO.Error (", ioError, ") during SessionLog.EnterAction"]];
recentEnterActionError ← TRUE; -- don't Complain again until some call to EnterAction succeeds
};
END;
};
DescribeIOError: PROC [ec: IO.ErrorCode] RETURNS [rope: Rope.ROPE] = {
rope ← SELECT ec FROM
Null => "Null",
NotImplementedForThisStream => "NotImplementedForThisStream",
StreamClosed => "StreamClosed",
Failure => "Failure",
IllegalBackup => "IllegalBackup",
BufferOverflow => "BufferOverflow",
BadIndex => "BadIndex",
SyntaxError => "SyntaxError",
Overflow => "Overflow",
PFInvalidCode => "PFInvalidCode",
PFInvalidPFProcs => "PFInvalidPFProcs",
PFCantBindConversionProc => "PFCantBindConversionProc",
PFFormatSyntaxError => "PFFormatSyntaxError",
PFTypeMismatch => "PFTypeMismatch",
PFUnprintableValue => "PFUnprintableValue",
ENDCASE => "unknown";
};
Playing Back
PlayScript: PUBLIC PROC [f: IO.STREAM, clientData: REF ANY, notifyProc: SlackProcess.ActionProc] = {
endOfStream: BOOLFALSE;
WHILE NOT endOfStream DO
endOfStream ← PlayAction[f, clientData, notifyProc];
ENDLOOP;
};
recentPlayActionError: BOOLFALSE;
PlayAction: PUBLIC PROC [f: IO.STREAM, clientData: REF ANY, notifyProc: SlackProcess.ActionProc] RETURNS [endOfStream: BOOLFALSE] = {
ioError: Rope.ROPE;
BEGIN
ENABLE IO.Error => {ioError ← DescribeIOError[ec]; GOTO IOError};
c: CHAR;
mouseEvent: BOOL;
point: Vector2.VEC;
good: BOOL;
token: REF ANY;
actionList: LIST OF REF;
ReadBlank[f];
For compatibility with the old style of script.
c ← IO.GetChar[f
!IO.EndOfStream => {endOfStream ← TRUE; CONTINUE}];
IF endOfStream THEN RETURN;
IF c = '* THEN {
atom: ATOM;
refPoint: REF Vector2.VEC;
good ← ReadHorizontalBlank[f];
IF NOT good THEN ERROR;
point ← ReadPoint[f];
refPoint ← NEW[Vector2.VEC ← point];
token ← ReadSpacesAndToken[f];
atom ← NARROW[token];
UNTIL token = $EndOfLine DO
token ← ReadSpacesAndToken[f];
ENDLOOP;
actionList ← LIST[atom, refPoint];
}
End of compatibility.
ELSE {
mouseEvent ← FALSE;
IO.Backup[f, c];
token ← $Start;
UNTIL token = $EndOfLine DO
token ← ReadSpacesAndToken[f];
IF token = $EndOfLine THEN LOOP;
actionList ← AppendToken[token, actionList]
ENDLOOP;
};
notifyProc[clientData, actionList];
EXITS
IOError => {
IF NOT recentPlayActionError THEN
Complain[Rope.Cat["IO.Error (", ioError, ") during SessionLog.PlayAction"]];
recentPlayActionError ← TRUE; -- don't Complain again until some call to PlayAction succeeds
};
END;
};
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;
};
Parsing
IsDigitOrOp: PROC [c: CHAR] RETURNS [BOOL] = {
RETURN[c IN ['0..'9] OR c = '- OR c = '+];
};
ReadSpacesAndToken: PROC [f: IO.STREAM] RETURNS [token: REF ANY] = {
word: Rope.ROPE;
good: BOOL;
int: INT;
card: CARD;
real: REAL;
firstChar: CHAR;
good ← 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[]];
};
firstChar = '[ => {
x, y, w, h: REAL;
nextChar: CHAR;
ReadRope[f, "["];
x ← ReadBlankAndReal[f];
nextChar ← IO.PeekChar[f];
SELECT TRUE FROM
nextChar = ', => {
ReadRope[f, ","];
y ← ReadBlankAndReal[f];
ReadRope[f, "]"];
token ← NEW[Vector2.VEC ← [x, y]];
};
ENDCASE => {
y ← ReadBlankAndReal[f];
w ← ReadBlankAndReal[f];
h ← ReadBlankAndReal[f];
ReadRope[f, "]"];
token ← NEW[Imager.Rectangle ← [x, y, w, h]];
};
};
IsDigitOrOp[firstChar] => {
word ← ReadBlankAndWord[f];
IF Rope.Find[word, "."] = -1 THEN { -- an integer
IF Rope.Fetch[word, 0] = '- THEN {
int ← IO.GetInt[IO.RIS[word]];
token ← NEW[INT ← int];
}
ELSE {
card ← IO.GetCard[IO.RIS[word]];
IF card > 2147483647 THEN token ← NEW[CARD ← card] -- too big to fit in an INT
ELSE token ← NEW[INT ← card];
};
}
ELSE {
real ← IO.GetReal[IO.RIS[word]];
token ← NEW[REAL ← real];
};
};
ENDCASE => {
word ← ReadBlankAndWord[f];
token ← Atom.MakeAtom[word];
};
};
ReadLine: PROC [f: IO.STREAM] RETURNS [line: Rope.ROPE] = {
Reads a rope UNTIL <CR> is encountered.
LineBreakProc: SAFE PROC [char: CHAR] RETURNS [IO.CharClass] = {
SELECT char FROM
IO.CR, IO.LF =>RETURN [break];
ENDCASE => RETURN [other];
};
end: BOOLFALSE;
[line, ----] ← IO.GetTokenRope[f, LineBreakProc
!IO.EndOfStream => {end ← TRUE; CONTINUE}];
IF end THEN {line ← NIL; RETURN};
};
ReadSpaceAndChar: 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 ← ReadHorizontalBlank[f];
IF good THEN ERROR;
token ← NEW[CHAR ← c];
};
ReadBlank: PROC [f: IO.STREAM] = {
Reads, <SPACE>'s, <CR>'s, and <TAB>'s until something else is encountered. Doesn't mind if no white space characters are found. Treats comments as white space.
[] ← IO.SkipWhitespace[f, TRUE];
};
ReadHorizontalBlank: PROC [f: IO.STREAM] RETURNS [good: BOOL] = {
Reads <SPACE>'s, and <TABS>'s until something else is encountered. Returns good = FALSE if a CR or LF is encountered before non-white space
HorizontalBlankProc: SAFE PROC [char: CHAR] RETURNS [IO.CharClass] = TRUSTED {
SELECT char FROM
IO.TAB, IO.SP => RETURN [other];
ENDCASE => RETURN [break];
};
whiteSpace: Rope.ROPE;
c: CHAR;
end: BOOLFALSE;
good ← TRUE;
[whiteSpace, ----] ← IO.GetTokenRope[f, HorizontalBlankProc
!IO.EndOfStream => {end ← TRUE; CONTINUE}];
IF end THEN {good ← FALSE; RETURN};
c ← Rope.Fetch[whiteSpace, 0];
SELECT c FROM
IO.CR, IO.LF => good ← FALSE;
IO.TAB, IO.SP => { -- there was some whitespace
nextC: CHARIO.PeekChar[f];
IF nextC = IO.CR OR nextC = IO.LF THEN { -- the whitespace comes at the end of a line
good ← FALSE;
[] ← IO.GetChar[f];
}
ELSE good ← TRUE; -- normal whitespace
};
ENDCASE => {good ← TRUE; IO.Backup[f, c]}; -- there was no whitespace
};
ReadBlankAndReal: PROC [f: IO.STREAM] RETURNS [r: REAL] = {
A convenience function. Equivalent to ReadBlank[f]; r ← ReadReal[f];
ReadBlank[f];
r ← ReadReal[f];
};
ReadReal: PROC [f: IO.STREAM] RETURNS [r: REAL] = {
Reads digits up to the next ), ], <CR>, <SPACE> or <COMMA>. Leaves these terminators on the stream.
RealBreakProc: SAFE PROC [char: CHAR] RETURNS [IO.CharClass] = TRUSTED {
SELECT char FROM
'), '], ', => RETURN [break];
IO.CR, IO.LF =>RETURN [break];
IO.SP => RETURN [break];
ENDCASE => RETURN [other];
};
realText, buffer: REF TEXT;
end: BOOLFALSE;
buffer ← RefText.ObtainScratch[50];
[realText, ----] ← IO.GetToken[f, RealBreakProc, buffer
!IO.EndOfStream => {end ← TRUE; CONTINUE}];
IF end THEN {r ← 0.0; RETURN};
IF RefText.Find[realText, ".", 0, FALSE] = -1 THEN realText ← RefText.Append[realText, ".0"];
r ← Convert.RealFromRope[RefText.TrustTextAsRope[realText]];
RefText.ReleaseScratch[buffer];
};
ReadPoint: PROC [f: IO.STREAM] RETURNS [point: Vector2.VEC] = {
Assumes the next rope on the stream will be of the form "[<real1>,<real2>]".
ReadBlank[f];
ReadRope[f, "["];
point.x ← ReadBlankAndReal[f];
ReadRope[f, ","];
point.y ← ReadBlankAndReal[f];
ReadRope[f, "]"];
};
ReadRope: PROC [f: IO.STREAM, rope: Rope.ROPE] = {
Removes the given rope from the top of the stream. Used to remove formatting words and phrases from 3d files. We are not interested in these strings but only in the data in between them.
Signals SyntaxError if some other rope is on top.
c: CHAR;
endofstream: BOOLFALSE;
FOR i: INT IN[1..Rope.Length[rope]] DO
c ← IO.GetChar[f
! IO.EndOfStream => {endofstream ← TRUE; CONTINUE}];
IF endofstream THEN
SIGNAL SyntaxError [IO.GetIndex[f], NIL, rope];
IF NOT c = Rope.Fetch[rope,i-1] THEN
SIGNAL SyntaxError [IO.GetIndex[f], Rope.FromChar[c], rope];
ENDLOOP;
};
ReadBlankAndWord: PROC [f: IO.STREAM] RETURNS [word: Rope.ROPE] = {
ReadBlank[f];
word ← ReadWord[f];
};
ReadWord: PROC [f: IO.STREAM] RETURNS [word: Rope.ROPE] = {
Used to read in a rope which is data.
WordBreakProc: PROC [char: CHAR] RETURNS [IO.CharClass] = {
SELECT char FROM
IO.TAB => RETURN [break];
IO.CR, IO.LF =>RETURN [break];
IO.SP => RETURN [break];
', => RETURN [break];
'] => RETURN [break];
') => RETURN [break];
ENDCASE => RETURN [other];
};
[word, ----] ← IO.GetTokenRope[f, WordBreakProc
!IO.EndOfStream => {word ← NIL; CONTINUE}];
};
END.