From 0490ebfefc2fb9383f8ff3a3302af2842e928377 Mon Sep 17 00:00:00 2001 From: alyx Date: Wed, 8 Jun 2022 22:02:16 +0000 Subject: moved types.js to chips2.js, added a basic copy of the old logic, written with the new type parser --- lib/chips2.js | 406 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/types.js | 324 ---------------------------------------------- 2 files changed, 406 insertions(+), 324 deletions(-) create mode 100644 lib/chips2.js delete mode 100644 lib/types.js (limited to 'lib') diff --git a/lib/chips2.js b/lib/chips2.js new file mode 100644 index 0000000..2c4c786 --- /dev/null +++ b/lib/chips2.js @@ -0,0 +1,406 @@ +//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 => 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 => 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(info=false) { + 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 info ? `${this.typename}: ${this.template}` : 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()); +} + + +//chips.js reimplemented with the above + +let root = document.documentElement; +if (window.location.pathname != '/grapher/') { + root.addEventListener("mousemove", e => { + root.style.setProperty('--mouse-x', e.clientX + "px"); + root.style.setProperty('--mouse-y', e.clientY + "px"); + }); +} + +const portColors = { + float: '#186adc', + int: '#0f6522', + exec: '#f55b18', + string: '#794284', + bool: '#ea2e50', + any: '#f6eee8', + special: '#f4c61e' +} +const typeRegex = /(?:^|(?<=<))(?:int|float|bool|string|exec)(?:$|(?=>))/; +const unionRegex = /^T\d*$|(?<=List<)T\d*(?=>)/; + +function computeType(tn, TypeParams, to) { + if (tn != "exec") { + const t = Object.entries({...TypeParams, ...to}).reduce((t,i) => t.resolve(...i), new Type(tn, Object.keys(TypeParams))); + return {typeclass: t.getClasses(), type: t.toString()}; + } else { + return {typeclass: ['exec'], type: 'exec'}; + } +} + +//keeping this the same for now +function generateChipHTML(NodeDescs, typeoverride = undefined) { + for (let cur of NodeDescs) { + let ins = cur.Inputs; + let outs = cur.Outputs; + + const root = newEl('div', 'chip'); + const header = newEl('div', 'chipheader'); + header.innerText = cur.Name; + const input = newEl('div', 'input'); + const output = newEl('div', 'output'); + root.append(header, input, output); + + for (const inp of ins) { + //work out the type + let {typeclass, type} = computeType(inp.ReadonlyType, cur.ReadonlyTypeParams, typeoverride); + + const port = newEl('div', ''); + port.innerHTML = inp.Name + " "; + typeclass.map(port.classList.add.bind(port.classList)); + + const tooltip = newEl('div', 'type'); + tooltip.innerText = type; + + input.append(port, tooltip); + } + + for (const out of outs) { + //work out the type + let {typeclass, type} = computeType(out.ReadonlyType, cur.ReadonlyTypeParams, typeoverride); + + const port = newEl('div', ''); + port.innerHTML = out.Name + " "; + typeclass.map(port.classList.add.bind(port.classList)); + + const tooltip = newEl('div', 'type'); + tooltip.innerText = type; + + output.append(port, tooltip); + } + + return root; + } + +} + +function ListAllTypes(Nodes) { + return Object.keys(Type.all); +} \ No newline at end of file diff --git a/lib/types.js b/lib/types.js deleted file mode 100644 index 3fbb1c9..0000000 --- a/lib/types.js +++ /dev/null @@ -1,324 +0,0 @@ -//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()); -} \ No newline at end of file -- cgit v1.2.3-70-g09d2