Skip to content

Instantly share code, notes, and snippets.

@jjbubudi
Last active February 17, 2019 05:05
Show Gist options
  • Save jjbubudi/107f3b2e5c51b320c6d096d4f564ad0d to your computer and use it in GitHub Desktop.
Save jjbubudi/107f3b2e5c51b320c6d096d4f564ad0d to your computer and use it in GitHub Desktop.
p2.ts
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