SessionLogImpl.mesa
Copyright Ó 1986, 1987, 1992 by Xerox Corporation. All rights reserved.
Last edited by Bier on November 4, 1987 4:39:10 pm PST
Pier, October 28, 1987 11:39:17 am PST
Bier, March 13, 1991 5:26 pm PST
Michael Plass, March 25, 1992 11:27 am PST
Christian Jacobi, October 2, 1992 11:46 am PDT
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).
DIRECTORY
Atom, Convert, Imager, IO, RefText, Rope, SessionLog, SimpleFeedback, SlackProcess, Vector2;
SessionLogImpl:
CEDAR
PROGRAM
IMPORTS Atom, Convert, IO, RefText, Rope, SimpleFeedback
EXPORTS SessionLog = BEGIN
SyntaxError: SIGNAL [position: NAT, wasThere: Rope.ROPE, notThere: Rope.ROPE] = CODE;
Recording
Complain:
PROC [r: Rope.
ROPE] = {
SimpleFeedback.Append[$SessionLog, oneLiner, $Error, r];
SimpleFeedback.Blink[$SessionLog, $Error];
};
recentEnterActionError:
BOOL ¬
FALSE;
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.PutRope["\"\""] -- assume its an empty rope
ELSE
WITH actionList.first
SELECT
FROM
atom: ATOM => f.PutF1["%g", [rope[Atom.GetPName[atom]]]];
n: REF INT => f.PutF1["%g", [integer[n]]];
r: REF REAL => f.PutF1["%g", [real[r]]];
c: REF CHAR => f.PutF1["'%g", [character[c]]];
cd: REF CARD => f.PutF1["%g", [cardinal[cd]]];
rope: Rope.ROPE => f.PutF1["\"%g\"", [rope[rope]]];
refText: REF TEXT => f.PutF1["\"%g\"", [text[refText]]];
refPoint: REF Vector2.VEC => f.PutF["[%g, %g]", [real[refPoint.x]], [real[refPoint.y]]];
refRect: REF Imager.Rectangle => f.PutFL["[%g %g %g %g]", LIST[[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: BOOL ¬ FALSE;
WHILE
NOT endOfStream
DO
endOfStream ¬ PlayAction[f, clientData, notifyProc];
ENDLOOP;
};
recentPlayActionError:
BOOL ¬
FALSE;
PlayAction:
PUBLIC
PROC [f:
IO.
STREAM, clientData:
REF
ANY, notifyProc: SlackProcess.ActionProc]
RETURNS [endOfStream:
BOOL ¬
FALSE] = {
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;
};
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: BOOL ¬ FALSE;
[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: BOOL ¬ FALSE;
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: CHAR ¬ IO.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: BOOL ¬ FALSE;
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: BOOL ¬ FALSE;
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.