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.