File: FeedbackImpl.mesa
Copyright Ó 1990 by Xerox Corporation. All rights reserved.
Last edited by: Eric Bier on September 5, 1987 1:44:49 pm PDT
Pier, October 23, 1987 3:23:36 pm PDT
Bier, May 1, 1990 11:14 pm PDT
Last tweaked by Mike Spreitzer on December 7, 1990 4:16 pm PST
DIRECTORY Ascii, Basics, Feedback, FeedbackConcreteTypes, FeedbackClasses, FeedbackSignals, FeedbackTypes, IO, IOUtils, RefTab, Rope, SimpleFeedback;
FeedbackImpl: CEDAR PROGRAM
IMPORTS IO, IOUtils, RefTab, Rope
EXPORTS FeedbackTypes, Feedback, FeedbackClasses, FeedbackSignals
= BEGIN
Familiar Types
ROPE: TYPE = Rope.ROPE;
MsgType: TYPE = SimpleFeedback.MsgType;
MsgClass: TYPE = SimpleFeedback.MsgClass;
MsgRouter: TYPE = REF MsgRouterObj;
MsgRouterObj: PUBLIC TYPE = FeedbackConcreteTypes.MsgRouterObj;
MsgHandler: TYPE ~ REF MsgHandlerObj;
MsgHandlerObj: PUBLIC TYPE = FeedbackConcreteTypes.MsgHandlerObj;
The Problem
Problem: PUBLIC SIGNAL [msg: Rope.ROPE] ← CODE;
Router Operations By Name
typeBreaksAt: PUBLIC ARRAY MsgType OF Feedback.Breaks ← [
oneLiner: [TRUE, TRUE],
begin: [TRUE, FALSE],
middle: [FALSE, FALSE],
end: [FALSE, TRUE]];
breaksToType: PUBLIC ARRAY --begin:--BOOL OF ARRAY --end:--BOOL OF MsgType ← [
FALSE: [FALSE: middle, TRUE: end],
TRUE: [FALSE: begin, TRUE: oneLiner]];
AppendByName: PUBLIC PROC [routerName: ATOM, msgType: MsgType, msgClass: MsgClass, msg: Rope.ROPE] = {
router: MsgRouter ← EnsureRouter[routerName];
Append[router, msgType, msgClass, msg];
};
BlinkByName: PUBLIC PROC [routerName: ATOM, msgClass: MsgClass ← $All] = {
router: MsgRouter ← EnsureRouter[routerName];
Blink[router, msgClass];
};
SetRouterOnByName: PUBLIC PROC [routerName: ATOM, on: BOOL] = {
router: MsgRouter ← EnsureRouter[routerName];
SetRouterOn[router, on];
};
GetRouterOnByName: PUBLIC PROC [routerName: ATOM] RETURNS [on: BOOL] = {
router: MsgRouter ← EnsureRouter[routerName];
RETURN GetRouterOn[router]};
PutFByName: PUBLIC PROC [routerName: ATOM, msgType: MsgType, msgClass: MsgClass, format: Rope.ROPENIL, v1, v2, v3, v4, v5: IO.Value ← [null[]] ] = {
router: MsgRouter ← EnsureRouter[routerName];
PutF[router, msgType, msgClass, format, v1, v2, v3, v4, v5];
};
ClearHeraldByName: PUBLIC PROC [routerName: ATOM, msgClass: MsgClass ← $All] = {
router: MsgRouter ← EnsureRouter[routerName];
ClearHerald[router, msgClass];
};
Creating and Registering Routers
gRouters: RefTab.Ref ← RefTab.Create[]; -- all of the MsgRouters in the world
CreateRouter: PUBLIC PROC RETURNS [router: MsgRouter] = {
router ← NEW[MsgRouterObj ← [defaultHandler: defaultHandler]];
RETURN};
RegisterRouter: PUBLIC PROC [router: MsgRouter, routerName: ATOM] RETURNS [oldRouter: MsgRouter ← NIL] = {
found: BOOLFALSE;
val: REF;
[found, val] ← gRouters.Fetch[routerName];
IF found THEN oldRouter ← NARROW[val];
[] ← gRouters.Store[routerName, router];
};
RouterFromName: PUBLIC PROC [routerName: ATOM] RETURNS [router: MsgRouter ← NIL] = {
found: BOOLFALSE;
val: REF;
[found, val] ← gRouters.Fetch[routerName];
IF found THEN router ← NARROW[val];
};
EnsureRouter: PUBLIC PROC [routerName: ATOM] RETURNS [router: MsgRouter] = {
router ← RouterFromName[routerName];
IF router = NIL THEN {
router ← CreateRouter[];
[] ← RegisterRouter[router, routerName];
};
};
ScanRouters: PUBLIC PROC [foreach: PROC [ATOM, MsgRouter] RETURNS [stop: BOOLFALSE]] RETURNS [result: Feedback.RouterScanResult ← []] ~ {
Pass: PROC [key, val: REF ANY] RETURNS [stop: BOOLFALSE] --RefTab.EachPairAction-- ~ {
routerName: ATOM ~ NARROW[key];
router: MsgRouter ~ NARROW[val];
IF foreach[routerName, router] THEN result ← [TRUE, routerName, router];
RETURN [result.stopped]};
[] ← gRouters.Pairs[Pass];
RETURN};
Operating on Routers
Append: PUBLIC PROC [router: MsgRouter, msgType: MsgType, msgClass: MsgClass, msg: ROPE] = {
mh: MsgHandler ~ GetEffectiveHandler[router, msgClass];
mh.PutF[mh, msgType, msgClass, "%g", [rope[msg]]];
RETURN};
PutF: PUBLIC PROC [router: MsgRouter, msgType: MsgType, msgClass: MsgClass, format: ROPENIL, v1, v2, v3, v4, v5: IO.Value ← [null[]] ] = {
mh: MsgHandler ~ GetEffectiveHandler[router, msgClass];
mh.PutF[mh, msgType, msgClass, format, v1, v2, v3, v4, v5];
RETURN};
Blink: PUBLIC PROC [router: MsgRouter, msgClass: MsgClass] = {
mh: MsgHandler ~ GetEffectiveHandler[router, msgClass];
mh.Blink[mh, msgClass];
};
SetRouterOn: PUBLIC PROC [router: MsgRouter, on: BOOL] = {
router.on ← on;
};
GetRouterOn: PUBLIC PROC [router: MsgRouter] RETURNS [on: BOOL] = {
on ← router.on;
};
ClearHerald: PUBLIC PROC [router: MsgRouter, msgClass: MsgClass] = {
mh: MsgHandler ~ GetEffectiveHandler[router, msgClass];
mh.ClearHerald[mh, msgClass];
RETURN};
Directing Routers
CreateHandler: PUBLIC PROC
[
PutF: PROC [mh: MsgHandler, msgType: MsgType, msgClass: MsgClass, format: ROPE, v1, v2, v3, v4, v5: IO.Value ← [null[]] ],
ClearHerald: PROC [mh: MsgHandler, msgClass: MsgClass],
Blink: PROC [mh: MsgHandler, msgClass: MsgClass],
data: REF ANYNIL]
RETURNS [MsgHandler]
~ {RETURN [NEW [MsgHandlerObj ← [PutF, ClearHerald, Blink, data]]]};
GetHandlerData: PUBLIC PROC [mh: MsgHandler] RETURNS [REF ANY]
~ {RETURN [mh.data]};
SetHandler: PUBLIC PROC [router: MsgRouter, msgClass: MsgClass, mh: MsgHandler] RETURNS [previous: MsgHandler] ~ {
SELECT msgClass FROM
$Default, $Every => {
varying: BOOL ~ msgClass=$Every AND router.c2h#NIL AND router.c2h.GetSize[]#0;
IF mh=NIL THEN mh ← defaultHandler;
IF varying THEN {router.c2h.Erase[]; previous ← NIL}
ELSE IF router.defaultHandler=defaultHandler THEN previous ← NIL
ELSE previous ← router.defaultHandler;
router.defaultHandler ← mh;
};
ENDCASE => {
IF router.c2h=NIL THEN router.c2h ← RefTab.Create[];
previous ← NARROW[router.c2h.Fetch[msgClass].val];
IF mh=NIL THEN [] ← router.c2h.Delete[msgClass]
ELSE [] ← router.c2h.Store[msgClass, mh];
};
RETURN};
SetMultiHandler: PUBLIC PROC [router: MsgRouter, msgClasses: LIST OF MsgClass, mh: MsgHandler] ~ {
FOR msgClasses ← msgClasses, msgClasses.rest WHILE msgClasses#NIL DO
[] ← SetHandler[router, msgClasses.first, mh];
ENDLOOP;
RETURN};
GetHandler: PUBLIC PROC [router: MsgRouter, msgClass: MsgClass] RETURNS [mh: MsgHandler] ~ {
SELECT msgClass FROM
$Default, $Every => {
IF msgClass=$Every AND router.c2h#NIL AND router.c2h.GetSize[]#0 THEN RETURN [NIL];
IF router.defaultHandler=defaultHandler THEN RETURN [NIL];
RETURN [router.defaultHandler]};
ENDCASE => {
IF router.c2h=NIL THEN RETURN [NIL];
mh ← NARROW[router.c2h.Fetch[msgClass].val];
RETURN}};
GetEffectiveHandler: PUBLIC PROC [router: MsgRouter, msgClass: MsgClass] RETURNS [mh: MsgHandler] ~ {
IF router=NIL OR NOT router.on THEN RETURN [doNothing];
IF msgClass=$Default OR msgClass=$Every THEN RETURN [router.defaultHandler];
IF router.c2h=NIL THEN RETURN [router.defaultHandler];
mh ← NARROW[router.c2h.Fetch[msgClass].val];
IF mh=NIL THEN mh ← router.defaultHandler;
RETURN};
ScanHandlers: PUBLIC PROC [router: MsgRouter, foreach: PROC [MsgClass, MsgHandler] RETURNS [stop: BOOLFALSE]] RETURNS [result: Feedback.HandlerScanResult ← []] ~ {
Pass: PROC [key, val: REF ANY] RETURNS [stop: BOOLFALSE] ~ {
msgClass: MsgClass ~ NARROW[key];
mh: MsgHandler ~ NARROW[val];
IF foreach[msgClass, mh] THEN result ← [TRUE, msgClass, mh];
RETURN [result.stopped]};
IF router.c2h#NIL THEN [] ← router.c2h.Pairs[Pass];
IF router.defaultHandler#defaultHandler AND NOT result.stopped THEN [] ← Pass[$Default, router.defaultHandler];
RETURN};
GetHandledClasses: PUBLIC PROC [router: MsgRouter] RETURNS [classes: LIST OF MsgClass ← NIL] ~ {
Note: PROC [msgClass: MsgClass, mh: MsgHandler] RETURNS [stop: BOOLFALSE]
~ {classes ← CONS[msgClass, classes]};
[] ← ScanHandlers[router, Note];
RETURN};
The Default Handler
defaultHandler: PUBLIC MsgHandler ~ NEW [MsgHandlerObj ← [DefaultPutF, DefaultClearHerald, DefaultBlink, NIL]];
dh: MsgHandler ← NIL;
SetGlobalDefaultHandlersBehavior: PUBLIC PROC [newDH: Feedback.MsgHandler] RETURNS [previous: Feedback.MsgHandler] ~ {
previous ← dh;
dh ← newDH};
DefaultPutF: PROC [mh: MsgHandler, msgType: MsgType, msgClass: MsgClass, format: Rope.ROPE, v1, v2, v3, v4, v5: IO.Value ← [null[]] ] ~ {
dh.PutF[dh, msgType, msgClass, format, v1, v2, v3, v4, v5];
RETURN};
DefaultClearHerald: PROC [mh: MsgHandler, msgClass: MsgClass] ~ {
dh.ClearHerald[dh, msgClass];
RETURN};
DefaultBlink: PROC [mh: MsgHandler, msgClass: MsgClass] ~ {
dh.Blink[dh, msgClass];
RETURN};
An Algebra of Routers, Handlers and Streams
doNothing: PUBLIC MsgHandler ~ NEW [MsgHandlerObj ← [
PutF: DontPutF,
ClearHerald: DontClearHerald,
Blink: DontBlink,
data: NIL]];
DontPutF: PROC [mh: MsgHandler, msgType: MsgType, msgClass: MsgClass, format: Rope.ROPE, v1, v2, v3, v4, v5: IO.Value ← [null[]] ] ~ {RETURN};
DontClearHerald: PROC [mh: MsgHandler, msgClass: MsgClass] ~ {RETURN};
DontBlink: PROC [mh: MsgHandler, msgClass: MsgClass] ~ {RETURN};
handleByProblem: PUBLIC MsgHandler ~ NEW [MsgHandlerObj ← [
PutF: ProblemPutF,
ClearHerald: DontClearHerald,
Blink: DontBlink,
data: NIL]];
ProblemPutF: PROC [mh: MsgHandler, msgType: MsgType, msgClass: MsgClass, format: Rope.ROPE, v1, v2, v3, v4, v5: IO.Value ← [null[]] ] ~ {
asRope: ROPE ~ IO.PutFR[format, v1, v2, v3, v4, v5];
SIGNAL Problem[asRope];
RETURN};
Splitter: TYPE ~ REF SplitterPrivate;
SplitterPrivate: TYPE ~ RECORD [h1, h2: MsgHandler];
CreateSplittingHandler: PUBLIC PROC [h1, h2: MsgHandler] RETURNS [MsgHandler] ~ {
sr: Splitter ~ NEW [SplitterPrivate ← [h1, h2]];
RETURN [NEW [MsgHandlerObj ← [SplitPutF, SplitClearHerald, SplitBlink, sr]]]};
IsSplittingHandler: PUBLIC PROC [mh: MsgHandler] RETURNS [is: BOOL, h1, h2: MsgHandler] ~ {
WITH mh.data SELECT FROM
sr: Splitter => RETURN [TRUE, sr.h1, sr.h2];
ENDCASE => RETURN [FALSE, NIL, NIL]};
SplitPutF: PROC [mh: MsgHandler, msgType: MsgType, msgClass: MsgClass, format: Rope.ROPE, v1, v2, v3, v4, v5: IO.Value ← [null[]] ] ~ {
sr: Splitter ~ NARROW[mh.data];
sr.h1.PutF[sr.h1, msgType, msgClass, format, v1, v2, v3, v4, v5];
sr.h2.PutF[sr.h2, msgType, msgClass, format, v1, v2, v3, v4, v5];
RETURN};
SplitClearHerald: PROC [mh: MsgHandler, msgClass: MsgClass] ~ {
sr: Splitter ~ NARROW[mh.data];
sr.h1.ClearHerald[sr.h1, msgClass];
sr.h2.ClearHerald[sr.h2, msgClass];
RETURN};
SplitBlink: PROC [mh: MsgHandler, msgClass: MsgClass] ~ {
sr: Splitter ~ NARROW[mh.data];
sr.h1.Blink[sr.h1, msgClass];
sr.h2.Blink[sr.h2, msgClass];
RETURN};
CreateHandlerOnRouter: PUBLIC PROC [router: MsgRouter] RETURNS [MsgHandler] ~ {
RETURN [NEW [MsgHandlerObj ← [HorPutF, HorClearHerald, HorBlink, router]]]};
IsHandlerOnRouter: PUBLIC PROC [mh: MsgHandler] RETURNS [is: BOOL, router: MsgRouter] ~ {
WITH mh.data SELECT FROM
router: MsgRouter => RETURN [TRUE, router];
ENDCASE => RETURN [FALSE, NIL]};
HorPutF: PROC [mh: MsgHandler, msgType: MsgType, msgClass: MsgClass, format: Rope.ROPE, v1, v2, v3, v4, v5: IO.Value ← [null[]] ] ~ {
router: MsgRouter ~ NARROW[mh.data];
PutF[router, msgType, msgClass, format, v1, v2, v3, v4, v5];
RETURN};
HorClearHerald: PROC [mh: MsgHandler, msgClass: MsgClass] ~ {
router: MsgRouter ~ NARROW[mh.data];
ClearHerald[router, msgClass];
RETURN};
HorBlink: PROC [mh: MsgHandler, msgClass: MsgClass] ~ {
router: MsgRouter ~ NARROW[mh.data];
Blink[router, msgClass];
RETURN};
HandlerOnStream: TYPE ~ REF HandlerOnStreamPrivate;
HandlerOnStreamPrivate: TYPE ~ RECORD [
to: IO.STREAM,
nlAsap: BOOL,
Blink: PROC [IO.STREAM, MsgClass] ← NIL,
state: {before, after, chars} ← after
];
CreateHandlerOnStream: PUBLIC PROC [to: IO.STREAM, nlAsap: BOOL, Blink: PROC [IO.STREAM, MsgClass] ← NIL] RETURNS [MsgHandler] ~ {
hos: HandlerOnStream ~ NEW [HandlerOnStreamPrivate ← [to, nlAsap, Blink]];
RETURN [NEW [MsgHandlerObj ← [StreamPutF, DontClearHerald, StreamBlink, hos]]]};
IsHandlerOnStream: PUBLIC PROC [mh: MsgHandler] RETURNS [is: BOOL, to: IO.STREAM, nlAsap: BOOL, Blink: PROC [IO.STREAM, MsgClass]] ~ {
WITH mh.data SELECT FROM
hos: HandlerOnStream => RETURN [TRUE, hos.to, hos.nlAsap, hos.Blink];
ENDCASE => RETURN [FALSE, NIL, FALSE, NIL]};
StreamPutF: PROC [mh: MsgHandler, msgType: MsgType, msgClass: MsgClass, format: Rope.ROPE, v1, v2, v3, v4, v5: IO.Value ← [null[]] ] ~ {
hos: HandlerOnStream ~ NARROW[mh.data];
IF hos.state=before OR (typeBreaksAt[msgType].begin AND hos.state#after) THEN hos.to.PutChar['\n];
hos.to.PutF[format, v1, v2, v3, v4, v5];
IF NOT typeBreaksAt[msgType].end THEN hos.state ← chars
ELSE IF hos.nlAsap THEN {hos.to.PutChar['\n]; hos.state ← after}
ELSE hos.state ← before;
RETURN};
StreamBlink: PROC [mh: MsgHandler, msgClass: MsgClass] ~ {
hos: HandlerOnStream ~ NARROW[mh.data];
IF hos.Blink#NIL THEN hos.Blink[hos.to, msgClass];
RETURN};
SendAsciiBell: PUBLIC PROC [to: IO.STREAM, msgClass: MsgClass] ~ {to.PutChar[Ascii.BEL]};
StoringHandler: TYPE ~ REF StoringHandlerPrivate;
StoringHandlerPrivate: TYPE ~ RECORD [
buffsize, avail: INT,
lastIsComplete: BOOLTRUE,
head, tail: LIST OF ROPE
];
CreateStoringHandler: PUBLIC PROC [buffsize: INTINT.LAST] RETURNS [MsgHandler] ~ {
head: LIST OF ROPE ~ LIST[NIL];
sh: StoringHandler ~ NEW [StoringHandlerPrivate ← [buffsize, buffsize, TRUE, head, head]];
RETURN [NEW [MsgHandlerObj ← [StoringPutF, DontClearHerald, DontBlink, sh]]]};
IsStoringHandler: PUBLIC PROC [mh: MsgHandler] RETURNS [is: BOOL, buffsize: INT] ~ {
WITH mh.data SELECT FROM
sh: StoringHandler => RETURN [TRUE, sh.buffsize];
ENDCASE => RETURN [FALSE, 0]};
StoringPutF: PROC [mh: MsgHandler, msgType: MsgType, msgClass: MsgClass, format: Rope.ROPE, v1, v2, v3, v4, v5: IO.Value ← [null[]] ] ~ {
sh: StoringHandler ~ NARROW[mh.data];
rope: ROPE ~ IO.PutFR[format, v1, v2, v3, v4, v5];
len: INT ~ rope.Length[];
IF len > sh.avail THEN RETURN;
sh.avail ← sh.avail - len;
IF sh.lastIsComplete OR typeBreaksAt[msgType].begin
THEN sh.tail ← sh.tail.rest ← LIST[rope]
ELSE sh.tail.first ← Rope.Concat[sh.tail.first, rope];
sh.lastIsComplete ← typeBreaksAt[msgType].end;
RETURN};
GetStore: PUBLIC PROC [from: MsgHandler] RETURNS [msgs: LIST OF ROPE, lastIsComplete: BOOL] ~ {
sh: StoringHandler ~ NARROW[from.data];
RETURN [sh.head.rest, sh.lastIsComplete]};
PlayStore: PUBLIC PROC [from, to: MsgHandler, withClass: MsgClass, thenClear: BOOL] ~ {
sh: StoringHandler ~ NARROW[from.data];
FOR ml: LIST OF ROPE ← sh.head.rest, ml.rest WHILE ml#NIL DO
to.PutF[to, IF ml.rest#NIL OR sh.lastIsComplete THEN oneLiner ELSE begin, withClass, ml.first];
ENDLOOP;
IF thenClear THEN {sh.head.rest ← NIL; sh.lastIsComplete ← TRUE};
RETURN};
StreamOnRouter: TYPE ~ REF StreamOnRouterPrivate;
StreamOnRouterPrivate: TYPE ~ RECORD [r: MsgRouter, c: MsgClass];
sorProcs: REF IO.StreamProcs ~ IO.CreateStreamProcs[
variety: output,
class: $StreamOnRouter,
putChar: SorPutChar,
putBlock: SorPutBlock,
unsafePutBlock: SorUnsafePutBlock,
close: SorClose];
sorPfProcs: IOUtils.PFProcs ~ IOUtils.CopyPFProcs[NIL];
CreateStreamOnRouter: PUBLIC PROC [msgRouter: MsgRouter, msgClass: MsgClass] RETURNS [s: IO.STREAM] ~ {
sor: StreamOnRouter ~ NEW [StreamOnRouterPrivate ← [msgRouter, msgClass]];
s ← IO.CreateStream[streamProcs: sorProcs, streamData: sor];
[] ← IOUtils.SetPFProcs[s, sorPfProcs];
RETURN};
IsStreamOnRouter: PUBLIC PROC [s: IO.STREAM] RETURNS [is: BOOL, msgRouter: MsgRouter, msgClass: MsgClass] ~ {
WITH s.streamData SELECT FROM
sor: StreamOnRouter => RETURN [TRUE, sor.r, sor.c];
ENDCASE => RETURN [FALSE, NIL, NIL]};
SorPutChar: PROC [self: IO.STREAM, char: CHAR] ~ {
sor: StreamOnRouter ~ NARROW[self.streamData];
IF char='\r OR char='\l
THEN Append[sor.r, end, sor.c, NIL]
ELSE Append[sor.r, middle, sor.c, Rope.FromChar[char]];
RETURN};
SorPutBlock: PROC [self: IO.STREAM, block: REF READONLY TEXT, startIndex, count: NAT] ~ {
sor: StreamOnRouter ~ NARROW[self.streamData];
IF startIndex >= block.length THEN RETURN;
{limit: NAT ~ MIN[block.length - startIndex, count] + startIndex;
WHILE startIndex < limit DO
after: NAT ← startIndex;
c: CHAR;
WHILE after<limit AND (c ← block[after])#'\l AND c#'\r DO after ← after.SUCC ENDLOOP;
IF after>startIndex THEN Append[sor.r, middle, sor.c, Rope.FromRefText[block, startIndex, after-startIndex]];
WHILE after<limit AND ((c ← block[after])='\l OR c='\r) DO
Append[sor.r, end, sor.c, NIL];
after ← after.SUCC;
ENDLOOP;
startIndex ← after;
ENDLOOP;
RETURN}};
SorUnsafePutBlock: PROC [self: IO.STREAM, block: Basics.UnsafeBlock] ~ TRUSTED {
sor: StreamOnRouter ~ NARROW[self.streamData];
IF block.count <= 0 THEN RETURN;
{bytes: LONG POINTER TO Basics.RawBytes ~ LOOPHOLE[block.base];
limit: INT ~ block.startIndex + block.count;
startIndex: INT ← block.startIndex;
WHILE startIndex < limit DO
after: NAT ← startIndex;
GiveChar: PROC RETURNS [c: CHAR]
~ TRUSTED {c ← VAL[bytes[startIndex]]; startIndex ← startIndex.SUCC};
c: CHAR;
WHILE after<limit AND (c ← VAL[bytes[after]])#'\l AND c#'\r DO after ← after.SUCC ENDLOOP;
IF after>startIndex THEN Append[sor.r, middle, sor.c, Rope.FromProc[after-startIndex, GiveChar]];
WHILE after<limit AND ((c ← VAL[bytes[after]])='\l OR c='\r) DO
Append[sor.r, end, sor.c, NIL];
after ← after.SUCC;
ENDLOOP;
startIndex ← after;
ENDLOOP;
RETURN}};
SorPf: PROC [stream: IO.STREAM, val: IO.Value, format: IOUtils.Format, char: CHAR] --IOUtils.PFCodeProc-- ~ {
sor: StreamOnRouter ~ NARROW[stream.streamData];
last: INT ~ format.form.SkipTo[skip: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"];
PutF[sor.r, middle, sor.c, format.form.Substr[start: format.first-1, len: 2+last-format.first], val];
RETURN};
SorClose: PROC [self: IO.STREAM, abort: BOOLFALSE] ~ {
IOUtils.AmbushStream[self: self, streamProcs: IOUtils.closedStreamProcs, streamData: NIL];
RETURN};
Start: PROC = {
dh ← CreateStoringHandler[10000];
FOR c: CHAR IN ['a .. 'z] DO
[] ← IOUtils.SetPFCodeProc[sorPfProcs, c, SorPf];
ENDLOOP;
RETURN};
Start[];
END.