pluotsorbet/shumway/module.ts

878 строки
24 KiB
TypeScript

var jsGlobal = (function() { return this || (1, eval)('this'); })();
var inBrowser = typeof console != "undefined";
declare var putstr;
declare var printErr;
declare var dateNow: () => number;
interface String {
padRight(c: string, n: number): string;
padLeft(c: string, n: number): string;
endsWith(s: string): boolean;
}
interface Math {
imul(a: number, b: number): number;
/**
* Returns the number of leading zeros of a number.
* @param x A numeric expression.
*/
clz32(x: number): number;
}
interface Error {
stack: string;
}
if (!jsGlobal.performance) {
jsGlobal.performance = {};
}
if (!jsGlobal.performance.now) {
jsGlobal.performance.now = typeof dateNow !== 'undefined' ? dateNow : Date.now;
}
function log(message?: any, ...optionalParams: any[]): void {
if (inBrowser) {
console.log.apply(console, arguments);
} else {
jsGlobal.print.apply(jsGlobal, arguments);
}
}
function warn(message?: any, ...optionalParams: any[]): void {
if (inBrowser) {
console.warn.apply(console, arguments);
} else {
jsGlobal.print(Shumway.IndentingWriter.RED + message + Shumway.IndentingWriter.ENDC);
}
}
module Shumway {
var release: boolean = true;
export function isNullOrUndefined(value) {
return value == undefined;
}
export function isObject(value): boolean {
return typeof value === "object" || typeof value === 'function';
}
export interface Map<T> {
[name: string]: T
}
export module Debug {
export function backtrace() {
try {
throw new Error();
} catch (e) {
return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
}
}
export function error(message: string) {
//if (!inBrowser) {
// warn(message + "\n\nStack Trace:\n" + Debug.backtrace());
//} else {
// warn(message);
//}
throw new Error(message);
}
export function assert(condition: any, message: any = "assertion failed") {
if (condition === "") { // avoid inadvertent false positive
condition = true;
}
if (!condition) {
Debug.error(message.toString());
}
}
export function assertUnreachable(msg: string): void {
var location = new Error().stack.split('\n')[1];
throw new Error("Reached unreachable location " + location + msg);
}
export function assertNotImplemented(condition: boolean, message: string) {
if (!condition) {
Debug.error("notImplemented: " + message);
}
}
export function warning(message: string) {
release || warn(message);
}
export function notUsed(message: string) {
release || Debug.assert(false, "Not Used " + message);
}
export function notImplemented(message: string) {
log("release: " + release);
release || Debug.assert(false, "Not Implemented " + message);
}
export function abstractMethod(message: string) {
Debug.assert(false, "Abstract Method " + message);
}
var somewhatImplementedCache = {};
export function somewhatImplemented(message: string) {
if (somewhatImplementedCache[message]) {
return;
}
somewhatImplementedCache[message] = true;
Debug.warning("somewhatImplemented: " + message);
}
export function unexpected(message?: any) {
Debug.assert(false, "Unexpected: " + message);
}
export function untested(message?: any) {
Debug.warning("Congratulations, you've found a code path for which we haven't found a test case. Please submit the test case: " + message);
}
}
export enum LogLevel {
Error = 0x1,
Warn = 0x2,
Debug = 0x4,
Log = 0x8,
Info = 0x10,
All = 0x1f
}
export class IndentingWriter {
public static PURPLE = '\033[94m';
public static YELLOW = '\033[93m';
public static GREEN = '\033[92m';
public static RED = '\033[91m';
public static BOLD_RED = '\033[1;91m';
public static ENDC = '\033[0m';
public static logLevel: LogLevel = LogLevel.All;
public static stdout = inBrowser ? console.info.bind(console) : print;
public static stdoutNoNewline = inBrowser ? console.info.bind(console) : putstr;
public static stderr = inBrowser ? console.error.bind(console) : printErr;
private _tab: string;
private _padding: string;
private _suppressOutput: boolean;
private _out: (s: string) => void;
private _outNoNewline: (s: string) => void;
constructor(suppressOutput: boolean = false, out?) {
this._tab = " ";
this._padding = "";
this._suppressOutput = suppressOutput;
this._out = out || IndentingWriter.stdout;
this._outNoNewline = out || IndentingWriter.stdoutNoNewline;
}
write(str: string = "", writePadding = false) {
if (!this._suppressOutput) {
this._outNoNewline((writePadding ? this._padding : "") + str);
}
}
writeLn(str: string = "") {
if (!this._suppressOutput) {
this._out(this._padding + str);
}
}
writeTimeLn(str: string = "") {
if (!this._suppressOutput) {
this._out(this._padding + performance.now().toFixed(2) + " " + str);
}
}
writeComment(str: string) {
var lines = str.split("\n");
if (lines.length === 1) {
this.writeLn("// " + lines[0]);
} else {
this.writeLn("/**");
for (var i = 0; i < lines.length; i++) {
this.writeLn(" * " + lines[i]);
}
this.writeLn(" */");
}
}
writeLns(str: string) {
var lines = str.split("\n");
for (var i = 0; i < lines.length; i++) {
this.writeLn(lines[i]);
}
}
errorLn(str: string) {
if (IndentingWriter.logLevel & LogLevel.Error) {
this.boldRedLn(str);
}
}
warnLn(str: string) {
if (IndentingWriter.logLevel & LogLevel.Warn) {
this.yellowLn(str);
}
}
debugLn(str: string) {
if (IndentingWriter.logLevel & LogLevel.Debug) {
this.purpleLn(str);
}
}
logLn(str: string) {
if (IndentingWriter.logLevel & LogLevel.Log) {
this.writeLn(str);
}
}
infoLn(str: string) {
if (IndentingWriter.logLevel & LogLevel.Info) {
this.writeLn(str);
}
}
yellowLn(str: string) {
this.colorLn(IndentingWriter.YELLOW, str);
}
greenLn(str: string) {
this.colorLn(IndentingWriter.GREEN, str);
}
boldRedLn(str: string) {
this.colorLn(IndentingWriter.BOLD_RED, str);
}
redLn(str: string) {
this.colorLn(IndentingWriter.RED, str);
}
purpleLn(str: string) {
this.colorLn(IndentingWriter.PURPLE, str);
}
colorLn(color: string, str: string) {
if (!this._suppressOutput) {
if (!inBrowser) {
this._out(this._padding + color + str + IndentingWriter.ENDC);
} else {
this._out(this._padding + str);
}
}
}
redLns(str: string) {
this.colorLns(IndentingWriter.RED, str);
}
colorLns(color: string, str: string) {
var lines = str.split("\n");
for (var i = 0; i < lines.length; i++) {
this.colorLn(color, lines[i]);
}
}
enter(str: string) {
if (!this._suppressOutput) {
this._out(this._padding + str);
}
this.indent();
}
leaveAndEnter(str: string) {
this.leave(str);
this.indent();
}
leave(str: string) {
this.outdent();
if (!this._suppressOutput) {
this._out(this._padding + str);
}
}
indent() {
this._padding += this._tab;
}
outdent() {
if (this._padding.length > 0) {
this._padding = this._padding.substring(0, this._padding.length - this._tab.length);
}
}
writeArray(arr: any[], detailed: boolean = false, noNumbers: boolean = false) {
detailed = detailed || false;
for (var i = 0, j = arr.length; i < j; i++) {
var prefix = "";
if (detailed) {
if (arr[i] === null) {
prefix = "null";
} else if (arr[i] === undefined) {
prefix = "undefined";
} else {
prefix = arr[i].constructor.name;
}
prefix += " ";
}
var number = noNumbers ? "" : ("" + i).padRight(' ', 4);
this.writeLn(number + prefix + arr[i]);
}
}
}
export module NumberUtilities {
export function pow2(exponent: number): number {
if (exponent === (exponent | 0)) {
if (exponent < 0) {
return 1 / (1 << -exponent);
}
return 1 << exponent;
}
return Math.pow(2, exponent);
}
export function clamp(value: number, min: number, max: number) {
return Math.max(min, Math.min(max, value));
}
/**
* Rounds *.5 to the nearest even number.
* See https://en.wikipedia.org/wiki/Rounding#Round_half_to_even for details.
*/
export function roundHalfEven(value: number): number {
if (Math.abs(value % 1) === 0.5) {
var floor = Math.floor(value);
return floor % 2 === 0 ? floor : Math.ceil(value);
}
return Math.round(value);
}
export function epsilonEquals(value: number, other: number): boolean {
return Math.abs(value - other) < 0.0000001;
}
}
export class ColorStyle {
static TabToolbar = "#252c33";
static Toolbars = "#343c45";
static HighlightBlue = "#1d4f73";
static LightText = "#f5f7fa";
static ForegroundText = "#b6babf";
static Black = "#000000";
static VeryDark = "#14171a";
static Dark = "#181d20";
static Light = "#a9bacb";
static Grey = "#8fa1b2";
static DarkGrey = "#5f7387";
static Blue = "#46afe3";
static Purple = "#6b7abb";
static Pink = "#df80ff";
static Red = "#eb5368";
static Orange = "#d96629";
static LightOrange = "#d99b28";
static Green = "#70bf53";
static BlueGrey = "#5e88b0";
private static _randomStyleCache;
private static _nextStyle = 0;
static randomStyle() {
if (!ColorStyle._randomStyleCache) {
ColorStyle._randomStyleCache = [
"#ff5e3a",
"#ff9500",
"#ffdb4c",
"#87fc70",
"#52edc7",
"#1ad6fd",
"#c644fc",
"#ef4db6",
"#4a4a4a",
"#dbddde",
"#ff3b30",
"#ff9500",
"#ffcc00",
"#4cd964",
"#34aadc",
"#007aff",
"#5856d6",
"#ff2d55",
"#8e8e93",
"#c7c7cc",
"#5ad427",
"#c86edf",
"#d1eefc",
"#e0f8d8",
"#fb2b69",
"#f7f7f7",
"#1d77ef",
"#d6cec3",
"#55efcb",
"#ff4981",
"#ffd3e0",
"#f7f7f7",
"#ff1300",
"#1f1f21",
"#bdbec2",
"#ff3a2d"
];
}
return ColorStyle._randomStyleCache[(ColorStyle._nextStyle ++) % ColorStyle._randomStyleCache.length];
}
static contrastStyle(rgb: string): string {
// http://www.w3.org/TR/AERT#color-contrast
var c = parseInt(rgb.substr(1), 16);
var yiq = (((c >> 16) * 299) + (((c >> 8) & 0xff) * 587) + ((c & 0xff) * 114)) / 1000;
return (yiq >= 128) ? '#000000' : '#ffffff';
}
static reset() {
ColorStyle._nextStyle = 0;
}
}
export module StringUtilities {
import assert = Debug.assert;
export function repeatString(c: string, n: number): string {
var s = "";
for (var i = 0; i < n; i++) {
s += c;
}
return s;
}
export function memorySizeToString(value: number) {
value |= 0;
var K = 1024;
var M = K * K;
if (value < K) {
return value + " B";
} else if (value < M) {
return (value / K).toFixed(2) + "KB";
} else {
return (value / M).toFixed(2) + "MB";
}
}
/**
* Returns a reasonably sized description of the |value|, to be used for debugging purposes.
*/
export function toSafeString(value) {
if (typeof value === "string") {
return "\"" + value + "\"";
}
if (typeof value === "number" || typeof value === "boolean") {
return String(value);
}
if (value instanceof Array) {
return "[] " + value.length;
}
return typeof value;
}
export function toSafeArrayString(array) {
var str = [];
for (var i = 0; i < array.length; i++) {
str.push(toSafeString(array[i]));
}
return str.join(", ");
}
export function utf8decode(str: string): Uint8Array {
var bytes = new Uint8Array(str.length * 4);
var b = 0;
for (var i = 0, j = str.length; i < j; i++) {
var code = str.charCodeAt(i);
if (code <= 0x7f) {
bytes[b++] = code;
continue;
}
if (0xD800 <= code && code <= 0xDBFF) {
var codeLow = str.charCodeAt(i + 1);
if (0xDC00 <= codeLow && codeLow <= 0xDFFF) {
// convert only when both high and low surrogates are present
code = ((code & 0x3FF) << 10) + (codeLow & 0x3FF) + 0x10000;
++i;
}
}
if ((code & 0xFFE00000) !== 0) {
bytes[b++] = 0xF8 | ((code >>> 24) & 0x03);
bytes[b++] = 0x80 | ((code >>> 18) & 0x3F);
bytes[b++] = 0x80 | ((code >>> 12) & 0x3F);
bytes[b++] = 0x80 | ((code >>> 6) & 0x3F);
bytes[b++] = 0x80 | (code & 0x3F);
} else if ((code & 0xFFFF0000) !== 0) {
bytes[b++] = 0xF0 | ((code >>> 18) & 0x07);
bytes[b++] = 0x80 | ((code >>> 12) & 0x3F);
bytes[b++] = 0x80 | ((code >>> 6) & 0x3F);
bytes[b++] = 0x80 | (code & 0x3F);
} else if ((code & 0xFFFFF800) !== 0) {
bytes[b++] = 0xE0 | ((code >>> 12) & 0x0F);
bytes[b++] = 0x80 | ((code >>> 6) & 0x3F);
bytes[b++] = 0x80 | (code & 0x3F);
} else {
bytes[b++] = 0xC0 | ((code >>> 6) & 0x1F);
bytes[b++] = 0x80 | (code & 0x3F);
}
}
return bytes.subarray(0, b);
}
export function utf8encode(bytes: Uint8Array): string {
var j = 0, str = "";
while (j < bytes.length) {
var b1 = bytes[j++] & 0xFF;
if (b1 <= 0x7F) {
str += String.fromCharCode(b1);
} else {
var currentPrefix = 0xC0;
var validBits = 5;
do {
var mask = (currentPrefix >> 1) | 0x80;
if((b1 & mask) === currentPrefix) break;
currentPrefix = (currentPrefix >> 1) | 0x80;
--validBits;
} while (validBits >= 0);
if (validBits <= 0) {
// Invalid UTF8 character -- copying as is
str += String.fromCharCode(b1);
continue;
}
var code = (b1 & ((1 << validBits) - 1));
var invalid = false;
for (var i = 5; i >= validBits; --i) {
var bi = bytes[j++];
if ((bi & 0xC0) != 0x80) {
// Invalid UTF8 character sequence
invalid = true;
break;
}
code = (code << 6) | (bi & 0x3F);
}
if (invalid) {
// Copying invalid sequence as is
for (var k = j - (7 - i); k < j; ++k) {
str += String.fromCharCode(bytes[k] & 255);
}
continue;
}
if (code >= 0x10000) {
str += String.fromCharCode((((code - 0x10000) >> 10) & 0x3FF) |
0xD800, (code & 0x3FF) | 0xDC00);
} else {
str += String.fromCharCode(code);
}
}
}
return str;
}
// https://gist.github.com/958841
export function base64ArrayBuffer(arrayBuffer: ArrayBuffer) {
var base64 = '';
var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var bytes = new Uint8Array(arrayBuffer);
var byteLength = bytes.byteLength;
var byteRemainder = byteLength % 3;
var mainLength = byteLength - byteRemainder;
var a, b, c, d;
var chunk;
// Main loop deals with bytes in chunks of 3
for (var i = 0; i < mainLength; i = i + 3) {
// Combine the three bytes into a single integer
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
// Use bitmasks to extract 6-bit segments from the triplet
a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
d = chunk & 63; // 63 = 2^6 - 1
// Convert the raw binary segments to the appropriate ASCII encoding
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
}
// Deal with the remaining bytes and padding
if (byteRemainder == 1) {
chunk = bytes[mainLength];
a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
// Set the 4 least significant bits to zero
b = (chunk & 3) << 4; // 3 = 2^2 - 1
base64 += encodings[a] + encodings[b] + '==';
} else if (byteRemainder == 2) {
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
// Set the 2 least significant bits to zero
c = (chunk & 15) << 2; // 15 = 2^4 - 1
base64 += encodings[a] + encodings[b] + encodings[c] + '=';
}
return base64;
}
export function escapeString(str: string) {
if (str !== undefined) {
str = str.replace(/[^\w$]/gi,"$"); /* No dots, colons, dashes and /s */
if (/^\d/.test(str)) { /* No digits at the beginning */
str = '$' + str;
}
}
return str;
}
/**
* Workaround for max stack size limit.
*/
export function fromCharCodeArray(buffer: Uint8Array): string {
var str = "", SLICE = 1024 * 16;
for (var i = 0; i < buffer.length; i += SLICE) {
var chunk = Math.min(buffer.length - i, SLICE);
str += String.fromCharCode.apply(null, buffer.subarray(i, i + chunk));
}
return str;
}
var _encoding = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$_';
export function variableLengthEncodeInt32(n) {
var e = _encoding;
var bitCount = (32 - Math.clz32(n));
release || assert (bitCount <= 32, bitCount);
var l = Math.ceil(bitCount / 6);
// Encode length followed by six bit chunks.
var s = e[l];
for (var i = l - 1; i >= 0; i--) {
var offset = (i * 6);
s += e[(n >> offset) & 0x3F];
}
release || assert (StringUtilities.variableLengthDecodeInt32(s) === n, n + " : " + s + " - " + l + " bits: " + bitCount);
return s;
}
export function toEncoding(n) {
return _encoding[n];
}
export function fromEncoding(s) {
var c = s.charCodeAt(0);
var e = 0;
if (c >= 65 && c <= 90) {
return c - 65;
} else if (c >= 97 && c <= 122) {
return c - 71;
} else if (c >= 48 && c <= 57) {
return c + 4;
} else if (c === 36) {
return 62;
} else if (c === 95) {
return 63;
}
release || assert (false, "Invalid Encoding");
}
export function variableLengthDecodeInt32(s) {
var l = StringUtilities.fromEncoding(s[0]);
var n = 0;
for (var i = 0; i < l; i++) {
var offset = ((l - i - 1) * 6);
n |= StringUtilities.fromEncoding(s[1 + i]) << offset;
}
return n;
}
export function trimMiddle(s: string, maxLength: number): string {
if (s.length <= maxLength) {
return s;
}
var leftHalf = maxLength >> 1;
var rightHalf = maxLength - leftHalf - 1;
return s.substr(0, leftHalf) + "\u2026" + s.substr(s.length - rightHalf, rightHalf);
}
export function multiple(s: string, count: number): string {
var o = "";
for (var i = 0; i < count; i++) {
o += s;
}
return o;
}
export function indexOfAny(s: string, chars: string [], position: number) {
var index = s.length;
for (var i = 0; i < chars.length; i++) {
var j = s.indexOf(chars[i], position);
if (j >= 0) {
index = Math.min(index, j);
}
}
return index === s.length ? -1 : index;
}
var _concat3array = new Array(3);
var _concat4array = new Array(4);
var _concat5array = new Array(5);
var _concat6array = new Array(6);
var _concat7array = new Array(7);
var _concat8array = new Array(8);
var _concat9array = new Array(9);
/**
* The concatN() functions concatenate multiple strings in a way that
* avoids creating intermediate strings, unlike String.prototype.concat().
*
* Note that these functions don't have identical behaviour to using '+',
* because they will ignore any arguments that are |undefined| or |null|.
* This usually doesn't matter.
*/
export function concat3(s0: any, s1: any, s2: any) {
_concat3array[0] = s0;
_concat3array[1] = s1;
_concat3array[2] = s2;
return _concat3array.join('');
}
export function concat4(s0: any, s1: any, s2: any, s3: any) {
_concat4array[0] = s0;
_concat4array[1] = s1;
_concat4array[2] = s2;
_concat4array[3] = s3;
return _concat4array.join('');
}
export function concat5(s0: any, s1: any, s2: any, s3: any, s4: any) {
_concat5array[0] = s0;
_concat5array[1] = s1;
_concat5array[2] = s2;
_concat5array[3] = s3;
_concat5array[4] = s4;
return _concat5array.join('');
}
export function concat6(s0: any, s1: any, s2: any, s3: any, s4: any,
s5: any) {
_concat6array[0] = s0;
_concat6array[1] = s1;
_concat6array[2] = s2;
_concat6array[3] = s3;
_concat6array[4] = s4;
_concat6array[5] = s5;
return _concat6array.join('');
}
export function concat7(s0: any, s1: any, s2: any, s3: any, s4: any,
s5: any, s6: any) {
_concat7array[0] = s0;
_concat7array[1] = s1;
_concat7array[2] = s2;
_concat7array[3] = s3;
_concat7array[4] = s4;
_concat7array[5] = s5;
_concat7array[6] = s6;
return _concat7array.join('');
}
export function concat8(s0: any, s1: any, s2: any, s3: any, s4: any,
s5: any, s6: any, s7: any) {
_concat8array[0] = s0;
_concat8array[1] = s1;
_concat8array[2] = s2;
_concat8array[3] = s3;
_concat8array[4] = s4;
_concat8array[5] = s5;
_concat8array[6] = s6;
_concat8array[7] = s7;
return _concat8array.join('');
}
export function concat9(s0: any, s1: any, s2: any, s3: any, s4: any,
s5: any, s6: any, s7: any, s8: any) {
_concat9array[0] = s0;
_concat9array[1] = s1;
_concat9array[2] = s2;
_concat9array[3] = s3;
_concat9array[4] = s4;
_concat9array[5] = s5;
_concat9array[6] = s6;
_concat9array[7] = s7;
_concat9array[8] = s8;
return _concat9array.join('');
}
}
export class CircularBuffer {
index: number;
start: number;
array: ArrayBufferView;
_size: number;
_mask: number;
constructor(Type, sizeInBits: number = 12) {
this.index = 0;
this.start = 0;
this._size = 1 << sizeInBits;
this._mask = this._size - 1;
this.array = new Type(this._size);
}
public get (i) {
return this.array[i];
}
public forEachInReverse(visitor) {
if (this.isEmpty()) {
return;
}
var i = this.index === 0 ? this._size - 1 : this.index - 1;
var end = (this.start - 1) & this._mask;
while (i !== end) {
if (visitor(this.array[i], i)) {
break;
}
i = i === 0 ? this._size - 1 : i - 1;
}
}
public write(value) {
this.array[this.index] = value;
this.index = (this.index + 1) & this._mask;
if (this.index === this.start) {
this.start = (this.start + 1) & this._mask;
}
}
public isFull(): boolean {
return ((this.index + 1) & this._mask) === this.start;
}
public isEmpty(): boolean {
return this.index === this.start;
}
public reset() {
this.index = 0;
this.start = 0;
}
}
}