import { Exception, IComparable, IEquatable, IHashable, combineHashCodes, compare, compareArrays, equalArrays, equals, sameConstructor, numberHash, structuralHash } from "./Util.ts"; // This type is only used internally for .ts files in the library // F# Result type is in Choice.fs export type Result = { tag: "ok"; value: T } | { tag: "error"; error: string }; export function seqToString(self: Iterable): string { let count = 0; let str = "["; for (const x of self) { if (count === 0) { str += toString(x); } else if (count === 100) { str += "; ..."; break; } else { str += "; " + toString(x); } count++; } return str + "]"; } export function toString(x: any, callStack = 0): string { if (x != null && typeof x === "object") { if (typeof x.toString === "function" && x.toString !== Object.prototype.toString) { return x.toString(); } else if (Symbol.iterator in x) { return seqToString(x); } else { // TODO: Date? const cons = Object.getPrototypeOf(x)?.constructor; return cons === Object && callStack < 10 // Same format as recordToString ? "{ " + Object.entries(x).map(([k, v]) => k + " = " + toString(v, callStack + 1)).join("\n ") + " }" : cons?.name ?? ""; } } return String(x); } export function unionToString(name: string, fields: any[]) { function unionFieldToString(x: any): string { if (typeof x === "string") { return '"' + x + '"'; } return toString(x); } if (fields.length === 0) { return name; } else { let fieldStr; let withParens = true; if (fields.length === 1) { fieldStr = unionFieldToString(fields[0]); withParens = fieldStr.indexOf(" ") >= 0; } else { fieldStr = fields.map((x: any) => unionFieldToString(x)).join(", "); } return name + (withParens ? " (" : " ") + fieldStr + (withParens ? ")" : ""); } } export abstract class Union implements IEquatable>, IComparable> { abstract readonly tag: Tag; abstract readonly fields: any[]; abstract cases(): string[]; public get name(): Name { return this.cases()[this.tag] as Name; } public toJSON() { return this.fields.length === 0 ? this.name : [this.name].concat(this.fields); } public toString() { return unionToString(this.name, this.fields); } public GetHashCode() { const hashes = this.fields.map((x: any) => structuralHash(x)); hashes.splice(0, 0, numberHash(this.tag)); return combineHashCodes(hashes); } public Equals(other: Union) { if (this === other) { return true; } else if (!sameConstructor(this, other)) { return false; } else if (this.tag === other.tag) { return equalArrays(this.fields, other.fields); } else { return false; } } public CompareTo(other: Union) { if (this === other) { return 0; } else if (!sameConstructor(this, other)) { return -1; } else if (this.tag === other.tag) { return compareArrays(this.fields, other.fields); } else { return this.tag < other.tag ? -1 : 1; } } } function recordToJSON(self: T) { const o: any = {}; const keys = Object.keys(self as any); for (let i = 0; i < keys.length; i++) { o[keys[i]] = (self as any)[keys[i]]; } return o; } function recordToString(self: T) { return "{ " + Object.entries(self as any).map(([k, v]) => k + " = " + toString(v)).join("\n ") + " }"; } function recordGetHashCode(self: T) { const hashes = Object.values(self as any).map((v) => structuralHash(v)); return combineHashCodes(hashes); } function recordEquals(self: T, other: T) { if (self === other) { return true; } else if (!sameConstructor(self, other)) { return false; } else { const thisNames = Object.keys(self as any); for (let i = 0; i < thisNames.length; i++) { if (!equals((self as any)[thisNames[i]], (other as any)[thisNames[i]])) { return false; } } return true; } } function recordCompareTo(self: T, other: T) { if (self === other) { return 0; } else if (!sameConstructor(self, other)) { return -1; } else { const thisNames = Object.keys(self as any); for (let i = 0; i < thisNames.length; i++) { const result = compare((self as any)[thisNames[i]], (other as any)[thisNames[i]]); if (result !== 0) { return result; } } return 0; } } export abstract class Record implements IEquatable, IComparable, IHashable { toJSON() { return recordToJSON(this); } toString() { return recordToString(this); } GetHashCode() { return recordGetHashCode(this); } Equals(other: Record) { return recordEquals(this, other); } CompareTo(other: Record) { return recordCompareTo(this, other); } } export class FSharpRef { private readonly getter: () => T; private readonly setter: (v: T) => void; get contents() { return this.getter(); } set contents(v) { this.setter(v) } constructor(contentsOrGetter: T | (() => T), setter?: (v: T) => void) { if (typeof setter === "function") { this.getter = contentsOrGetter as () => T; this.setter = setter } else { this.getter = () => contentsOrGetter as T; this.setter = (v) => { contentsOrGetter = v }; } } } // EXCEPTIONS export abstract class FSharpException extends Exception implements IEquatable, IComparable { toJSON() { return recordToJSON(this); } toString() { return recordToString(this); } GetHashCode() { return recordGetHashCode(this); } Equals(other: FSharpException) { return recordEquals(this, other); } CompareTo(other: FSharpException) { return recordCompareTo(this, other); } } export class MatchFailureException extends FSharpException { public arg1: string; public arg2: number; public arg3: number; constructor(arg1: string, arg2: number, arg3: number) { super(); this.arg1 = arg1; this.arg2 = arg2 | 0; this.arg3 = arg3 | 0; this.message = "The match cases were incomplete"; } } export class Attribute { }