File: FeedbackImpl.mesa
Copyright Ó 1990, 1991 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
Michael Plass, October 1, 1991 9:35 am PDT
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;
Friends
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]];
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: BOOL ¬ FALSE;
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: BOOL ¬ FALSE;
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:
BOOL ¬
FALSE]]
RETURNS [result: Feedback.RouterScanResult ¬ []] ~ {
Pass:
PROC [key, val:
REF
ANY]
RETURNS [stop:
BOOL ¬
FALSE]
--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.PutFL[mh, msgType, msgClass, "%g", LIST[[rope[msg]]]];
RETURN};
PutFL:
PUBLIC
PROC [router: MsgRouter, msgType: MsgType, msgClass: MsgClass, format:
ROPE ¬
NIL, list:
LIST
OF
IO.Value ¬
NIL ] = {
mh: MsgHandler ~ GetEffectiveHandler[router, msgClass];
mh.PutFL[mh, msgType, msgClass, format, list];
RETURN};
PutF:
PUBLIC
PROC [router: MsgRouter, msgType: MsgType, msgClass: MsgClass, format:
ROPE ¬
NIL, v1:
IO.Value ] = {
mh: MsgHandler ~ GetEffectiveHandler[router, msgClass];
mh.PutFL[mh, msgType, msgClass, format, LIST[v1]];
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
[
PutFL: PROC [mh: MsgHandler, msgType: MsgType, msgClass: MsgClass, format: ROPE, list: LIST OF IO.Value ¬ NIL ],
ClearHerald: PROC [mh: MsgHandler, msgClass: MsgClass],
Blink: PROC [mh: MsgHandler, msgClass: MsgClass],
data: REF ANY ¬ NIL]
RETURNS [MsgHandler]
~ {RETURN [NEW [MsgHandlerObj ¬ [PutFL, 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:
BOOL ¬
FALSE]]
RETURNS [result: Feedback.HandlerScanResult ¬ []] ~ {
Pass:
PROC [key, val:
REF
ANY]
RETURNS [stop:
BOOL ¬
FALSE] ~ {
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:
BOOL ¬
FALSE]
~ {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, list:
LIST
OF
IO.Value ] ~ {
dh.PutFL[dh, msgType, msgClass, format, list];
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 ¬ [
PutFL: DontPutF,
ClearHerald: DontClearHerald,
Blink: DontBlink,
data: NIL]];
DontPutF: PROC [mh: MsgHandler, msgType: MsgType, msgClass: MsgClass, format: Rope.ROPE, list: LIST OF IO.Value ¬ NIL ] ~ {RETURN};
DontClearHerald: PROC [mh: MsgHandler, msgClass: MsgClass] ~ {RETURN};
DontBlink: PROC [mh: MsgHandler, msgClass: MsgClass] ~ {RETURN};
handleByProblem:
PUBLIC MsgHandler ~
NEW [MsgHandlerObj ¬ [
PutFL: ProblemPutF,
ClearHerald: DontClearHerald,
Blink: DontBlink,
data: NIL]];
ProblemPutF:
PROC [mh: MsgHandler, msgType: MsgType, msgClass: MsgClass, format: Rope.
ROPE, list:
LIST
OF
IO.Value ¬
NIL ] ~ {
asRope: ROPE ~ IO.PutFLR[format, list];
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, list:
LIST
OF
IO.Value ¬
NIL ] ~ {
sr: Splitter ~ NARROW[mh.data];
sr.h1.PutFL[sr.h1, msgType, msgClass, format, list];
sr.h2.PutFL[sr.h2, msgType, msgClass, format, list];
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, list:
LIST
OF
IO.Value ¬
NIL ] ~ {
router: MsgRouter ~ NARROW[mh.data];
PutFL[router, msgType, msgClass, format, list];
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, list:
LIST
OF
IO.Value ¬
NIL ] ~ {
hos: HandlerOnStream ~ NARROW[mh.data];
IF hos.state=before OR (typeBreaksAt[msgType].begin AND hos.state#after) THEN hos.to.PutChar['\n];
hos.to.PutFL[format, list];
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: BOOL ¬ TRUE,
head, tail: LIST OF ROPE
];
CreateStoringHandler:
PUBLIC
PROC [buffsize:
INT ¬
INT.
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, list:
LIST
OF
IO.Value ¬
NIL ] ~ {
sh: StoringHandler ~ NARROW[mh.data];
rope: ROPE ~ IO.PutFLR[format, list];
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.PutFL[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:
BOOL ¬
FALSE] ~ {
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.