<> <> <> <> 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] = { <> 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 = { <> 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 { <> <> 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.ROPE _ NARROW[event.rest.first]; startTimeCard: CARD _ NARROW[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; <> <<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] = { < is encountered.>> LineBreakProc: SAFE PROC [char: CHAR] RETURNS [IO.CharClass] = CHECKED { SELECT char FROM IO.CR =>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: 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.