<> DIRECTORY Atom USING [Gensym, GetPName, PropList, GetPropFromList, PutPropOnList], Convert USING [ValueToRope], IO USING [EndOf, EndOfStream, GetRefAny, int, PutChar, PutF, PutRope, PutTV, RIS, rope, ROPE, SkipOver, STREAM, SyntaxError, TAB, WhiteSpace], List USING [Append, DReverse, Memb, Nconc1, NthTail], Rope USING [Cat, Concat, Equal, Fetch, Find, IsEmpty, Length, Replace, ROPE, Substr], UserExec USING [CommandProc, ExecHandle, Expression, GetStreams, HistoryEvent, HistoryEventRecord, HistoryList, RegisterCommand, RegisterTransformation, RopeSubst, TransformProc], TiogaOps USING [PutTextKey, GetTextKey, FirstChild, ViewerDoc, Ref, TextKeyNotFound], UserExecExtras USING [], UserExecPrivate USING [ExecPrivateRecord, ExpressionPrivateRecord, HistoryEventPrivateRecord, Zone, HistoryErrorCode] ; HistoryImpl: CEDAR MONITOR LOCKS NARROW[exec.privateStuff, REF ExecPrivateRecord] USING exec: UserExec.ExecHandle IMPORTS Atom, Convert, IO, List, Rope, UserExec, TiogaOps, UserExecPrivate EXPORTS UserExec, UserExecExtras, UserExecPrivate = BEGIN OPEN IO; <> HistoryEvent: TYPE = UserExec.HistoryEvent; HistoryList: TYPE = UserExec.HistoryList; EventAddress: TYPE = REF ANY; < negative numbers count backwards, positive numbers refer to corresponding event number>> < match any event containing indicated text.>> < of form ^xyz, match event beginning with xyz, otherwise match event containing indicated text as identifier, i.e. neighboring characters not alphanumeric>> <> ExecPrivateRecord: PUBLIC TYPE = UserExecPrivate.ExecPrivateRecord; ExpressionPrivateRecord: PUBLIC TYPE = UserExecPrivate.ExpressionPrivateRecord; HistoryEventPrivateRecord: PUBLIC TYPE = UserExecPrivate.HistoryEventPrivateRecord; <> HistoryError: PUBLIC ERROR [ec: UserExecPrivate.HistoryErrorCode, msg: Rope.ROPE _ NIL] = CODE; -- raised by historyfind GetInput: PROC [history: HistoryList, stream: IO.STREAM] RETURNS[ROPE] = { input, input1: ROPE _ NIL; event: HistoryEvent; eventAddress: EventAddress _ NIL; DO Add: PROC [r: ROPE] = { IF input = NIL THEN input _ r ELSE input _ Rope.Concat[Rope.Replace[base: input, start: Rope.Length[input] - 1, len: 1, with: "; "], r]; }; IO.SkipOver[stream, IO.WhiteSpace]; IF stream.EndOf[] THEN RETURN[input]; eventAddress _ IO.GetRefAny[stream ! IO.SyntaxError => IF Rope.Equal[msg, "Illegal character: ,"] THEN LOOP]; event _ HistoryFind[history: history, eventAddress: eventAddress]; IF Atom.GetPropFromList[propList: event.props, prop: $History] # NIL AND event.subEvents # NIL THEN FOR l: LIST OF HistoryEvent _ event.subEvents, l.rest UNTIL l = NIL DO Add[l.first.input]; ENDLOOP ELSE Add[event.input]; ENDLOOP; }; RedoCommand: UserExec.TransformProc = { private: REF UserExecPrivate.ExecPrivateRecord = exec.privateStuff; props: Atom.PropList = event.props; history: HistoryList = private.historyList; out: STREAM = UserExec.GetStreams[exec].out; result _ GetInput[history, event.commandLineStream]; IF result = NIL THEN {event: HistoryEvent = history.rest.first; result _ event.input; }; event.props _ Atom.PutPropOnList[event.props, $History, event.input]; }; CreateEvent: PUBLIC PROCEDURE [exec: UserExec.ExecHandle, input: ROPE] RETURNS [event: HistoryEvent] = { event _ UserExecPrivate.Zone.NEW[UserExec.HistoryEventRecord _ [ input: input, privateStuff: UserExecPrivate.Zone.NEW[UserExecPrivate.HistoryEventPrivateRecord _ [eventNum: 1] ] ] ]; IF exec = NIL THEN RETURN; {node: TiogaOps.Ref = TiogaOps.FirstChild[TiogaOps.ViewerDoc[exec.viewer]]; -- assumes only one node in typescript. private: REF ExecPrivateRecord = exec.privateStuff; DoIt: ENTRY PROC [exec: UserExec.ExecHandle] = { eventsPrivateStuff: REF UserExecPrivate.HistoryEventPrivateRecord = event.privateStuff; eventsPrivateStuff.eventNum _ private.eventNum; private.eventNum _ private.eventNum + 1; private.historyList _ CONS[event, private.historyList]; TiogaOps.PutTextKey[node: node, where: TiogaOps.GetTextKey[node: node, key: exec ! TiogaOps.TextKeyNotFound => CONTINUE].where, key: event]; }; DoIt[exec]; }; }; -- of CreateEvent CreateSubEvent: PUBLIC PROC [event: HistoryEvent, input: ROPE] RETURNS[subEvent: HistoryEvent] = { <> eventsPrivateStuff: REF UserExecPrivate.HistoryEventPrivateRecord = event.privateStuff; subEvent _ UserExecPrivate.Zone.NEW[UserExec.HistoryEventRecord _ [ input: input, dontCorrect: event.dontCorrect, privateStuff: UserExecPrivate.Zone.NEW[UserExecPrivate.HistoryEventPrivateRecord _ [eventNum: eventsPrivateStuff.eventNum, showInput: eventsPrivateStuff.showInput, inCommandFile: eventsPrivateStuff.inCommandFile, inCMFile: eventsPrivateStuff.inCMFile ]] ] ]; IF event.subEvents = NIL THEN event.subEvents _ LIST[subEvent] ELSE TRUSTED {event.subEvents _ LOOPHOLE[List.Nconc1[LOOPHOLE[event.subEvents, LIST OF REF ANY], subEvent], LIST OF HistoryEvent]}; }; ShowHistory: UserExec.CommandProc = { private: REF UserExecPrivate.ExecPrivateRecord _ exec.privateStuff; concreteEvent: HistoryEvent = event; props: Atom.PropList _ concreteEvent.props; eventAddress: REF ANY _ NIL; out: STREAM = UserExec.GetStreams[exec].out; commandLineStream: STREAM = IO.RIS[concreteEvent.commandLine]; anything: BOOL _ FALSE; concreteEvent.props _ Atom.PutPropOnList[concreteEvent.props, $History, concreteEvent.input]; concreteEvent.input _ ""; DO IO.SkipOver[commandLineStream, IO.WhiteSpace]; IF commandLineStream.EndOf[] THEN EXIT; eventAddress _ IO.GetRefAny[commandLineStream ! IO.EndOfStream => EXIT]; PrintHistory[eventAddress: eventAddress, history: private.historyList, handle: out]; anything _ TRUE; ENDLOOP; IF NOT anything THEN PrintHistory[eventAddress: NIL, history: private.historyList, handle: out]; -- history{cr} means everything }; PrintHistory: PROCEDURE [eventAddress: EventAddress, history: UserExec.HistoryList, handle: STREAM] = { PrintEvents[handle: handle, events: IF eventAddress = NIL THEN history.rest ELSE LIST[HistoryFind[history, eventAddress]]]; }; PrintEvents: PROCEDURE [handle: STREAM, events: HistoryList] = { PrintEvent1: PROC [event: HistoryEvent, indent: NAT] = { eventsPrivateStuff: REF UserExecPrivate.HistoryEventPrivateRecord = event.privateStuff; props: Atom.PropList _ event.props; expr: UserExec.Expression = event.expression; Indent: PROC = {FOR i: NAT IN [0..indent) DO handle.PutChar[TAB]; ENDLOOP}; PrintInput: PROC [input: ROPE] = { Indent[]; handle.PutF["%g*n", rope[input]]; }; <> <<{Indent[]; handle.PutF["%g*n", rope[historyCommand]]}; >> PrintInput[event.input]; IF event.subEvents # NIL THEN FOR lst: LIST OF HistoryEvent _ event.subEvents, lst.rest UNTIL lst = NIL DO PrintEvent1[lst.first, indent + 1]; ENDLOOP ELSE IF eventsPrivateStuff.state = completed AND expr = NIL THEN { IF eventsPrivateStuff.value # NIL THEN handle.PutTV[eventsPrivateStuff.value] } ELSE { Indent[]; SELECT eventsPrivateStuff.state FROM causedAnError => handle.PutRope["{never finished - error}\n"]; notFinishedYet => handle.PutRope["{event not yet completed}\n"]; aborted => handle.PutRope["{aborted}\n"]; completed => { privateExpr: REF ExpressionPrivateRecord = expr.privateStuff; IF NOT Rope.IsEmpty[privateExpr.valueRope] THEN handle.PutF["= %g\n", rope[privateExpr.valueRope]]; }; ENDCASE => ERROR; }; }; -- PrintEvent1 WHILE events # NIL DO event: HistoryEvent = events.first; eventsPrivateStuff: REF UserExecPrivate.HistoryEventPrivateRecord = event.privateStuff; handle.PutF["%d.", int[eventsPrivateStuff.eventNum]]; PrintEvent1[event, 1]; events _ events.rest; ENDLOOP; }; -- of PrintEvents <> HistoryFind: PROCEDURE [history: HistoryList, eventAddress: EventAddress, dontBackup: BOOLEAN _ FALSE] RETURNS [event: HistoryEvent] = { HistoryFindInt: PROC [n: INTEGER] = TRUSTED { IF n < 0 THEN n _ -n - 1 ELSE {event: HistoryEvent = history.first; eventsPrivateStuff: REF UserExecPrivate.HistoryEventPrivateRecord = event.privateStuff; n _ eventsPrivateStuff.eventNum - n; }; IF n < 0 OR (history _ LOOPHOLE[List.NthTail[LOOPHOLE[history, LIST OF REF ANY], n], HistoryList]) = NIL THEN ERROR HistoryError[NotFound]; }; ref: REF ANY; list: LIST OF EventAddress; IF history = NIL THEN ERROR; IF ~dontBackup THEN history _ history.rest; -- strips off this history command IF eventAddress = NIL THEN NULL ELSE WITH eventAddress SELECT FROM l: LIST OF REF ANY => list _ l; ENDCASE => list _ LIST[eventAddress]; WHILE list # NIL DO IF history = NIL THEN GOTO NotFound; WITH (ref _ list.first) SELECT FROM x: REF INT => HistoryFindInt[x^]; x: REF INTEGER => HistoryFindInt[x^]; ENDCASE => UNTIL history = NIL DO IF EventMatch[history.first, ref] THEN EXIT; history _ history.rest; REPEAT FINISHED => GOTO NotFound; ENDLOOP; list _ list.rest; ENDLOOP; RETURN[history.first]; EXITS NotFound => ERROR HistoryError[NotFound]; }; -- of HistoryFind EventMatch: PROCEDURE [event: HistoryEvent, key: REF ANY] RETURNS [BOOLEAN] = { IF Atom.GetPropFromList[propList: event.props, prop: $History] # NIL AND event.subEvents # NIL THEN RETURN[EventMatch[event.subEvents.first, key]]; WITH key SELECT FROM r: ROPE => RETURN[(Rope.Find[s1: event.input, s2: r, case: FALSE] # -1)]; a: ATOM => { r: ROPE _ Atom.GetPName[a]; IF Rope.Fetch[r, 0] = '^ THEN -- match only at start of event. RETURN[Rope.Find[s1: event.input, s2: Rope.Substr[base: r, start: 1]] = 0] ELSE IF Rope.Fetch[r, 0] = '< AND Rope.Fetch[r, Rope.Length[r] - 1] = '> THEN -- match as id, i.e. neighboring characters cannot be alphanumeric. { i: INT _ 0; eventLength: INT = Rope.Length[event.input]; rLen: INT _ Rope.Length[r]; IsSepr: PROC [pos: INT] RETURNS[BOOLEAN] = { RETURN[pos >= eventLength OR pos < 0 OR (SELECT Rope.Fetch[event.input, pos] FROM IN ['A..'Z], IN ['a..'z], IN ['0..'9] => FALSE, ENDCASE => TRUE) ] }; r _ Rope.Substr[base: r, start: 1, len: rLen - 2]; -- strip off <> rLen _ rLen - 2; WHILE (i _ Rope.Find[s1: event.input, s2: r, pos1: i, case: FALSE]) # -1 DO IF IsSepr[i - 1] AND IsSepr[i + rLen] THEN RETURN[TRUE]; i _ i + 1; REPEAT FINISHED => RETURN[FALSE]; ENDLOOP; } ELSE RETURN[(Rope.Find[s1: event.input, s2: r, case: FALSE] # -1)]; }; ENDCASE => RETURN[FALSE]; }; -- of EventMatch SynonymList: TYPE = LIST OF REF SynonymRecord; SynonymRecord: TYPE = RECORD[key, val: ROPE]; UseCommand: UserExec.TransformProc = { private: REF UserExecPrivate.ExecPrivateRecord = exec.privateStuff; history: HistoryList = private.historyList; out: STREAM = UserExec.GetStreams[exec].out; stream: STREAM = event.commandLineStream; props: Atom.PropList = event.props; new, old: LIST OF LIST OF REF ANY _ NIL; eventSpec, previouslySeen: LIST OF REF ANY _ NIL; oldInput: ROPE; tem, lst: LIST OF REF ANY _ NIL; -- temporaries UseRecord: TYPE = RECORD[args: LIST OF LIST OF REF ANY, oldInput: ROPE]; synonyms: SynonymList; state: {New, Old} _ New; IntMemb: PROC [i: INT, lst: LIST OF REF ANY] RETURNS[BOOLEAN] = { FOR l: LIST OF REF ANY _ lst, l.rest UNTIL l = NIL DO WITH l.first SELECT FROM r: REF INT => IF r^ = i THEN RETURN[TRUE]; ENDCASE; ENDLOOP; RETURN[FALSE] }; RopeMemb: PROC [rope: ROPE, lst: LIST OF REF ANY] RETURNS[BOOLEAN] = { FOR l: LIST OF REF ANY _ lst, l.rest UNTIL l = NIL DO WITH l.first SELECT FROM r: ROPE => IF Rope.Equal[rope, r] THEN RETURN[TRUE]; ENDCASE; ENDLOOP; RETURN[FALSE] }; RealMemb: PROC [r: REAL, lst: LIST OF REF ANY] RETURNS[BOOLEAN] = { FOR l: LIST OF REF ANY _ lst, l.rest UNTIL l = NIL DO WITH l.first SELECT FROM ref: REF REAL => IF ABS[ref^ - r] < .00001 THEN RETURN[TRUE]; ENDCASE; ENDLOOP; RETURN[FALSE] }; IO.SkipOver[stream, IO.WhiteSpace]; IF stream.EndOf[] THEN ERROR HistoryError[UseWhat]; <> DO x: REF ANY _ IO.GetRefAny[stream ! IO.EndOfStream => { SELECT state FROM New => new _ CONS[lst, new]; Old => old _ CONS[lst, old]; ENDCASE => ERROR; EXIT; }; ]; IF lst # NIL THEN -- IN, AND, FOR not interpreted as separators when they appear first, last, or immediately following another operator, e.g. USE IN FOR IN AND AND FOR FOR will work WITH x SELECT FROM a: ATOM => SELECT TRUE FROM Rope.Equal["FOR", Atom.GetPName[a], FALSE] => IF state = New THEN { new _ CONS[lst, new]; -- should go on end. would really like to use Nconc1 but then would have to loophole. previouslySeen _ List.Append[lst, previouslySeen]; -- lis of all new items seen state _ Old; lst _ NIL; LOOP }; Rope.Equal["AND", Atom.GetPName[a], FALSE] => { IF state = Old THEN old _ CONS[lst, old] ELSE IF state = New THEN new _ CONS[lst, new]; -- e.g. user types USE A AND B following previous USE command, i.e. no old state _ New; lst _ NIL; LOOP; }; Rope.Equal["IN", Atom.GetPName[a], FALSE] => { IF state = New AND new = NIL THEN new _ CONS[lst, new] ELSE IF state = Old THEN old _ CONS[lst, old]; oldInput _ GetInput[history, stream]; EXIT; }; ENDCASE; ENDCASE; -- not an atom lst _ List.Nconc1[lst, x]; { seen: BOOLEAN; rope: ROPE; WITH x SELECT FROM a: ATOM => IF seen _ List.Memb[a, previouslySeen] THEN rope _ Atom.GetPName[a]; r: REF INT => IF seen _ IntMemb[r^, previouslySeen] THEN rope _ Convert.ValueToRope[[signed[r^]]]; r: REF REAL => IF seen _ RealMemb[r^, previouslySeen] THEN rope _ Convert.ValueToRope[[real[r^]]]; r: ROPE => IF seen _ RopeMemb[r, previouslySeen] THEN rope _ r; ENDCASE => seen _ FALSE; IF seen THEN synonyms _ CONS[NEW[SynonymRecord _ [rope, Atom.GetPName[Atom.Gensym[]]]], synonyms]; -- enables USE A B FOR B A, USE A FOR B AND B FOR A, or USE A FOR B AND B C FOR A <<*** WHAT ABOUT USE "X" FOR "Y" AND "Y" FOR "X"?? >> }; ENDLOOP; <> <> <<{useArgs _ CONS[old, oldInput]; to be saved on history list in case user gives a user command referring to this event>> IF oldInput = NIL THEN {IF old # NIL THEN -- In case of USE FOO FOR FIE, i.e. no IN, searches for FIE. oldInput _ GetInput[history, IO.RIS[MakeRope[old.first.first], stream]] ELSE -- e.g. compile mumble, followed by use bind. { oldInput _ HistoryFind[history: history, eventAddress: NIL].input; old _ LIST[LIST[IO.GetRefAny[IO.RIS[rope: oldInput]]]]; }; }; event.props _ Atom.PutPropOnList[event.props, $UseRecord, NEW[UseRecord _ [args: old, oldInput: oldInput]]]; <> <<{old _ NARROW[useArgs.first, LIST OF REF ANY];>> <> <> <> <> <> <> TRUSTED {old _ LOOPHOLE[List.DReverse[LOOPHOLE[old]]] ;-- must get order right or trick with synonyms won't work, because will have already substituted for it. new _ LOOPHOLE[List.DReverse[LOOPHOLE[new]]]; }; result _ UseCommandImpl[old, new, oldInput, synonyms]; event.props _ Atom.PutPropOnList[event.props, $History, event.input]; }; -- of UseCommand MakeRope: PROC [ref: REF ANY] RETURNS[ROPE] = { WITH ref SELECT FROM a: ATOM => RETURN[Atom.GetPName[a]]; r: REF INT => RETURN[Convert.ValueToRope[[signed[r^]]]]; r: REF REAL => RETURN[Convert.ValueToRope[[real[r^]]]]; r: ROPE => RETURN[r]; r: REF BOOLEAN => IF r^ THEN RETURN["TRUE"] ELSE RETURN["FALSE"]; ENDCASE => RETURN[""]; }; UseCommandImpl: PROC [oldList, newList: LIST OF LIST OF REF ANY, oldInput: ROPE, synonyms: SynonymList] RETURNS[newInput: ROPE] = { success: BOOL _ FALSE; UseImpl1: PROC [old, new: LIST OF REF ANY, oldInput: ROPE] RETURNS[ROPE] = { old0: LIST OF REF ANY _ old; new0: LIST OF REF ANY _ new; tem: ROPE; oldflg, newflg: BOOLEAN _ FALSE; val: ROPE _ NIL; r, r1: ROPE; AppendRope: PROC = { IF val = NIL THEN val _ r ELSE val _ Rope.Cat[Rope.Substr[base: val, len: Rope.Length[val] - 1], "; ", r]; }; r _ oldInput; DO -- whole body is one big iteration tem _ MakeRope[new0.first]; <> FOR s: SynonymList _ synonyms, s.rest UNTIL s = NIL DO IF Rope.Equal[s.first.key, tem] THEN tem _ s.first.val; ENDLOOP; r1 _ UserExec.RopeSubst[base: r, new: tem, old: MakeRope[old0.first]]; IF r # r1 THEN success _ TRUE; r _ r1; IF (new0 _ new0.rest) = NIL THEN newflg _ TRUE; IF (old0 _ old0.rest) = NIL THEN oldflg _ TRUE; IF new0 # NIL AND old0 # NIL THEN LOOP; <> {IF old0 = NIL AND new0 = NIL THEN GOTO Out; <> AppendRope[]; r _ oldInput; }; <> IF new0 = NIL THEN new0 _ new; IF old0 = NIL THEN old0 _ old; REPEAT Out => {AppendRope[]; RETURN[val]}; ENDLOOP; }; -- of UseImpl1 <
> newInput _ oldInput; UNTIL newList = NIL DO newInput _ UseImpl1[oldList.first, newList.first, newInput]; oldList _ oldList.rest; newList _ newList.rest; ENDLOOP; IF oldList # NIL THEN ERROR HistoryError[UseWhat]; IF ~success THEN ERROR HistoryError[NotFound]; FOR l: SynonymList _ synonyms, l.rest UNTIL l = NIL DO newInput _ UserExec.RopeSubst[new: NARROW[synonyms.first.key], old: NARROW[synonyms.first.val], base: newInput]; ENDLOOP; }; -- of UseCommandImpl <> UserExec.RegisterTransformation["Redo", RedoCommand, "replays the indicated event(s).","replays the indicated event(s), e.g. redo comp bind. Events can be indicated by (a) their event number, a positive integer; (b) relative event number, a negative integer which indicates how many events before the present, e.g. redo -1 is the last event; or (c) a pattern that matches with the input of the event, without regard for case. A sequence of characters will match anywhere in the event, an expression of the form will only match with id where its neighbors are not alphanumeric."]; UserExec.RegisterCommand["History", ShowHistory, "Shows History.", "Shows History. History{cr} shows entire history list. History {eventNum} just the corresponding event, e.g. History -1"]; UserExec.RegisterTransformation["Use", UseCommand, "substitutes new text for old text in the indicated event(s)", "Form is Use New For Old In eventSpec. e.g. Use mesa For bcd IN -1. Substitution is always on a character basis.If eventSpec specifies more than one event, a compound event will be constructed. If there are more new arguments than old arguments, the substitution is distributed, e.g. Use 1 2 3 For 0 IN -1 -2 will construct an event consisting of the concatenation of the last two events, and then execute that event first with 1 substituted for 0, then 2 for 0 then 3 for 0. If the eventSpec is omitted, the first event that contains the first old argument is used, e.g. Use mesa For bcd will search for first event containing 'bcd' and substitute in that. If no old argument specified, and the event was itself the result of a use command, substitute for the original old arguments, e.g. use a for b, followed by use c d e is equivalent to use c d e for b. Finally, if no new arguments are specified, old is first token, e.g. compile mumble, followed by use bind."]; END. -- of HistoryImpl change before fixed redo redo loop GetInput: PROC [history: HistoryList, stream: IO.STREAM] RETURNS[ROPE] = { input: ROPE _ NIL; event: HistoryEvent; eventAddress: EventAddress _ NIL; DO IO.SkipOver[stream, IO.WhiteSpace]; IF stream.EndOf[] THEN RETURN[input]; eventAddress _ IO.GetRefAny[stream]; event _ HistoryFind[history: history, eventAddress: eventAddress]; IF input = NIL THEN input _ event.input ELSE input _ Rope.Concat[Rope.Replace[base: input, start: Rope.Length[input] - 1, len: 1, with: "; "], event.input]; ENDLOOP; };