Last active
February 17, 2019 05:05
-
-
Save jjbubudi/107f3b2e5c51b320c6d096d4f564ad0d to your computer and use it in GitHub Desktop.
p2.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Int64 } from './compat/long'; | |
export type Double = number; | |
export type Float = number; | |
export type Int32 = number; | |
export type Int64 = Long; | |
export type Uint32 = number; | |
export type Uint64 = Long; | |
export type Sint32 = number; | |
export type Sint64 = Long; | |
export type Fixed32 = number; | |
export type Fixed64 = Long; | |
export type Sfixed32 = number; | |
export type Sfixed64 = Long; | |
export type Bytes = Uint8Array; | |
export type Byte = number; | |
export interface Long { | |
readonly low: Uint32; | |
readonly high: Uint32; | |
toString(): string; | |
unsafeToNumber(): number; | |
} | |
export interface ByteInputStream { | |
hasNext(): boolean; | |
take(n: number): () => boolean; | |
read(): Byte; | |
skip(n: number): void; | |
} | |
export interface ByteOutputStream { | |
write(byte: Byte): void; | |
toBytes(): Uint8Array; | |
} | |
export class Uint8ArrayInputStream implements ByteInputStream { | |
private offset = 0; | |
private readonly byteLength: number; | |
constructor(private readonly bytes: Uint8Array) { | |
this.byteLength = bytes.byteLength; | |
} | |
hasNext(): boolean { | |
return this.offset < this.byteLength; | |
} | |
take(n: number): () => boolean { | |
const end = this.offset + n; | |
return () => this.offset < end; | |
} | |
read(): Byte { | |
return this.bytes[this.offset++]; | |
} | |
skip(n: number): void { | |
this.offset += n; | |
} | |
} | |
export class Uint8ArrayOutputStream implements ByteOutputStream { | |
private offset = 0; | |
private bytes = new Uint8Array(16); | |
write(byte: Byte) { | |
this.bytes[this.offset++] = byte; | |
} | |
toBytes(): Uint8Array { | |
return this.bytes.slice(0, this.offset); | |
} | |
} | |
export function readUint32(stream: ByteInputStream): number { | |
let byte = stream.read(); | |
let value = byte & 0x7F; | |
if (byte < 0x80) { | |
return value; | |
} | |
byte = stream.read(); | |
value |= (byte & 0x7F) << 7; | |
if (byte < 0x80) { | |
return value; | |
} | |
byte = stream.read(); | |
value |= (byte & 0x7F) << 14; | |
if (byte < 0x80) { | |
return value; | |
} | |
byte = stream.read(); | |
value |= (byte & 0x7F) << 21; | |
if (byte < 0x80) { | |
return value; | |
} | |
byte = stream.read(); | |
value |= (byte & 0x7F) << 28; | |
if (byte < 0x80) { | |
return value >>> 0; | |
} | |
// If we get here, it's a negative integer | |
// Since we already read 5 bytes, we will skip over the remaining 5 | |
stream.skip(5); | |
return value; | |
} | |
export function readSint32(stream: ByteInputStream): number { | |
const uint32 = readUint32(stream); | |
return (uint32 >>> 1) ^ - (uint32 & 1); | |
} | |
export function readInt64(stream: ByteInputStream): string { | |
let temp; | |
let low = 0; | |
let high = 0; | |
// Read the first four bytes of the varint, stopping at the terminator if we | |
// see it. | |
for (let i = 0; i < 4; i++) { | |
temp = stream.read(); | |
low |= (temp & 0x7F) << (i * 7); | |
if (temp < 128) { | |
return new Int64(low >>> 0, 0).toString(); | |
} | |
} | |
// Read the fifth byte, which straddles the low and high dwords. | |
temp = stream.read(); | |
low |= (temp & 0x7F) << 28; | |
high |= (temp & 0x7F) >> 4; | |
if (temp < 128) { | |
return new Int64(low >>> 0, high >>> 0).toString(); | |
} | |
// Read the sixth through tenth byte. | |
for (let i = 0; i < 5; i++) { | |
temp = stream.read(); | |
high |= (temp & 0x7F) << (i * 7 + 3); | |
if (temp < 128) { | |
return new Int64(low >>> 0, high >>> 0).toString(); | |
} | |
} | |
return new Int64(low, high).toString(); | |
} | |
export function readFixed32(stream: ByteInputStream): number { | |
const a = stream.read(); | |
const b = stream.read(); | |
const c = stream.read(); | |
const d = stream.read(); | |
return ((a << 0) | (b << 8) | (c << 16) | (d << 24)) >>> 0; | |
} | |
export function readFloat(stream: ByteInputStream): number { | |
const bits = readFixed32(stream); | |
const sign = ((bits >> 31) * 2 + 1); | |
const exp = (bits >>> 23) & 0xFF; | |
const mantissa = bits & 0x7FFFFF; | |
if (exp === 0xFF) { | |
if (mantissa) { | |
return NaN; | |
} else { | |
return sign * Infinity; | |
} | |
} | |
if (exp === 0) { | |
// Denormal. | |
return sign * 1.401298464324817e-45 * mantissa; | |
} else { | |
return sign * Math.pow(2, exp - 150) * | |
(mantissa + 8388608); | |
} | |
} | |
export function readDouble(stream: ByteInputStream): number { | |
const low = readFixed32(stream); | |
const high = readFixed32(stream); | |
const sign = ((high >> 31) * 2 + 1); | |
const exp = (high >>> 20) & 0x7FF; | |
const mant = 4294967296 * (high & 0xFFFFF) + low; | |
if (exp === 0x7FF) { | |
if (mant) { | |
return NaN; | |
} else { | |
return sign * Infinity; | |
} | |
} | |
if (exp === 0) { | |
// Denormal. | |
return sign * 5e-324 * mant; | |
} else { | |
return sign * Math.pow(2, exp - 1075) * (mant + 4503599627370496); | |
} | |
} | |
export function readBoolean(stream: ByteInputStream): boolean { | |
return !!readUint32(stream); | |
} | |
export function readPackedBoolean(stream: ByteInputStream, array: boolean[]): boolean[] { | |
const length = readUint32(stream); | |
const next = stream.take(length); | |
while (next()) { | |
array.push(readBoolean(stream)); | |
} | |
return array; | |
} | |
export function readString(stream: ByteInputStream): string { | |
const length = readUint32(stream); | |
const codeUnits = new Array(length); | |
let charactersRead = 0; | |
let cursor = 0; | |
let result = ''; | |
while (cursor < length) { | |
const c = stream.read(); cursor++; | |
if (c < 128) { // Regular 7-bit ASCII. | |
codeUnits[charactersRead++] = c; | |
} else if (c < 192) { | |
// UTF-8 continuation mark. We are out of sync. This | |
// might happen if we attempted to read a character | |
// with more than four bytes. | |
continue; | |
} else if (c < 224) { // UTF-8 with two bytes. | |
const c2 = stream.read(); cursor++; | |
codeUnits[charactersRead++] = ((c & 31) << 6) | (c2 & 63); | |
} else if (c < 240) { // UTF-8 with three bytes. | |
const c2 = stream.read(); cursor++; | |
const c3 = stream.read(); cursor++; | |
codeUnits[charactersRead++] = ((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63); | |
} else if (c < 248) { // UTF-8 with 4 bytes. | |
const c2 = stream.read(); cursor++; | |
const c3 = stream.read(); cursor++; | |
const c4 = stream.read(); cursor++; | |
// Characters written on 4 bytes have 21 bits for a codepoint. | |
// We can't fit that on 16bit characters, so we use surrogates. | |
let codepoint = ((c & 7) << 18) | ((c2 & 63) << 12) | ((c3 & 63) << 6) | (c4 & 63); | |
// Surrogates formula from wikipedia. | |
// 1. Subtract 0x10000 from codepoint | |
codepoint -= 0x10000; | |
// 2. Split this into the high 10-bit value and the low 10-bit value | |
// 3. Add 0xD800 to the high value to form the high surrogate | |
// 4. Add 0xDC00 to the low value to form the low surrogate: | |
const low = (codepoint & 1023) + 0xDC00; | |
const high = ((codepoint >> 10) & 1023) + 0xD800; | |
codeUnits[charactersRead++] = high; | |
codeUnits[charactersRead++] = low; | |
} | |
} | |
// TODO chunk it if length exceeds 8192 | |
result += String.fromCharCode.apply(null, codeUnits); | |
return result; | |
} | |
export function writeUint32(value: number, stream: ByteOutputStream) { | |
while (value > 0x7F) { | |
stream.write((value & 0x7F) | 0x80); | |
value = value >>> 7; | |
} | |
stream.write(value); | |
} | |
export function writeSint32(value: number, stream: ByteOutputStream) { | |
writeUint32(((value << 1) ^ (value >> 31)) >>> 0, stream); | |
} | |
export function writeInt64(value: string, stream: ByteOutputStream) { | |
const int64 = Int64.fromString(value); | |
writeInt64FromBits(int64.low, int64.high, stream); | |
} | |
function writeInt64FromBits(low: number, high: number, stream: ByteOutputStream) { | |
// Break the binary representation into chunks of 7 bits, set the 8th bit | |
// in each chunk if it's not the final chunk, and append to the result. | |
while (high > 0 || low > 127) { | |
stream.write((low & 0x7f) | 0x80); | |
low = ((low >>> 7) | (high << 25)) >>> 0; | |
high = high >>> 7; | |
} | |
stream.write(low); | |
} | |
export function writeFloat(value: number, stream: ByteOutputStream) { | |
const sign = (value < 0) ? 1 : 0; | |
value = sign ? -value : value; | |
// Handle zeros. | |
if (value === 0) { | |
if ((1 / value) > 0) { | |
// Positive zero. | |
writeFixed32(0x00000000, stream); | |
} else { | |
// Negative zero. | |
writeFixed32(0x80000000, stream); | |
} | |
return; | |
} | |
// Handle nans. | |
if (isNaN(value)) { | |
writeFixed32(0x7FFFFFFF, stream); | |
return; | |
} | |
// Handle infinities. | |
if (value > 3.4028234663852886e+38) { | |
writeFixed32(((sign << 31) | (0x7F800000)) >>> 0, stream); | |
return; | |
} | |
// Handle denormals. | |
if (value < 1.1754943508222875e-38) { | |
const mantissa = Math.round(value / 1.401298464324817e-45); | |
writeFixed32(((sign << 31) | mantissa) >>> 0, stream); | |
} else { | |
const exp = Math.floor(Math.log(value) / Math.LN2); | |
const mantissa = Math.round(value * Math.pow(2, -exp) * 8388608); | |
writeFixed32(((sign << 31) | ((exp + 127) << 23) | mantissa & 0x7FFFFF) >>> 0, stream); | |
} | |
} | |
export function writeDouble(value: number, stream: ByteOutputStream) { | |
const sign = (value < 0) ? 1 : 0; | |
value = sign ? -value : value; | |
// Handle zeros. | |
if (value === 0) { | |
if ((1 / value) > 0) { | |
// Positive zero. | |
writeFixed32(0x00000000, stream); | |
writeFixed32(0x00000000, stream); | |
} else { | |
// Negative zero. | |
writeFixed32(0x00000000, stream); | |
writeFixed32(0x80000000, stream); | |
} | |
return; | |
} | |
// Handle nans. | |
if (isNaN(value)) { | |
writeFixed32(0x7FFFFFFF, stream); | |
writeFixed32(0xFFFFFFFF, stream); | |
return; | |
} | |
// Handle infinities. | |
if (value > 1.7976931348623157e+308) { | |
writeFixed32(0, stream); | |
writeFixed32(((sign << 31) | (0x7FF00000)) >>> 0, stream); | |
return; | |
} | |
// Handle denormals. | |
if (value < 2.2250738585072014e-308) { | |
// Number is a denormal. | |
const mant = value / 5e-324; | |
const mantHigh = (mant / 8388608); | |
writeFixed32(mant >>> 0, stream); | |
writeFixed32(((sign << 31) | mantHigh) >>> 0, stream); | |
} else { | |
const exp = Math.min(Math.floor(Math.log(value) / Math.LN2), 1023); | |
const mant = value * Math.pow(2, -exp); | |
const mantHigh = (mant * 1048576) & 0xFFFFF; | |
const mantLow = (mant * 4503599627370496) >>> 0; | |
writeFixed32(mantLow, stream); | |
writeFixed32(((sign << 31) | ((exp + 1023) << 20) | mantHigh) >>> 0, stream); | |
} | |
} | |
export function writeFixed32(value: number, stream: ByteOutputStream) { | |
stream.write((value >>> 0) & 0xFF); | |
stream.write((value >>> 8) & 0xFF); | |
stream.write((value >>> 16) & 0xFF); | |
stream.write((value >>> 24) & 0xFF); | |
} | |
export function writeBoolean(value: boolean, stream: ByteOutputStream) { | |
writeUint32(value ? 1 : 0, stream); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment