diff options
-rw-r--r-- | grapher/index.html | 9 | ||||
-rw-r--r-- | grapher/style.css | 6 | ||||
-rw-r--r-- | index.html | 21 | ||||
-rw-r--r-- | lib/types.js | 222 | ||||
-rw-r--r-- | pwathings/appver.json | 2 | ||||
-rw-r--r-- | style.css | 9 | ||||
-rw-r--r-- | types.html | 79 |
7 files changed, 303 insertions, 45 deletions
diff --git a/grapher/index.html b/grapher/index.html index 4a70153..b81b31b 100644 --- a/grapher/index.html +++ b/grapher/index.html @@ -10,17 +10,12 @@ <link crossorigin href="/oi.css" rel="stylesheet" type="text/css"> <link crossorigin href="style.css" rel="stylesheet" type="text/css" /> <link crossorigin href="/lib/chips.css" rel="stylesheet" type="text/css" /> - - <link crossorigin href="/lib/util.js" rel="preload" as="script"/> - <link crossorigin href="/lib/chips.js" rel="preload" as="script"/> - <link crossorigin href="script.js" rel="preload" as="script"/> - <link crossorigin href="/oi.css" rel="preload" as="style"/> - <link crossorigin href="/lib/chips.css" rel="preload" as="style"/> - <link crossorigin href="style.css" rel="preload" as="style"/> + <link crossorigin href="/circuits.json" rel="preload" as="fetch"/> <link crossorigin href="https://raw.githubusercontent.com/tyleo-rec/CircuitsV2Resources/master/misc/circuitsv2.json" rel="preload" as="fetch"/> <script crossorigin src="/lib/util.js"></script> + <script crossorigin src="/lib/types.js"></script> <script crossorigin src="/lib/chips.js"></script> <script crossorigin src="script.js"></script> <noscript><style> #searcher, #graph, #canvas { display: none } </style></noscript> diff --git a/grapher/style.css b/grapher/style.css index 7b9f51e..119452c 100644 --- a/grapher/style.css +++ b/grapher/style.css @@ -245,4 +245,8 @@ button:hover { background: var(--foreforeground); } -a {color: white;}
\ No newline at end of file +a {color: white;} + +#searchbox:not([open]) + #graph :is(.exec, .float, .int, .bool, .string, .special, .any):hover + .type { + left: calc(var(--mouse-x) + 10px - (var(--chipOffsetX) * 1px) + 13cm); +}
\ No newline at end of file @@ -43,6 +43,27 @@ <button id="enabler" onclick="reloader();">Click to enable offline use!</button> <section id="changelog"> <article> + <h1>June 8, 2022 - <code> + <span style="color:lightblue;">typedef</span> + (<span style="color:crimson;">you</span>|<span style="color:crimson;">me</span>) + <span style="color:khaki;">Frosty</span>;</code></h1> + <hr> + <ul> + <li>Continued work on type inference logic.</li> + <ul> + <li><q>as well as plenty of methods for computing intersections between them.</q></li> + <li>But actually now! The <code><span style="color:khaki;">intersect</span>(<span style="color:crimson;">T1</span>,<span style="color:crimson;">T2</span>)</code> function generates the intersection between 2 types.</li> + <li>Support exists for things as dense as nested unions, even.</li> + <li><b>As Always:</b></li> + <ul> + <li>You can view it at <a href="/types.html">types.html</a>, by opening the DevTools console or reading the table.</li> + <li>The actual logic is contained in <code>/lib/types.js</code>.</li> + </ul> + </ul> + <li>Adjusted the frosted glass a bit.</li> + </ul> + </article> + <article> <h1>June 6, 2022 - Just the same as you and me!</h1> <hr> <ul> diff --git a/lib/types.js b/lib/types.js index 0253780..3fbb1c9 100644 --- a/lib/types.js +++ b/lib/types.js @@ -2,6 +2,7 @@ //TypeName: string, Params: Array<string> function Type(TypeName,Params=[]) { + if (!Type.all) Type.all = {}; this.typename = ""; this.mode = ""; @@ -49,7 +50,6 @@ function Type(TypeName,Params=[]) { } 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)) { @@ -79,12 +79,20 @@ function Type(TypeName,Params=[]) { workspace += c; } if (workspace) types.push(workspace); - this.template = types.map(t => t.trim()).map(t => Params.includes(t) ? t : new Type(t, Params)); + 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 + //Standard Type, or Template Param else if (/^[^()|<>,]+$/.test(TypeName)) { - this.typename = TypeName; - this.mode = (TypeName == 'any') ? "any" : "standard" + 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; + } } } @@ -99,49 +107,78 @@ Type.prototype.toString = function() { case "any": case "standard": return this.typename; + case "param": + return this.template.toString(); } } -Type.prototype._resolve = function(idx, val) { - if (val === undefined && this.template.length === 1) { - val = idx; - idx = 0; - } - if (0 <= idx && this.template.length > idx) { - this.template = this.template?.map(t => (t === name) ? val.copy() : ((typeof t == 'string') ? t : t.resolve(name, val))) +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(idx, val) { +Type.prototype.resolve = function(key, val) { const t = this.copy(); - t._resolve(idx, val); + t._resolve(key, val); return t; } Type.prototype.copy = function() { - const t = new Type(this.toString(), this.template?.filter(t => typeof t == 'string')); - return t; + 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.intersect = function(ot, params=[]) { - if (!ot instanceof Type) ot = new Type(ot, params) - if (this.mode == 'any') return ot.copy(); - if (ot.mode == 'any') return this.copy(); - if (ot.mode != this.mode) return undefined; - - switch (ot.mode) { +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': - if (this.template.length != ot.template.length) return undefined; - case 'standard': - if (ot.typename == this.typename) - return this.copy(); - else return undefined; - + 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) @@ -159,4 +196,129 @@ Type.QuickTD("float"); Type.QuickTD("string"); Type.QuickTD("bool"); Type.QuickTD("Vector3"); -Type.QuickTD("List<T>", ["T"]);
\ No newline at end of file +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 + " "; + 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 diff --git a/pwathings/appver.json b/pwathings/appver.json index da656d1..6923e35 100644 --- a/pwathings/appver.json +++ b/pwathings/appver.json @@ -1,3 +1,3 @@ { - "ver": 8 + "ver": 9 }
\ No newline at end of file @@ -179,7 +179,8 @@ ul.links > li::marker { margin: 0; padding: 0.5cm; position: absolute; - bottom: 0; + bottom: -1px; + z-index: 100; } @media (orientation:portrait) { @@ -202,4 +203,10 @@ ul.links > li::marker { display: block; height: calc(var(--widhei) - 2cm); } +} + +code { + background: #041720; + padding: 0.5mm; + border-radius: 0.5mm; }
\ No newline at end of file @@ -5,9 +5,32 @@ <meta name="viewport" content="width=device-width"> <title>Document</title> - <script src="/lib/types.js" ></script> + <script src="/lib/util.js"></script> + <script src="/lib/types.js"></script> + <script src="/lib/chips.js"></script> + <link href="/lib/chips.css" rel="stylesheet"></link> + <style> + table { + background: #333; + color: white; + } + table * { + border: solid #888 1px; + } + table th { + background: #222; + } + table td { + font-family: sans-serif; + } + </style> + +</head> +<body> + <table><thead><tr id="head"><th>Type</th></tr></thead><tbody id="body"></tbody></table> <script> + console.log("Testing type parsing...") console.log(new Type("int")) console.log(new Type("List<int>")) console.log(new Type("(int, T1)", ["T1"])) @@ -15,11 +38,57 @@ console.log(new Type("(int|float|Vector3)")) console.log(new Type("List<(int|float|Vector3)>")) console.log(new Type("(List<List<(int|float|Vector3)>>|List<any>)")) + console.log("Done!") - </script> + console.log("Testing type intersections"); + const tistart = performance.now(); + const types = [ + //standard + new Type("int"), + new Type("bool"), + //template + new Type("List<int>"), + new Type("List<float>"), + new Type("List<List<int>>"), + new Type("List<List<any>>"), + new Type("List<(int|float)>"), + new Type("List<(float|Vector3)>"), + new Type("List<any>"), + //union + new Type("(int|float|bool)"), + new Type("(List<any>|bool)"), + //any + new Type("any"), + //dumb shit + new Type("List<(List<(int|float)>|Vector3)>"), + ]; + const head = document.getElementById("head"); + const body = document.getElementById("body"); -</head> -<body> - + for (const t of types) { + const headel = document.createElement("th"); + headel.innerText = t; + head.appendChild(headel); + + const row = document.createElement("tr"); + body.appendChild(row); + const title = document.createElement("th"); + title.innerText = t; + row.appendChild(title); + for (const t2 of types) { + const sect = document.createElement("td"); + sect.innerText = intersect(t,t2); + row.appendChild(sect); + } + } + + console.log(`Done in ${performance.now() - tistart}ms!`) + + console.log("Testing chip class...") + const node = {"ReadonlyPaletteName": "Set Rotation","ReadonlyChipName": "Set Rotation","Description": "Sets the rotation of the target player or object. Players will rotate about the vertical axis only. Will fail in the following cases: If the target object is currently held, select/frozen by the maker pen, or is the child of a gizmo. Will also fail on players that are seated.","IsBetaChip": true,"DeprecationStage": "Active","PaletteNameSource": "FirstNodeDesc","ChipNameSource": "FirstNodeDesc","NodeDescs": [{"Name": "Set Rotation","ReadonlyTypeParams": {"T": "(Player | Rec Room Object)","U": "(Vector3 | Quaternion)"},"Inputs": [{"Name": "","ReadonlyType": "exec","Description": ""},{"Name": "Target","ReadonlyType": "T","Description": ""},{"Name": "Rotation","ReadonlyType": "U","Description": ""}],"Outputs": [{"Name": "","ReadonlyType": "exec","Description": ""},{"Name": "Success","ReadonlyType": "bool","Description": ""}]}],"NodeFilters": [{"FilterPath": ["Player","Physics"]},{"FilterPath": ["Object","Physics"]}]}; + document.body.append((x=new Chip(node).root)); + document.body.appendChild(x); + + </script> </body> </html>
\ No newline at end of file |