viem
Version:
244 lines (233 loc) • 7.08 kB
text/typescript
import {
NegativeOffsetError,
type NegativeOffsetErrorType,
PositionOutOfBoundsError,
type PositionOutOfBoundsErrorType,
RecursiveReadLimitExceededError,
type RecursiveReadLimitExceededErrorType,
} from '../errors/cursor.js'
import type { ErrorType } from '../errors/utils.js'
import type { ByteArray } from '../types/misc.js'
export type Cursor = {
bytes: ByteArray
dataView: DataView
position: number
positionReadCount: Map<number, number>
recursiveReadCount: number
recursiveReadLimit: number
remaining: number
assertReadLimit(position?: number): void
assertPosition(position: number): void
decrementPosition(offset: number): void
getReadCount(position?: number): number
incrementPosition(offset: number): void
inspectByte(position?: number): ByteArray[number]
inspectBytes(length: number, position?: number): ByteArray
inspectUint8(position?: number): number
inspectUint16(position?: number): number
inspectUint24(position?: number): number
inspectUint32(position?: number): number
pushByte(byte: ByteArray[number]): void
pushBytes(bytes: ByteArray): void
pushUint8(value: number): void
pushUint16(value: number): void
pushUint24(value: number): void
pushUint32(value: number): void
readByte(): ByteArray[number]
readBytes(length: number, size?: number): ByteArray
readUint8(): number
readUint16(): number
readUint24(): number
readUint32(): number
setPosition(position: number): () => void
_touch(): void
}
type CursorErrorType =
| CursorAssertPositionErrorType
| CursorDecrementPositionErrorType
| CursorIncrementPositionErrorType
| ErrorType
type CursorAssertPositionErrorType = PositionOutOfBoundsErrorType | ErrorType
type CursorDecrementPositionErrorType = NegativeOffsetErrorType | ErrorType
type CursorIncrementPositionErrorType = NegativeOffsetErrorType | ErrorType
type StaticCursorErrorType =
| NegativeOffsetErrorType
| RecursiveReadLimitExceededErrorType
const staticCursor: Cursor = {
bytes: new Uint8Array(),
dataView: new DataView(new ArrayBuffer(0)),
position: 0,
positionReadCount: new Map(),
recursiveReadCount: 0,
recursiveReadLimit: Number.POSITIVE_INFINITY,
assertReadLimit() {
if (this.recursiveReadCount >= this.recursiveReadLimit)
throw new RecursiveReadLimitExceededError({
count: this.recursiveReadCount + 1,
limit: this.recursiveReadLimit,
})
},
assertPosition(position) {
if (position < 0 || position > this.bytes.length - 1)
throw new PositionOutOfBoundsError({
length: this.bytes.length,
position,
})
},
decrementPosition(offset) {
if (offset < 0) throw new NegativeOffsetError({ offset })
const position = this.position - offset
this.assertPosition(position)
this.position = position
},
getReadCount(position) {
return this.positionReadCount.get(position || this.position) || 0
},
incrementPosition(offset) {
if (offset < 0) throw new NegativeOffsetError({ offset })
const position = this.position + offset
this.assertPosition(position)
this.position = position
},
inspectByte(position_) {
const position = position_ ?? this.position
this.assertPosition(position)
return this.bytes[position]
},
inspectBytes(length, position_) {
const position = position_ ?? this.position
this.assertPosition(position + length - 1)
return this.bytes.subarray(position, position + length)
},
inspectUint8(position_) {
const position = position_ ?? this.position
this.assertPosition(position)
return this.bytes[position]
},
inspectUint16(position_) {
const position = position_ ?? this.position
this.assertPosition(position + 1)
return this.dataView.getUint16(position)
},
inspectUint24(position_) {
const position = position_ ?? this.position
this.assertPosition(position + 2)
return (
(this.dataView.getUint16(position) << 8) +
this.dataView.getUint8(position + 2)
)
},
inspectUint32(position_) {
const position = position_ ?? this.position
this.assertPosition(position + 3)
return this.dataView.getUint32(position)
},
pushByte(byte: ByteArray[number]) {
this.assertPosition(this.position)
this.bytes[this.position] = byte
this.position++
},
pushBytes(bytes: ByteArray) {
this.assertPosition(this.position + bytes.length - 1)
this.bytes.set(bytes, this.position)
this.position += bytes.length
},
pushUint8(value: number) {
this.assertPosition(this.position)
this.bytes[this.position] = value
this.position++
},
pushUint16(value: number) {
this.assertPosition(this.position + 1)
this.dataView.setUint16(this.position, value)
this.position += 2
},
pushUint24(value: number) {
this.assertPosition(this.position + 2)
this.dataView.setUint16(this.position, value >> 8)
this.dataView.setUint8(this.position + 2, value & ~4294967040)
this.position += 3
},
pushUint32(value: number) {
this.assertPosition(this.position + 3)
this.dataView.setUint32(this.position, value)
this.position += 4
},
readByte() {
this.assertReadLimit()
this._touch()
const value = this.inspectByte()
this.position++
return value
},
readBytes(length, size) {
this.assertReadLimit()
this._touch()
const value = this.inspectBytes(length)
this.position += size ?? length
return value
},
readUint8() {
this.assertReadLimit()
this._touch()
const value = this.inspectUint8()
this.position += 1
return value
},
readUint16() {
this.assertReadLimit()
this._touch()
const value = this.inspectUint16()
this.position += 2
return value
},
readUint24() {
this.assertReadLimit()
this._touch()
const value = this.inspectUint24()
this.position += 3
return value
},
readUint32() {
this.assertReadLimit()
this._touch()
const value = this.inspectUint32()
this.position += 4
return value
},
get remaining() {
return this.bytes.length - this.position
},
setPosition(position) {
const oldPosition = this.position
this.assertPosition(position)
this.position = position
return () => (this.position = oldPosition)
},
_touch() {
if (this.recursiveReadLimit === Number.POSITIVE_INFINITY) return
const count = this.getReadCount()
this.positionReadCount.set(this.position, count + 1)
if (count > 0) this.recursiveReadCount++
},
}
type CursorConfig = { recursiveReadLimit?: number | undefined }
export type CreateCursorErrorType =
| CursorErrorType
| StaticCursorErrorType
| ErrorType
export function createCursor(
bytes: ByteArray,
{ recursiveReadLimit = 8_192 }: CursorConfig = {},
): Cursor {
const cursor: Cursor = Object.create(staticCursor)
cursor.bytes = bytes
cursor.dataView = new DataView(
bytes.buffer,
bytes.byteOffset,
bytes.byteLength,
)
cursor.positionReadCount = new Map()
cursor.recursiveReadLimit = recursiveReadLimit
return cursor
}