aboutsummaryrefslogtreecommitdiffstats
path: root/lib/chips2.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chips2.js')
-rw-r--r--lib/chips2.js406
1 files changed, 406 insertions, 0 deletions
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<string>
+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];
+ this.mode = "templated";
+ 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) {
+ 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>", ["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 + "&nbsp;";
+ 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 + "&nbsp;";
+ 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 + "&nbsp;";
+ 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