//Gods-awful type-checking logic. //TypeName: string, Params: Array function Type(TypeName,Params=[]) { if (!Type.all) Type.all = {}; this.typename = ""; this.mode = ""; if (TypeName instanceof Type) { const t = TypeName.copy(); for (const k in t) { this[k] = t[k]; } return; } //Union/Tuple if (/^\(.+\)$/.test(TypeName)) { const types = []; var depth = 0; var workspace = ""; for (const c of /^\((.+)\)$/.exec(TypeName)[1]) { switch (c) { case '(': case '<': depth++; break; case ')': case '>': depth--; break; case ',': if (depth == 0) { this.mode = "tuple"; types.push(workspace); workspace = ""; continue; } break; case '|': if (depth == 0) { this.mode = "union"; types.push(workspace); workspace = ""; continue; } break; } workspace += c; } if (workspace) types.push(workspace); this.template = types.map(t => t.trim()).map(t => Params.includes(t) ? Type.any : new Type(t, Params)); } //Template else if (/^[^<]+<.+>$/.test(TypeName)) { this.typename = /^([^<]+)$/.exec(TypeName)[1]) { switch (c) { case '(': case '<': depth++; break; case ')': case '>': depth--; break; case ',': if (depth == 0) { types.push(workspace); workspace = ""; continue; } break; } workspace += c; } if (workspace) types.push(workspace); this.template = types.map(t => t.trim()).map(t => Params.includes(t) ? Type.any : new Type(t, Params)); Type.all[this.typename] = () => new Type(TypeName, Array.from(Params)); } //Standard Type, or Template Param else if (/^[^()|<>,]+$/.test(TypeName)) { if (!Params.includes(TypeName)) { this.typename = TypeName; this.mode = (TypeName == 'any') ? "any" : "standard"; if (this.mode == "standard") Type.all[this.typename] = () => new Type(TypeName); } else { this.mode = "param"; this.typename = TypeName; this.template = Type.any; } } } Type.prototype.toString = function() { switch (this.mode) { case "tuple": return `(${this.template.join(',')})`; case "union": return `(${this.template.join('|')})`; case "templated": return `${this.typename}<${this.template.join(',')}>`; case "any": case "standard": return this.typename; case "param": return this.template.toString(); } } Type.prototype._resolve = function(key, val) { if (this.mode != "param") { if (val === undefined && this.template?.length === 1) { val = key; key = 0; } if (this.template?.length) this.template = this.template.map(t => t.resolve(key, val)); } else { if (this.typename == key) this.template = new Type(val) } } Type.prototype.resolve = function(key, val) { const t = this.copy(); t._resolve(key, val); return t; } Type.prototype.copy = function() { if (this.mode != "param") { const t = new Type(this.toString()); for (const k in this.template) t.template[k] = this.template[k].copy(); return t; } else { const t = new Type(this.typename, [this.typename]); t._resolve(this.typename, this.toString()) return t; } } Type.prototype.getClasses = function() { if (this.mode == "param") return this.template.getClasses(); const ret = new Set(); switch (this.mode) { case 'standard': switch (this.typename) { case 'bool': case 'int': case 'float': case 'string': ret.add(this.typename); break; default: ret.add('special'); break; } break; case 'templated': if (this.template.length == 1) { if (this.typename == "List") ret.add("list"); this.template[0].getClasses().forEach(ret.add.bind(ret)); } else { ret.add('any'); } break; case 'union': case 'tuple': case 'any': ret.add('any'); break; } return Array.from(ret); } //just makes my life that much easier when writing code. Type.any is nicer than new Type('any') Type.typeDef = (function(typename, name, params=[]) { Object.defineProperty(this, typename, { get: () => new Type(name, params) }) }).bind(Type) Type.QuickTD = (function(name, params=[]) { Object.defineProperty(this, (new Type(name, params)).typename, { get: () => new Type(name, params) }) }).bind(Type) Type.QuickTD("any"); Type.QuickTD("int"); Type.QuickTD("float"); Type.QuickTD("string"); Type.QuickTD("bool"); Type.QuickTD("Vector3"); Type.typeDef("list", "List", ["T"]); function intersect(type1, type2) { const T1 = new Type(type1.toString()); const T2 = new Type(type2.toString()); if(T1 == T2) return T1; if (T1.mode == "any") return T2; if (T2.mode == "any") return T1; if (T1.mode == T2.mode) { switch (T1.mode) { case "standard": return (String(T1) == String(T2)) ? T1 : null; case "templated": case "tuple": if ( T1.typename != T2.typename || T1.template.length != T2.template.length ) return null; var t = T1.copy(); for (const k in T1.template) t.template[k] = intersect(T1.template[k], T2.template[k]); if (t.template.includes(null)) return null; else return t; case "union": const rettempl = Array.from( new Set( T1.template.map( t => T2.template .map(t2 => intersect(t,t2)) .filter(t => t ?? false)) .flat().map(String) )).map(n => new Type(n)); if (rettempl.length > 1) { const ret = T1.copy(); ret.template = rettempl; return ret; } else if (rettempl.length) return rettempl[0]; return null } } else if ([T1.mode,T2.mode].includes("union")) { const UT = (T1.mode == "union") ? T1 : T2; const ST = (T1.mode == "union") ? T2 : T1; const isect = Array.from( new Set( UT.template .map(t => intersect(ST, t)) .filter(t => t ?? false) .map(String) )).map(n => new Type(n)); if (isect.length > 1) { const ret = UT.copy(); ret.template = isect; return ret; } else if (isect.length) return isect[0]; } return null; } function intersectParams(type1, type2) { const sect = intersect(type1, type2); if (!(sect ?? false)) return null; function walkDown(t, tres) { const ret = {}; if (t.mode == "param") { ret[t.typename] = tres; } else if ( t.mode == tres.mode && t.typename == tres.typename && t.template?.length == tres.template?.length ) { for (const p in t.template) ret = {...ret, ...walkDown(t.template[p], tres.template[p])}; } return ret; } const resolution1 = walkDown(type1, sect); const resolution2 = walkDown(type2, sect); return [resolution1, resolution2]; } function Chip(Entry) { this.types = []; if (Entry.ChipNameSource != "FirstNodeDesc") throw new TypeError("Chip is not FirstNodeDesc mode!"); const cur = Entry.NodeDescs[0]; const params = cur.ReadonlyTypeParams; const passed = Object.keys(params) this.root = newEl('div', 'chip'); const header = newEl('div', 'chipheader'); header.innerText = cur.Name; const input = newEl('div', 'input'); const output = newEl('div', 'output'); this.root.append(header, input, output); const genPort = (port) => { let classes,name; if (port.ReadonlyType != "exec") { const type = Object.entries(params).reduce((t,[k,v])=>t.resolve(k,v),new Type(port.ReadonlyType, passed)); Object.entries(params).forEach(t => type.resolve(t[0], t[1])); classes = type.getClasses(); name = type.toString(); this.types.push(type); } else { classes = ["exec"]; name = "exec"; } const ret = newEl('div', ''); ret.innerHTML = port.Name + " "; classes.forEach(p => ret.classList.add(p)) const tooltip = newEl('div', 'type'); tooltip.innerText = name; return [ret, tooltip]; } input.append(...cur.Inputs.map(genPort).flat()); output.append(...cur.Outputs.map(genPort).flat()); }