-- 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...