license import type { Chunk , Chunker } from './chunker.js' ; import { chunkEquals } from './chunker.js' ; const E_END = 'Iterator exhausted before seek ended.' ; typeParam typeParam TData - Type of data this seeker’s read methods will return (not
 * necessarily the same as the `TData` parameter of  Chunk , see e.g.
 *  CodePointSeeker )
 *
 *  public export interface Seeker < TChunk extends Chunk < any > , TData extends Iterable < any > = string > extends RelativeSeeker < TData > , AbsoluteSeeker < TData > , ChunkSeeker < TChunk , TData > { } public export interface RelativeSeeker < TData extends Iterable < any > = string > { param length throws seekBy ( length ) : void ; /**
     * Read forward or backward by a number of characters.
     *
     * Equal to  seekBy , but returning the characters passed.
     *
     *  param length param roundUp param lessIsFine returns throws read ( length ? , roundUp ? , lessIsFine ? ) : TData ; } public export interface AbsoluteSeeker < TData extends Iterable < any > = string > { /**
     * The current position in the file in terms of character count: i.e. the
     * number of characters before the place currently being pointed at.
     */ readonly position : number ; param target throws seekTo ( target ) : void ; /**
     * Read forward or backward from the current to the given position in the
     * file, returning the characters that have been passed.
     *
     * Equal to  seekTo , but returning the characters passed.
     *
     *  param target param roundUp returns throws readTo ( target , roundUp ? ) : TData ; } /**
 * Seek/read to (and within) specfic chunks the file consists of; and access the
 * chunk and offset in that chunk corresponding to the current position.
 *
 * Note that all offset numbers in this interface are representing units of the
 *  Chunk . data ; which might differ from that of
 * `TData`.
 *
 *  public export interface ChunkSeeker < TChunk extends Chunk < any > , TData extends Iterable < any > = string > { /**
     * The chunk containing the current position.
     *
     * When the position falls at the edge between two chunks, `currentChunk` is
     * always the later one (thus  offsetInChunk would be zero). Note that
     * an empty chunk (for which position zero is at both its edges) can
     * hence never be the current chunk unless it is the last chunk in the file.
      readonly currentChunk : TChunk ; /**
     * The offset inside `currentChunk` corresponding to the current position.
     * Can be between zero and the length of the chunk (inclusive; but it could
     * equal the length of the chunk only if currentChunk is the last chunk).
     */ readonly offsetInChunk : number ; param chunk param offset throws seekToChunk ( chunk , offset ? ) : void ; /**
     * Read to the start of a given chunk, or to an offset relative to that.
     *
     * Equal to  seekToChunk , but returning the characters passed.
     *
     *  param chunk param offset returns throws readToChunk ( chunk , offset ? ) : TData ; } /**
 * A TextSeeker is constructed around a  Chunker , to let it be treated as
 * a continuous sequence of characters.
 *
 * Seeking to a given numeric position will cause a `TextSeeker` to pull chunks
 * from the underlying `Chunker`, counting their lengths until the requested
 * position is reached. `Chunks` are not stored but simply read again when
 * seeking backwards.
 *
 * The `Chunker` is presumed to read an unchanging file. If a chunk’s length
 * would change while seeking, a TextSeeker’s absolute positioning would be
 * incorrect.
 *
 * See  CodePointSeeker for a  Seeker that counts Unicode *code
 * points* instead of Javascript’s ‘normal’ characters.
 *
 *  public export class TextSeeker < TChunk extends Chunk < string > > implements Seeker < TChunk > { get currentChunk ( ) : TChunk { return this . chunker . currentChunk ; } private currentChunkPosition = 0 ; offsetInChunk = 0 ; get position ( ) : number { return this . currentChunkPosition + this . offsetInChunk ; } constructor ( protected chunker ) { this . seekTo ( 0 ) ; } read ( length , roundUp = false , lessIsFine = false ) { return this . _readOrSeekTo ( true , this . position + length , roundUp , lessIsFine ) ; } readTo ( target , roundUp = false ) { return this . _readOrSeekTo ( true , target , roundUp ) ; } seekBy ( length ) { this . seekTo ( this . position + length ) ; } seekTo ( target ) { this . _readOrSeekTo ( false , target ) ; } seekToChunk ( target , offset = 0 ) { this . _readOrSeekToChunk ( false , target , offset ) ; } readToChunk ( target , offset = 0 ) { return this . _readOrSeekToChunk ( true , target , offset ) ; } private _readOrSeekToChunk ( read , target , offset ? ) ; private _readOrSeekToChunk ( read , target , offset ? ) ; private _readOrSeekToChunk ( read , target , offset = 0 ) { const oldPosition = this . position ; let result = '' ; if ( ! this . chunker . precedesCurrentChunk ( target ) ) { while ( ! chunkEquals ( this . currentChunk , target ) ) { const [ data , nextChunk ] = this . _readToNextChunk ( ) ; if ( read ) result += data ; if ( nextChunk === null ) throw new RangeError ( E_END ) ; } } else { while ( ! chunkEquals ( this . currentChunk , target ) ) { const [ data , previousChunk ] = this . _readToPreviousChunk ( ) ; if ( read ) result = data + result ; if ( previousChunk === null ) throw new RangeError ( E_END ) ; } } const targetPosition = this . currentChunkPosition + offset ; if ( ! read ) { this . seekTo ( targetPosition ) ; } else { if ( targetPosition >= this . position ) { result += this . readTo ( targetPosition ) ; } else if ( targetPosition >= oldPosition ) { this . seekTo ( targetPosition ) ; result = result . slice ( 0 , targetPosition - oldPosition ) ; } else { this . seekTo ( oldPosition ) ; result = this . readTo ( targetPosition ) ; } return result ; } } private _readOrSeekTo ( read , target , roundUp ? , lessIsFine ? ) ; private _readOrSeekTo ( read , target , roundUp ? , lessIsFine ? ) ; private _readOrSeekTo ( read , target , roundUp = false , lessIsFine = false ) { let result = '' ; if ( this . position <= target ) { while ( true ) { const endOfChunk = this . currentChunkPosition + this . currentChunk . data . length ; if ( endOfChunk <= target ) { const [ data , nextChunk ] = this . _readToNextChunk ( ) ; if ( read ) result += data ; if ( nextChunk === null ) { if ( this . position === target || lessIsFine ) break ; else throw new RangeError ( E_END ) ; } } else { const newOffset = roundUp ? this . currentChunk . data . length : target - this . currentChunkPosition ; if ( read ) result += this . currentChunk . data . substring ( this . offsetInChunk , newOffset ) ; this . offsetInChunk = newOffset ; if ( roundUp ) this . seekBy ( 0 ) ; break ; } } } else { while ( this . position > target ) { if ( this . currentChunkPosition <= target ) { const newOffset = roundUp ? 0 : target - this . currentChunkPosition ; if ( read ) result = this . currentChunk . data . substring ( newOffset , this . offsetInChunk ) + result ; this . offsetInChunk = newOffset ; break ; } else { const [ data , previousChunk ] = this . _readToPreviousChunk ( ) ; if ( read ) result = data + result ; if ( previousChunk === null ) { if ( lessIsFine ) break ; else throw new RangeError ( E_END ) ; } } } } if ( read ) return result ; } _readToNextChunk ( ) { const data = this . currentChunk . data . substring ( this . offsetInChunk ) ; const chunkLength = this . currentChunk . data . length ; const nextChunk = this . chunker . nextChunk ( ) ; if ( nextChunk !== null ) { this . currentChunkPosition += chunkLength ; this . offsetInChunk = 0 ; } else { this . offsetInChunk = chunkLength ; } return [ data , nextChunk ] ; } _readToPreviousChunk ( ) { const data = this . currentChunk . data . substring ( 0 , this . offsetInChunk ) ; const previousChunk = this . chunker . previousChunk ( ) ; if ( previousChunk !== null ) { this . currentChunkPosition -= this . currentChunk . data . length ; this . offsetInChunk = this . currentChunk . data . length ; } else { this . offsetInChunk = 0 ; } return [ data , previousChunk ] ; } }