DIRECTORY Basics USING [LongDiv], BasicTime USING [GMT], Booting USING [switches], BootingBackdoor USING [Boot], Disk USING [Channel, NextChannel, PageCount], File USING [CedarVolumeSubType, GetVolumeName, Handle, Info, RC, SystemVolume, Volume, VolumeID, VolumeFile], FileBackdoor USING [CreateLogicalVolume, CreatePhysicalVolume, EraseVolume, IsDebugger, SetRoot], FS USING [ Close, ComponentPositions, Copy, Create, Error, ExpandName, FileInfo, GetName, Open, OpenFile, SetByteCountAndCreatedTime, StreamFromOpenFile ], FSBackdoor USING [ GetFileHandle ], FSPseudoServers USING [ GetPseudoServers, InsertPseudoServer, Lookup, PseudoServerList, PseudoServerFromRope, RopeFromPseudoServer ], IagoBackdoor, IagoCommands, IagoOps --USING everything--, IO USING [ int, rope, BreakProc, Close, EndOfStream, GetLineRope, GetTokenRope, PutChar, PutF, PutF1, PutFR, PutRope, STREAM, time ], PhysicalVolume USING [ Physical, PhysicalInfo, SetPhysicalRoot ], PrinterNames USING [DefaultNames, Get, Set], ProcessorFace USING [GetClockPulses, microsecondsPerHundredPulses], PupStreamBackdoor USING [maxOldGatewayBytes, SetMaxBufferSize], Rope USING [ Cat, Concat, Equal, Length, ROPE, SkipTo, Substr ], SystemNames USING [ LocalDir ], SystemSite USING [ Get ], SystemVersion USING [ machineType ], ThisMachine USING [ Name ], UserCredentials USING [ChangeState]; PrinterIagoImpl: CEDAR PROGRAM IMPORTS Basics, Booting, BootingBackdoor, Disk, File, FileBackdoor, FS, FSBackdoor, FSPseudoServers, IagoBackdoor, IagoOps, IO, PhysicalVolume, PrinterNames, ProcessorFace, PupStreamBackdoor, Rope, SystemNames, SystemSite, SystemVersion, ThisMachine, UserCredentials EXPORTS ~ BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; CreatePrinterWorld: PUBLIC PROC [in, out: STREAM] = { Install: PROC [where: File.Volume, which: File.VolumeFile[microcode..bootFile]] = { volumeName: ROPE ~ File.GetVolumeName[where]; remote: ROPE = RemoteRootFileName[which]; local: ROPE _ IagoOps.LocalRootFileName[which, volumeName]; fsFile: FS.OpenFile; file: File.Handle; createTime: BasicTime.GMT; IO.PutF[out, "\nInstalling %g as %g ... ", IO.rope[remote], IO.rope[local] ]; local _ FS.Copy[from: remote, to: local]; createTime _ FS.FileInfo[local].created; fsFile _ FS.Open[name: local, lock: write]; file _ FSBackdoor.GetFileHandle[fsFile]; FileBackdoor.SetRoot[where, which, file]; FS.SetByteCountAndCreatedTime[file: fsFile, created: createTime]; FS.Close[fsFile]; IO.PutRope[out, "done"]; }; format, protect: BOOL; altoText: ROPE _ NIL; myName: ROPE = ThisMachine.Name[]; d: Disk.Channel = Disk.NextChannel[NIL]; newP: PhysicalVolume.Physical; newC: File.Volume; makeLabeled: BOOL _ FALSE; minVMSize: INT; defaultVMSize: INT; maxVMSize: INT ~ IagoBackdoor.MaxVMSize[]; -- based on machine type (smaller on DLion) helpVMSize: ROPE ~ IO.PutFR["? give number of VM pages in decimal (min: 0 or %g, max: %g)", IO.int[minVMSize], IO.int[maxVMSize]]; cedarVMPages: INT _ -1; [minVMSize, defaultVMSize] _ IagoBackdoor.DefaultVMSize[d]; -- based on real memory size and disk size IO.PutChar[out, '\n]; IO.PutRope[out, "\n\nHas your disk already been formatted? (Confirm if the disk has previously been used for Cedar or Pilot, AND you have not reduced the amount of disk reserved for Alto volumes) "]; format _ NOT IagoOps.Confirm[in, out]; makeLabeled _ FALSE; protect _ FALSE; WHILE cedarVMPages < minVMSize AND cedarVMPages # 0 DO cedarVMPages _ IagoOps.GetNumber[ in: in, out: out, default: defaultVMSize, max: maxVMSize, prompt: "\n\nSpecify VM size (in pages): ", help: helpVMSize ]; ENDLOOP; IO.PutRope[out, "\n\nI intend to perform the following operations:"]; IF format THEN IO.PutF1[out, " - format the entire disk%g;", IO.rope[altoText] ]; IO.PutF[out, " - create a new %g, physical volume named \"%g\";", IO.rope[IF makeLabeled THEN "labeled" ELSE "unLabeled"], IO.rope[myName] ]; IO.PutF[out, " - create a %g client volume named \"Cedar\" with a %g page VM; - install microcode, germ and boot files(s) from the release directory; - make the \"Cedar\" volume be the physical boot volume; - boot the volume(s) to initialize the software. ", IO.rope[IF makeLabeled THEN "labeled" ELSE "unLabeled" ], IO.int[cedarVMPages] ]; IF NOT IagoOps.ConfirmDestruction[in, out, "the entire disk on drive RD0"] THEN RETURN; IagoBackdoor.CloseFSVolumes[]; IO.PutChar[out, '\n]; IF format THEN TRUSTED { IagoBackdoor.DoFormatOrScan[in: in, out: out, d: d, format: TRUE, passes: 1] }; IO.PutRope[out, "\nCreating physical volume ... "]; newP _ FileBackdoor.CreatePhysicalVolume[where: d, name: myName, id: IagoOps.NewID[], useLabels: makeLabeled]; IagoOps.ReservePages[in, out, newP]; [] _ UserCredentials.ChangeState[IF protect THEN name ELSE nameHint]; IO.PutRope[out, "done"]; IO.PutRope[out, "\nCreating logical volume \"Cedar\" ... "]; newC _ FileBackdoor.CreateLogicalVolume[ id: IagoOps.NewID[], where: LIST[newP], size: PhysicalVolume.PhysicalInfo[newP].free, name: "Cedar", useLabels: makeLabeled, subType: FS, volatileVAM: TRUE]; IO.PutRope[out, "done. Erasing it ... "]; FileBackdoor.EraseVolume[newC]; IO.PutRope[out, "done"]; IF cedarVMPages # 0 THEN IagoBackdoor.CreateVMFile[out, newC, cedarVMPages]; [] _ WriteRemoteNames[File.GetVolumeName[newC], out]; Install[where: newC, which: microcode]; Install[where: newC, which: germ]; Install[where: newC, which: bootFile]; PhysicalVolume.SetPhysicalRoot[newC, microcode]; PhysicalVolume.SetPhysicalRoot[newC, germ]; PhysicalVolume.SetPhysicalRoot[newC, bootFile]; { init: ROPE = InitialMicrocodeFileName[]; IO.PutRope[out, "\nInstalling "]; IO.PutRope[out, init]; IF IagoBackdoor.DoInitialMicrocodeInstallation[out, d, init] THEN [] _ BootingBackdoor.Boot[ [logical[newC]], [d: Booting.switches[d]] ]; }; }; SetPrinterRemoteNamesCommand: PROC [in, out: STREAM] = { names: PrinterNames.DefaultNames = PrinterNames.Get[]; printerHost: ROPE _ StripBrackets[names.printerHost]; fontHost: ROPE _ StripBrackets[names.fontHost]; printerHost _ StripBrackets[IagoOps.GetArg[in, out, "\n Printer Host: ", printerHost, NIL]]; IF printerHost = NIL THEN GO TO bailOut; fontHost _ StripBrackets[IagoOps.GetArg[in, out, "\n Font Host: ", fontHost, NIL]]; IF fontHost = NIL THEN GO TO bailOut; names.printerHost _ printerHost _ Rope.Cat["[", printerHost, "]"]; names.fontHost _ fontHost _ Rope.Cat["[", fontHost, "]"]; [] _ WriteRemoteNames[NIL, out]; EXITS bailOut => { IO.PutRope[out, "\nEmpty host names not permitted, command aborted.\n"]; }; }; SetPrinterDirectoriesCommand: PROC [in, out: STREAM] = { names: PrinterNames.DefaultNames = PrinterNames.Get[]; currentSystem: ROPE _ names.currentSystem; currentFont: ROPE _ names.currentFont; printerType: ROPE _ names.printerType; currentSystem _ IagoOps.GetArg[in, out, "\n System Directory: ", currentSystem, NIL]; IF currentSystem = NIL THEN GO TO bailOut; currentFont _ IagoOps.GetArg[in, out, "\n Font Directory: ", currentFont, NIL]; IF currentFont = NIL THEN GO TO bailOut; names.currentSystem _ currentSystem; names.currentFont _ currentFont; [] _ WriteRemoteNames[NIL, out]; EXITS bailOut => { IO.PutRope[out, "\nEmpty directory names not permitted, command aborted.\n"]; }; }; SetPrinterTypeCommand: PROC [in, out: STREAM] = { names: PrinterNames.DefaultNames = PrinterNames.Get[]; printerType: ROPE _ names.printerType; printerType _ IagoOps.GetArg[in, out, "\n Printer Type: ", printerType, NIL]; IF printerType = NIL THEN GO TO bailOut; names.printerType _ printerType; [] _ WriteRemoteNames[NIL, out]; EXITS bailOut => { IO.PutRope[out, "\nEmpty printer type not permitted, command aborted.\n"]; }; }; machine: ROPE _ NIL; ExtensionArray: TYPE ~ ARRAY File.VolumeFile OF ROPE; extension: REF ExtensionArray _ NEW [ExtensionArray _ ALL[NIL]]; localName: REF ExtensionArray _ NEW [ExtensionArray _ ALL[NIL]]; StripBrackets: PROC [name: ROPE] RETURNS [ROPE] = { len: INT _ Rope.Length[name]; pos1: INT _ Rope.SkipTo[name, 0, "["]; pos2: INT _ Rope.SkipTo[name, 0, "]"]; nlen: INT _ pos2-pos1-1; IF pos2 = len AND pos1 = len AND len > 0 THEN RETURN [name]; IF nlen <= 0 THEN RETURN [NIL]; RETURN [Rope.Substr[name, pos1+1, nlen]]; }; RemoteRootFileName: PUBLIC PROC [which: File.VolumeFile] RETURNS [ROPE] = { defaultNames: PrinterNames.DefaultNames _ PrinterNames.Get[]; base: ROPE ~ SELECT which FROM germ => machine, microcode => Rope.Cat[defaultNames.printerType, "Cedar", machine], bootFile => Rope.Concat["CedarPrinter", machine], ENDCASE => "Unknown"; ext: ROPE ~ IagoOps.RootFileExtension[which]; RETURN [Rope.Cat[defaultNames.currentSystem, base, ext]]; }; InstallBootFileCommand: PROC [in, out: STREAM] = { InstallLogicalFile[in, out, bootFile]; }; InstallGermFileCommand: PROC [in, out: STREAM] = { InstallLogicalFile[in, out, germ]; }; InstallMicrocodeFileCommand: PROC [in, out: STREAM] = { InstallLogicalFile[in, out, microcode]; }; InstallLogicalFile: PROC [in, out: STREAM, which: File.VolumeFile[checkpoint..bootFile]] = { v: File.Volume = IagoOps.GetLogical[in, out, "For "]; vName: ROPE ~ File.GetVolumeName[v]; originName: ROPE ~ IagoOps.GetFile[in: in, out: out, extension: IagoOps.RootFileExtension[which], default: RemoteRootFileName[which], check: TRUE ]; localName: ROPE _ NIL; fsFile: FS.OpenFile; file: File.Handle; createTime: BasicTime.GMT; PhysicalToo: PROC RETURNS[BOOL] = { IF which # bootFile THEN { IO.PutRope[out, "\nInstalling on the physical volume"]; RETURN[TRUE]; }; IF FileBackdoor.IsDebugger[v] THEN RETURN[FALSE]; IO.PutRope[out, "\nDo you want to use this file when you boot the physical volume? "]; RETURN[IagoOps.Confirm[in, out]] }; OnVolume: PROC [name: ROPE] RETURNS [BOOL] ~ { cp: FS.ComponentPositions; [fullFName: name, cp: cp] _ FS.ExpandName[name]; IF cp.server.length#0 THEN RETURN [FALSE]; -- not local IF cp.dir.length=0 THEN RETURN [File.SystemVolume[]=v] -- no explicit volume ELSE RETURN [Rope.Equal[name.Substr[cp.dir.start, cp.dir.length], vName, FALSE]]; }; IO.PutRope[out, " ... "]; createTime _ FS.FileInfo[name: originName].created; IO.PutF1[out, "%u ...", IO.time[createTime]]; { Help: PROC = { IO.PutF1[out, "? Please type the name of a local file on the %g volume", IO.rope[vName]]; }; localName _ IagoOps.LocalRootFileName[which, vName]; -- the default DO localName _ IagoOps.GetArg[ in: in, out: out, prompt: "\nCopy to local file name (please confirm or alter): ", default: localName, help: Help]; IF OnVolume[localName] THEN EXIT ELSE Help[]; ENDLOOP; IO.PutRope[out, " ... copying ... "]; FileBackdoor.SetRoot[v, which, NIL, [0]]; Pause[1000]; -- wait for logger to do its thing localName _ FS.Copy[from: originName, to: localName]; }; createTime _ FS.FileInfo[localName].created; fsFile _ FS.Open[name: localName, lock: write]; file _ FSBackdoor.GetFileHandle[fsFile]; IF File.Info[file].volume # v THEN IO.PutRope[out, "I'm confused: the file is on the wrong volume"] ELSE { IO.PutRope[out, "installing ... "]; FileBackdoor.SetRoot[v, which, file]; FS.SetByteCountAndCreatedTime[file: fsFile, created: createTime]; Pause[1000]; -- wait for logger to do its thing IO.PutRope[out, "done"]; }; FS.Close[fsFile]; IF PhysicalToo[] THEN { PhysicalVolume.SetPhysicalRoot[v, which]; IO.PutRope[out, " ... done"] }; }; InitialMicrocodeFileName: PUBLIC PROC RETURNS [ROPE] = { defaultNames: PrinterNames.DefaultNames _ PrinterNames.Get[]; shortName: ROPE _ "InitialUnknown.eb"; SELECT SystemVersion.machineType FROM dolphin => shortName _ "InitialPilotD0.eb"; dorado => shortName _ "InitialDiskDorado.eb"; dandelion => shortName _ "InitialDiskDLion.db"; dicentra => shortName _ "InitialEtherDicentra.eb"; ENDCASE; RETURN [Rope.Concat[defaultNames.currentSystem, shortName]]; }; WriteRemoteNames: PUBLIC PROC [volume: ROPE _ NIL, out: STREAM _ NIL] RETURNS [success: BOOL _ FALSE] = { WriteRemoteNamesInner: PROC ~ { remoteNames: PrinterNames.DefaultNames ~ PrinterNames.Get[]; localDir: ROPE ~ SystemNames.LocalDir[volumeName: volume, subDirs: "DontDeleteMe"]; openFile: FS.OpenFile ~ FS.Create[Rope.Concat[localDir, "PrinterNames"]]; fileName: ROPE ~ FS.GetName[openFile].fullFName; stream: STREAM ~ FS.StreamFromOpenFile[openFile: openFile, accessRights: $write]; IO.PutF1[stream, "PrinterHost: %g\n", [rope[remoteNames.printerHost]] ]; IO.PutF1[stream, "FontHost: %g\n", [rope[remoteNames.fontHost]] ]; IO.PutF1[stream, "CurrentSystem: %g\n", [rope[remoteNames.currentSystem]] ]; IO.PutF1[stream, "CurrentFont: %g\n", [rope[remoteNames.currentFont]] ]; IO.PutF1[stream, "PrinterType: %g\n", [rope[remoteNames.printerType]] ]; FOR each: FSPseudoServers.PseudoServerList _ FSPseudoServers.GetPseudoServers[], each.rest WHILE each # NIL DO IO.PutRope[stream, FSPseudoServers.RopeFromPseudoServer[each]]; IO.PutRope[stream, "\n"]; ENDLOOP; IO.Close[stream]; IF out#NIL THEN IO.PutF1[out, "\nRemote names saved to %g\n", [rope[fileName]]]; success _ TRUE; }; IF NOT Booting.switches[n] THEN WriteRemoteNamesInner[! FS.Error => { IF out#NIL THEN out.PutF1["\nRemote names set, but not saved to disk:\n %g\n", [rope[error.explanation]] ]; CONTINUE }; ]; SetPupBufferSize[SystemSite.Get[].registry]; }; ReadRemoteNames: PROC [volume: ROPE _ NIL, out: STREAM _ NIL] RETURNS [success: BOOL _ FALSE] = { fileName: ROPE _ NIL; Malformed: ERROR ~ CODE; ReadRemoteNamesInner: PROC ~ { names: PrinterNames.DefaultNames _ PrinterNames.Get[]; localDir: ROPE ~ SystemNames.LocalDir[volumeName: volume, subDirs: "DontDeleteMe"]; openFile: FS.OpenFile ~ FS.Open[Rope.Concat[localDir, "PrinterNames"]]; stream: STREAM ~ FS.StreamFromOpenFile[openFile: openFile, accessRights: $read]; GetKey: PROC [stream: STREAM, key: ROPE] RETURNS [BOOL] ~ { token: ROPE ~ GetToken[stream]; RETURN [Rope.Equal[token, key, FALSE]]; }; fileName _ FS.GetName[openFile].fullFName; IF NOT GetKey[stream, "PrinterHost:"] THEN ERROR Malformed; names.printerHost _ GetToken[stream]; IF NOT GetKey[stream, "FontHost:"] THEN ERROR Malformed; names.fontHost _ GetToken[stream]; IF NOT GetKey[stream, "CurrentSystem:"] THEN ERROR Malformed; names.currentSystem _ GetToken[stream]; IF NOT GetKey[stream, "CurrentFont:"] THEN ERROR Malformed; names.currentFont _ GetToken[stream]; IF NOT GetKey[stream, "PrinterType:"] THEN ERROR Malformed; names.printerType _ GetToken[stream]; PrinterNames.Set[names]; [] _ IO.GetLineRope[stream ! IO.EndOfStream => CONTINUE]; DO -- Parse the pseudo-servers line: ROPE ~ IO.GetLineRope[stream ! IO.EndOfStream => EXIT]; new: FSPseudoServers.PseudoServerList _ FSPseudoServers.PseudoServerFromRope[line].new; IF new = NIL THEN EXIT; FSPseudoServers.InsertPseudoServer[new]; ENDLOOP; IO.Close[stream]; success _ TRUE; }; IF NOT Booting.switches[n] THEN ReadRemoteNamesInner[! FS.Error => { IF out#NIL THEN out.PutF1["\nUnable to read remote names from disk:\n %g\n", [rope[error.explanation]] ]; CONTINUE; }; Malformed, IO.EndOfStream => { IF out#NIL THEN out.PutF1["\nTrouble reading remote names:\n The content of %g is malformed.\n", [rope[fileName]] ]; CONTINUE; }; ]; EnsureServer["Cedar", "Cyan", TRUE]; EnsureServer["Printer", "Cyan", TRUE]; EnsureServer["Fonts", "Cyan", TRUE]; SetPupBufferSize[SystemSite.Get[].registry]; }; SetPupBufferSize: PROC [registry: ROPE] ~ { IF NOT Rope.Equal["pa", registry, FALSE] THEN PupStreamBackdoor.SetMaxBufferSize[PupStreamBackdoor.maxOldGatewayBytes]; }; EnsureServer: PROC [server: ROPE, defaultHost: ROPE, noWrite: BOOL] = { IF FSPseudoServers.Lookup[server] = NIL THEN { write: ROPE ~ IF noWrite THEN "$" ELSE defaultHost; read: ROPE ~ defaultHost; rope: ROPE ~ IO.PutFR["%g %g %g", IO.rope[server], IO.rope[write], IO.rope[read]]; FSPseudoServers.InsertPseudoServer[FSPseudoServers.PseudoServerFromRope[rope].new]; }; }; GetToken: PROC [st: STREAM] RETURNS [token: ROPE _ NIL] = { MyBreak: IO.BreakProc = { SELECT char FROM ' , '\t, '\n, ',, '; => RETURN [sepr]; ENDCASE; RETURN [other]; }; token _ IO.GetTokenRope[st, MyBreak ! IO.EndOfStream => CONTINUE].token; }; pulsesPerMilli: CARDINAL = Basics.LongDiv[100000, ProcessorFace.microsecondsPerHundredPulses]; Pause: PROC [msecs: CARDINAL _ 10] = { THROUGH [0..msecs) DO init: CARD = ProcessorFace.GetClockPulses[]; DO current: CARD = ProcessorFace.GetClockPulses[]; IF current-init > pulsesPerMilli THEN EXIT; ENDLOOP; ENDLOOP; }; InitializeNames: PROC ~ { machine _ SELECT SystemVersion.machineType FROM dolphin => "D0", dorado => "Dorado", dandelion => "DLion", dicentra => "Dicentra", daybreak => "Dove", ENDCASE => "UnknownMachine"; extension[checkpoint] _ ".outload"; extension[germ] _ ".germ"; extension[microcode] _ (SELECT SystemVersion.machineType FROM dandelion, dicentra => ".db", ENDCASE => ".eb"); extension[bootFile] _ ".boot"; localName[checkpoint] _ "Checkpoint"; localName[microcode] _ "Microcode"; localName[germ] _ "Germ"; localName[bootFile] _ "BootFile"; localName[debugger] _ "Debugger"; localName[debuggee] _ "Debuggee"; localName[VM] _ "VM"; localName[VAM] _ "VAM"; localName[fs] _ "FS"; localName[alpine] _ "Alpine"; }; Init: PROC [] = BEGIN InitializeNames[]; [] _ ReadRemoteNames[]; IagoOps.RegisterCommand[[CreatePrinterWorld, "Create Printer World", TRUE]]; IagoOps.RegisterCommand[[SetPrinterRemoteNamesCommand, "Set Printer Names", TRUE]]; IagoOps.RegisterCommand[[SetPrinterDirectoriesCommand, "Set Printer Directories", TRUE]]; IagoOps.RegisterCommand[[SetPrinterTypeCommand, "Set Printer Type", TRUE]]; IagoOps.RegisterCommand[[InstallBootFileCommand, "Install Boot File"]]; IagoOps.RegisterCommand[[InstallGermFileCommand, "Install Germ"]]; IagoOps.RegisterCommand[[InstallMicrocodeFileCommand, "Install Cedar Microcode"]]; END; Init[]; END. ˆPrinterIagoImpl.mesa Copyright Σ 1987 by Xerox Corporation. All rights reserved. Tim Diebert: April 7, 1987 11:39:44 am PDT BootFile USING [MemorySizeToFileSize], grab the original create time before FS.Open[... lock: write ...] changes it memImagePages: INT; -- Room for a physical memory image plus possible junk TRUSTED { memImagePages _ BootFile.MemorySizeToFileSize[VMSideDoor.rmPages]+24; Has to be trusted because BootFile.MemorySizeToFileSize is misdeclared! }; Write the remote names on the system volume Write the remote names on the system volume Write the remote names on the system volume See what local name the user would like before doing the copy, zap the entry in the root Restore the original create time; it was clobbered by FS.Open[... lock: write ...] Writes the remote names on the given volume (NIL => systems volume), and write messages to the out stream (default: no messages). Reads the remote names from the given volume (default: system volume). DKW: We assume for now that communication with sites outside the pa registry involves gateways that cannot yet cope with large buffers. When large buffers are accepted throughout the internet, this crock can go away. Presumably you will notice this when the PupStreamBackdoor interface disappears. This is a standard name that needs to be defined. [char: CHAR] RETURNS [IO.CharClass] A simple processor-independent pausing routine. Κ€˜™Icode™œ˜Q—K˜—Kšœ˜Kšœ œ$˜3Kšœœ˜-šœ˜š œœ˜KšœGœ˜YKšœ˜—Kšœ5ž˜CKšœ'™'š˜šœ˜K˜K˜ K˜@K˜K˜ —Kšœœœœ˜-Kšœ˜—Kšœ#˜%K™0Kšœœ˜)Kšœž"˜0Kšœ œ'˜5Kšœ˜—Kšœ œ˜,Kšœ œ$˜/Kšœ(˜(šœ˜Kšœœ>˜Ešœ˜Kšœ!˜#Kšœ%˜%šœ?˜AKšœ6œ™R—Kšœž"˜0Kšœ˜Kšœ˜——Kšœ˜šœœ˜Kšœ)˜)Kšœ˜—Kšœ˜—K˜š  œœœœœ˜8Kšœ=˜=Kšœ œ˜&šœ˜%Kšœ+˜+Kšœ-˜-Kšœ/˜/Kšœ2˜2Kšœ˜—Kšœ6˜˜QKšœF˜HKšœ@˜BKšœJ˜LKšœF˜HKšœF˜HšœXœœ˜nKšœ=˜?Kšœ˜Kšœ˜—Kšœ˜Kšœœœœ>˜PKšœ œ˜K˜—šœœœ˜7šœ ˜ Kšœœœ]˜lKš˜K˜—K˜—Kšœ,˜,K˜K˜—š œœ œœœœœ œœ˜aKšœF™FKšœ œœ˜Kš  œœœ˜š œœ˜Kšœ6˜6Kšœ œE˜SKšœ œ œ-˜GKšœœœ=˜Pš  œœ œœœœ˜;Jšœœ˜Jšœœ˜'J˜—Kšœ œ˜*Kšœœ œœ ˜;Kšœ%˜%Kšœœœœ ˜8Kšœ"˜"Kšœœ"œœ ˜=Kšœ'˜'Kšœœ œœ ˜;Kšœ%˜%Kšœœ œœ ˜;Kšœ%˜%Kšœ˜Kš œœœΟr œœ˜9šœž˜Kš œœœœ‘ œœ˜=KšœW˜WKšœœœœ˜Kšœ(˜(Kšœ˜—Kšœ˜Kšœ œ˜Kšœ˜—šœœœ˜6šœ ˜ Kšœœœ[˜jKšœ˜ K˜—šœ œ˜Kšœœœf˜uKšœ˜ K˜—K˜—Kšœœ˜$Kšœ œ˜&Kšœœ˜$Kšœ,˜,K˜K˜—š œœ œ˜+Kšœ«™«šœœœ˜-KšœI˜I—K˜K˜—š   œœ œœ œ˜Gšœ"œœ˜.Kšœ1™1Kš œœœ œœ ˜3Kšœœ˜Kš œœœœœœ ˜RKšœS˜SK˜—K˜K˜—š  œœœœ œœ˜;•StartOfExpansion' -- [char: CHAR] RETURNS [IO.CharClass]šœ œ˜KšΠck#™#šœ˜Kšœœ˜&Kšœ˜—Kšœ ˜K˜—Kšœœœœ˜HK˜—K˜šœœ˜KšœC˜CK˜—š œœ œ ˜&Kšœ/™/šœ ˜Kšœœ"˜,š˜Kšœ œ"˜/Kšœœœ˜+Kšœ˜—Kšœ˜—K˜K˜—š œœ˜šœ œ˜/K˜K˜K˜K˜Kšœ˜Kšœ˜—Kšœ#˜#Kšœ˜šœœ˜=Kšœœ ˜0—Kšœ˜Kšœ%˜%Kšœ#˜#Kšœ˜Kšœ!˜!Kšœ!˜!Kšœ!˜!Kšœ œ ˜Kšœ œ ˜Kšœ˜Kšœ˜K˜K˜—š œœ˜Kšœ˜Kšœ˜KšœEœ˜LKšœLœ˜SKšœRœ˜YKšœDœ˜KKšœG˜GKšœB˜BKšœR˜RKšœ˜—K˜K˜—Kšœ˜—…—D˜]