-- FileStreamImpl.mesa, edited by Johnsson, 13-Feb-81 14:14:14 DIRECTORY ByteBlt USING [ByteBlt], DCSFileTypes USING [tLeaderPage], Environment USING [ Block, Byte, bytesPerPage, PageCount, wordsPerPage], File USING [ Capability, GetAttributes, GetSize, grow, PageCount, Permissions, read, SetSize, shrink, write], FileStream USING [Subtype], Heap USING [systemMDSZone], Inline USING [ BITAND, BITOR, HighHalf, LongCOPY, LongDiv, LongDivMod, LongMult, LowHalf], Space USING [ CopyIn, Create, CreateUniformSwapUnits, Delete, Error, ForceOut, GetAttributes, GetHandle, GetWindow, Handle, LongPointer, Map, PageFromLongPointer, Unmap, virtualMemory], Stream USING [ Block, Byte, defaultInputOptions, EndOfStream, GetProcedure, Handle, InputOptions, Object, PutProcedure, SendAttentionProcedure, SetSSTProcedure, ShortBlock, WaitAttentionProcedure], System USING [GetGreenwichMeanTime, GreenwichMeanTime]; FileStreamImpl: PROGRAM IMPORTS ByteBlt, File, Heap, Inline, Space, Stream, System EXPORTS FileStream SHARES File = BEGIN bytesPerPage: CARDINAL = Environment.bytesPerPage; FSObject: TYPE = RECORD [ stream: Stream.Object, index: CARDINAL ← 0, bufferBytes: CARDINAL ← 0, dataBytesInBuffer: CARDINAL ← 0, firstFilePageInBuffer: CARDINAL ← 0, filePages: CARDINAL ← 0, password: CARDINAL ← fsPassword, buffer: LONG POINTER TO PACKED ARRAY [0..0) OF Environment.Byte ← NIL, leader: LONG POINTER TO LeaderPage ← NIL, eofInBuffer: BOOLEAN ← FALSE, read: BOOLEAN, write: BOOLEAN, grow: BOOLEAN, shrink: BOOLEAN, lengthChanged: BOOLEAN ← FALSE, dataOffset: [0..1] ← 0, file: File.Capability, bufferSpace: Space.Handle ← NULL]; fsPassword: CARDINAL = 102774B; FSHandle: TYPE = POINTER TO FSObject; conversionFudge: CARDINAL = LOOPHOLE[@LOOPHOLE[0,FSHandle].stream]; ConvertHandle: PROCEDURE [h: Stream.Handle] RETURNS [FSHandle] = INLINE {RETURN[LOOPHOLE[h-conversionFudge]]}; InvalidHandle: PUBLIC ERROR [errorStream: Stream.Handle] = CODE; InvalidOperation: PUBLIC ERROR [errorStream: Stream.Handle] = CODE; ValidateHandle: PROCEDURE [h: Stream.Handle] RETURNS [FSHandle] = INLINE BEGIN t: FSHandle ← ConvertHandle[h]; IF t.password # fsPassword THEN ERROR InvalidHandle[h]; RETURN[t]; END; FileTooLong: PUBLIC ERROR [longFile: POINTER TO File.Capability] = CODE; Create: PUBLIC PROCEDURE [ capability: File.Capability, options: Stream.InputOptions ← Stream.defaultInputOptions] RETURNS [Stream.Handle] = BEGIN fsh: FSHandle = Heap.systemMDSZone.NEW[FSObject]; permissions: File.Permissions = capability.permissions; filePages: File.PageCount = File.GetSize[capability]; IF Inline.HighHalf[filePages] # 0 THEN ERROR FileTooLong[@capability]; capability.permissions ← Inline.BITOR[permissions, File.read]; fsh↑ ← [ stream: [ getByte: GetByte, putByte: PutByte, getWord: GetWord, putWord: PutWord, get: GetBlock, put: PutBlock, delete: Delete, setSST: SetSSTNop, sendAttention: SendAttentionNop, waitAttention: WaitAttentionNop, options: options], read: Inline.BITAND[permissions, File.read] # 0, write: Inline.BITAND[permissions, File.write] # 0, grow: Inline.BITAND[permissions, File.grow] # 0, shrink: Inline.BITAND[permissions, File.shrink] # 0, filePages: Inline.LowHalf[filePages], file: capability]; IF ~fsh.read THEN BEGIN fsh.stream.getByte ← fsh.stream.getWord ← GetError; fsh.stream.get ← GetBlockError; END; IF ~fsh.write THEN BEGIN fsh.stream.putByte ← LOOPHOLE[PutError]; fsh.stream.putWord ← PutError; fsh.stream.put ← PutBlockError; END; InitLeader[fsh]; SetupBuffer[fsh, fsh.dataOffset]; RETURN[@fsh.stream]; END; -- Generic Stream procedures; assume handle ok GetByte: PROCEDURE [sH: Stream.Handle] RETURNS [byte: UNSPECIFIED[0..256)] = BEGIN fsh: FSHandle ← ConvertHandle[sH]; WHILE fsh.index = fsh.dataBytesInBuffer DO IF fsh.eofInBuffer THEN {SIGNAL Stream.EndOfStream[0]; RETURN[0]}; AdvanceBuffer[fsh]; ENDLOOP; byte ← fsh.buffer[fsh.index]; fsh.index ← fsh.index + 1; RETURN END; GetWord: PROCEDURE [sH: Stream.Handle] RETURNS [UNSPECIFIED] = BEGIN t: UNSPECIFIED = CARDINAL[GetByte[sH]] * 256; RETURN[t + GetByte[sH]] END; GetError: PROCEDURE [sH: Stream.Handle] RETURNS [UNSPECIFIED] = {ERROR InvalidOperation[sH]}; PutByte: PROCEDURE [sH: Stream.Handle, byte: UNSPECIFIED[0..256)] = BEGIN fsh: FSHandle ← ConvertHandle[sH]; WHILE fsh.index = fsh.dataBytesInBuffer DO IF fsh.eofInBuffer AND ~fsh.grow THEN {Stream.EndOfStream[0]; RETURN}; IF fsh.dataBytesInBuffer = fsh.bufferBytes THEN AdvanceBuffer[fsh] ELSE BEGIN fsh.dataBytesInBuffer ← fsh.dataBytesInBuffer + 1; fsh.lengthChanged ← TRUE; END; ENDLOOP; fsh.buffer[fsh.index] ← byte; fsh.index ← fsh.index + 1; RETURN END; PutWord: PROCEDURE [sH: Stream.Handle, word: UNSPECIFIED] = BEGIN PutByte[sH, CARDINAL[word] / 256]; PutByte[sH, CARDINAL[word] MOD 256]; RETURN END; PutError: PROCEDURE [sH: Stream.Handle, word: UNSPECIFIED] = {ERROR InvalidOperation[sH]}; GetBlock: Stream.GetProcedure = BEGIN fsh: FSHandle = ConvertHandle[sH]; bufferBlock: Environment.Block; countRemaining: CARDINAL ← block.stopIndexPlusOne-block.startIndex; countTransferred: CARDINAL; sst ← 0; bytesTransferred ← 0; why ← normal; WHILE countRemaining # 0 DO bufferBlock ← [ blockPointer: fsh.buffer, startIndex: fsh.index, stopIndexPlusOne: fsh.dataBytesInBuffer]; countTransferred ← ByteBlt.ByteBlt[from: bufferBlock, to: block]; fsh.index ← fsh.index + countTransferred; bytesTransferred ← bytesTransferred + countTransferred; IF (countRemaining ← countRemaining - countTransferred) = 0 THEN EXIT; IF fsh.eofInBuffer THEN BEGIN IF options.signalEndOfStream THEN SIGNAL Stream.EndOfStream[block.startIndex+countTransferred]; IF options.signalShortBlock THEN ERROR Stream.ShortBlock; why ← endOfStream; EXIT END; block.startIndex ← block.startIndex + countTransferred; AdvanceBuffer[fsh]; ENDLOOP; RETURN END; GetBlockError: Stream.GetProcedure = {ERROR InvalidOperation[sH]}; PutBlock: Stream.PutProcedure = BEGIN fsh: FSHandle = ConvertHandle[sH]; bufferBlock: Environment.Block; countRemaining: CARDINAL ← block.stopIndexPlusOne-block.startIndex; countTransferred: CARDINAL; WHILE countRemaining # 0 DO bufferBlock ← [ blockPointer: fsh.buffer, startIndex: fsh.index, stopIndexPlusOne: fsh.dataBytesInBuffer]; IF fsh.eofInBuffer AND fsh.grow THEN bufferBlock.stopIndexPlusOne ← fsh.bufferBytes; countTransferred ← ByteBlt.ByteBlt[from: block, to: bufferBlock]; fsh.index ← fsh.index + countTransferred; IF fsh.eofInBuffer AND fsh.index > fsh.dataBytesInBuffer THEN { fsh.dataBytesInBuffer ← fsh.index; fsh.lengthChanged ← TRUE}; IF (countRemaining ← countRemaining - countTransferred) = 0 THEN EXIT; IF fsh.eofInBuffer THEN IF ~fsh.grow THEN { IF fsh.stream.options.signalEndOfStream THEN SIGNAL Stream.EndOfStream[block.startIndex+countTransferred]; IF fsh.stream.options.signalShortBlock THEN ERROR Stream.ShortBlock; EXIT} ELSE { fsh.dataBytesInBuffer ← fsh.bufferBytes; fsh.lengthChanged ← TRUE}; block.startIndex ← block.startIndex + countTransferred; AdvanceBuffer[fsh]; ENDLOOP; IF endPhysicalRecord THEN Cleanup[fsh]; RETURN END; PutBlockError: Stream.PutProcedure = {ERROR InvalidOperation[sH]}; Delete: PROCEDURE [sH: Stream.Handle] = BEGIN fsh: FSHandle ← ConvertHandle[sH]; newFilePages: CARDINAL; index: LONG CARDINAL ← GetIndex[@fsh.stream]; ReadLeader[fsh]; EmptyBuffer[fsh, TRUE]; IF ~fsh.read AND index # 0 THEN fsh.leader.length ← index; newFilePages ← Inline.LowHalf[PagesForBytes[fsh.leader.length]] + fsh.dataOffset; IF fsh.write AND newFilePages < fsh.filePages AND fsh.shrink THEN File.SetSize[fsh.file, newFilePages]; CloseLeader[fsh]; Zero[fsh, SIZE[FSObject]]; Heap.systemMDSZone.FREE[@fsh]; RETURN END; SetSSTNop: Stream.SetSSTProcedure = {}; SendAttentionNop: Stream.SendAttentionProcedure = {}; WaitAttentionNop: Stream.WaitAttentionProcedure = {RETURN[0]}; -- FileStream specific procedures; must validate handle EndOf: PUBLIC PROCEDURE [sH: Stream.Handle] RETURNS [BOOLEAN] = BEGIN fsh: FSHandle = ValidateHandle[sH]; RETURN[fsh.eofInBuffer AND fsh.index = fsh.dataBytesInBuffer] END; GetIndex: PUBLIC PROCEDURE [sH: Stream.Handle] RETURNS [LONG CARDINAL] = BEGIN fsh: FSHandle = ValidateHandle[sH]; RETURN[Inline.LongMult[fsh.firstFilePageInBuffer-fsh.dataOffset, bytesPerPage] + fsh.index] END; IndexOutOfRange: PUBLIC SIGNAL [errorStream: Stream.Handle] = CODE; SetIndex: PUBLIC PROCEDURE [sH: Stream.Handle, nextByte: LONG CARDINAL] = BEGIN fsh: FSHandle = ValidateHandle[sH]; filePage, byte: CARDINAL; bufferPage: CARDINAL = fsh.firstFilePageInBuffer; bufferPages: CARDINAL = fsh.bufferBytes/bytesPerPage; IF (Inline.LongMult[bufferPage-fsh.dataOffset, bytesPerPage] + fsh.index) = nextByte THEN RETURN; IF Inline.HighHalf[nextByte] >= bytesPerPage THEN GOTO OutOfRange; [filePage, byte] ← Inline.LongDivMod[nextByte,bytesPerPage]; filePage ← filePage + fsh.dataOffset; IF filePage NOT IN[bufferPage..bufferPage+bufferPages) THEN BEGIN growFile: BOOLEAN ← FALSE; IF fsh.lengthChanged THEN Cleanup[fsh]; IF nextByte > fsh.leader.length THEN IF ~fsh.grow THEN GOTO OutOfRange ELSE {fsh.filePages ← filePage + growIncrement; growFile ← TRUE}; SetupBuffer[fsh, MAX[filePage,fsh.dataOffset+swapUnitSize]-swapUnitSize, growFile]; END; fsh.index ← (filePage-fsh.firstFilePageInBuffer) * bytesPerPage + byte; IF fsh.index > fsh.dataBytesInBuffer THEN {fsh.dataBytesInBuffer ← fsh.index; fsh.lengthChanged ← TRUE}; RETURN; EXITS OutOfRange => SIGNAL IndexOutOfRange[sH]; END; GetLength: PUBLIC PROCEDURE [sH: Stream.Handle] RETURNS [LONG CARDINAL] = BEGIN fsh: FSHandle = ValidateHandle[sH]; Cleanup[fsh]; RETURN[fsh.leader.length] END; SetLength: PUBLIC PROCEDURE [sH: Stream.Handle, fileLength: LONG CARDINAL] = BEGIN fsh: FSHandle = ValidateHandle[sH]; newFilePages: CARDINAL; growFile: BOOLEAN ← FALSE; bufferPage: CARDINAL = fsh.firstFilePageInBuffer; bufferPages: CARDINAL = fsh.bufferBytes/bytesPerPage; Cleanup[fsh]; IF Inline.HighHalf[fileLength] >= bytesPerPage THEN GOTO OutOfRange; newFilePages ← Inline.LongDiv[fileLength+bytesPerPage-1,bytesPerPage]+fsh.dataOffset; IF newFilePages > fsh.filePages THEN IF fsh.grow THEN {fsh.filePages ← newFilePages; growFile ← TRUE} ELSE GOTO OutOfRange; fsh.leader.length ← fileLength; WriteLeader[fsh]; SetupBuffer[fsh, IF newFilePages IN [bufferPage..bufferPage+bufferPages) THEN fsh.firstFilePageInBuffer ELSE MAX[newFilePages, fsh.dataOffset+swapUnitSize] - swapUnitSize, growFile]; fsh.index ← fsh.dataBytesInBuffer; RETURN; EXITS OutOfRange => SIGNAL IndexOutOfRange[sH]; END; GetCapability: PUBLIC PROCEDURE [sH: Stream.Handle] RETURNS [File.Capability] = {fsh: FSHandle = ValidateHandle[sH]; RETURN[fsh.file]}; -- LeaderPage leaderVersionID: CARDINAL = 01240; maxLeaderNameCharacters: CARDINAL = 40; LeaderPage: TYPE = MACHINE DEPENDENT RECORD [ versionID: CARDINAL, dataType: FileStream.Subtype, create, write, read: System.GreenwichMeanTime, length: LONG CARDINAL, nameLength: CARDINAL, name: PACKED ARRAY [0..maxLeaderNameCharacters) OF CHARACTER]; InitLeader: PROCEDURE [fsh: FSHandle] = BEGIN leaderSpace: Space.Handle = Space.Create[1, Space.virtualMemory]; leader: LONG POINTER TO LeaderPage = Space.LongPointer[leaderSpace]; hasLeader: BOOLEAN ← File.GetAttributes[fsh.file].type = DCSFileTypes.tLeaderPage; maxLength: LONG CARDINAL ← Inline.LongMult[fsh.filePages, bytesPerPage]; IF fsh.filePages = 0 THEN IF fsh.grow THEN File.SetSize[fsh.file, fsh.filePages ← growIncrement + 1] ELSE hasLeader ← FALSE; IF hasLeader THEN BEGIN leaderCap: File.Capability ← fsh.file; now: System.GreenwichMeanTime = System.GetGreenwichMeanTime[]; leaderCap.permissions ← File.read+File.write; IF maxLength # 0 THEN maxLength ← maxLength - bytesPerPage; Space.Map[leaderSpace, [leaderCap, 0]]; fsh.dataOffset ← 1; IF leader.versionID # leaderVersionID THEN BEGIN Zero[leader, Environment.wordsPerPage]; leader.versionID ← leaderVersionID; leader.length ← maxLength; fsh.lengthChanged ← TRUE; END ELSE IF leader.length > maxLength THEN {leader.length ← maxLength; fsh.lengthChanged ← TRUE}; IF fsh.write THEN leader.create ← leader.write ← now; IF fsh.read THEN leader.read ← now ELSE leader.read ← [0]; END ELSE BEGIN Space.Map[leaderSpace]; Zero[leader, Environment.wordsPerPage]; leader.length ← maxLength; END; Space.ForceOut[leaderSpace]; fsh.leader ← leader; RETURN END; CloseLeader: PROCEDURE [fsh: FSHandle] = {Space.Delete[GetMappedSpace[fsh.leader]]; fsh.leader ← NIL}; SetLeaderProperties: PUBLIC PROCEDURE [ sH: Stream.Handle, create, write, read: System.GreenwichMeanTime ← [0], name: STRING ← NIL, type: FileStream.Subtype ← null] = BEGIN fsh: FSHandle = ValidateHandle[sH]; leader: LONG POINTER TO LeaderPage = fsh.leader; ReadLeader[fsh]; IF create # 0 THEN leader.create ← create; IF write # 0 THEN leader.write ← write; IF read # 0 THEN leader.read ← read; IF name # NIL THEN BEGIN leader.nameLength ← MIN[LENGTH[leader.name],name.length]; FOR i: CARDINAL IN [0..leader.nameLength) DO leader.name[i] ← name[i]; ENDLOOP; END; IF type # null THEN leader.dataType ← type; WriteLeader[fsh]; RETURN END; GetLeaderProperties: PUBLIC PROCEDURE [sH: Stream.Handle, name: STRING ← NIL] RETURNS [type: FileStream.Subtype, create, write, read: System.GreenwichMeanTime] = BEGIN fsh: FSHandle = ValidateHandle[sH]; leader: LONG POINTER TO LeaderPage = fsh.leader; ReadLeader[fsh]; IF name # NIL THEN BEGIN name.length ← MIN[leader.nameLength,name.maxlength]; FOR i: CARDINAL IN [0..name.length) DO name[i] ← leader.name[i]; ENDLOOP; END; RETURN[leader.dataType, leader.create, leader.write, leader.read] END; -- Leader page operations without a stream NoLeaderPage: PUBLIC ERROR [errorCap: POINTER TO File.Capability] = CODE; GetLeader: PROCEDURE [cap: POINTER TO File.Capability] RETURNS [leader: LONG POINTER TO LeaderPage] = BEGIN leaderSpace: Space.Handle; pages: File.PageCount; IF File.GetAttributes[cap↑].type # DCSFileTypes.tLeaderPage OR (pages←File.GetSize[cap↑]) = 0 THEN ERROR NoLeaderPage[cap]; leaderSpace ← Space.Create[1, Space.virtualMemory]; leader ← Space.LongPointer[leaderSpace]; cap.permissions ← File.read + File.write; Space.Map[leaderSpace, [cap↑, 0]]; IF leader.versionID # leaderVersionID THEN { Zero[leader,Environment.wordsPerPage]; leader.versionID ← leaderVersionID; leader.length ← (pages-1)*Environment.bytesPerPage}; RETURN END; GetLeaderPropertiesForCapability: PUBLIC PROCEDURE [cap: File.Capability, name: STRING ← NIL] RETURNS [type: FileStream.Subtype, create, write, read: System.GreenwichMeanTime, length: LONG CARDINAL] = BEGIN leader: LONG POINTER TO LeaderPage = GetLeader[@cap]; IF name # NIL THEN BEGIN name.length ← MIN[leader.nameLength,name.maxlength]; FOR i: CARDINAL IN [0..name.length) DO name[i] ← leader.name[i]; ENDLOOP; END; type ← leader.dataType; create ← leader.create; write ← leader.write; read ← leader.read; length ← leader.length; Space.Delete[GetMappedSpace[leader]]; RETURN END; SetLeaderPropertiesForCapability: PUBLIC PROCEDURE [ cap: File.Capability, create, write, read: System.GreenwichMeanTime ← [0], name: STRING ← NIL, type: FileStream.Subtype ← null] = BEGIN leader: LONG POINTER TO LeaderPage = GetLeader[@cap]; IF create # 0 THEN leader.create ← create; IF write # 0 THEN leader.write ← write; IF read # 0 THEN leader.read ← read; IF name # NIL THEN BEGIN leader.nameLength ← MIN[LENGTH[leader.name],name.length]; FOR i: CARDINAL IN [0..leader.nameLength) DO leader.name[i] ← name[i]; ENDLOOP; END; IF type # null THEN leader.dataType ← type; Space.Delete[GetMappedSpace[leader]]; RETURN END; -- Utilities maxBufferPages: CARDINAL = 70; maxBufferBytes: CARDINAL = maxBufferPages * Environment.bytesPerPage; swapUnitSize: CARDINAL = 5; growIncrement: CARDINAL = 10; Cleanup: PROCEDURE [fsh: FSHandle] = BEGIN Space.ForceOut[fsh.bufferSpace]; ReadLeader[fsh]; IF fsh.lengthChanged THEN BEGIN fsh.leader.length ← Inline.LongMult[fsh.firstFilePageInBuffer-fsh.dataOffset, bytesPerPage] + fsh.dataBytesInBuffer; WriteLeader[fsh]; fsh.lengthChanged ← FALSE; END; RETURN END; ReadLeader: PROCEDURE [fsh: FSHandle] = BEGIN leaderSpace: Space.Handle = GetMappedSpace[fsh.leader]; Space.CopyIn[leaderSpace, Space.GetWindow[leaderSpace] ! Space.Error => CONTINUE]; END; WriteLeader: PROCEDURE [fsh: FSHandle] = BEGIN Space.ForceOut[GetMappedSpace[fsh.leader]]; END; SetupBuffer: PROCEDURE [ fsh: FSHandle, filePage: CARDINAL, setLength: BOOLEAN ← FALSE] = BEGIN filePagesFollowing: CARDINAL; indexOfStartOfBuffer: LONG CARDINAL; IF fsh.buffer = NIL THEN BEGIN fsh.bufferSpace ← Space.Create[maxBufferPages, Space.virtualMemory]; Space.CreateUniformSwapUnits[swapUnitSize, fsh.bufferSpace]; fsh.buffer ← Space.LongPointer[fsh.bufferSpace]; END ELSE EmptyBuffer[fsh, FALSE]; IF setLength THEN File.SetSize[fsh.file, fsh.filePages]; Space.Map[fsh.bufferSpace, [fsh.file, filePage]]; filePagesFollowing ← fsh.filePages - filePage; indexOfStartOfBuffer ← LONG[filePage-fsh.dataOffset]*bytesPerPage; IF fsh.eofInBuffer ← (fsh.leader.length <= indexOfStartOfBuffer + maxBufferBytes) THEN BEGIN fsh.dataBytesInBuffer ← Inline.LowHalf[ fsh.leader.length - indexOfStartOfBuffer] END ELSE fsh.dataBytesInBuffer ← maxBufferBytes; fsh.bufferBytes ← MIN[filePagesFollowing, maxBufferPages] * bytesPerPage; fsh.firstFilePageInBuffer ← filePage; RETURN END; EmptyBuffer: PROCEDURE [fsh: FSHandle, delete: BOOLEAN] = BEGIN IF fsh.buffer = NIL THEN RETURN; Cleanup[fsh]; IF delete THEN {Space.Delete[fsh.bufferSpace]; fsh.buffer ← NIL} ELSE Space.Unmap[fsh.bufferSpace]; fsh.index ← fsh.bufferBytes ← fsh.dataBytesInBuffer ← 0; RETURN END; AdvanceBuffer: PROCEDURE [fsh: FSHandle] = BEGIN filePagesInBuffer: CARDINAL = fsh.bufferBytes/bytesPerPage; nextPage: CARDINAL = fsh.firstFilePageInBuffer + filePagesInBuffer; changeSize: BOOLEAN ← FALSE; IF nextPage >= fsh.filePages THEN {fsh.filePages ← fsh.filePages + growIncrement; changeSize ← TRUE}; SetupBuffer[fsh, MAX[nextPage,fsh.dataOffset+swapUnitSize]-swapUnitSize, changeSize]; fsh.index ← MIN[(nextPage-fsh.firstFilePageInBuffer) * bytesPerPage, fsh.dataBytesInBuffer]; RETURN END; GetMappedSpace: PROCEDURE [p: LONG POINTER] RETURNS [h: Space.Handle] = BEGIN mapped: BOOLEAN; parent: Space.Handle; h ← Space.GetHandle[Space.PageFromLongPointer[p]]; DO [parent: parent, mapped: mapped] ← Space.GetAttributes[h]; IF mapped THEN EXIT; h ← parent; ENDLOOP; RETURN END; PagesForBytes: PROCEDURE [w: LONG CARDINAL] RETURNS [p: LONG CARDINAL] = INLINE {RETURN[(w+bytesPerPage-1)/bytesPerPage]}; Zero: PROCEDURE [p: LONG POINTER, n: CARDINAL] = BEGIN IF n = 0 THEN RETURN; p↑ ← 0; Inline.LongCOPY[from: p, to: p+1, nwords: n-1]; RETURN END; END...