/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const t$2=globalThis,e$3=t$2.ShadowRoot&&(void 0===t$2.ShadyCSS||t$2.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s$1=Symbol(),o$3=new WeakMap;class n$3{constructor(t,e,o){if(this._$cssResult$=!0,o!==s$1)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e;}get styleSheet(){let t=this.o;const s=this.t;if(e$3&&void 0===t){const e=void 0!==s&&1===s.length;e&&(t=o$3.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&o$3.set(s,t));}return t}toString(){return this.cssText}}const r$3=t=>new n$3("string"==typeof t?t:t+"",void 0,s$1),i$5=(t,...e)=>{const o=1===t.length?t[0]:e.reduce(((e,s,o)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+t[o+1]),t[0]);return new n$3(o,t,s$1)},S$1=(s,o)=>{if(e$3)s.adoptedStyleSheets=o.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet));else for(const e of o){const o=document.createElement("style"),n=t$2.litNonce;void 0!==n&&o.setAttribute("nonce",n),o.textContent=e.cssText,s.appendChild(o);}},c$2=e$3?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const s of t.cssRules)e+=s.cssText;return r$3(e)})(t):t;
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/const{is:i$4,defineProperty:e$2,getOwnPropertyDescriptor:r$2,getOwnPropertyNames:h$1,getOwnPropertySymbols:o$2,getPrototypeOf:n$2}=Object,a$1=globalThis,c$1=a$1.trustedTypes,l$1=c$1?c$1.emptyScript:"",p$1=a$1.reactiveElementPolyfillSupport,d$1=(t,s)=>t,u$1={toAttribute(t,s){switch(s){case Boolean:t=t?l$1:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t);}return t},fromAttribute(t,s){let i=t;switch(s){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t);}catch(t){i=null;}}return i}},f$1=(t,s)=>!i$4(t,s),y$1={attribute:!0,type:String,converter:u$1,reflect:!1,hasChanged:f$1};Symbol.metadata??=Symbol("metadata"),a$1.litPropertyMetadata??=new WeakMap;class b extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t);}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,s=y$1){if(s.state&&(s.attribute=!1),this._$Ei(),this.elementProperties.set(t,s),!s.noAccessor){const i=Symbol(),r=this.getPropertyDescriptor(t,i,s);void 0!==r&&e$2(this.prototype,t,r);}}static getPropertyDescriptor(t,s,i){const{get:e,set:h}=r$2(this.prototype,t)??{get(){return this[s]},set(t){this[s]=t;}};return {get(){return e?.call(this)},set(s){const r=e?.call(this);h.call(this,s),this.requestUpdate(t,r,i);},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??y$1}static _$Ei(){if(this.hasOwnProperty(d$1("elementProperties")))return;const t=n$2(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties);}static finalize(){if(this.hasOwnProperty(d$1("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(d$1("properties"))){const t=this.properties,s=[...h$1(t),...o$2(t)];for(const i of s)this.createProperty(i,t[i]);}const t=this[Symbol.metadata];if(null!==t){const s=litPropertyMetadata.get(t);if(void 0!==s)for(const[t,i]of s)this.elementProperties.set(t,i);}this._$Eh=new Map;for(const[t,s]of this.elementProperties){const i=this._$Eu(t,s);void 0!==i&&this._$Eh.set(i,t);}this.elementStyles=this.finalizeStyles(this.styles);}static finalizeStyles(s){const i=[];if(Array.isArray(s)){const e=new Set(s.flat(1/0).reverse());for(const s of e)i.unshift(c$2(s));}else void 0!==s&&i.push(c$2(s));return i}static _$Eu(t,s){const i=s.attribute;return !1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev();}_$Ev(){this._$ES=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach((t=>t(this)));}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.();}removeController(t){this._$EO?.delete(t);}_$E_(){const t=new Map,s=this.constructor.elementProperties;for(const i of s.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t);}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return S$1(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach((t=>t.hostConnected?.()));}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach((t=>t.hostDisconnected?.()));}attributeChangedCallback(t,s,i){this._$AK(t,i);}_$EC(t,s){const i=this.constructor.elementProperties.get(t),e=this.constructor._$Eu(t,i);if(void 0!==e&&!0===i.reflect){const r=(void 0!==i.converter?.toAttribute?i.converter:u$1).toAttribute(s,i.type);this._$Em=t,null==r?this.removeAttribute(e):this.setAttribute(e,r),this._$Em=null;}}_$AK(t,s){const i=this.constructor,e=i._$Eh.get(t);if(void 0!==e&&this._$Em!==e){const t=i.getPropertyOptions(e),r="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:u$1;this._$Em=e,this[e]=r.fromAttribute(s,t.type),this._$Em=null;}}requestUpdate(t,s,i){if(void 0!==t){if(i??=this.constructor.getPropertyOptions(t),!(i.hasChanged??f$1)(this[t],s))return;this.P(t,s,i);}!1===this.isUpdatePending&&(this._$ES=this._$ET());}P(t,s,i){this._$AL.has(t)||this._$AL.set(t,s),!0===i.reflect&&this._$Em!==t&&(this._$Ej??=new Set).add(t);}async _$ET(){this.isUpdatePending=!0;try{await this._$ES;}catch(t){Promise.reject(t);}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,s]of this._$Ep)this[t]=s;this._$Ep=void 0;}const t=this.constructor.elementProperties;if(t.size>0)for(const[s,i]of t)!0!==i.wrapped||this._$AL.has(s)||void 0===this[s]||this.P(s,this[s],i);}let t=!1;const s=this._$AL;try{t=this.shouldUpdate(s),t?(this.willUpdate(s),this._$EO?.forEach((t=>t.hostUpdate?.())),this.update(s)):this._$EU();}catch(s){throw t=!1,this._$EU(),s}t&&this._$AE(s);}willUpdate(t){}_$AE(t){this._$EO?.forEach((t=>t.hostUpdated?.())),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t);}_$EU(){this._$AL=new Map,this.isUpdatePending=!1;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return !0}update(t){this._$Ej&&=this._$Ej.forEach((t=>this._$EC(t,this[t]))),this._$EU();}updated(t){}firstUpdated(t){}}b.elementStyles=[],b.shadowRootOptions={mode:"open"},b[d$1("elementProperties")]=new Map,b[d$1("finalized")]=new Map,p$1?.({ReactiveElement:b}),(a$1.reactiveElementVersions??=[]).push("2.0.4");
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const t$1=globalThis,i$3=t$1.trustedTypes,s=i$3?i$3.createPolicy("lit-html",{createHTML:t=>t}):void 0,e$1="$lit$",h=`lit$${Math.random().toFixed(9).slice(2)}$`,o$1="?"+h,n$1=`<${o$1}>`,r$1=document,l=()=>r$1.createComment(""),c=t=>null===t||"object"!=typeof t&&"function"!=typeof t,a=Array.isArray,u=t=>a(t)||"function"==typeof t?.[Symbol.iterator],d="[ \t\n\f\r]",f=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,v=/-->/g,_=/>/g,m=RegExp(`>|${d}(?:([^\\s"'>=/]+)(${d}*=${d}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),p=/'/g,g=/"/g,$=/^(?:script|style|textarea|title)$/i,y=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),x=y(1),T=Symbol.for("lit-noChange"),E=Symbol.for("lit-nothing"),A=new WeakMap,C=r$1.createTreeWalker(r$1,129);function P(t,i){if(!a(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==s?s.createHTML(i):i}const V=(t,i)=>{const s=t.length-1,o=[];let r,l=2===i?"":3===i?"":"")),o]};class N{constructor({strings:t,_$litType$:s},n){let r;this.parts=[];let c=0,a=0;const u=t.length-1,d=this.parts,[f,v]=V(t,s);if(this.el=N.createElement(f,n),C.currentNode=this.el.content,2===s||3===s){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes);}for(;null!==(r=C.nextNode())&&d.length0){r.textContent=i$3?i$3.emptyScript:"";for(let i=0;i2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=E;}_$AI(t,i=this,s,e){const h=this.strings;let o=!1;if(void 0===h)t=S(this,t,i,0),o=!c(t)||t!==this._$AH&&t!==T,o&&(this._$AH=t);else {const e=t;let n,r;for(t=h[0],n=0;n{const e=s?.renderBefore??i;let h=e._$litPart$;if(void 0===h){const t=s?.renderBefore??null;e._$litPart$=h=new R(i.insertBefore(l(),t),t,void 0,s??{});}return h._$AI(t),h};
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/class r extends b{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const s=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=B(s,this.renderRoot,this.renderOptions);}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0);}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1);}render(){return T}}r._$litElement$=!0,r["finalized"]=!0,globalThis.litElementHydrateSupport?.({LitElement:r});const i$2=globalThis.litElementPolyfillSupport;i$2?.({LitElement:r});(globalThis.litElementVersions??=[]).push("4.1.1");
class Configuration {
static VERSION = "2.0.0"
static nodeColors = {
black: i$5`20, 20, 20`,
blue: i$5`84, 122, 156`,
darkBlue: i$5`32, 80, 128`,
darkerBlue: i$5`18, 18, 130`,
darkTurquoise: i$5`19, 100, 137`,
gray: i$5`150,150,150`,
green: i$5`95, 129, 90`,
intenseGreen: i$5`42, 140, 42`,
lime: i$5`150, 160, 30`,
red: i$5`151, 33, 32`,
turquoise: i$5`46, 104, 106`,
violet: i$5`126, 28, 150`,
yellow: i$5`148, 116, 24`,
}
static alphaPattern = "repeating-conic-gradient(#7c8184 0% 25%, #c2c3c4 0% 50%) 50% / 10px 10px"
static colorDragEventName = "ueb-color-drag"
static colorPickEventName = "ueb-color-pick"
static colorWindowEventName = "ueb-color-window"
static colorWindowName = "Color Picker"
static defaultCommentHeight = 96
static defaultCommentWidth = 400
static distanceThreshold = 20 // px
static dragEventName = "ueb-drag"
static dragGeneralEventName = "ueb-drag-general"
static edgeScrollThreshold = 50
static editTextEventName = {
begin: "ueb-edit-text-begin",
end: "ueb-edit-text-end",
}
static expandGridSize = 400
static focusEventName = {
begin: "blueprint-focus",
end: "blueprint-unfocus",
}
static fontSize = i$5`13px`
static gridExpandThreshold = 0.25 // remaining size factor threshold to cause an expansion event
static gridLineWidth = 1 // px
static gridSet = 8
static gridShrinkThreshold = 4 // exceding size factor threshold to cause a shrink event
static gridSize = 16 // px
static hexColorRegex = /^\s*#(?[0-9a-fA-F]{2})(?[0-9a-fA-F]{2})(?[0-9a-fA-F]{2})([0-9a-fA-F]{2})?|#(?[0-9a-fA-F])(?[0-9a-fA-F])(?[0-9a-fA-F])\s*$/
static indentation = " "
static keysSeparator = /[\.\(\)]/
static knotOffset = [-Configuration.gridSize, -0.5 * Configuration.gridSize]
static lineTracePattern = /LineTrace(Single|Multi)(\w*)/
static linkCurveHeight = 15 // px
static linkCurveWidth = 80 // px
static linkMinWidth = 100 // px
static nameRegexSpaceReplacement = new RegExp(
// Leading K2_ or K2Node_ is removed
"^K2(?:[Nn]ode)?_"
// End of a word (lower case followed by either upper case or number): "AlphaBravo" => "Alpha Bravo"
+ "|(?<=[a-z])(?=[A-Z0-9])"
// End of upper case word (upper case followed by either word or number)
+ "|(?<=[A-Z])"
+ /* Except "UVs" */ "(? "Alpha Bravo"
+ "|\\s*_+\\s*"
+ "|\\s{2,}",
"g"
)
/**
* @param {Number} start
* @param {Number} c1
* @param {Number} c2
*/
static linkRightSVGPath = (start, c1, c2, arc = false) => {
const end = 100 - start;
const mid = arc
? 50 + (c2 - start)
: 50;
const fin = arc ? end + c1 - start : end - c1 + start;
return `M ${start} 0 C ${c1.toFixed(2)} 0, ${c2.toFixed(2)} 0, ${mid.toFixed(2)} 50 S ${fin.toFixed(2)} 100, `
+ `${end.toFixed(3)} 100`
}
static maxZoom = 7
static minZoom = -12
static mouseClickButton = 0
static mouseRightClickButton = 2
static mouseWheelZoomThreshold = 80
static nodeDragEventName = "ueb-node-drag"
static nodeDragGeneralEventName = "ueb-node-drag-general"
static nodeTitle = (name, counter) => `${name}_${counter}`
static nodeRadius = 8 // px
static nodeReflowEventName = "ueb-node-reflow"
static paths = {
actorBoundEvent: "/Script/BlueprintGraph.K2Node_ActorBoundEvent",
addDelegate: "/Script/BlueprintGraph.K2Node_AddDelegate",
ambientSound: "/Script/Engine.AmbientSound",
asyncAction: "/Script/BlueprintGraph.K2Node_AsyncAction",
blueprint: "/Script/Engine.Blueprint",
blueprintGameplayTagLibrary: "/Script/GameplayTags.BlueprintGameplayTagLibrary",
blueprintMapLibrary: "/Script/Engine.BlueprintMapLibrary",
blueprintSetLibrary: "/Script/Engine.BlueprintSetLibrary",
callArrayFunction: "/Script/BlueprintGraph.K2Node_CallArrayFunction",
callDelegate: "/Script/BlueprintGraph.K2Node_CallDelegate",
callFunction: "/Script/BlueprintGraph.K2Node_CallFunction",
clearDelegate: "/Script/BlueprintGraph.K2Node_ClearDelegate",
comment: "/Script/UnrealEd.EdGraphNode_Comment",
commutativeAssociativeBinaryOperator: "/Script/BlueprintGraph.K2Node_CommutativeAssociativeBinaryOperator",
componentBoundEvent: "/Script/BlueprintGraph.K2Node_ComponentBoundEvent",
createDelegate: "/Script/BlueprintGraph.K2Node_CreateDelegate",
customEvent: "/Script/BlueprintGraph.K2Node_CustomEvent",
doN: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:Do N",
doOnce: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:DoOnce",
dynamicCast: "/Script/BlueprintGraph.K2Node_DynamicCast",
eAttachmentRule: "/Script/Engine.EAttachmentRule",
edGraph: "/Script/Engine.EdGraph",
eDrawDebugTrace: "/Script/Engine.EDrawDebugTrace",
eMaterialSamplerType: "/Script/Engine.EMaterialSamplerType",
eNiagara_Float4Channel: "/Niagara/Enums/ENiagara_Float4Channel.ENiagara_Float4Channel",
enum: "/Script/CoreUObject.Enum",
enumLiteral: "/Script/BlueprintGraph.K2Node_EnumLiteral",
eSamplerSourceMode: "/Script/Engine.ESamplerSourceMode",
eSearchCase: "/Script/CoreUObject.ESearchCase",
eSearchDir: "/Script/CoreUObject.ESearchDir",
eSpawnActorCollisionHandlingMethod: "/Script/Engine.ESpawnActorCollisionHandlingMethod",
eTextureMipValueMode: "/Script/Engine.ETextureMipValueMode",
eTraceTypeQuery: "/Script/Engine.ETraceTypeQuery",
event: "/Script/BlueprintGraph.K2Node_Event",
eWorldPositionIncludedOffsets: "/Script/Engine.EWorldPositionIncludedOffsets",
executionSequence: "/Script/BlueprintGraph.K2Node_ExecutionSequence",
flipflop: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:FlipFlop",
forEachElementInEnum: "/Script/BlueprintGraph.K2Node_ForEachElementInEnum",
forEachLoop: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:ForEachLoop",
forEachLoopWithBreak: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:ForEachLoopWithBreak",
forLoop: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:ForLoop",
forLoopWithBreak: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:ForLoopWithBreak",
functionEntry: "/Script/BlueprintGraph.K2Node_FunctionEntry",
functionResult: "/Script/BlueprintGraph.K2Node_FunctionResult",
gameplayTag: "/Script/GameplayTags.GameplayTag",
getInputAxisKeyValue: "/Script/BlueprintGraph.K2Node_GetInputAxisKeyValue",
ifThenElse: "/Script/BlueprintGraph.K2Node_IfThenElse",
inputAxisKeyEvent: "/Script/BlueprintGraph.K2Node_InputAxisKeyEvent",
inputDebugKey: "/Script/InputBlueprintNodes.K2Node_InputDebugKey",
inputKey: "/Script/BlueprintGraph.K2Node_InputKey",
inputVectorAxisEvent: "/Script/BlueprintGraph.K2Node_InputVectorAxisEvent",
isValid: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:IsValid",
kismetArrayLibrary: "/Script/Engine.KismetArrayLibrary",
kismetMathLibrary: "/Script/Engine.KismetMathLibrary",
knot: "/Script/BlueprintGraph.K2Node_Knot",
linearColor: "/Script/CoreUObject.LinearColor",
literal: "/Script/BlueprintGraph.K2Node_Literal",
macro: "/Script/BlueprintGraph.K2Node_MacroInstance",
makeArray: "/Script/BlueprintGraph.K2Node_MakeArray",
makeMap: "/Script/BlueprintGraph.K2Node_MakeMap",
makeSet: "/Script/BlueprintGraph.K2Node_MakeSet",
makeStruct: "/Script/BlueprintGraph.K2Node_MakeStruct",
materialExpressionComponentMask: "/Script/Engine.MaterialExpressionComponentMask",
materialExpressionConstant: "/Script/Engine.MaterialExpressionConstant",
materialExpressionConstant2Vector: "/Script/Engine.MaterialExpressionConstant2Vector",
materialExpressionConstant3Vector: "/Script/Engine.MaterialExpressionConstant3Vector",
materialExpressionConstant4Vector: "/Script/Engine.MaterialExpressionConstant4Vector",
materialExpressionFunctionInput: "/Script/Engine.MaterialExpressionFunctionInput",
materialExpressionLogarithm: "/Script/InterchangeImport.MaterialExpressionLogarithm",
materialExpressionLogarithm10: "/Script/Engine.MaterialExpressionLogarithm10",
materialExpressionLogarithm2: "/Script/Engine.MaterialExpressionLogarithm2",
materialExpressionMaterialFunctionCall: "/Script/Engine.MaterialExpressionMaterialFunctionCall",
materialExpressionSquareRoot: "/Script/Engine.MaterialExpressionSquareRoot",
materialExpressionSubtract: "/Script/Engine.MaterialExpressionSubtract",
materialExpressionTextureCoordinate: "/Script/Engine.MaterialExpressionTextureCoordinate",
materialExpressionTextureSample: "/Script/Engine.MaterialExpressionTextureSample",
materialExpressionWorldPosition: "/Script/Engine.MaterialExpressionWorldPosition",
materialGraphNode: "/Script/UnrealEd.MaterialGraphNode",
materialGraphNodeComment: "/Script/UnrealEd.MaterialGraphNode_Comment",
metasoundEditorGraphExternalNode: "/Script/MetasoundEditor.MetasoundEditorGraphExternalNode",
multiGate: "/Script/BlueprintGraph.K2Node_MultiGate",
niagaraBool: "/Script/Niagara.NiagaraBool",
niagaraClipboardContent: "/Script/NiagaraEditor.NiagaraClipboardContent",
niagaraDataInterfaceCollisionQuery: "/Script/Niagara.NiagaraDataInterfaceCollisionQuery",
niagaraDataInterfaceCurlNoise: "/Script/Niagara.NiagaraDataInterfaceCurlNoise",
niagaraDataInterfaceVolumeTexture: "/Script/Niagara.NiagaraDataInterfaceVolumeTexture",
niagaraFloat: "/Script/Niagara.NiagaraFloat",
niagaraInt32: "/Script/Niagara.NiagaraInt32",
niagaraNodeConvert: "/Script/NiagaraEditor.NiagaraNodeConvert",
niagaraNodeFunctionCall: "/Script/NiagaraEditor.NiagaraNodeFunctionCall",
niagaraNodeInput: "/Script/NiagaraEditor.NiagaraNodeInput",
niagaraNodeOp: "/Script/NiagaraEditor.NiagaraNodeOp",
niagaraParameterMap: "/Script/Niagara.NiagaraParameterMap",
niagaraPosition: "/Script/Niagara.NiagaraPosition",
pawn: "/Script/Engine.Pawn",
pcgEditorGraphNode: "/Script/PCGEditor.PCGEditorGraphNode",
pcgEditorGraphNodeInput: "/Script/PCGEditor.PCGEditorGraphNodeInput",
pcgEditorGraphNodeOutput: "/Script/PCGEditor.PCGEditorGraphNodeOutput",
pcgHiGenGridSizeSettings: "/Script/PCG.PCGHiGenGridSizeSettings",
pcgSubgraphSettings: "/Script/PCG.PCGSubgraphSettings",
promotableOperator: "/Script/BlueprintGraph.K2Node_PromotableOperator",
quat4f: "/Script/CoreUObject.Quat4f",
removeDelegate: "/Script/BlueprintGraph.K2Node_RemoveDelegate",
reverseForEachLoop: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:ReverseForEachLoop",
rotator: "/Script/CoreUObject.Rotator",
select: "/Script/BlueprintGraph.K2Node_Select",
self: "/Script/BlueprintGraph.K2Node_Self",
slateBlueprintLibrary: "/Script/UMG.SlateBlueprintLibrary",
spawnActorFromClass: "/Script/BlueprintGraph.K2Node_SpawnActorFromClass",
switchEnum: "/Script/BlueprintGraph.K2Node_SwitchEnum",
switchGameplayTag: "/Script/GameplayTagsEditor.GameplayTagsK2Node_SwitchGameplayTag",
switchInteger: "/Script/BlueprintGraph.K2Node_SwitchInteger",
switchName: "/Script/BlueprintGraph.K2Node_SwitchName",
switchString: "/Script/BlueprintGraph.K2Node_SwitchString",
timeline: "/Script/BlueprintGraph.K2Node_Timeline",
timeManagementBlueprintLibrary: "/Script/TimeManagement.TimeManagementBlueprintLibrary",
transform: "/Script/CoreUObject.Transform",
userDefinedEnum: "/Script/Engine.UserDefinedEnum",
variableGet: "/Script/BlueprintGraph.K2Node_VariableGet",
variableSet: "/Script/BlueprintGraph.K2Node_VariableSet",
vector: "/Script/CoreUObject.Vector",
vector2D: "/Script/CoreUObject.Vector2D",
vector2f: "/Script/CoreUObject.Vector2f",
vector3f: "/Script/CoreUObject.Vector3f",
vector4f: "/Script/CoreUObject.Vector4f",
whileLoop: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:WhileLoop",
}
static pinInputWrapWidth = 145 // px
static removeEventName = "ueb-element-delete"
static scale = {
[-12]: 0.133333,
[-11]: 0.166666,
[-10]: 0.2,
[-9]: 0.233333,
[-8]: 0.266666,
[-7]: 0.3,
[-6]: 0.333333,
[-5]: 0.375,
[-4]: 0.5,
[-3]: 0.675,
[-2]: 0.75,
[-1]: 0.875,
0: 1,
1: 1.25,
2: 1.375,
3: 1.5,
4: 1.675,
5: 1.75,
6: 1.875,
7: 2,
}
static smoothScrollTime = 1000 // ms
static stringEscapedCharacters = /["\\]/g // Try to remove
static subObjectAttributeNamePrefix = "#SubObject"
/** @param {ObjectEntity} objectEntity */
static subObjectAttributeNameFromEntity = (objectEntity, nameOnly = false) =>
this.subObjectAttributeNamePrefix + (!nameOnly && objectEntity.Class ? `_${objectEntity.Class.type}` : "")
+ "_" + objectEntity.Name
/** @param {ObjectReferenceEntity} objectReferenceEntity */
static subObjectAttributeNameFromReference = (objectReferenceEntity, nameOnly = false) =>
this.subObjectAttributeNamePrefix + (!nameOnly ? "_" + objectReferenceEntity.type : "")
+ "_" + objectReferenceEntity.path
static subObjectAttributeNameFromName = name => this.subObjectAttributeNamePrefix + "_" + name
static switchTargetPattern = /\/Script\/[\w\.\/\:]+K2Node_Switch([A-Z]\w+)+/
static trackingMouseEventName = {
begin: "ueb-tracking-mouse-begin",
end: "ueb-tracking-mouse-end",
}
static unescapedBackslash = /(?<=(?:[^\\]|^)(?:\\\\)*)\\(?!\\)/ // Try to remove
static windowApplyEventName = "ueb-window-apply"
static windowApplyButtonText = "OK"
static windowCancelEventName = "ueb-window-cancel"
static windowCancelButtonText = "Cancel"
static windowCloseEventName = "ueb-window-close"
static CommonEnums = {
[this.paths.eAttachmentRule]: [
"KeepRelative",
"KeepWorld",
"SnapToTarget",
],
[this.paths.eDrawDebugTrace]: ["None", "ForOneFrame", "ForDuration", "Persistent"],
[this.paths.eMaterialSamplerType]: [
"Color",
"Grayscale",
"Alpha",
"Normal",
"Masks",
"Distance Field Font",
"Linear Color",
"Linear Grayscale",
"Data",
"External",
"Virtual Color",
"Virtual Grayscale",
"Virtual Alpha",
"Virtual Normal",
"Virtual Mask",
"Virtual Linear Color",
"Virtual Linear Grayscal",
],
[this.paths.eNiagara_Float4Channel]: [
["NewEnumerator0", "R"],
["NewEnumerator1", "G"],
["NewEnumerator2", "B"],
["NewEnumerator3", "A"],
],
[this.paths.eSamplerSourceMode]: ["From texture asset", "Shared: Wrap", "Shared: Clamp", "Hidden"],
[this.paths.eSearchCase]: ["CaseSensitive", "IgnoreCase"],
[this.paths.eWorldPositionIncludedOffsets]: [
"Absolute World Position (Including Material Shader Offsets)",
"Absolute World Position (Excluding Material Shader Offsets)",
"Camera Relative World Position (Including Material Shader Offsets)",
"Camera Relative World Position (Excluding Material Shader Offsets)",
],
[this.paths.eSearchDir]: ["FromStart", "FromEnd"],
[this.paths.eSpawnActorCollisionHandlingMethod]: [
["Undefined", "Default"],
["AlwaysSpawn", "Always Spawn, Ignore Collisions"],
["AdjustIfPossibleButAlwaysSpawn", "Try To Adjust Location, But Always Spawn"],
["AdjustIfPossibleButDontSpawnIfColliding", "Try To Adjust Location, Don't Spawn If Still Colliding"],
["DontSpawnIfColliding", "Do Not Spawn"],
],
[this.paths.eTextureMipValueMode]: [
"None (use computed mip level)",
"MipLevel (absolute, 0 is full resolution)",
"MipBias (relative to the computed mip level)",
"Derivative (explicit derivative to compute mip level)",
],
[this.paths.eTraceTypeQuery]: [["TraceTypeQuery1", "Visibility"], ["TraceTypeQuery2", "Camera"]]
}
static ModifierKeys = [
"Ctrl",
"Shift",
"Alt",
"Meta",
]
static rgba = ["R", "G", "B", "A"]
static Keys = {
/* UE name: JS name */
"Backspace": "Backspace",
"Tab": "Tab",
"LeftControl": "ControlLeft",
"RightControl": "ControlRight",
"LeftShift": "ShiftLeft",
"RightShift": "ShiftRight",
"LeftAlt": "AltLeft",
"RightAlt": "AltRight",
"Enter": "Enter",
"Pause": "Pause",
"CapsLock": "CapsLock",
"Escape": "Escape",
"Space": "Space",
"PageUp": "PageUp",
"PageDown": "PageDown",
"End": "End",
"Home": "Home",
"ArrowLeft": "ArrowLeft",
"ArrowUp": "ArrowUp",
"ArrowRight": "ArrowRight",
"ArrowDown": "ArrowDown",
"PrintScreen": "PrintScreen",
"Insert": "Insert",
"Delete": "Delete",
"Zero": "Digit0",
"One": "Digit1",
"Two": "Digit2",
"Three": "Digit3",
"Four": "Digit4",
"Five": "Digit5",
"Six": "Digit6",
"Seven": "Digit7",
"Eight": "Digit8",
"Nine": "Digit9",
"A": "KeyA",
"B": "KeyB",
"C": "KeyC",
"D": "KeyD",
"E": "KeyE",
"F": "KeyF",
"G": "KeyG",
"H": "KeyH",
"I": "KeyI",
"K": "KeyK",
"L": "KeyL",
"M": "KeyM",
"N": "KeyN",
"O": "KeyO",
"P": "KeyP",
"Q": "KeyQ",
"R": "KeyR",
"S": "KeyS",
"T": "KeyT",
"U": "KeyU",
"V": "KeyV",
"W": "KeyW",
"X": "KeyX",
"Y": "KeyY",
"Z": "KeyZ",
"NumPadZero": "Numpad0",
"NumPadOne": "Numpad1",
"NumPadTwo": "Numpad2",
"NumPadThree": "Numpad3",
"NumPadFour": "Numpad4",
"NumPadFive": "Numpad5",
"NumPadSix": "Numpad6",
"NumPadSeven": "Numpad7",
"NumPadEight": "Numpad8",
"NumPadNine": "Numpad9",
"Multiply": "NumpadMultiply",
"Add": "NumpadAdd",
"Subtract": "NumpadSubtract",
"Decimal": "NumpadDecimal",
"Divide": "NumpadDivide",
"F1": "F1",
"F2": "F2",
"F3": "F3",
"F4": "F4",
"F5": "F5",
"F6": "F6",
"F7": "F7",
"F8": "F8",
"F9": "F9",
"F10": "F10",
"F11": "F11",
"F12": "F12",
"NumLock": "NumLock",
"ScrollLock": "ScrollLock",
}
}
class Utility {
/** @param {Number} value */
static clamp(value, min = -Infinity, max = Infinity) {
return Math.min(Math.max(value, min), max)
}
/** @param {HTMLElement} element */
static getScale(element) {
// @ts-expect-error
const scale = element.blueprint?.getScale() ?? getComputedStyle(element).getPropertyValue("--ueb-scale");
return scale != "" ? parseFloat(scale) : 1
}
/**
* @param {Number} num
* @param {Number} decimals
*/
static minDecimals(num, decimals = 1, epsilon = 1e-8) {
const powered = num * 10 ** decimals;
if (Math.abs(powered % 1) > epsilon) {
// More decimal digits than required
return num.toString()
}
return num.toFixed(decimals)
}
/**
* @param {Number} num
* @param {Number} decimals
*/
static roundDecimals(num, decimals = 1) {
const power = 10 ** decimals;
return Math.round(num * power) / power
}
/** @param {Number} num */
static printExponential(num) {
if (num == Number.POSITIVE_INFINITY) {
return "inf"
} else if (num == Number.NEGATIVE_INFINITY) {
return "-inf"
}
const int = Math.round(num);
if (int >= 1000) {
const exp = Math.floor(Math.log10(int));
const dec = Math.round(num / 10 ** (exp - 2)) / 100;
// Not using num.toExponential() because of the omitted leading 0 on the exponential
return `${dec}e+${exp < 10 ? "0" : ""}${exp}`
}
const intPart = Math.floor(num);
if (intPart == 0) {
return num.toString()
}
return this.roundDecimals(num, Math.max(0, 3 - Math.floor(num).toString().length)).toString()
}
/**
* @param {Number} a
* @param {Number} b
*/
static approximatelyEqual(a, b, epsilon = 1e-8) {
return !(Math.abs(a - b) > epsilon)
}
/**
* @param {Coordinates} viewportLocation
* @param {HTMLElement} movementElement
*/
static convertLocation(viewportLocation, movementElement, ignoreScale = false) {
const scaleCorrection = ignoreScale ? 1 : 1 / Utility.getScale(movementElement);
const bounding = movementElement.getBoundingClientRect();
const location = /** @type {Coordinates} */([
Math.round((viewportLocation[0] - bounding.x) * scaleCorrection),
Math.round((viewportLocation[1] - bounding.y) * scaleCorrection)
]);
return location
}
/**
* @param {IEntity} entity
* @param {String} key
* @returns {Boolean}
*/
static isSerialized(entity, key) {
return entity["attributes"]?.[key]?.serialized
?? entity.constructor["attributes"]?.[key]?.serialized
?? false
}
/** @param {String[]} keys */
static objectGet(target, keys, defaultValue = undefined) {
if (target === undefined) {
return undefined
}
if (!(keys instanceof Array)) {
throw new TypeError("UEBlueprint: Expected keys to be an array")
}
if (keys.length == 0 || !(keys[0] in target) || target[keys[0]] === undefined) {
return defaultValue
}
if (keys.length == 1) {
return target[keys[0]]
}
return Utility.objectGet(target[keys[0]], keys.slice(1), defaultValue)
}
/**
* @param {String[]} keys
* @returns {Boolean}
*/
static objectSet(target, keys, value, defaultDictType = Object) {
if (!(keys instanceof Array)) {
throw new TypeError("Expected keys to be an array.")
}
if (keys.length == 1) {
if (keys[0] in target || target[keys[0]] === undefined) {
target[keys[0]] = value;
return true
}
} else if (keys.length > 0) {
if (!(target[keys[0]] instanceof Object)) {
target[keys[0]] = new defaultDictType();
}
return Utility.objectSet(target[keys[0]], keys.slice(1), value, defaultDictType)
}
return false
}
/**
* @param {Number} x
* @param {Number} y
* @param {Number} gridSize
* @returns {Coordinates}
*/
static snapToGrid(x, y, gridSize) {
if (gridSize === 1) {
return [x, y]
}
return [
gridSize * Math.floor(x / gridSize),
gridSize * Math.floor(y / gridSize)
]
}
/**
* @template T
* @param {T[]} reference
* @param {T[]} additional
* @param {(v: T) => void} adding - Process added element
* @param {(l: T, r: T) => Boolean} predicate
* @returns {T[]}
*/
static mergeArrays(reference = [], additional = [], predicate = (l, r) => l == r, adding = v => { }) {
let result = [];
reference = [...reference];
additional = [...additional];
restart:
while (true) {
for (let j = 0; j < additional.length; ++j) {
for (let i = 0; i < reference.length; ++i) {
if (predicate(reference[i], additional[j])) {
// Found an element in common in the two arrays
result.push(
// Take and append all the elements skipped from a
...reference.splice(0, i),
// Take and append all the elements skippend from b
...additional.splice(0, j).map(v => (adding(v), v)),
// Take and append the element in common
...reference.splice(0, 1)
);
additional.shift(); // Remove the same element from b
continue restart
}
}
}
break restart
}
// Append remaining the elements in the arrays and make it unique
result.push(...reference);
result.push(
...additional
.filter(vb => !result.some(vr => predicate(vr, vb)))
.map((v, k) => (adding(v), v))
);
return result
}
/** @param {String} value */
static escapeNewlines(value) {
return value
.replaceAll("\n", "\\n") // Replace newline with \n
.replaceAll("\t", "\\t") // Replace tab with \t
}
/** @param {String} value */
static escapeString(value, inline = true) {
let result = value.replaceAll(new RegExp(`(${Configuration.stringEscapedCharacters.source})`, "g"), '\\$1');
if (inline) {
result = result
.replaceAll("\n", "\\n") // Replace newline with \n
.replaceAll("\t", "\\t"); // Replace tab with \t
}
return result
}
/** @param {String} value */
static unescapeString(value) {
return value
.replaceAll(new RegExp(Configuration.unescapedBackslash.source + "t", "g"), "\t") // Replace tab with \t
.replaceAll(new RegExp(Configuration.unescapedBackslash.source + "n", "g"), "\n") // Replace newline with \n
.replaceAll(new RegExp(`\\\\(${Configuration.stringEscapedCharacters.source})`, "g"), "$1")
}
/** @param {String} value */
static clearHTMLWhitespace(value) {
return value
.replaceAll(" ", "\u00A0") // whitespace
.replaceAll(/
|
/g, "\n") // newlines
.replaceAll(/(\)/g, "") // html comments
}
/** @param {String} value */
static encodeHTMLWhitespace(value) {
return value.replaceAll(" ", "\u00A0")
}
/** @param {String} value */
static capitalFirstLetter(value) {
if (value.length === 0) {
return value
}
return value.charAt(0).toUpperCase() + value.slice(1)
}
/** @param {String} value */
static formatStringName(value = "") {
return value
// Remove leading b (for boolean values) or newlines
.replace(/^\s*b(?=[A-Z])/, "")
// Insert a space where needed, possibly removing unnecessary elading characters
.replaceAll(Configuration.nameRegexSpaceReplacement, " ")
.trim()
.split(" ")
.map(v => Utility.capitalFirstLetter(v))
.join(" ")
}
/** @param {String} value */
static getIdFromReference(value) {
return value
.replace(/(?:.+\.)?([^\.]+)$/, "$1")
.replaceAll(/(?<=[a-z\d])(?=[A-Z])|(?<=[a-zA-Z])(?=\d)|(?<=[A-Z]{2})(?=[A-Z][a-z])/g, "-")
.toLowerCase()
}
/** @param {String} pathValue */
static getNameFromPath(pathValue, dropCounter = false) {
// From end to the first "/" or "."
const regex = dropCounter ? /([^\.\/]+?)(?:_\d+)$/ : /([^\.\/]+)$/;
return pathValue.match(regex)?.[1] ?? ""
}
/**
* @param {Number} x
* @param {Number} y
* @returns {Coordinates}
*/
static getPolarCoordinates(x, y, positiveTheta = false) {
let theta = Math.atan2(y, x);
if (positiveTheta && theta < 0) {
theta = 2 * Math.PI + theta;
}
return [
Math.sqrt(x * x + y * y),
theta,
]
}
/**
* @param {Number} r
* @param {Number} theta
* @returns {Coordinates}
*/
static getCartesianCoordinates(r, theta) {
return [
r * Math.cos(theta),
r * Math.sin(theta)
]
}
/**
* @param {Number} begin
* @param {Number} end
*/
static range(begin = 0, end = 0, step = end >= begin ? 1 : -1) {
return Array.from({ length: Math.ceil((end - begin) / step) }, (_, i) => begin + (i * step))
}
/** @param {String[]} words */
static getFirstWordOrder(words) {
return new RegExp(/\s*/.source + words.join(/[^\n]+\n\s*/.source) + /\s*/.source)
}
/**
* @param {HTMLElement} element
* @param {String} value
*/
static paste(element, value) {
const event = new ClipboardEvent("paste", {
bubbles: true,
cancelable: true,
clipboardData: new DataTransfer(),
});
event.clipboardData.setData("text", value);
element.dispatchEvent(event);
}
/** @param {Blueprint} blueprint */
static async copy(blueprint) {
const event = new ClipboardEvent("copy", {
bubbles: true,
cancelable: true,
clipboardData: new DataTransfer(),
});
blueprint.dispatchEvent(event);
}
static animate(
from,
to,
intervalSeconds,
callback,
acknowledgeRequestAnimationId = id => { },
timingFunction = x => {
const v = x ** 3.5;
return v / (v + ((1 - x) ** 3.5))
}
) {
let startTimestamp;
const doAnimation = currentTimestamp => {
if (startTimestamp === undefined) {
startTimestamp = currentTimestamp;
}
let delta = (currentTimestamp - startTimestamp) / intervalSeconds;
if (Utility.approximatelyEqual(delta, 1) || delta > 1) {
delta = 1;
} else {
acknowledgeRequestAnimationId(requestAnimationFrame(doAnimation));
}
const currentValue = from + (to - from) * timingFunction(delta);
callback(currentValue);
};
acknowledgeRequestAnimationId(requestAnimationFrame(doAnimation));
}
}
/**
* @template {IEntity} EntityT
* @template {ITemplate} TemplateT
*/
class IElement extends r {
/** @type {Blueprint} */
#blueprint
get blueprint() {
return this.#blueprint
}
set blueprint(v) {
this.#blueprint = v;
}
/** @type {EntityT} */
#entity
get entity() {
return this.#entity
}
set entity(entity) {
this.#entity = entity;
}
/** @type {TemplateT} */
#template
get template() {
return this.#template
}
isInitialized = false
isSetup = false
/** @type {IInput[]} */
inputObjects = []
/**
* @param {EntityT} entity
* @param {TemplateT} template
*/
initialize(entity, template) {
this.requestUpdate();
this.#entity = entity;
this.#template = template;
this.#template.initialize(this);
if (this.isConnected) {
this.updateComplete.then(() => this.setup());
}
this.isInitialized = true;
}
connectedCallback() {
super.connectedCallback();
this.blueprint = /** @type {Blueprint} */(this.closest("ueb-blueprint"));
if (this.isInitialized) {
this.requestUpdate();
this.updateComplete.then(() => this.setup());
}
}
disconnectedCallback() {
super.disconnectedCallback();
if (this.isSetup) {
this.updateComplete.then(() => this.cleanup());
}
this.acknowledgeDelete();
}
createRenderRoot() {
return this
}
setup() {
this.template.setup();
this.isSetup = true;
}
cleanup() {
this.template.cleanup();
this.isSetup = false;
}
/** @param {PropertyValues} changedProperties */
willUpdate(changedProperties) {
super.willUpdate(changedProperties);
this.template.willUpdate(changedProperties);
}
/** @param {PropertyValues} changedProperties */
update(changedProperties) {
super.update(changedProperties);
this.template.update(changedProperties);
}
render() {
return this.template.render()
}
/** @param {PropertyValues} changedProperties */
firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this.template.firstUpdated(changedProperties);
this.template.inputSetup();
}
/** @param {PropertyValues} changedProperties */
updated(changedProperties) {
super.updated(changedProperties);
this.template.updated(changedProperties);
}
acknowledgeDelete() {
let deleteEvent = new CustomEvent(Configuration.removeEventName);
this.dispatchEvent(deleteEvent);
}
/** @param {IElement} element */
isSameGraph(element) {
return this.blueprint && this.blueprint == element?.blueprint
}
}
class SVGIcon {
static arrayPin = x`
`
static branchNode = x`
`
static breakStruct = x`
`
static cast = x`
`
static close = x`
`
static convert = x`
`
static correct = x`
`
static delegate = x`
`
static doN = x`
`
static doOnce = x`
`
static enum = x`
`
static event = x`
`
static execPin = x`
`
static expandIcon = x`
`
static flipflop = x`
`
static forEachLoop = x`
`
static functionSymbol = x`
`
static gamepad = x`
`
static genericPin = x`
`
static keyboard = x`
`
static loop = x`
`
static macro = x`
`
static mapPin = x`
`
static makeArray = x`
`
static makeMap = x`
`
static makeSet = x`
`
static makeStruct = x`
`
static metasoundFunction = x`
`
static mouse = x`
`
static node = x`
`
static operationPin = x`
`
static pcgStackPin = x`
`
static pcgPin = x`
`
static pcgParamPin = x`
`
static pcgSpatialPin = x`
`
static plusCircle = x`
`
static questionMark = x`
`
static referencePin = x`
`
static reject = x`
`
static setPin = x`
`
static select = x`
`
static sequence = x`
`
static sound = x`
`
static spawnActor = x`
`
static staticPin = x`
`
static switch = x`
`
static timer = x`
`
static touchpad = x`
`
}
class Reply {
/**
* @template T
* @param {Number} position
* @param {T} value
* @param {PathNode} bestPath
* @returns {Result}
*/
static makeSuccess(position, value, bestPath = null, bestPosition = 0) {
return {
status: true,
value: value,
position: position,
bestParser: bestPath,
bestPosition: bestPosition,
}
}
/**
* @param {PathNode} bestPath
* @returns {Result}
*/
static makeFailure(position = 0, bestPath = null, bestPosition = 0) {
return {
status: false,
value: null,
position,
bestParser: bestPath,
bestPosition: bestPosition,
}
}
/** @param {Parsernostrum} parsernostrum */
static makeContext(parsernostrum = null, input = "") {
return /** @type {Context} */({
parsernostrum,
input,
highlighted: null,
})
}
static makePathNode(parser, index = 0, previous = null) {
return /** @type {PathNode} */({
parent: previous,
current: parser,
index,
})
}
}
/** @template T */
class Parser {
static indentation = " "
static highlight = "Last valid parser"
/** @type {(new (...args: any) => Parser) & typeof Parser} */
Self
/** @param {String} value */
static frame(value, label = "", indentation = "") {
label = value ? "[ " + label + " ]" : "";
let rows = value.split("\n");
const width = Math.max(...rows.map(r => r.length));
const rightPadding = width < label.length ? " ".repeat(label.length - width) : "";
for (let i = 0; i < rows.length; ++i) {
rows[i] =
indentation
+ "| "
+ rows[i]
+ " ".repeat(width - rows[i].length)
+ rightPadding
+ " |";
}
if (label.length < width) {
label = label + "─".repeat(width - label.length);
}
const rowA = "┌─" + label + "─┐";
const rowB = indentation + "└─" + "─".repeat(label.length) + "─┘";
rows = [rowA, ...rows, rowB];
return rows.join("\n")
}
/**
* @param {PathNode} path
* @param {Number} index
* @returns {PathNode}
*/
makePath(path, index) {
return { current: this, parent: path, index }
}
/**
* @param {Context} context
* @param {PathNode} path
*/
isHighlighted(context, path) {
if (context.highlighted instanceof Parser) {
return context.highlighted === this
}
if (!context.highlighted || !path?.current) {
return false
}
let a, b;
for (
a = path,
b = /** @type {PathNode} */(context.highlighted);
a.current && b.current;
a = a.parent,
b = b.parent
) {
if (a.current !== b.current || a.index !== b.index) {
return false
}
}
return !a.current && !b.current
}
/** @param {PathNode?} path */
isVisited(path) {
if (!path) {
return false
}
for (path = path.parent; path != null; path = path.parent) {
if (path.current === this) {
return true
}
}
return false
}
/**
* @param {Context} context
* @param {Number} position
* @param {PathNode} path
* @param {Number} index
* @returns {Result}
*/
parse(context, position, path, index) {
return null
}
/** @param {PathNode} path */
toString(context = Reply.makeContext(null, ""), indentation = "", path = null, index = 0) {
path = this.makePath(path, index);
if (this.isVisited(path)) {
return "<...>"
}
const isVisited = this.isVisited(path);
const isHighlighted = this.isHighlighted(context, path);
let result = isVisited ? "<...>" : this.doToString(context, isHighlighted ? "" : indentation, path, index);
if (isHighlighted) {
/** @type {String[]} */
result = Parser.frame(result, Parser.highlight, indentation);
}
return result
}
/**
* @protected
* @param {Context} context
* @param {String} indentation
* @param {PathNode} path
* @param {Number} index
*/
doToString(context, indentation, path, index) {
return `${this.constructor.name} does not implement toString()`
}
}
/** @template {String} T */
class StringParser extends Parser {
#value
get value() {
return this.#value
}
/** @param {T} value */
constructor(value) {
super();
this.#value = value;
}
/**
* @param {Context} context
* @param {Number} position
* @param {PathNode} path
* @param {Number} index
* @returns {Result}
*/
parse(context, position, path, index) {
path = this.makePath(path, index);
const end = position + this.#value.length;
const value = context.input.substring(position, end);
const result = this.#value === value
? Reply.makeSuccess(end, this.#value, path, end)
: Reply.makeFailure();
return result
}
/**
* @protected
* @param {Context} context
* @param {String} indentation
* @param {PathNode} path
* @param {Number} index
*/
doToString(context, indentation, path, index) {
return `"${this.value.replaceAll("\n", "\\n").replaceAll('"', '\\"')}"`
}
}
/** @extends Parser<""> */
class SuccessParser extends Parser {
static instance = new SuccessParser()
/**
* @param {Context} context
* @param {Number} position
* @param {PathNode} path
* @param {Number} index
* @returns {Result<"">}
*/
parse(context, position, path, index) {
path = this.makePath(path, index);
return Reply.makeSuccess(position, "", path, 0)
}
/**
* @protected
* @param {Context} context
* @param {String} indentation
* @param {PathNode} path
* @param {Number} index
*/
doToString(context, indentation, path, index) {
return ""
}
}
/**
* @template {any[]} T
* @typedef {T extends [infer A] ? A
* : T extends [infer A, ...infer B] ? A | Union
* : never
* } Union
*/
/**
* @template {any[]} T
* @extends Parser>
*/
class AlternativeParser extends Parser {
#parsers
get parsers() {
return this.#parsers
}
/** @param {Parser[]} parsers */
constructor(...parsers) {
super();
this.#parsers = parsers;
}
/**
* @param {Context} context
* @param {Number} position
* @param {PathNode} path
* @param {Number} index
*/
parse(context, position, path, index) {
path = this.makePath(path, index);
const result = /** @type {Result>} */(Reply.makeSuccess(0, ""));
for (let i = 0; i < this.#parsers.length; ++i) {
const outcome = this.#parsers[i].parse(context, position, path, i);
if (outcome.bestPosition > result.bestPosition) {
result.bestParser = outcome.bestParser;
result.bestPosition = outcome.bestPosition;
}
if (outcome.status) {
result.value = outcome.value;
result.position = outcome.position;
return result
}
}
result.status = false;
result.value = null;
return result
}
/**
* @protected
* @param {Context} context
* @param {String} indentation
* @param {PathNode} path
* @param {Number} index
*/
doToString(context, indentation, path, index) {
// Short syntax for optional parser
if (this.#parsers.length === 2 && this.#parsers[1] instanceof SuccessParser) {
let result = this.#parsers[0].toString(context, indentation, path, 0);
if (!(this.#parsers[0] instanceof StringParser)) {
result = "<" + result + ">";
}
result += "?";
return result
}
const deeperIndentation = indentation + Parser.indentation;
let result = "ALT<\n"
+ deeperIndentation
+ this.#parsers
.map((parser, i) => parser.toString(
context,
deeperIndentation + " ".repeat(i === 0 ? 0 : Parser.indentation.length - 2),
path,
i,
))
.join("\n" + deeperIndentation + "| ")
+ "\n" + indentation + ">";
return result
}
}
/**
* @template S
* @template T
* @extends Parser
*/
class ChainedParser extends Parser {
#parser
get parser() {
return this.#parser
}
#fn
/**
* @param {Parser} parser
* @param {(v: S, input: String, position: Number) => Parsernostrum} chained
*/
constructor(parser, chained) {
super();
this.#parser = parser;
this.#fn = chained;
}
/**
* @param {Context} context
* @param {Number} position
* @param {PathNode} path
* @param {Number} index
* @returns {Result}
*/
parse(context, position, path, index) {
path = this.makePath(path, index);
const outcome = this.#parser.parse(context, position, path, 0);
if (!outcome.status) {
// @ts-expect-error
return outcome
}
const result = this.#fn(outcome.value, context.input, outcome.position)
.getParser()
.parse(context, outcome.position, path, 0);
if (outcome.bestPosition > result.bestPosition) {
result.bestParser = outcome.bestParser;
result.bestPosition = outcome.bestPosition;
}
return result
}
/**
* @protected
* @param {Context} context
* @param {String} indentation
* @param {PathNode} path
* @param {Number} index
*/
doToString(context, indentation, path, index) {
const result = this.#parser.toString(context, indentation, path, 0) + " => chained";
return result
}
}
/** @extends Parser */
class FailureParser extends Parser {
static instance = new FailureParser()
/**
* @param {Context} context
* @param {Number} position
* @param {PathNode} path
* @param {Number} index
*/
parse(context, position, path, index) {
return Reply.makeFailure()
}
/**
* @protected
* @param {Context} context
* @param {String} indentation
* @param {PathNode} path
* @param {Number} index
*/
doToString(context, indentation, path, index) {
return ""
}
}
/**
* @template T
* @extends Parser
*/
class Label extends Parser {
#parser
get parser() {
return this.#parser
}
#label = ""
/**
* @param {Parser} parser
* @param {String} label
*/
constructor(parser, label) {
super();
this.#parser = parser;
this.#label = label;
}
/**
* @param {PathNode} path
* @param {Number} index
*/
makePath(path, index) {
return path // Label does not alter the path
}
/**
* @param {Context} context
* @param {Number} position
* @param {PathNode} path
* @param {Number} index
*/
parse(context, position, path, index) {
this.parse = this.#parser.parse.bind(this.#parser);
return this.parse(context, position, path, index)
}
/**
* @protected
* @param {Context} context
* @param {String} indentation
* @param {PathNode} path
* @param {Number} index
*/
doToString(context, indentation, path, index) {
let result = this.#parser.toString(context, "", path, index);
result = Parser.frame(result, this.#label, indentation);
return result
}
}
/**
* @template T
* @extends Parser
*/
class LazyParser extends Parser {
#parser
/** @type {Parser} */
#resolvedPraser
/** @param {() => Parsernostrum} parser */
constructor(parser) {
super();
this.#parser = parser;
}
/**
* @param {PathNode} path
* @param {Number} index
*/
makePath(path, index) {
return path
}
/**
* @param {Context} context
* @param {PathNode} path
*/
isHighlighted(context, path) {
if (super.isHighlighted(context, path)) {
// If LazyParser is highlighted, then highlight its child
const childrenPath = { parent: path, parser: this.#resolvedPraser, index: 0 };
context.highlighted = context.highlighted instanceof Parser ? this.#resolvedPraser : childrenPath;
}
return false
}
resolve() {
if (!this.#resolvedPraser) {
this.#resolvedPraser = this.#parser().getParser();
}
return this.#resolvedPraser
}
/**
* @param {Context} context
* @param {Number} position
* @param {PathNode} path
* @param {Number} index
* @returns {Result}
*/
parse(context, position, path, index) {
this.resolve();
this.parse = this.#resolvedPraser.parse.bind(this.#resolvedPraser);
return this.parse(context, position, path, index)
}
/**
* @protected
* @param {Context} context
* @param {String} indentation
* @param {PathNode} path
* @param {Number} index
*/
doToString(context, indentation, path, index) {
this.resolve();
this.doToString = this.#resolvedPraser.toString.bind(this.#resolvedPraser);
return this.doToString(context, indentation, path, index)
}
}
/** @extends Parser<""> */
class Lookahead extends Parser {
#parser
get parser() {
return this.#parser
}
#type
get type() {
return this.#type
}
/**
* @readonly
* @enum {String}
*/
static Type = {
NEGATIVE_AHEAD: "?!",
NEGATIVE_BEHIND: "?}
*/
parse(context, position, path, index) {
path = this.makePath(path, index);
let result = this.#parser.parse(context, position, path, 0);
result = result.status == (this.#type === Lookahead.Type.POSITIVE_AHEAD)
? Reply.makeSuccess(position, "", path, position)
: Reply.makeFailure();
return result
}
/**
* @protected
* @param {Context} context
* @param {String} indentation
* @param {PathNode} path
* @param {Number} index
*/
doToString(context, indentation, path, index) {
return "(" + this.#type + this.#parser.toString(context, indentation, path, 0) + ")"
}
}
/**
* @template T
* @extends Parser
*/
class RegExpParser extends Parser {
/** @type {RegExp} */
#regexp
get regexp() {
return this.#regexp
}
/** @type {RegExp} */
#anchoredRegexp
#matchMapper
static #createEscapeable = character => String.raw`[^${character}\\]*(?:\\.[^${character}\\]*)*`
static #numberRegex = /[-\+]?(?:\d*\.)?\d+/
static common = {
number: new RegExp(this.#numberRegex.source + String.raw`(?!\.)`),
numberInteger: /[\-\+]?\d+(?!\.\d)/,
numberNatural: /\d+/,
numberExponential: new RegExp(this.#numberRegex.source + String.raw`(?:[eE][\+\-]?\d+)?(?!\.)`),
numberUnit: /\+?(?:0(?:\.\d+)?|1(?:\.0+)?)(?![\.\d])/,
numberByte: /0*(?:25[0-5]|2[0-4]\d|1?\d?\d)(?!\d|\.)/,
whitespace: /\s+/,
whitespaceOpt: /\s*/,
whitespaceInline: /[^\S\n]+/,
whitespaceInlineOpt: /[^\S\n]*/,
whitespaceMultiline: /\s*?\n\s*/,
doubleQuotedString: new RegExp(`"(${this.#createEscapeable('"')})"`),
singleQuotedString: new RegExp(`'(${this.#createEscapeable("'")})'`),
backtickQuotedString: new RegExp("`(" + this.#createEscapeable("`") + ")`"),
}
/**
* @param {RegExp} regexp
* @param {(match: RegExpExecArray) => T} matchMapper
*/
constructor(regexp, matchMapper) {
super();
this.#regexp = regexp;
this.#anchoredRegexp = new RegExp(`^(?:${regexp.source})`, regexp.flags);
this.#matchMapper = matchMapper;
}
/**
* @param {Context} context
* @param {Number} position
* @param {PathNode} path
* @param {Number} index
* @returns {Result}
*/
parse(context, position, path, index) {
path = this.makePath(path, index);
const match = this.#anchoredRegexp.exec(context.input.substring(position));
if (match) {
position += match[0].length;
}
const result = match
? Reply.makeSuccess(position, this.#matchMapper(match), path, position)
: Reply.makeFailure();
return result
}
/**
* @protected
* @param {Context} context
* @param {String} indentation
* @param {PathNode} path
* @param {Number} index
*/
doToString(context, indentation, path, index) {
let result = "/" + this.#regexp.source + "/";
const shortname = Object.entries(RegExpParser.common).find(([k, v]) => v.source === this.#regexp.source)?.[0];
if (shortname) {
result = "P." + shortname;
}
return result
}
}
/**
* @template S
* @template T
* @extends Parser
*/
class MapParser extends Parser {
#parser
get parser() {
return this.#parser
}
#mapper
get mapper() {
return this.#mapper
}
/**
* @param {Parser} parser
* @param {(v: S) => T} mapper
*/
constructor(parser, mapper) {
super();
this.#parser = parser;
this.#mapper = mapper;
}
/**
* @param {Context} context
* @param {PathNode} path
*/
isHighlighted(context, path) {
if (super.isHighlighted(context, path)) {
// If MapParser is highlighted, then highlight its child
const childrenPath = { parent: path, parser: this.#parser, index: 0 };
context.highlighted = context.highlighted instanceof Parser ? this.#parser : childrenPath;
}
return false
}
/**
* @param {Context} context
* @param {Number} position
* @param {PathNode} path
* @param {Number} index
* @returns {Result}
*/
parse(context, position, path, index) {
path = this.makePath(path, index);
// @ts-expect-error
const result = /** @type {Result} */(this.#parser.parse(context, position, path, 0));
if (result.status) {
// @ts-expect-error
result.value = this.#mapper(result.value);
}
return result
}
/**
* @protected
* @param {Context} context
* @param {String} indentation
* @param {PathNode} path
* @param {Number} index
*/
doToString(context, indentation, path, index) {
let result = this.#parser.toString(context, indentation, path, 0);
if (this.#parser instanceof RegExpParser) {
if (Object.values(RegExpParser.common).includes(this.#parser.regexp)) {
if (
this.#parser.regexp === RegExpParser.common.numberInteger
&& this.#mapper === /** @type {(v: any) => BigInt} */(BigInt)
) {
return "P.numberBigInteger"
}
return result
}
}
let serializedMapper = this.#mapper.toString();
if (serializedMapper.length > 60 || serializedMapper.includes("\n")) {
serializedMapper = "(...) => { ... }";
}
result += ` -> map<${serializedMapper}>`;
return result
}
}
/** @extends {RegExpParser} */
class RegExpArrayParser extends RegExpParser {
/** @param {RegExpExecArray} match */
static #mapper = match => match
/** @param {RegExp} regexp */
constructor(regexp) {
super(regexp, RegExpArrayParser.#mapper);
}
}
/** @extends {RegExpParser} */
class RegExpValueParser extends RegExpParser {
/** @param {RegExp} regexp */
constructor(regexp, group = 0) {
super(regexp, match => match[group]);
}
}
/**
* @template {any[]} T
* @extends Parser
*/
class SequenceParser extends Parser {
#parsers
get parsers() {
return this.#parsers
}
/** @param {Parser[]} parsers */
constructor(...parsers) {
super();
this.#parsers = parsers;
}
/**
* @param {Context} context
* @param {Number} position
* @param {PathNode} path
* @param {Number} index
* @returns {Result}
*/
parse(context, position, path, index) {
path = this.makePath(path, index);
const value = /** @type {ParserValue} */(new Array(this.#parsers.length));
const result = Reply.makeSuccess(position, value);
for (let i = 0; i < this.#parsers.length; ++i) {
const outcome = this.#parsers[i].parse(context, result.position, path, i);
if (outcome.bestPosition > result.bestPosition) {
result.bestParser = outcome.bestParser;
result.bestPosition = outcome.bestPosition;
}
if (!outcome.status) {
result.status = false;
result.value = null;
break
}
result.value[i] = outcome.value;
result.position = outcome.position;
}
return result
}
/**
* @protected
* @param {Context} context
* @param {String} indentation
* @param {PathNode} path
* @param {Number} index
*/
doToString(context, indentation, path, index) {
const deeperIndentation = indentation + Parser.indentation;
const result = "SEQ<\n"
+ deeperIndentation
+ this.#parsers
.map((parser, index) => parser.toString(context, deeperIndentation, path, index))
.join("\n" + deeperIndentation)
+ "\n" + indentation + ">";
return result
}
}
/**
* @template T
* @extends Parser
*/
class TimesParser extends Parser {
#parser
get parser() {
return this.#parser
}
#min
get min() {
return this.#min
}
#max
get max() {
return this.#max
}
/** @param {Parser} parser */
constructor(parser, min = 0, max = Number.POSITIVE_INFINITY) {
super();
if (min > max) {
throw new Error("Min is greater than max")
}
this.#parser = parser;
this.#min = min;
this.#max = max;
}
/**
* @param {Context} context
* @param {Number} position
* @param {PathNode} path
* @param {Number} index
* @returns {Result}
*/
parse(context, position, path, index) {
path = this.makePath(path, index);
const value = /** @type {ParserValue[]} */([]);
const result = Reply.makeSuccess(position, value, path);
for (let i = 0; i < this.#max; ++i) {
const outcome = this.#parser.parse(context, result.position, path, 0);
if (outcome.bestPosition > result.bestPosition) {
result.bestParser = outcome.bestParser;
result.bestPosition = outcome.bestPosition;
}
if (!outcome.status) {
if (i < this.#min) {
result.status = false;
result.value = null;
}
break
}
// @ts-expect-error
result.value.push(outcome.value);
result.position = outcome.position;
}
// @ts-expect-error
return result
}
/**
* @protected
* @param {Context} context
* @param {String} indentation
* @param {PathNode} path
* @param {Number} index
*/
doToString(context, indentation, path, index) {
let result = this.parser.toString(context, indentation, path, 0);
const serialized =
this.#min === 0 && this.#max === 1 ? "?"
: this.#min === 0 && this.#max === Number.POSITIVE_INFINITY ? "*"
: this.#min === 1 && this.#max === Number.POSITIVE_INFINITY ? "+"
: "{"
+ this.#min
+ (this.#min !== this.#max ? "," + this.#max : "")
+ "}";
result += serialized;
return result
}
}
/** @template T */
class Parsernostrum {
#parser
/** @type {(new (parser: Parser) => Parsernostrum) & typeof Parsernostrum} */
Self
static lineColumnFromOffset(string, offset) {
const lines = string.substring(0, offset).split('\n');
const line = lines.length;
const column = lines[lines.length - 1].length + 1;
return { line, column }
}
/** @param {[any, ...any] | RegExpExecArray} param0 */
static #firstElementGetter = ([v, _]) => v
/** @param {[any, any, ...any] | RegExpExecArray} param0 */
static #secondElementGetter = ([_, v]) => v
static #arrayFlatter = ([first, rest]) => [first, ...rest]
/**
* @template T
* @param {T} v
* @returns {T extends Array ? String : T}
*/
// @ts-expect-error
static #joiner = v => v instanceof Array ? v.join("") : v
// Prefedined parsers
/** Parser accepting any valid decimal, possibly signed number */
static number = this.reg(RegExpParser.common.number).map(Number)
/** Parser accepting any digits only number */
static numberInteger = this.reg(RegExpParser.common.numberInteger).map(Number)
/** Parser accepting any digits only number and returns a BigInt */
// @ts-expect-error
static numberBigInteger = this.reg(this.numberInteger.getParser().parser.regexp).map(BigInt)
/** Parser accepting any digits only number */
static numberNatural = this.reg(RegExpParser.common.numberNatural).map(Number)
/** Parser accepting any valid decimal, possibly signed, possibly in the exponential form number */
static numberExponential = this.reg(RegExpParser.common.numberExponential).map(Number)
/** Parser accepting any valid decimal number between 0 and 1 */
static numberUnit = this.reg(RegExpParser.common.numberUnit).map(Number)
/** Parser accepting any integer between 0 and 255 */
static numberByte = this.reg(RegExpParser.common.numberByte).map(Number)
/** Parser accepting whitespace */
static whitespace = this.reg(RegExpParser.common.whitespace)
/** Parser accepting whitespace */
static whitespaceOpt = this.reg(RegExpParser.common.whitespaceOpt)
/** Parser accepting whitespace that spans on a single line */
static whitespaceInline = this.reg(RegExpParser.common.whitespaceInline)
/** Parser accepting whitespace that spans on a single line */
static whitespaceInlineOpt = this.reg(RegExpParser.common.whitespaceInlineOpt)
/** Parser accepting whitespace that contains a list a newline */
static whitespaceMultiline = this.reg(RegExpParser.common.whitespaceMultiline)
/** Parser accepting a double quoted string and returns the content */
static doubleQuotedString = this.reg(RegExpParser.common.doubleQuotedString, 1)
/** Parser accepting a single quoted string and returns the content */
static singleQuotedString = this.reg(RegExpParser.common.singleQuotedString, 1)
/** Parser accepting a backtick quoted string and returns the content */
static backtickQuotedString = this.reg(RegExpParser.common.backtickQuotedString, 1)
/** @param {Parser} parser */
constructor(parser, optimized = false) {
this.#parser = parser;
}
/** @param {PathNode} path */
static #simplifyPath(path) {
/** @type {PathNode[]} */
const array = [];
while (path) {
array.push(path);
path = path.parent;
}
array.reverse();
/** @type {Map} */
let visited = new Map();
for (let i = 1; i < array.length; ++i) {
const existing = visited.get(array[i].current);
if (existing !== undefined) {
if (array[i + 1]) {
array[i + 1].parent = array[existing];
}
visited = new Map([...visited.entries()].filter(([parser, index]) => index <= existing || index > i));
visited.set(array[i].current, existing);
array.splice(existing + 1, i - existing);
i = existing;
} else {
visited.set(array[i].current, i);
}
}
return array[array.length - 1]
}
getParser() {
return this.#parser
}
/** @param {String} input */
run(input) {
const result = this.#parser.parse(Reply.makeContext(this, input), 0, Reply.makePathNode(), 0);
if (result.position !== input.length) {
result.status = false;
}
return /** @type {Result} */(result)
}
/**
* @param {String} input
* @throws {Error} when the parser fails to match
*/
parse(input, printParser = true) {
const result = this.run(input);
if (result.status) {
return result.value
}
const chunkLength = 60;
const chunkRange = /** @type {[Number, Number]} */(
[Math.ceil(chunkLength / 2), Math.floor(chunkLength / 2)]
);
const position = Parsernostrum.lineColumnFromOffset(input, result.bestPosition);
let bestPosition = result.bestPosition;
const inlineInput = input.replaceAll(
/^(\s)+|\s{6,}|\s*?\n\s*/g,
(m, startingSpace, offset) => {
let replaced = startingSpace ? "..." : " ... ";
if (offset <= result.bestPosition) {
if (result.bestPosition < offset + m.length) {
bestPosition -= result.bestPosition - offset;
} else {
bestPosition -= m.length - replaced.length;
}
}
return replaced
}
);
const string = inlineInput.substring(0, chunkLength).trimEnd();
const leadingWhitespaceLength = Math.min(
input.substring(result.bestPosition - chunkRange[0]).match(/^\s*/)[0].length,
chunkRange[0] - 1,
);
let offset = Math.min(bestPosition, chunkRange[0] - leadingWhitespaceLength);
chunkRange[0] = Math.max(0, bestPosition - chunkRange[0]) + leadingWhitespaceLength;
chunkRange[1] = Math.min(input.length, chunkRange[0] + chunkLength);
let segment = inlineInput.substring(...chunkRange);
if (chunkRange[0] > 0) {
segment = "..." + segment;
offset += 3;
}
if (chunkRange[1] < inlineInput.length - 1) {
segment = segment + "...";
}
const bestParser = this.toString(Parser.indentation, true, Parsernostrum.#simplifyPath(result.bestParser));
throw new Error(
`Could not parse: ${string}\n\n`
+ `Input: ${segment}\n`
+ " " + " ".repeat(offset)
+ `^ From here (line: ${position.line}, `
+ `column: ${position.column}, `
+ `offset: ${result.bestPosition})${result.bestPosition === input.length ? ", end of string" : ""}\n`
+ (printParser
? "\n"
+ (result.bestParser ? "Last valid parser matched:" : "No parser matched:")
+ bestParser
+ "\n"
: ""
)
)
}
// Parsers
/**
* @template {String} S
* @param {S} value
*/
static str(value) {
return new this(new StringParser(value))
}
/** @param {RegExp} value */
static reg(value, group = 0) {
return new this(new RegExpValueParser(value, group))
}
/** @param {RegExp} value */
static regArray(value) {
return new this(new RegExpArrayParser(value))
}
static success() {
return new this(SuccessParser.instance)
}
static failure() {
return new this(FailureParser.instance)
}
// Combinators
/**
* @template {Parsernostrum[]} P
* @param {P} parsers
* @returns {Parsernostrum>}
*/
static seq(...parsers) {
return new this(new SequenceParser(...parsers.map(p => p.getParser())))
}
/**
* @template {Parsernostrum[]} P
* @param {P} parsers
* @returns {Parsernostrum>>}
*/
static alt(...parsers) {
return new this(new AlternativeParser(...parsers.map(p => p.getParser())))
}
/**
* @template {Parsernostrum} P
* @param {P} parser
*/
static lookahead(parser) {
return new this(new Lookahead(parser.getParser(), Lookahead.Type.POSITIVE_AHEAD))
}
/**
* @template {Parsernostrum} P
* @param {() => P} parser
* @returns {Parsernostrum>}
*/
static lazy(parser) {
return new this(new LazyParser(parser))
}
/** @param {Number} min */
times(min, max = min) {
return new Parsernostrum(new TimesParser(this.#parser, min, max))
}
many() {
return this.times(0, Number.POSITIVE_INFINITY)
}
/** @param {Number} n */
atLeast(n) {
return this.times(n, Number.POSITIVE_INFINITY)
}
/** @param {Number} n */
atMost(n) {
return this.times(0, n)
}
/**
* @param {any} emptyResult
* @returns {Parsernostrum}
*/
opt(emptyResult = "") {
let success = Parsernostrum.success();
if (emptyResult !== "") {
success = success.map(() => emptyResult);
}
// @ts-expect-error
return Parsernostrum.alt(this, success)
}
/**
* @template {Parsernostrum} P
* @param {P} separator
*/
sepBy(separator, atLeast = 1, allowTrailing = false) {
let result = Parsernostrum.seq(
this,
Parsernostrum.seq(separator, this).map(Parsernostrum.#secondElementGetter).atLeast(atLeast - 1),
...(allowTrailing ? [separator.opt([])] : [])
).map(Parsernostrum.#arrayFlatter);
if (atLeast === 0) {
result = result.opt([]);
}
return result
}
skipSpace() {
return Parsernostrum.seq(this, Parsernostrum.whitespaceOpt).map(Parsernostrum.#firstElementGetter)
}
/**
* @template R
* @param {(v: T) => R} fn
* @returns {Parsernostrum}
*/
map(fn) {
return new Parsernostrum(new MapParser(this.#parser, fn))
}
/**
* @template {Parsernostrum} P
* @param {(v: T, input: String, position: Number) => P} fn
* @returns {P}
*/
chain(fn) {
// @ts-expect-error
return new Parsernostrum(new ChainedParser(this.#parser, fn))
}
/**
* @param {(v: T, input: String, position: Number) => boolean} fn
* @return {Parsernostrum}
*/
assert(fn) {
return this.chain((v, input, position) => fn(v, input, position)
? Parsernostrum.success().map(() => v)
: Parsernostrum.failure()
)
}
join(value = "") {
return this.map(Parsernostrum.#joiner)
}
/** @return {Parsernostrum} */
label(value = "") {
return new Parsernostrum(new Label(this.#parser, value))
}
/** @param {Parsernostrum | Parser | PathNode} highlight */
toString(indentation = "", newline = false, highlight = null) {
if (highlight instanceof Parsernostrum) {
highlight = highlight.getParser();
}
const context = Reply.makeContext(this, "");
context.highlighted = highlight;
const path = Reply.makePathNode();
return (newline ? "\n" + indentation : "") + this.#parser.toString(context, indentation, path)
}
}
/** @abstract */
class IEntity {
/** @type {(v: String) => String} */
static same = v => v
/** @type {(entity: IEntity, serialized: String) => String} */
static notWrapped = (entity, serialized) => serialized
/** @type {(entity: IEntity, serialized: String) => String} */
static defaultWrapped = (entity, serialized) => `${entity.#lookbehind}(${serialized})`
static wrap = this.defaultWrapped
static attributeSeparator = ","
static keySeparator = "="
/** @type {(k: String) => String} */
static printKey = k => k
static grammar = Parsernostrum.lazy(() => this.createGrammar())
/** @type {P} */
static unknownEntityGrammar
static unknownEntity
/** @type {{ [key: String]: typeof IEntity }} */
static attributes = {}
/** @type {String | String[]} */
static lookbehind = ""
/** @type {(type: typeof IEntity) => InstanceType} */
static default
static nullable = false
static ignored = false // Never serialize or deserialize
static serialized = false // Value is written and read as string
static expected = false // Must be there
static inlined = false // The key is a subobject or array and printed as inlined (A.B=123, A(0)=123)
/** @type {Boolean} */
static quoted // Key is serialized with quotes
static silent = false // Do not serialize if default
static trailing = false // Add attribute separator after the last attribute when serializing
/** @type {String[]} */
#keys
get keys() {
return this.#keys ?? Object.keys(this)
}
set keys(value) {
this.#keys = [... new Set(value)];
}
// @ts-expect-error
#lookbehind = /** @type {String} */(this.constructor.lookbehind)
get lookbehind() {
return this.#lookbehind.trim()
}
set lookbehind(value) {
this.#lookbehind = value;
}
#ignored = /** @type {typeof IEntity} */(this.constructor).ignored
get ignored() {
return this.#ignored
}
set ignored(value) {
this.#ignored = value;
}
#inlined = /** @type {typeof IEntity} */(this.constructor).inlined
get inlined() {
return this.#inlined
}
set inlined(value) {
this.#inlined = value;
}
#quoted
get quoted() {
return this.#quoted ?? /** @type {typeof IEntity} */(this.constructor).quoted ?? false
}
set quoted(value) {
this.#quoted = value;
}
/** @type {Boolean} */
#trailing
get trailing() {
return this.#trailing ?? /** @type {typeof IEntity} */(this.constructor).trailing ?? false
}
set trailing(value) {
this.#trailing = value;
}
constructor(values = {}) {
const attributes = /** @type {typeof IEntity} */(this.constructor).attributes;
const keys = Utility.mergeArrays(
Object.keys(values),
Object.entries(attributes).filter(([k, v]) => v.default !== undefined).map(([k, v]) => k)
);
for (const key of keys) {
if (values[key] !== undefined) {
if (values[key].constructor === Object) {
// It is part of a nested key (words separated by ".")
values[key] = new (
attributes[key] !== undefined ? attributes[key] : IEntity.unknownEntity
)(values[key]);
}
const computedEntity = /** @type {ComputedTypeEntityConstructor} */(attributes[key]);
this[key] = values[key];
if (computedEntity?.compute) {
/** @type {typeof IEntity} */
const actualEntity = computedEntity.compute(this);
const parsed = actualEntity.grammar.run(values[key].toString());
if (parsed.status) {
this[key] = parsed.value;
}
}
continue
}
const attribute = attributes[key];
if (attribute.default !== undefined) {
this[key] = attribute.default(attribute);
continue
}
}
}
/**
* @protected
* @returns {P}
*/
static createGrammar() {
return this.unknownEntityGrammar
}
static actualClass() {
let self = this;
while (!self.name) {
self = Object.getPrototypeOf(self);
}
return self
}
static className() {
return this.actualClass().name
}
/**
* @protected
* @template {typeof IEntity} T
* @this {T}
* @returns {T}
*/
static asUniqueClass(alwaysCreate = false) {
let result = this;
if (this.name.length || alwaysCreate) {
// @ts-expect-error
result = (() => class extends this { })(); // Comes from a lambda otherwise the class will have name "result"
result.grammar = result.createGrammar(); // Reassign grammar to capture the correct this from subclass
}
return result
}
/**
* @template {typeof IEntity} T
* @this {T}
* @param {String} value
*/
static withLookbehind(value) {
const result = this.asUniqueClass();
result.lookbehind = value;
return result
}
/**
* @template {typeof IEntity} T
* @this {T}
* @param {(type: T) => (InstanceType | NullEntity)} value
* @returns {T}
*/
static withDefault(value = type => new type()) {
const result = this.asUniqueClass();
result.default = value;
return result
}
/**
* @template {typeof IEntity} T
* @this {T}
*/
static flagNullable(value = true) {
const result = this.asUniqueClass();
result.nullable = value;
return result
}
/**
* @template {typeof IEntity} T
* @this {T}
*/
static flagIgnored(value = true) {
const result = this.asUniqueClass();
result.ignored = value;
return result
}
/**
* @template {typeof IEntity} T
* @this {T}
*/
static flagSerialized(value = true) {
const result = this.asUniqueClass();
result.serialized = value;
return result
}
/**
* @template {typeof IEntity} T
* @this {T}
*/
static flagInlined(value = true) {
const result = this.asUniqueClass();
result.inlined = value;
return result
}
/**
* @template {typeof IEntity} T
* @this {T}
*/
static flagQuoted(value = true) {
const result = this.asUniqueClass();
result.quoted = value;
return result
}
/**
* @template {typeof IEntity} T
* @this {T}
*/
static flagSilent(value = true) {
const result = this.asUniqueClass();
result.silent = value;
return result
}
/**
* @template {typeof IEntity} T
* @this {T}
*/
static flagTrailing(value = true) {
const result = this.asUniqueClass();
result.trailing = value;
return result
}
/**
* @protected
* @param {String} string
*/
static asSerializedString(string) {
return `"${string.replaceAll(/(?<=(?:[^\\]|^)(?:\\\\)*?)"/g, '\\"')}"`
}
/** @param {String} key */
showProperty(key) {
/** @type {IEntity} */
let value = this[key];
const valueType = /** @type {typeof IEntity} */(value.constructor);
if (valueType.silent && valueType.default !== undefined) {
if (valueType["#default"] === undefined) {
valueType["#default"] = valueType.default(valueType);
}
const defaultValue = valueType["#default"];
return !value.equals(defaultValue)
}
return true
}
/**
*
* @param {String} attributeName
* @param {(v: any) => void} callback
*/
listenAttribute(attributeName, callback) {
const descriptor = Object.getOwnPropertyDescriptor(this, attributeName);
const setter = descriptor.set;
if (setter) {
descriptor.set = v => {
setter(v);
callback(v);
};
Object.defineProperties(this, { [attributeName]: descriptor });
} else if (descriptor.value) {
Object.defineProperties(this, {
["#" + attributeName]: {
value: descriptor.value,
writable: true,
enumerable: false,
},
[attributeName]: {
enumerable: true,
get() {
return this["#" + attributeName]
},
set(v) {
callback(v);
this["#" + attributeName] = v;
},
},
});
}
}
/** @this {IEntity | Array} */
doSerialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
printKey = Self.printKey,
keySeparator = Self.keySeparator,
attributeSeparator = Self.attributeSeparator,
wrap = Self.wrap,
) {
const isSelfOverriden = Self !== this.constructor;
let result = "";
let first = true;
const keys = this instanceof IEntity ? this.keys : Object.keys(this);
for (const key of keys) {
/** @type {IEntity} */
const value = this[key];
const valueType = /** @type {typeof IEntity} */(value?.constructor);
if (value === undefined || this instanceof IEntity && !this.showProperty(key)) {
continue
}
if (first) {
first = false;
} else {
result += attributeSeparator;
}
let keyValue = this instanceof Array ? `(${key})` : key;
if (keyValue.length && (Self.attributes[key]?.quoted || value.quoted)) {
keyValue = `"${keyValue}"`;
}
if (value.inlined) {
const inlinedPrintKey = valueType.className() === "ArrayEntity"
? k => printKey(`${keyValue}${k}`)
: k => printKey(`${keyValue}.${k}`);
result += value.serialize(
insideString,
indentation,
undefined,
inlinedPrintKey,
keySeparator,
attributeSeparator,
Self.notWrapped
);
continue
}
keyValue = printKey(keyValue);
if (keyValue.length) {
result += (attributeSeparator.includes("\n") ? indentation : "") + keyValue + keySeparator;
}
let serialization = value?.serialize(insideString, indentation);
result += serialization;
}
if (this instanceof IEntity && (isSelfOverriden && Self.trailing || this.trailing) && result.length) {
result += attributeSeparator;
}
return wrap(/** @type {IEntity} */(this), result)
}
/** @this {IEntity | Array} */
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
printKey = Self.printKey,
keySeparator = Self.keySeparator,
attributeSeparator = Self.attributeSeparator,
wrap = Self.wrap,
) {
Self !== this.constructor;
let result = this instanceof Array
? IEntity.prototype.doSerialize.bind(this)(insideString, indentation, Self, printKey, keySeparator, attributeSeparator, wrap)
: this.doSerialize(insideString, indentation, Self, printKey, keySeparator, attributeSeparator, wrap);
if (Self.serialized) {
result = IEntity.asSerializedString(result);
}
return result
}
equals(other) {
if (!(other instanceof IEntity)) {
return false
}
const thisKeys = Object.keys(this);
const otherKeys = Object.keys(other);
const thisType = /** @type {typeof IEntity} */(this.constructor).actualClass();
const otherType = /** @type {typeof IEntity} */(other.constructor).actualClass();
if (
thisKeys.length !== otherKeys.length
|| this.lookbehind != other.lookbehind
|| !(other instanceof thisType) && !(this instanceof otherType)
) {
return false
}
for (let i = 0; i < thisKeys.length; ++i) {
const k = thisKeys[i];
if (!otherKeys.includes(k)) {
return false
}
const a = this[k];
const b = other[k];
if (a instanceof IEntity) {
if (!a.equals(b)) {
return false
}
} else if (a instanceof Array && b instanceof Array) {
if (a.length !== b.length) {
return false
}
for (let j = 0; j < a.length; ++j) {
if (!(a[j] instanceof IEntity && a[j].equals(b[j])) && a[j] !== b[j]) {
return false
}
}
} else {
if (a !== b) {
return false
}
}
}
return true
}
/** @returns {IEntity | Boolean | Number | String | BigInt | (IEntity | Boolean | Number | String | BigInt)[]} */
valueOf() {
return this
}
}
class BooleanEntity extends IEntity {
static grammar = this.createGrammar()
static booleanConverter = {
fromAttribute: (value, type) => {
},
toAttribute: (value, type) => {
if (value === true) {
return "true"
}
if (value === false) {
return "false"
}
return ""
}
}
#uppercase = true
get uppercase() {
return this.#uppercase
}
set uppercase(value) {
this.#uppercase = value;
}
/** @returns {P} */
static createGrammar() {
return Parsernostrum.regArray(/(true)|(True)|(false)|(False)/)
.map(v => {
const result = (v[1] ?? v[2]) ? new this(true) : new this(false);
result.uppercase = (v[2] ?? v[4]) !== undefined;
return result
})
.label("BooleanEntity")
}
constructor(value = false) {
super();
this.value = value;
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
) {
let result = this.value
? this.#uppercase ? "True" : "true"
: this.#uppercase ? "False" : "false";
if (Self.serialized) {
result = `"${result}"`;
}
return result
}
valueOf() {
return this.value
}
}
class ElementFactory {
/** @type {Map} */
static #elementConstructors = new Map()
/**
* @param {String} tagName
* @param {IElementConstructor} entityConstructor
*/
static registerElement(tagName, entityConstructor) {
ElementFactory.#elementConstructors.set(tagName, entityConstructor);
}
/** @param {String} tagName */
static getConstructor(tagName) {
return ElementFactory.#elementConstructors.get(tagName)
}
}
/** @template {(typeof IEntity)[]} T */
class AlternativesEntity extends IEntity {
/** @type {(typeof IEntity)[]} */
static alternatives = []
static className() {
let result = super.className();
if (this.alternatives.length) {
result += ".accepting(" + this.alternatives.map(v => v.className()).join(", ") + ")";
}
return result
}
static createGrammar() {
const grammars = this.alternatives.map(entity => entity.grammar);
if (this.alternatives.length == 0 || grammars.includes(this.unknownEntityGrammar)) {
return this.unknownEntityGrammar
}
return Parsernostrum.alt(...grammars)
}
/**
* @template {(typeof IEntity)[]} Types
* @param {Types} types
*/
static accepting(...types) {
const result = /** @type {typeof AlternativesEntity & { alternatives: Types }} */(
this.asUniqueClass()
);
result.alternatives = types;
result.grammar = result.createGrammar();
return result
}
}
class Grammar {
/** @type {String} */
// @ts-expect-error
static numberRegexSource = Parsernostrum.number.getParser().parser.regexp.source
static separatedBy = (source, separator, min = 1) =>
new RegExp(
source + "(?:" + separator + source + ")"
+ (min === 1 ? "*" : min === 2 ? "+" : `{${min},}`)
)
static Regex = class {
static HexDigit = /[0-9a-fA-F]/
static InsideString = /(?:[^"\\]|\\.)*/
static InsideSingleQuotedString = /(?:[^'\\]|\\.)*/
static Integer = /[\-\+]?\d+(?!\d|\.)/
static Number = /[-\+]?(?:\d*\.)?\d+(?!\d|\.)/
static RealUnit = /\+?(?:0(?:\.\d+)?|1(?:\.0+)?)(?![\.\d])/ // A number between 0 and 1 included
static Word = Grammar.separatedBy("[a-zA-Z]", "_")
static Symbol = /[a-zA-Z_]\w*/
static DotSeparatedSymbols = Grammar.separatedBy(this.Symbol.source, "\\.")
static MultipleWordsSymbols = Grammar.separatedBy(this.Symbol.source, "(?:\\.|\\ +)")
static PathFragment = Grammar.separatedBy(this.Symbol.source, "[\\.:]")
static PathSpaceFragment = Grammar.separatedBy(this.Symbol.source, "[\\.:\\ ]")
static Path = new RegExp(`(?:\\/${this.PathFragment.source}){2,}`) // Multiple (2+) /PathFragment
}
/* --- Primitive --- */
static null = Parsernostrum.reg(/\(\s*\)/).map(() => null)
static true = Parsernostrum.reg(/true/i).map(() => true)
static false = Parsernostrum.reg(/false/i).map(() => false)
static number = Parsernostrum.regArray(
// @ts-expect-error
new RegExp(`(${Parsernostrum.number.getParser().parser.regexp.source})|(\\+?inf)|(-inf)`)
).map(([_0, n, plusInf, minusInf]) => n ? Number(n) : plusInf ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY)
// @ts-expect-error
static bigInt = Parsernostrum.reg(new RegExp(Parsernostrum.number.getParser().parser.regexp.source)).map(BigInt)
.map(result =>
result[2] !== undefined
? Number.POSITIVE_INFINITY
: result[3] !== undefined
? Number.NEGATIVE_INFINITY
: Number(result[1])
)
static naturalNumber = Parsernostrum.lazy(() => Parsernostrum.reg(/\d+/).map(Number))
static string = Parsernostrum.doubleQuotedString.map(insideString => Utility.unescapeString(insideString))
/* --- Fragment --- */
static colorValue = Parsernostrum.numberByte
static word = Parsernostrum.reg(Grammar.Regex.Word)
static symbol = Parsernostrum.reg(Grammar.Regex.Symbol)
static symbolQuoted = Parsernostrum.reg(new RegExp('"(' + Grammar.Regex.Symbol.source + ')"'), 1)
static attributeName = Parsernostrum.reg(Grammar.Regex.DotSeparatedSymbols)
static attributeNameQuoted = Parsernostrum.reg(new RegExp('"(' + Grammar.Regex.InsideString.source + ')"'), 1)
static guid = Parsernostrum.reg(new RegExp(`${Grammar.Regex.HexDigit.source}{32}`))
static commaSeparation = Parsernostrum.reg(/\s*,\s*(?!\))/)
static commaOrSpaceSeparation = Parsernostrum.reg(/\s*,\s*(?!\))|\s+/)
static equalSeparation = Parsernostrum.reg(/\s*=\s*/)
static hexColorChannel = Parsernostrum.reg(new RegExp(Grammar.Regex.HexDigit.source + "{2}"))
/* --- Factory --- */
/**
* @param {typeof IEntity} entityType
* @param {String[]} key
* @returns {typeof IEntity}
*/
static getAttribute(entityType, [key, ...keys]) {
const attribute = entityType?.attributes?.[key];
if (!attribute) {
return
}
if (attribute.prototype instanceof AlternativesEntity) {
for (const alternative of /** @type {typeof AlternativesEntity} */(attribute).alternatives) {
const candidate = this.getAttribute(alternative, keys);
if (candidate) {
return candidate
}
}
}
if (keys.length > 0) {
return this.getAttribute(attribute, keys)
}
return attribute
}
/** @param {typeof IEntity} entityType */
static createAttributeGrammar(
entityType,
attributeNameGrammar = this.attributeName,
valueSeparator = this.equalSeparation,
handleObjectSet = (values, attributeKey, attributeValue) => { },
) {
return Parsernostrum.seq(
attributeNameGrammar,
valueSeparator,
).chain(([attributeName, _1]) => {
const attributeKey = attributeName.split(Configuration.keysSeparator);
const attributeValue = this.getAttribute(entityType, attributeKey);
const grammar = attributeValue ? attributeValue.grammar : IEntity.unknownEntityGrammar;
const inlined = attributeKey.length > 1;
return grammar.map(attributeValue =>
values => {
Utility.objectSet(values, attributeKey, attributeValue);
attributeKey.reduce(
(acc, cur, i) => {
acc[cur]["inlined"] = inlined && i < attributeKey.length - 1;
return acc[cur]
},
values
);
handleObjectSet(values, attributeKey, attributeValue);
}
)
})
}
/**
* @template {typeof IEntity & (new (...values: any) => InstanceType)} T
* @param {T} entityType
* @param {Number} completeness
* @return {Parsernostrum>}
*/
static createEntityGrammar(entityType, entriesSeparator = this.commaSeparation, completeness = null, minKeys = 1) {
const lookbehind = entityType.lookbehind instanceof Array
? entityType.lookbehind.join("|")
: entityType.lookbehind;
return Parsernostrum.seq(
Parsernostrum.reg(new RegExp(String.raw`(${lookbehind}\s*)\(\s*`), 1),
this.createAttributeGrammar(entityType).sepBy(entriesSeparator, minKeys),
Parsernostrum.reg(/\s*(,\s*)?\)/, 1), // optional trailing comma
)
.map(([lookbehind, attributes, trailing]) => {
let values = {};
if (lookbehind.length) {
values["lookbehind"] = lookbehind;
}
attributes.forEach(attributeSetter => attributeSetter(values));
values["trailing"] = trailing !== undefined;
return values
})
// Decide if we accept the entity or not. It is accepted if it doesn't have too many unexpected keys
.chain(values => {
if (entityType.lookbehind instanceof Array || entityType.lookbehind !== lookbehind) {
entityType = entityType.withLookbehind(lookbehind);
}
const keys = Object.keys(values);
const expectedKeys = Object.keys(entityType.attributes);
return completeness != null
? Parsernostrum.success()
.assert(
v => keys.filter(k => expectedKeys.includes(k)).length / expectedKeys.length >= completeness
)
.map(() => new entityType(values))
: Parsernostrum.success().map(() => new entityType(values))
})
}
}
class ColorChannelEntity extends IEntity {
static grammar = this.createGrammar()
constructor(value = 0) {
super();
this.value = value;
}
/** @returns {P} */
static createGrammar() {
return Parsernostrum.number.map(v => new this(v))
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
) {
let result = this.value.toFixed(6);
if (Self.serialized) {
result = `"${result}"`;
}
return result
}
valueOf() {
return this.value
}
toString() {
return this.value.toString()
}
}
class LinearColorEntity extends IEntity {
static attributes = {
...super.attributes,
R: ColorChannelEntity.withDefault(),
G: ColorChannelEntity.withDefault(),
B: ColorChannelEntity.withDefault(),
A: ColorChannelEntity.withDefault(type => new type(1)),
}
static grammar = this.createGrammar()
#H = new ColorChannelEntity()
get H() {
return this.#H
}
set H(value) {
this.#H = value;
}
#S = new ColorChannelEntity()
get S() {
return this.#S
}
set S(value) {
this.#S = value;
}
#V = new ColorChannelEntity()
get V() {
return this.#V
}
set V(value) {
this.#V = value;
}
constructor(values) {
super(values);
if (values instanceof Array) {
values = {
R: values[0] ?? 0,
G: values[1] ?? 0,
B: values[2] ?? 0,
A: values[3] ?? 1,
};
}
/** @type {InstanceType} */ this.R;
/** @type {InstanceType} */ this.G;
/** @type {InstanceType} */ this.B;
/** @type {InstanceType} */ this.A;
this.#updateHSV();
}
/** @returns {P} */
static createGrammar() {
return Grammar.createEntityGrammar(this, Grammar.commaSeparation, 0.5).label("LinearColorEntity")
}
/** @param {LinearColorEntity} value */
static printLinearColor(value) {
return `${Math.round(value.R.valueOf() * 255)}, ${Math.round(value.G.valueOf() * 255)}, ${Math.round(value.B.valueOf() * 255)}`
}
/** @param {Number} x */
static linearToSRGB(x) {
if (x <= 0) {
return 0
} else if (x >= 1) {
return 1
} else if (x < 0.0031308) {
return x * 12.92
} else {
return Math.pow(x, 1 / 2.4) * 1.055 - 0.055
}
}
/** @param {Number} x */
static sRGBtoLinear(x) {
if (x <= 0) {
return 0
} else if (x >= 1) {
return 1
} else if (x < 0.04045) {
return x / 12.92
} else {
return Math.pow((x + 0.055) / 1.055, 2.4)
}
}
static getWhite() {
return new LinearColorEntity({
R: new ColorChannelEntity(1),
G: new ColorChannelEntity(1),
B: new ColorChannelEntity(1),
})
}
static getLinearColorFromHexGrammar() {
const hexDigit = /[0-9a-fA-F]/;
return Parsernostrum.regArray(new RegExp(
"#(" + hexDigit.source + "{2})"
+ "(" + hexDigit.source + "{2})"
+ "(" + hexDigit.source + "{2})"
+ "(" + hexDigit.source + "{2})?"
)).map(([m, R, G, B, A]) => new this({
R: parseInt(R, 16) / 255,
G: parseInt(G, 16) / 255,
B: parseInt(B, 16) / 255,
A: parseInt(A ?? "FF", 16) / 255,
}))
}
static getLinearColorRGBListGrammar() {
return Parsernostrum.seq(
Parsernostrum.numberByte,
Grammar.commaSeparation,
Parsernostrum.numberByte,
Grammar.commaSeparation,
Parsernostrum.numberByte,
).map(([R, _1, G, _3, B]) => new this({
R: R / 255,
G: G / 255,
B: B / 255,
A: 1,
}))
}
static getLinearColorRGBGrammar() {
return Parsernostrum.seq(
Parsernostrum.reg(/rgb\s*\(\s*/),
this.getLinearColorRGBListGrammar(),
Parsernostrum.reg(/\s*\)/)
).map(([_0, linearColor, _2]) => linearColor)
}
static getLinearColorRGBAGrammar() {
return Parsernostrum.seq(
Parsernostrum.reg(/rgba\s*\(\s*/),
this.getLinearColorRGBListGrammar(),
Parsernostrum.reg(/\s*\)/)
).map(([_0, linearColor, _2]) => linearColor)
}
static getLinearColorFromAnyFormat() {
return Parsernostrum.alt(
this.getLinearColorFromHexGrammar(),
this.getLinearColorRGBAGrammar(),
this.getLinearColorRGBGrammar(),
this.getLinearColorRGBListGrammar(),
)
}
#updateHSV() {
const r = this.R.value;
const g = this.G.value;
const b = this.B.value;
if (Utility.approximatelyEqual(r, g) && Utility.approximatelyEqual(r, b) && Utility.approximatelyEqual(g, b)) {
this.S.value = 0;
this.V.value = r;
return
}
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const d = max - min;
let h;
switch (max) {
case min:
h = 0;
break
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break
case g:
h = (b - r) / d + 2;
break
case b:
h = (r - g) / d + 4;
break
}
h /= 6;
this.H.value = h;
this.S.value = max == 0 ? 0 : d / max;
this.V.value = max;
}
/**
* @param {Number} r
* @param {Number} g
* @param {Number} b
* @param {Number} a
*/
setFromRGBA(r, g, b, a = 1) {
this.R.value = r;
this.G.value = g;
this.B.value = b;
this.A.value = a;
this.#updateHSV();
}
/**
* @param {Number} h
* @param {Number} s
* @param {Number} v
* @param {Number} a
*/
setFromHSVA(h, s, v, a = 1) {
const i = Math.floor(h * 6);
const f = h * 6 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
const values = [v, q, p, p, t, v];
const [r, g, b] = [values[i % 6], values[(i + 4) % 6], values[(i + 2) % 6]];
this.R.value = r;
this.G.value = g;
this.B.value = b;
this.A.value = a;
this.H.value = h;
this.S.value = s;
this.V.value = v;
}
/**
* @param {Number} x
* @param {Number} y
* @param {Number} v
* @param {Number} a
*/
setFromWheelLocation(x, y, v, a) {
const [r, theta] = Utility.getPolarCoordinates(x, y, true);
this.setFromHSVA(1 - theta / (2 * Math.PI), r, v, a);
}
toDimmedColor(minV = 0) {
const result = new LinearColorEntity();
result.setFromRGBANumber(this.toNumber());
result.setFromHSVA(
result.H.value,
result.S.value * 0.6,
Math.pow(result.V.value + minV, 0.55) * 0.7
);
return result
}
toCSSRGBValues() {
const r = Math.round(this.R.value * 255);
const g = Math.round(this.G.value * 255);
const b = Math.round(this.B.value * 255);
return i$5`${r}, ${g}, ${b}`
}
toRGBA() {
return [
Math.round(this.R.value * 255),
Math.round(this.G.value * 255),
Math.round(this.B.value * 255),
Math.round(this.A.value * 255),
]
}
toSRGBA() {
return [
Math.round(LinearColorEntity.linearToSRGB(this.R.value) * 255),
Math.round(LinearColorEntity.linearToSRGB(this.G.value) * 255),
Math.round(LinearColorEntity.linearToSRGB(this.B.value) * 255),
Math.round(this.A.value * 255),
]
}
toRGBAString() {
return this
.toRGBA()
.map(v => v.toString(16).toUpperCase().padStart(2, "0"))
.join("")
}
toSRGBAString() {
return this
.toSRGBA()
.map(v => v.toString(16).toUpperCase().padStart(2, "0"))
.join("")
}
toHSVA() {
return [this.H.value, this.S.value, this.V.value, this.A.value]
}
toNumber() {
return (
Math.round(this.R.value * 0xff) << 24)
+ (Math.round(this.G.value * 0xff) << 16)
+ (Math.round(this.B.value * 0xff) << 8)
+ Math.round(this.A.value * 0xff)
}
/** @returns {[Number, Number, Number, Number]} */
toArray() {
return [this.R.value, this.G.value, this.B.value, this.A.value]
}
/** @param {Number} number */
setFromRGBANumber(number) {
this.A.value = (number & 0xff) / 0xff;
this.B.value = ((number >> 8) & 0xff) / 0xff;
this.G.value = ((number >> 16) & 0xff) / 0xff;
this.R.value = ((number >> 24) & 0xff) / 0xff;
this.#updateHSV();
}
/** @param {Number} number */
setFromSRGBANumber(number) {
this.A.value = (number & 0xff) / 0xff;
this.B.value = LinearColorEntity.sRGBtoLinear(((number >> 8) & 0xff) / 0xff);
this.G.value = LinearColorEntity.sRGBtoLinear(((number >> 16) & 0xff) / 0xff);
this.R.value = LinearColorEntity.sRGBtoLinear(((number >> 24) & 0xff) / 0xff);
this.#updateHSV();
}
toString() {
return LinearColorEntity.printLinearColor(this)
}
}
/** @param {ObjectEntity} entity */
function nodeColor(entity) {
switch (entity.getType()) {
case Configuration.paths.materialExpressionConstant2Vector:
case Configuration.paths.materialExpressionConstant3Vector:
case Configuration.paths.materialExpressionConstant4Vector:
return Configuration.nodeColors.yellow
case Configuration.paths.materialExpressionFunctionInput:
case Configuration.paths.materialExpressionTextureCoordinate:
case Configuration.paths.materialExpressionWorldPosition:
case Configuration.paths.pcgEditorGraphNodeInput:
case Configuration.paths.pcgEditorGraphNodeOutput:
return Configuration.nodeColors.red
case Configuration.paths.makeStruct:
return Configuration.nodeColors.darkBlue
case Configuration.paths.materialExpressionMaterialFunctionCall:
return Configuration.nodeColors.blue
case Configuration.paths.materialExpressionTextureSample:
return Configuration.nodeColors.darkTurquoise
case Configuration.paths.niagaraNodeInput:
switch (entity["Usage"]?.toString()) {
case "Attribute": return Configuration.nodeColors.intenseGreen
case "Parameter": return Configuration.nodeColors.red
case "RapidIterationParameter": return Configuration.nodeColors.black
case "SystemConstant": return Configuration.nodeColors.gray
case "TranslatorConstant": return Configuration.nodeColors.gray
default: return Configuration.nodeColors.red
}
}
switch (entity.getClass()) {
case Configuration.paths.callFunction:
return entity.bIsPureFunc?.valueOf()
? Configuration.nodeColors.green
: Configuration.nodeColors.blue
case Configuration.paths.niagaraNodeFunctionCall:
return Configuration.nodeColors.darkerBlue
case Configuration.paths.dynamicCast:
return Configuration.nodeColors.turquoise
case Configuration.paths.inputDebugKey:
case Configuration.paths.inputKey:
return Configuration.nodeColors.red
case Configuration.paths.createDelegate:
case Configuration.paths.enumLiteral:
case Configuration.paths.makeArray:
case Configuration.paths.makeMap:
case Configuration.paths.materialGraphNode:
case Configuration.paths.select:
return Configuration.nodeColors.green
case Configuration.paths.executionSequence:
case Configuration.paths.ifThenElse:
case Configuration.paths.macro:
case Configuration.paths.multiGate:
return Configuration.nodeColors.gray
case Configuration.paths.functionEntry:
case Configuration.paths.functionResult:
return Configuration.nodeColors.violet
case Configuration.paths.timeline:
return Configuration.nodeColors.yellow
}
if (entity.switchTarget()) {
return Configuration.nodeColors.lime
}
if (entity.isEvent()) {
return Configuration.nodeColors.red
}
if (entity.isComment()) {
return (entity.CommentColor ? entity.CommentColor : LinearColorEntity.getWhite())
.toDimmedColor()
.toCSSRGBValues()
}
const pcgSubobject = entity.getPcgSubobject();
if (pcgSubobject) {
if (pcgSubobject.NodeTitleColor) {
return pcgSubobject.NodeTitleColor.toDimmedColor(0.1).toCSSRGBValues()
}
switch (entity.PCGNode?.getName(true)) {
case "Branch":
case "Select":
return Configuration.nodeColors.intenseGreen
}
}
if (entity.bIsPureFunc?.valueOf()) {
return Configuration.nodeColors.green
}
if (entity["Input"]?.["Name"]) {
return Configuration.nodeColors.gray
}
return Configuration.nodeColors.blue
}
/** @template {typeof IEntity} T */
class MirroredEntity extends IEntity {
/** @type {typeof IEntity} */
static type
/** @param {() => InstanceType} getter */
constructor(getter = null) {
super();
const self = /** @type {typeof MirroredEntity} */(this.constructor);
getter ??= self.default !== undefined ? /** @type {MirroredEntity} */(self.default(self)).getter : getter;
this.getter = getter;
}
static createGrammar(elementGrammar = this.type?.grammar ?? Parsernostrum.lazy(() => this.unknownEntityGrammar)) {
return this.type?.grammar.map(v => new this(() => v))
}
/**
* @template {typeof IEntity} T
* @this {T}
* @param {(type: T) => (InstanceType | NullEntity)} value
* @returns {T}
*/
// @ts-expect-error
static withDefault(value = type => new type(() => new (type.type)())) {
// @ts-expect-error
return super.withDefault(value)
}
/**
* @template {typeof IEntity} T
* @param {T} type
*/
static of(type) {
const result = /** @type {{type: T, grammar: P> } & typeof MirroredEntity} */(
this.asUniqueClass()
);
result.type = type;
result.grammar = result.createGrammar();
return result
}
doSerialize(
insideString = false,
indentation = "",
Self = /** @type {typeof MirroredEntity} */(this.constructor),
printKey = Self.printKey,
keySeparator = Self.keySeparator,
attributeSeparator = Self.attributeSeparator,
wrap = Self.wrap,
) {
const value = this.getter();
return value.serialize(insideString, indentation, Self.type, printKey, keySeparator, attributeSeparator, wrap)
}
/** @param {IEntity} other */
equals(other) {
if (other instanceof MirroredEntity) {
other = other.getter?.();
}
return this.getter?.().equals(other)
}
/** @returns {InstanceType} */
valueOf(arg) {
// @ts-expect-error
return this.getter(arg).valueOf()
}
toString() {
return this.getter().toString()
}
}
class NumberEntity extends IEntity {
static numberRegexSource = String.raw`${Grammar.numberRegexSource}(?<=(?:\.(\d*0+))?)`
static grammar = this.createGrammar()
/** @type {Number} */
static precision // Can override this.precision
#precision
get precision() {
return /** @type {typeof NumberEntity} */(this.constructor).precision ?? this.#precision
}
set precision(value) {
this.#precision = value;
}
/**
* @protected
* @type {Number}
*/
_value
get value() {
return this._value
}
set value(value) {
if (value === -0) {
value = 0;
}
this._value = value;
}
constructor(value = 0, precision = null) {
super();
this.value = Number(value);
if (precision !== null) {
this.#precision = Number(precision);
}
}
/** @returns {P} */
static createGrammar() {
return Parsernostrum.regArray(
new RegExp(`(?${this.numberRegexSource})|(?\\+?inf)|(?-inf)`)
).map(({ 2: precision, groups: { n, posInf, negInf } }) => new this(
n ? Number(n) : posInf ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY,
precision?.length
)
).label("NumberEntity")
}
/**
* @template {typeof NumberEntity} T
* @this {T}
* @returns {T}
*/
static withPrecision(value = 0) {
const result = this.asUniqueClass();
result.precision = value;
return result
}
/** @param {Number} num */
static printNumber(num) {
if (num == Number.POSITIVE_INFINITY) {
return "inf"
} else if (num == Number.NEGATIVE_INFINITY) {
return "-inf"
}
return Utility.minDecimals(num)
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof NumberEntity} */(this.constructor),
) {
if (this.value === Number.POSITIVE_INFINITY) {
return "+inf"
}
if (this.value === Number.NEGATIVE_INFINITY) {
return "-inf"
}
const precision = Self.precision ?? this.precision;
let result = precision !== undefined ? this.value.toFixed(precision) : this.value.toString();
if (Self.serialized) {
result = `"${result}"`;
}
return result
}
valueOf() {
return this.value
}
toString() {
return this.value.toString()
}
}
class VectorEntity extends IEntity {
static attributes = {
...super.attributes,
X: NumberEntity.withDefault(),
Y: NumberEntity.withDefault(),
Z: NumberEntity.withDefault(),
}
static grammar = this.createGrammar()
constructor(values) {
super(values);
/** @type {InstanceType} */ this.X;
/** @type {InstanceType} */ this.Y;
/** @type {InstanceType} */ this.Z;
}
/** @returns {P} */
static createGrammar() {
return Grammar.createEntityGrammar(this, Grammar.commaSeparation, 1).label("VectorEntity")
}
/** @returns {[Number, Number, Number]} */
toArray() {
return [this.X.valueOf(), this.Y.valueOf(), this.Z.valueOf()]
}
}
const paths$1 = Configuration.paths;
const sequencerScriptingNameRegex = /\/Script\/SequencerScripting\.MovieSceneScripting(.+)Channel/;
const keyNameValue = {
"A_AccentGrave": "à",
"Add": "Num +",
"C_Cedille": "ç",
"Decimal": "Num .",
"Divide": "Num /",
"E_AccentAigu": "é",
"E_AccentGrave": "è",
"F1": "F1", // Otherwise F and number will be separated
"F10": "F10",
"F11": "F11",
"F12": "F12",
"F2": "F2",
"F3": "F3",
"F4": "F4",
"F5": "F5",
"F6": "F6",
"F7": "F7",
"F8": "F8",
"F9": "F9",
"Gamepad_Special_Left_X": "Touchpad Button X Axis",
"Gamepad_Special_Left_Y": "Touchpad Button Y Axis",
"Mouse2D": "Mouse XY 2D-Axis",
"Multiply": "Num *",
"Section": "§",
"Subtract": "Num -",
"Tilde": "`",
};
const niagaraNodeNames = {
"Boolean::LogicAnd": "Logic AND",
"Boolean::LogicEq": "==",
"Boolean::LogicNEq": "!=",
"Boolean::LogicNot": "Logic NOT",
"Boolean::LogicOr": "Logic OR",
"Integer::BitAnd": "Bitwise AND",
"Integer::BitLShift": "Bitwise Left Shift",
"Integer::BitNot": "Bitwise NOT",
"Integer::BitOr": "Bitwise OR",
"Integer::BitRShift": "Bitwise Right Shift",
"Integer::BitXOr": "Bitwise XOR",
"Integer::EnumEq": "==",
"Integer::EnumNEq": "!=",
"Matrix::MatrixMultiply": "Multiply (Matrix * Matrix)",
"Matrix::MatrixVectorMultiply": "Multiply (Matrix * Vector4)",
// Numeric::
...Object.fromEntries(Object.entries({
"Add": "+",
"ArcCosine": "ArcCosine",
"ArcCosine(Degrees)": "ArcCos(D)",
"ArcCosine(Radians)": "ArcCos(R)",
"ArcSine": "ArcSine",
"ArcSine(Degrees)": "ArcSin(D)",
"ArcSine(Radians)": "ArcSin(R)",
"ArcTangent(Degrees)": "ArcTan(D)",
"ArcTangent(Radians)": "ArcTan(R)",
"CmpEQ": "==",
"CmpGE": ">=",
"CmpGT": ">",
"CmpLE": "<=",
"CmpLT": "<",
"CmpNEQ": "!=",
"Cosine(Degrees)": "Cos(D)",
"Cosine(Radians)": "Cos(R)",
"DegreesToRadians": "DegToRad",
"DistancePos": "Distance",
"Div": String.fromCharCode(0x00f7),
"FMod": "%",
"FModFast": "Modulo Fast",
"Length": "Len",
"Madd": `(A${String.fromCharCode(0x2a2f)}B)+C`,
"Mul": String.fromCharCode(0x2a2f),
"Negate": "-A",
"OneMinus": "1-A",
"PI": String.fromCharCode(0x03C0),
"RadiansToDegrees": "RadToDeg",
"Rand Float": "Random Float",
"Rand Integer": "Random Integer",
"Rand": "Random",
"Rcp": "Reciprocal",
"RSqrt": "Rcp Sqrt",
"Sine(Degrees)": "Sin(D)",
"Sine(Radians)": "Sin(R)",
"Subtract": "-",
"Tangent(Degrees)": "Tan(D)",
"Tangent(Radians)": "Tan(R)",
"TWO_PI": `2 ${String.fromCharCode(0x03C0)}`,
}).map(([k, v]) => ["Numeric::" + k, v])),
};
/** @param {String} value */
function numberFromText(value = "") {
value = value.toLowerCase();
switch (value) {
case "zero": return 0
case "one": return 1
case "two": return 2
case "three": return 3
case "four": return 4
case "five": return 5
case "six": return 6
case "seven": return 7
case "eight": return 8
case "nine": return 9
}
}
function keyName(value) {
/** @type {String} */
let result = keyNameValue[value];
if (result) {
return result
}
result = numberFromText(value)?.toString();
if (result) {
return result
}
const match = value.match(/NumPad([a-zA-Z]+)/);
if (match) {
result = numberFromText(match[1]).toString();
if (result) {
return "Num " + result
}
}
}
/**
* @param {ObjectEntity} entity
* @returns {String}
*/
function nodeTitle(entity) {
let value;
switch (entity.getType()) {
case paths$1.addDelegate:
value ??= "Bind Event to ";
case paths$1.clearDelegate:
value ??= "Unbind all Events from ";
case paths$1.removeDelegate:
value ??= "Unbind Event from ";
return value + Utility.formatStringName(
entity.DelegateReference?.MemberName?.toString().replace(/Delegate$/, "") ?? "None"
)
case paths$1.asyncAction:
if (entity.ProxyFactoryFunctionName) {
return Utility.formatStringName(entity.ProxyFactoryFunctionName?.toString())
}
case paths$1.actorBoundEvent:
case paths$1.componentBoundEvent:
return `${Utility.formatStringName(entity.DelegatePropertyName?.toString())} (${entity.ComponentPropertyName?.toString() ?? "Unknown"})`
case paths$1.callDelegate:
return `Call ${entity.DelegateReference?.MemberName?.toString() ?? "None"}`
case paths$1.createDelegate:
return "Create Event"
case paths$1.customEvent:
if (entity.CustomFunctionName) {
return entity.CustomFunctionName?.toString()
}
case paths$1.dynamicCast:
if (!entity.TargetType) {
return "Bad cast node" // Target type not found
}
return `Cast To ${entity.TargetType?.getName()}`
case paths$1.enumLiteral:
return `Literal enum ${entity.Enum?.getName()}`
case paths$1.event:
return `Event ${(entity.EventReference?.MemberName?.toString() ?? "").replace(/^Receive/, "")}`
case paths$1.executionSequence:
return "Sequence"
case paths$1.forEachElementInEnum:
return `For Each ${entity.Enum?.getName()}`
case paths$1.forEachLoopWithBreak:
return "For Each Loop with Break"
case paths$1.functionEntry:
return entity.FunctionReference?.MemberName?.toString() === "UserConstructionScript"
? "Construction Script"
: entity.FunctionReference?.MemberName?.toString()
case paths$1.functionResult:
return "Return Node"
case paths$1.ifThenElse:
return "Branch"
case paths$1.makeStruct:
if (entity.StructType) {
return `Make ${entity.StructType.getName()}`
}
case paths$1.materialExpressionComponentMask: {
const materialObject = entity.getMaterialSubobject();
if (materialObject) {
return `Mask ( ${Configuration.rgba
.filter(k => /** @type {MirroredEntity} */(materialObject[k]).getter().value === true)
.map(v => v + " ")
.join("")})`
}
}
case paths$1.materialExpressionConstant:
value ??= [entity.getCustomproperties().find(pinEntity => pinEntity.PinName.toString() == "Value")?.DefaultValue];
case paths$1.materialExpressionConstant2Vector:
value ??= [
entity.getCustomproperties().find(pinEntity => pinEntity.PinName?.toString() == "X")?.DefaultValue,
entity.getCustomproperties().find(pinEntity => pinEntity.PinName?.toString() == "Y")?.DefaultValue,
];
case paths$1.materialExpressionConstant3Vector:
case paths$1.materialExpressionConstant4Vector:
if (!value) {
const vector = entity.getCustomproperties()
.find(pinEntity => pinEntity.PinName?.toString() == "Constant")
?.DefaultValue;
value = vector instanceof VectorEntity ? [vector.X, vector.Y, vector.Z].map(v => v.valueOf())
: vector instanceof LinearColorEntity ? [vector.R, vector.G, vector.B, vector.A].map(v => v.valueOf())
: /** @type {Number[]} */([]);
}
if (value?.length > 0) {
return value.map(v => Utility.printExponential(v)).join(",")
}
value = undefined;
break
case paths$1.materialExpressionFunctionInput: {
const materialObject = entity.getMaterialSubobject();
const inputName = materialObject?.InputName ?? "In";
const inputType = materialObject?.InputType?.value.match(/^.+?_(\w+)$/)?.[1] ?? "Vector3";
return `Input ${inputName} (${inputType})`
}
case paths$1.materialExpressionLogarithm:
return "Ln"
case paths$1.materialExpressionLogarithm10:
return "Log10"
case paths$1.materialExpressionLogarithm2:
return "Log2"
case paths$1.materialExpressionMaterialFunctionCall:
const materialFunction = entity.getMaterialSubobject()?.MaterialFunction;
if (materialFunction) {
return materialFunction.getName()
}
break
case paths$1.materialExpressionSquareRoot:
return "Sqrt"
case paths$1.materialExpressionSubtract:
const materialObject = entity.getMaterialSubobject();
if (materialObject) {
return `Subtract(${materialObject.ConstA ?? "1"},${materialObject.ConstB ?? "1"})`
}
case paths$1.metasoundEditorGraphExternalNode: {
const name = entity["ClassName"]?.["Name"];
if (name) {
switch (name) {
case "Add": return "+"
default: return name
}
}
}
case paths$1.niagaraNodeConvert:
/** @type {String} */
const targetType = (entity["AutowireMakeType"]?.["ClassStructOrEnum"] ?? "")
.toString()
.match(/(?:Niagara)?(\w+)['"]*$/)
?.[1]
?? "";
return `Make ${targetType}`
case paths$1.pcgEditorGraphNodeInput:
return "Input"
case paths$1.pcgEditorGraphNodeOutput:
return "Output"
case paths$1.spawnActorFromClass:
let className = entity.getCustomproperties()
.find(pinEntity => pinEntity.PinName.toString() == "ReturnValue")
?.PinType
?.PinSubCategoryObject
?.getName();
if (className === "Actor") {
className = null;
}
return `SpawnActor ${Utility.formatStringName(className ?? "NONE")}`
case paths$1.switchEnum:
return `Switch on ${entity.Enum?.getName() ?? "Enum"}`
case paths$1.switchInteger:
return `Switch on Int`
case paths$1.variableGet:
return ""
case paths$1.variableSet:
return "SET"
}
const className = entity.getClass();
let switchTarget = entity.switchTarget();
if (switchTarget) {
if (switchTarget[0] !== "E") {
switchTarget = Utility.formatStringName(switchTarget);
}
return `Switch on ${switchTarget}`
}
if (entity.isComment()) {
return entity.NodeComment.toString()
}
const keyNameSymbol = entity.getHIDAttribute();
if (keyNameSymbol) {
const name = keyNameSymbol.toString();
let title = keyName(name) ?? Utility.formatStringName(name);
if (className === paths$1.inputDebugKey) {
title = "Debug Key " + title;
} else if (className === paths$1.getInputAxisKeyValue) {
title = "Get " + title;
}
return title
}
if (className === paths$1.macro) {
return Utility.formatStringName(entity.MacroGraphReference?.getMacroName())
}
const materialSubobject = entity.getMaterialSubobject();
if (materialSubobject) {
let result = nodeTitle(materialSubobject);
result = result.match(/Material Expression (.+)/)?.[1] ?? result;
return result
}
if (entity.isPcg() && entity.getPcgSubobject()) {
let pcgSubobject = entity.getPcgSubobject();
let result = pcgSubobject.NodeTitle ? pcgSubobject.NodeTitle.toString() : nodeTitle(pcgSubobject);
return result
}
const subgraphObject = entity.getSubgraphObject();
if (subgraphObject) {
return subgraphObject.Graph.getName()
}
const settingsObject = entity.getSettingsObject();
if (settingsObject) {
if (settingsObject.ExportPath?.valueOf()?.type === paths$1.pcgHiGenGridSizeSettings) {
return `Grid Size: ${(
settingsObject.HiGenGridSize?.toString().match(/\d+/)?.[0]?.concat("00")
?? settingsObject.HiGenGridSize?.toString().match(/^\w+$/)?.[0]
) ?? "256"}`
}
if (settingsObject.BlueprintElementInstance) {
return Utility.formatStringName(settingsObject.BlueprintElementType.getName())
}
if (settingsObject.Operation) {
const match = settingsObject.Name?.toString().match(/PCGMetadata(\w+)Settings_\d+/);
if (match) {
return Utility.formatStringName(match[1] + ": " + settingsObject.Operation)
}
}
const settingsSubgraphObject = settingsObject.getSubgraphObject();
if (settingsSubgraphObject && settingsSubgraphObject.Graph) {
return settingsSubgraphObject.Graph.getName()
}
}
let memberName = entity.FunctionReference?.MemberName?.toString();
if (memberName) {
const memberParent = entity.FunctionReference.MemberParent?.path ?? "";
switch (memberName) {
case "AddKey":
let result = memberParent.match(sequencerScriptingNameRegex);
if (result) {
return `Add Key (${Utility.formatStringName(result[1])})`
}
case "Concat_StrStr":
return "Append"
}
const memberNameTraceLineMatch = memberName.match(Configuration.lineTracePattern);
if (memberNameTraceLineMatch) {
return "Line Trace"
+ (memberNameTraceLineMatch[1] === "Multi" ? " Multi " : " ")
+ (memberNameTraceLineMatch[2] === ""
? "By Channel"
: Utility.formatStringName(memberNameTraceLineMatch[2])
)
}
switch (memberParent) {
case paths$1.blueprintGameplayTagLibrary:
case paths$1.kismetMathLibrary:
case paths$1.slateBlueprintLibrary:
case paths$1.timeManagementBlueprintLibrary:
const leadingLetter = memberName.match(/[BF]([A-Z]\w+)/);
if (leadingLetter) {
// Some functions start with B or F (Like FCeil, FMax, BMin)
memberName = leadingLetter[1];
}
switch (memberName) {
case "Abs": return "ABS"
case "BooleanAND": return "AND"
case "BooleanNAND": return "NAND"
case "BooleanOR": return "OR"
case "Exp": return "e"
case "LineTraceSingle": return "Line Trace By Channel"
case "Max": return "MAX"
case "MaxInt64": return "MAX"
case "Min": return "MIN"
case "MinInt64": return "MIN"
case "Not_PreBool": return "NOT"
case "Sin": return "SIN"
case "Sqrt": return "SQRT"
case "Square": return "^2"
// Dot products not respecting MemberName pattern
case "CrossProduct2D": return "cross"
case "Vector4_CrossProduct3": return "cross3"
case "DotProduct2D":
case "Vector4_DotProduct":
return "dot"
case "Vector4_DotProduct3": return "dot3"
}
if (memberName.startsWith("Add_")) {
return "+"
}
if (memberName.startsWith("And_")) {
return "&"
}
if (memberName.startsWith("Conv_")) {
return "" // Conversion nodes do not have visible names
}
if (memberName.startsWith("Cross_")) {
return "cross"
}
if (memberName.startsWith("Divide_")) {
return String.fromCharCode(0x00f7)
}
if (memberName.startsWith("Dot_")) {
return "dot"
}
if (memberName.startsWith("EqualEqual_")) {
return "=="
}
if (memberName.startsWith("Greater_")) {
return ">"
}
if (memberName.startsWith("GreaterEqual_")) {
return ">="
}
if (memberName.startsWith("Less_")) {
return "<"
}
if (memberName.startsWith("LessEqual_")) {
return "<="
}
if (memberName.startsWith("Multiply_")) {
return String.fromCharCode(0x2a2f)
}
if (memberName.startsWith("Not_")) {
return "~"
}
if (memberName.startsWith("NotEqual_")) {
return "!="
}
if (memberName.startsWith("Or_")) {
return "|"
}
if (memberName.startsWith("Percent_")) {
return "%"
}
if (memberName.startsWith("Subtract_")) {
return "-"
}
if (memberName.startsWith("Xor_")) {
return "^"
}
break
case paths$1.blueprintSetLibrary:
{
const setOperationMatch = memberName.match(/Set_(\w+)/);
if (setOperationMatch) {
return Utility.formatStringName(setOperationMatch[1]).toUpperCase()
}
}
break
case paths$1.blueprintMapLibrary:
{
const setOperationMatch = memberName.match(/Map_(\w+)/);
if (setOperationMatch) {
return Utility.formatStringName(setOperationMatch[1]).toUpperCase()
}
}
break
case paths$1.kismetArrayLibrary:
{
const arrayOperationMath = memberName.match(/Array_(\w+)/);
if (arrayOperationMath) {
return arrayOperationMath[1].toUpperCase()
}
}
break
}
return Utility.formatStringName(memberName)
}
if (entity.OpName) {
return niagaraNodeNames[entity.OpName.toString()]
?? Utility.formatStringName(entity.OpName.toString().replaceAll(/(?:^\w+(?)[]} values */
constructor(values = []) {
super();
this.values = values;
}
/** @returns {P>} */
static createGrammar(elementGrammar = this.type?.grammar ?? Parsernostrum.lazy(() => this.unknownEntityGrammar)) {
return this.inlined
? elementGrammar
: Parsernostrum.seq(
Parsernostrum.reg(/\(\s*/),
elementGrammar.sepBy(Grammar.commaSeparation).opt(),
Parsernostrum.reg(/\s*(,\s*)?\)/, 1),
).map(([_0, values, trailing]) => {
values = values instanceof Array ? values : [];
let Self = this;
if ((trailing !== undefined) !== Self.trailing) {
Self = Self.flagTrailing(trailing !== undefined);
}
return new Self(values)
}).label(`ArrayEntity of ${this.type?.className() ?? "unknown values"}`)
}
/**
* @template {typeof IEntity} T
* @this {T}
*/
static flagInlined(value = true) {
const result = this.asUniqueClass();
result.inlined = value;
result.grammar = /** @type {P} */(result.createGrammar());
return result
}
/**
* @template {typeof IEntity} T
* @param {T} type
*/
static of(type) {
const result = /** @type {{type: T, grammar: P> } & typeof ArrayEntity} */(
this.asUniqueClass()
);
result.type = type;
result.grammar = /** @type {P} */(result.createGrammar());
return result
}
doSerialize(
insideString = false,
indentation = "",
Self = /** @type {typeof ArrayEntity} */(this.constructor),
printKey = Self.printKey,
keySeparator = Self.keySeparator,
attributeSeparator = Self.attributeSeparator,
wrap = Self.wrap,
) {
if (Self.inlined) {
return super.serialize.bind(
this.values,
insideString,
indentation,
Self,
printKey,
keySeparator,
attributeSeparator,
wrap
)()
}
let result = this.values.map(v => v?.serialize(insideString)).join(Self.attributeSeparator);
if (this.trailing) {
result += Self.attributeSeparator;
}
return `(${result})`
}
valueOf() {
return this.values
}
/** @param {IEntity} other */
equals(other) {
if (!(other instanceof ArrayEntity) || this.values.length !== other.values.length) {
return false
}
for (let i = 0; i < this.values.length; ++i) {
if (!this.values[i].equals(other.values[i])) {
return false
}
}
return true
}
}
var crypto;
if (typeof window === "undefined") {
// When used in nodejs, mainly for test purpose
import('crypto').then(mod => crypto = mod.default).catch();
} else {
crypto = window.crypto;
}
class GuidEntity extends IEntity {
static grammar = this.createGrammar()
static generateGuid() {
let values = new Uint32Array(4);
crypto.getRandomValues(values);
let guid = "";
values.forEach(n => {
guid += ("0".repeat(8) + n.toString(16).toUpperCase()).slice(-8);
});
return guid
}
constructor(value = GuidEntity.generateGuid()) {
super();
this.value = value;
}
/** @returns {P} */
static createGrammar() {
return Parsernostrum.reg(/[0-9A-F]{32}/i).map(v => new this(v)).label("GuidEntity")
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
) {
let result = this.value;
if (Self.serialized) {
result = `"${result}"`;
}
return result
}
toString() {
return this.value
}
}
class IntegerEntity extends NumberEntity {
static grammar = this.createGrammar()
get value() {
return super.value
}
set value(value) {
value = Math.trunc(value);
if (value >= 1 << 31 && value < -(1 << 31)) {
value = Math.floor(value);
super.value = value;
}
}
/** @returns {P} */
static createGrammar() {
return Parsernostrum.numberInteger.map(v => new this(v))
}
}
class NaturalNumberEntity extends IntegerEntity {
static grammar = this.createGrammar()
get value() {
return super.value
}
set value(value) {
value = Math.round(Utility.clamp(value, 0));
super.value = value;
}
/** @returns {P} */
static createGrammar() {
return Parsernostrum.numberNatural.map(v => new this(v))
}
}
const colors = {
"Any": i$5`132, 132, 132`,
"Any[]": i$5`132, 132, 132`,
"audio": i$5`252, 148, 252`,
"blue": i$5`0, 0, 255`,
"bool": i$5`146, 0, 0`,
"byte": i$5`0, 110, 100`,
"class": i$5`88, 0, 186`,
"default": i$5`255, 255, 255`,
"delegate": i$5`255, 56, 56`,
"enum": i$5`0, 109, 99`,
"exec": i$5`240, 240, 240`,
"float": i$5`160, 252, 70`,
"green": i$5`0, 255, 0`,
"int": i$5`30, 224, 172`,
"int32": i$5`30, 224, 172`,
"int64": i$5`170, 224, 172`,
"interface": i$5`238, 252, 168`,
"name": i$5`200, 128, 252`,
"object": i$5`0, 168, 242`,
"Param": i$5`255, 166, 40`,
"Param[]": i$5`255, 166, 40`,
"Point": i$5`64, 138, 255`,
"Point[]": i$5`64, 137, 255`,
"real": i$5`54, 208, 0`,
"red": i$5`255, 0, 0`,
"string": i$5`251, 0, 208`,
"struct": i$5`0, 88, 200`,
"Surface": i$5`69, 196, 126`,
"Surface[]": i$5`69, 196, 126`,
"text": i$5`226, 121, 167`,
"time": i$5`148, 252, 252`,
"Volume": i$5`230, 69, 188`,
"Volume[]": i$5`230, 69, 188`,
"wildcard": i$5`128, 120, 120`,
[Configuration.paths.linearColor]: i$5`0, 88, 200`,
[Configuration.paths.niagaraBool]: i$5`146, 0, 0`,
[Configuration.paths.niagaraDataInterfaceCollisionQuery]: i$5`0, 168, 242`,
[Configuration.paths.niagaraDataInterfaceCurlNoise]: i$5`0, 168, 242`,
[Configuration.paths.niagaraDataInterfaceVolumeTexture]: i$5`0, 168, 242`,
[Configuration.paths.niagaraFloat]: i$5`160, 250, 68`,
[Configuration.paths.niagaraInt32]: i$5`30, 224, 172`,
[Configuration.paths.niagaraPosition]: i$5`251, 146, 251`,
[Configuration.paths.quat4f]: i$5`0, 88, 200`,
[Configuration.paths.rotator]: i$5`157, 177, 251`,
[Configuration.paths.transform]: i$5`227, 103, 0`,
[Configuration.paths.vector]: i$5`251, 198, 34`,
[Configuration.paths.vector2f]: i$5`0, 88, 200`,
[Configuration.paths.vector3f]: i$5`250, 200, 36`,
[Configuration.paths.vector4f]: i$5`0, 88, 200`,
};
const pinColorMaterial = i$5`120, 120, 120`;
/** @param {PinEntity} entity */
function pinColor(entity) {
if (entity.PinType.PinCategory?.toString() === "mask") {
const result = colors[entity.PinType.PinSubCategory?.toString()];
if (result) {
return result
}
} else if (entity.PinType.PinCategory?.toString() === "optional") {
return pinColorMaterial
}
const type = entity.getType();
return colors[type]
?? colors[entity.PinType.PinCategory?.toString().toLowerCase()]
?? (type.startsWith("/Script/Niagara.") ? colors["struct"] : colors["default"])
}
/** @param {PinEntity} entity */
function pinTitle(entity) {
let result = entity.PinFriendlyName
? entity.PinFriendlyName.toString()
: Utility.formatStringName(entity.PinName?.toString() ?? "");
let match;
if (match = entity.PinToolTip?.toString().match(/\s*(.+?(?=\n)|.+\S)\s*/)) {
if (match[1].toLowerCase() === result.toLowerCase()) {
return match[1] // In case they match, then keep the case of the PinToolTip
}
}
result = result.replace(/^Module\./, "");
return result
}
class ByteEntity extends IntegerEntity {
static grammar = this.createGrammar()
get value() {
return super.value
}
set value(value) {
value = Math.trunc(value);
if (value >= 0 && value < 1 << 8) {
super.value = value;
}
}
/** @returns {P} */
createGrammar() {
// @ts-expect-error
return Parsernostrum.numberByte.map(v => new this(v))
}
}
class StringEntity extends IEntity {
static grammar = this.createGrammar()
static escapedCharacters = /['"\\]/g
static unescapedBackslash = /(?<=(?:[^\\]|^)(?:\\\\)*)\\(?!\\)/
constructor(value = "") {
super();
this.value = value;
}
/** @returns {P} */
static createGrammar() {
return Parsernostrum.doubleQuotedString
.map(insideString => new this(StringEntity.unescape(insideString)))
.label("StringEntity")
}
/** @param {String} value */
static escape(value, inline = true) {
let result = value.replaceAll(new RegExp(`(${StringEntity.escapedCharacters.source})`, "g"), '\\$1');
if (inline) {
result = result
.replaceAll("\n", "\\n") // Replace newline with \n
.replaceAll("\t", "\\t"); // Replace tab with \t
}
return result
}
/** @param {String} value */
static unescape(value) {
return value
.replaceAll(new RegExp(StringEntity.unescapedBackslash.source + "t", "g"), "\t") // Replace tab with \t
.replaceAll(new RegExp(StringEntity.unescapedBackslash.source + "n", "g"), "\n") // Replace newline with \n
.replaceAll(new RegExp(`\\\\(${StringEntity.escapedCharacters.source})`, "g"), "$1")
}
doSerialize(insideString = false) {
let result = `"${StringEntity.escape(this.value)}"`;
if (insideString) {
result = StringEntity.escape(result, false);
}
return result
}
valueOf() {
return this.value
}
toString() {
return this.value
}
}
class ComputedTypeEntity extends IEntity {
static grammar = this.createGrammar()
/** @type {(entity: IEntity) => typeof IEntity} */
static f
static createGrammar() {
return StringEntity.grammar
}
/**
* @template {typeof ComputedTypeEntity.f} T
* @param {T} producer
*/
static from(producer) {
const result = /** @type {(typeof ComputedTypeEntity) & { f: T }} */(this.asUniqueClass());
result.f = producer;
return result
}
/** @param {IEntity} entity */
static compute(entity) {
return this.f(entity)
}
}
class SymbolEntity extends IEntity {
static attributeConverter = {
fromAttribute: (value, type) => new this(value),
toAttribute: (value, type) => value.toString()
}
static grammar = this.createGrammar()
/** @returns {P} */
static createGrammar() {
return Grammar.symbol.map(v => new this(v)).label("SymbolEntity")
}
constructor(value = "") {
super();
this.value = value;
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
) {
let result = this.value;
if (Self.serialized) {
result = `"${result}"`;
}
return result
}
toString() {
return this.value
}
}
class EnumEntity extends SymbolEntity {
static grammar = this.createGrammar()
/** @returns {P} */
static createGrammar() {
return Grammar.symbol.map(v => new this(v))
}
}
class EnumDisplayValueEntity extends EnumEntity {
static grammar = this.createGrammar()
/** @returns {P} */
static createGrammar() {
return Parsernostrum.reg(Grammar.Regex.InsideString).map(v => new this(v))
}
}
class InvariantTextEntity extends IEntity {
static lookbehind = "INVTEXT"
static grammar = this.createGrammar()
constructor(value = "") {
super();
this.value = value;
}
/** @returns {P} */
static createGrammar() {
return Parsernostrum.alt(
Parsernostrum.seq(
Parsernostrum.reg(new RegExp(`${this.lookbehind}\\s*\\(`)),
Parsernostrum.doubleQuotedString,
Parsernostrum.reg(/\s*\)/)
).map(([_0, value, _2]) => value),
Parsernostrum.reg(new RegExp(this.lookbehind)).map(() => "") // InvariantTextEntity can have no arguments
)
.map(value => new this(value))
.label("InvariantTextEntity")
}
doSerialize() {
return this.lookbehind + '("' + this.value + '")'
}
valueOf() {
return this.value
}
toString() {
return this.value
}
}
class LocalizedTextEntity extends IEntity {
static attributeSeparator = ", "
static printKey = k => ""
static lookbehind = "NSLOCTEXT"
static attributes = {
...super.attributes,
namespace: StringEntity.withDefault(),
key: StringEntity.withDefault(),
value: StringEntity.withDefault(),
}
static grammar = this.createGrammar()
constructor(values = {}) {
super(values);
/** @type {InstanceType} */ this.namespace;
/** @type {InstanceType} */ this.key;
/** @type {InstanceType} */ this.value;
}
/** @returns {P} */
static createGrammar() {
return Parsernostrum.regArray(new RegExp(
String.raw`${LocalizedTextEntity.lookbehind}\s*\(`
+ String.raw`\s*"(?${Grammar.Regex.InsideString.source})"\s*,`
+ String.raw`\s*"(?${Grammar.Regex.InsideString.source})"\s*,`
+ String.raw`\s*"(?${Grammar.Regex.InsideString.source})"\s*`
+ String.raw`(?,\s+)?`
+ String.raw`\)`,
"m"
)).map(({ groups: { namespace, key, value, trailing } }) => {
return new this({
namespace: new (this.attributes.namespace)(Utility.unescapeString(namespace)),
key: new (this.attributes.namespace)(Utility.unescapeString(key)),
value: new (this.attributes.namespace)(Utility.unescapeString(value)),
trailing: trailing !== undefined,
})
}).label("LocalizedTextEntity")
}
toString() {
return Utility.capitalFirstLetter(this.value.valueOf())
}
}
class FormatTextEntity extends IEntity {
static attributeSeparator = ", "
static lookbehind = ["LOCGEN_FORMAT_NAMED", "LOCGEN_FORMAT_ORDERED"]
static grammar = this.createGrammar()
/** @param {(StringEntity | LocalizedTextEntity | InvariantTextEntity | FormatTextEntity)[]} values */
constructor(values) {
super();
this.values = values;
}
/** @returns {P} */
static createGrammar() {
return Parsernostrum.lazy(() => Parsernostrum.seq(
// Resulting regex: /(LOCGEN_FORMAT_NAMED|LOCGEN_FORMAT_ORDERED)\s*/
Parsernostrum.reg(new RegExp(String.raw`(${this.lookbehind.join("|")})\s*\(\s*`), 1),
Parsernostrum.alt(
...[StringEntity, LocalizedTextEntity, InvariantTextEntity, FormatTextEntity].map(type => type.grammar)
).sepBy(Parsernostrum.reg(/\s*\,\s*/)),
Parsernostrum.reg(/\s*\)/)
)
.map(([lookbehind, values]) => {
const result = new this(values);
result.lookbehind = lookbehind;
return result
}))
.label("FormatTextEntity")
}
doSerialize(
insideString = false,
indentation = "",
Self = /** @type {typeof FormatTextEntity} */(this.constructor),
printKey = Self.printKey,
keySeparator = Self.keySeparator,
attributeSeparator = Self.attributeSeparator,
wrap = Self.wrap,
) {
const separator = Self.attributeSeparator;
return this.lookbehind + "("
+ this.values.map(v => v.serialize(insideString)).join(separator)
+ (Self.trailing ? separator : "")
+ ")"
}
toString() {
const pattern = this.values?.[0]?.toString(); // The pattern is always the first element of the array
if (!pattern) {
return ""
}
const values = this.values.slice(1).map(v => v?.valueOf());
let result = this.lookbehind == "LOCGEN_FORMAT_NAMED"
? pattern.replaceAll(/\{([a-zA-Z]\w*)\}/g, (substring, arg) => {
const argLocation = values.indexOf(arg) + 1;
return argLocation > 0 && argLocation < values.length
? values[argLocation]
: substring
})
: this.lookbehind == "LOCGEN_FORMAT_ORDERED"
? pattern.replaceAll(/\{(\d+)\}/g, (substring, arg) => {
const argValue = Number(arg);
return argValue < values.length
? values[argValue]
: substring
})
: "";
return result
}
}
class Integer64Entity extends IEntity {
static grammar = this.createGrammar()
/**
* @protected
* @type {bigint}
*/
_value
get value() {
return this._value
}
set value(value) {
if (value >= -(1n << 63n) && value < 1n << 63n) {
this._value = value;
}
}
/** @param {bigint | Number} value */
constructor(value = 0n) {
super();
this.value = BigInt(value);
}
/** @returns {P} */
static createGrammar() {
return Parsernostrum.numberBigInteger.map(v => new this(v))
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
) {
let result = this.value.toString();
if (Self.serialized) {
result = `"${result}"`;
}
return result
}
valueOf() {
return this.value
}
toString() {
return this.value.toString()
}
}
class ObjectReferenceEntity extends IEntity {
static typeReference = Parsernostrum.reg(
// @ts-expect-error
new RegExp(Grammar.Regex.Path.source + "|" + Grammar.symbol.getParser().regexp.source)
)
static fullReferenceGrammar = this.createFullReferenceGrammar()
static grammar = this.createGrammar()
#type
get type() {
return this.#type
}
set type(value) {
this.#type = value;
}
#path
get path() {
return this.#path
}
set path(value) {
this.#name = "";
this.#path = value;
}
#serializer
get full() {
return this.#serializer
}
set full(value) {
this.#serializer = value;
}
#name = ""
/** @param {(t: String, p: String) => String} serializer */
constructor(
type = "None",
path = "",
serializer = type.includes("/") || path
? (t, p) => `"${t + (p ? (`'${p}'`) : "")}"`
: (t, p) => t) {
super();
this.#type = type;
this.#path = path;
this.#serializer = serializer;
}
/** @returns {P} */
static createGrammar() {
return Parsernostrum.alt(
this.createFullReferenceSerializedGrammar(),
this.createFullReferenceGrammar(),
this.createTypeReferenceGrammar(),
).label("ObjectReferenceEntity")
}
/** @returns {P} */
static createFullReferenceGrammar() {
return Parsernostrum.regArray(
new RegExp(
// @ts-expect-error
"(" + this.typeReference.getParser().regexp.source + ")"
+ "(?:"
+ `'"(${Grammar.Regex.InsideString.source})"'`
+ "|"
+ `'(${Grammar.Regex.InsideSingleQuotedString.source})'`
+ ")"
)
).map(([full, type, fullQuotedPath, simpleQuotedPath]) => {
let fullQuoted = fullQuotedPath ? true : false;
let quotes = fullQuoted ? [`'"`, `"'`] : ["'", "'"];
return new this(
type,
fullQuoted ? fullQuotedPath : simpleQuotedPath,
(t, p) => t + quotes[0] + p + quotes[1]
)
})
}
/** @returns {P} */
static createFullReferenceSerializedGrammar() {
return Parsernostrum.regArray(
new RegExp(
'"(' + Grammar.Regex.InsideString.source + "?)"
+ "(?:'(" + Grammar.Regex.InsideSingleQuotedString.source + `?)')?"`
)
).map(([_0, type, path]) => new this(type, path, (t, p) => `"${t}${p ? `'${p}'` : ""}"`))
}
/** @returns {P} */
static createTypeReferenceGrammar() {
return this.typeReference.map(v => new this(v, "", (t, p) => t))
}
static createNoneInstance() {
return new this("None")
}
getName(dropCounter = false) {
if (!this.#name) {
if (!dropCounter) {
return this.#name = Utility.getNameFromPath(this.path.replace(/_C$/, ""), dropCounter)
}
return Utility.getNameFromPath(this.path.replace(/_C$/, ""), dropCounter)
}
return this.#name
}
doSerialize(insideString = false) {
let result = this.full(this.type, this.path);
if (insideString) {
result = Utility.escapeString(result, false);
}
return result
}
/** @param {IEntity} other */
equals(other) {
if (!(other instanceof ObjectReferenceEntity)) {
return false
}
return this.type == other.type && this.path == other.path
}
toString() {
return this.full(this.type, this.path)
}
}
class PinReferenceEntity extends IEntity {
static grammar = this.createGrammar()
/**
* @param {SymbolEntity} objectName
* @param {GuidEntity} pinGuid
*/
constructor(objectName = null, pinGuid = null) {
super();
this.objectName = objectName;
this.pinGuid = pinGuid;
}
/** @returns {P} */
static createGrammar() {
return Parsernostrum.seq(
SymbolEntity.grammar,
Parsernostrum.whitespace,
GuidEntity.grammar
)
.map(([objectName, _1, pinGuid]) => new this(objectName, pinGuid))
.label("PinReferenceEntity")
}
doSerialize() {
return this.objectName.serialize() + " " + this.pinGuid.serialize()
}
}
class FunctionReferenceEntity extends IEntity {
static attributes = {
...super.attributes,
MemberParent: ObjectReferenceEntity,
MemberName: StringEntity,
MemberGuid: GuidEntity,
}
static grammar = this.createGrammar()
constructor(values) {
super(values);
/** @type {InstanceType} */ this.MemberParent;
/** @type {InstanceType} */ this.MemberName;
/** @type {InstanceType} */ this.MemberGuid;
}
/** @returns {P} */
static createGrammar() {
return Grammar.createEntityGrammar(this, Grammar.commaSeparation, 0, 0)
}
}
class PinTypeEntity extends IEntity {
static attributes = {
...super.attributes,
PinCategory: StringEntity.withDefault(),
PinSubCategory: StringEntity,
PinSubCategoryObject: ObjectReferenceEntity,
PinSubCategoryMemberReference: FunctionReferenceEntity,
ContainerType: SymbolEntity,
bIsReference: BooleanEntity,
bIsConst: BooleanEntity,
bIsWeakPointer: BooleanEntity,
bIsUObjectWrapper: BooleanEntity,
bSerializeAsSinglePrecisionFloat: BooleanEntity,
}
static grammar = this.createGrammar()
constructor(values = {}) {
super(values);
/** @type {InstanceType} */ this.PinCategory;
/** @type {InstanceType} */ this.PinSubCategory;
/** @type {InstanceType} */ this.PinSubCategoryObject;
/** @type {InstanceType} */ this.PinSubCategoryMemberReference;
/** @type {InstanceType} */ this.ContainerType;
/** @type {InstanceType} */ this.bIsReference;
/** @type {InstanceType} */ this.bIsConst;
/** @type {InstanceType} */ this.bIsWeakPointer;
/** @type {InstanceType} */ this.bIsUObjectWrapper;
/** @type {InstanceType} */ this.bIsUObjectWrapper;
/** @type {InstanceType} */ this.bSerializeAsSinglePrecisionFloat;
}
/** @returns {P} */
static createGrammar() {
return Grammar.createEntityGrammar(this).label("PinTypeEntity")
}
/** @param {PinTypeEntity} other */
copyTypeFrom(other) {
for (const key of this.keys) {
if (other[key] !== undefined) {
this[key] = other[key];
}
}
}
}
class Vector2DEntity extends IEntity {
static attributes = {
...super.attributes,
X: NumberEntity.withDefault(),
Y: NumberEntity.withDefault(),
}
static grammar = this.createGrammar()
constructor(values) {
super(values);
/** @type {InstanceType} */ this.X;
/** @type {InstanceType} */ this.Y;
}
/** @returns {P} */
static createGrammar() {
return Grammar.createEntityGrammar(this, Grammar.commaSeparation, 1).label("Vector2DEntity")
}
/** @returns {[Number, Number]} */
toArray() {
return [this.X.valueOf(), this.Y.valueOf()]
}
}
class RBSerializationVector2DEntity extends Vector2DEntity {
static grammar = this.createGrammar()
/** @returns {P} */
static createGrammar() {
return Parsernostrum.alt(
Parsernostrum.regArray(new RegExp(
/X\s*=\s*/.source + "(?" + Grammar.numberRegexSource + ")"
+ "\\s+"
+ /Y\s*=\s*/.source + "(?" + Grammar.numberRegexSource + ")"
)).map(({ groups: { x, y } }) => new this({
X: new (Vector2DEntity.attributes.X)(x),
Y: new (Vector2DEntity.attributes.Y)(y),
})),
Vector2DEntity.grammar.map(v => new this({
X: v.X,
Y: v.Y,
}))
).label("RBSerializationVector2DEntity")
}
}
class RotatorEntity extends IEntity {
static attributes = {
...super.attributes,
R: NumberEntity.withDefault(),
P: NumberEntity.withDefault(),
Y: NumberEntity.withDefault(),
}
static grammar = this.createGrammar()
constructor(values) {
super(values);
/** @type {InstanceType} */ this.R;
/** @type {InstanceType} */ this.P;
/** @type {InstanceType} */ this.Y;
}
/** @returns {P} */
static createGrammar() {
return Grammar.createEntityGrammar(this, Grammar.commaSeparation, 1).label("RotatorEntity")
}
getRoll() {
return this.R
}
getPitch() {
return this.P
}
getYaw() {
return this.Y
}
}
class SimpleSerializationRotatorEntity extends RotatorEntity {
static attributeSeparator = ", "
static grammar = this.createGrammar()
/** @returns {P} */
static createGrammar() {
return Parsernostrum.alt(
Parsernostrum.regArray(new RegExp(
`(${NumberEntity.numberRegexSource})`
+ String.raw`\s*,\s*`
+ `(${NumberEntity.numberRegexSource})`
+ String.raw`\s*,\s*`
+ `(${NumberEntity.numberRegexSource})`
)).map(([_, p, pPrecision, y, yPrecision, r, rPrecision]) => new this({
R: new (RotatorEntity.attributes.R)(r, rPrecision?.length),
P: new (RotatorEntity.attributes.P)(p, pPrecision?.length),
Y: new (RotatorEntity.attributes.Y)(y, yPrecision?.length),
})),
RotatorEntity.grammar.map(v => new this({
R: v.R,
P: v.P,
Y: v.Y,
}))
).label("SimpleSerializationRotatorEntity")
}
doSerialize() {
const attributeSeparator = /** @type {typeof SimpleSerializationRotatorEntity} */(
this.constructor
).attributeSeparator;
return this.P.serialize() + attributeSeparator
+ this.Y.serialize() + attributeSeparator
+ this.R.serialize() + (this.trailing ? attributeSeparator : "")
}
}
class SimpleSerializationVector2DEntity extends Vector2DEntity {
static attributeSeparator = ", "
static grammar = this.createGrammar()
/** @returns {P} */
static createGrammar() {
return Parsernostrum.alt(
Parsernostrum.regArray(new RegExp(
`(${NumberEntity.numberRegexSource})`
+ String.raw`\s*,\s*`
+ `(${NumberEntity.numberRegexSource})`
)).map(([_, x, xPrecision, y, yPrecision]) => new this({
X: new (Vector2DEntity.attributes.X)(x, xPrecision?.length),
Y: new (Vector2DEntity.attributes.Y)(y, yPrecision?.length),
})),
Vector2DEntity.grammar.map(v => new this({
X: v.X,
Y: v.Y,
}))
).label("SimpleSerializationVector2DEntity")
}
doSerialize() {
const attributeSeparator = /** @type {typeof SimpleSerializationVector2DEntity} */(
this.constructor
).attributeSeparator;
return this.X.serialize() + attributeSeparator
+ this.Y.serialize() + (this.trailing ? attributeSeparator : "")
}
}
class Vector4DEntity extends IEntity {
static attributes = {
...super.attributes,
X: NumberEntity.withDefault(),
Y: NumberEntity.withDefault(),
Z: NumberEntity.withDefault(),
W: NumberEntity.withDefault(),
}
static grammar = this.createGrammar()
constructor(values) {
super(values);
/** @type {InstanceType} */ this.X;
/** @type {InstanceType} */ this.Y;
/** @type {InstanceType} */ this.Z;
/** @type {InstanceType} */ this.W;
}
/** @returns {P} */
static createGrammar() {
return Grammar.createEntityGrammar(this, Grammar.commaSeparation, 1).label("Vector4DEntity")
}
/** @returns {[Number, Number, Number, Number]} */
toArray() {
return [this.X.valueOf(), this.Y.valueOf(), this.Z.valueOf(), this.W.valueOf()]
}
}
class SimpleSerializationVector4DEntity extends Vector4DEntity {
static grammar = this.createGrammar()
/** @returns {P } */
static createGrammar() {
return Parsernostrum.alt(
Parsernostrum.regArray(new RegExp(
`(${Grammar.numberRegexSource})`
+ String.raw`\s*,\s*`
+ `(${Grammar.numberRegexSource})`
+ String.raw`\s*,\s*`
+ `(${Grammar.numberRegexSource})`
+ String.raw`\s*,\s*`
+ `(${Grammar.numberRegexSource})`
))
.map(([_0, x, y, z, w]) => new this({
X: new (Vector4DEntity.attributes.X)(x),
Y: new (Vector4DEntity.attributes.Y)(y),
Z: new (Vector4DEntity.attributes.Z)(z),
W: new (Vector4DEntity.attributes.W)(w),
})),
Vector4DEntity.grammar
)
}
}
class SimpleSerializationVectorEntity extends VectorEntity {
static allowShortSerialization = false
static attributeSeparator = ", "
static grammar = this.createGrammar()
/** @returns {P} */
static createGrammar() {
return Parsernostrum.alt(
Parsernostrum.regArray(new RegExp(
`(${NumberEntity.numberRegexSource})`
// If allow simple serialization then it can parse only a single number ...
+ (this.allowShortSerialization ? `(?:` : "")
+ String.raw`\s*,\s*`
+ `(${NumberEntity.numberRegexSource})`
+ String.raw`\s*,\s*`
+ `(${NumberEntity.numberRegexSource})`
// ... that will be assigned to X and the rest is optional and set to 0
+ (this.allowShortSerialization ? `)?` : "")
))
.map(([_, x, xPrecision, y, yPrecision, z, zPrecision]) => new this({
X: new (VectorEntity.attributes.X)(x, xPrecision?.length),
Y: new (VectorEntity.attributes.Y)(y, yPrecision?.length),
Z: new (VectorEntity.attributes.Z)(z, zPrecision?.length),
})),
VectorEntity.grammar.map(v => new this({
X: v.X,
Y: v.Y,
Z: v.Z,
}))
)
}
/**
* @template {typeof SimpleSerializationVectorEntity} T
* @this {T}
*/
static flagAllowShortSerialization(value = true) {
const result = this.asUniqueClass();
if (value !== result.allowShortSerialization) {
result.allowShortSerialization = value;
result.grammar = result.createGrammar();
}
return result
}
doSerialize() {
const attributeSeparator = /** @type {typeof SimpleSerializationVectorEntity} */(
this.constructor
).attributeSeparator;
return this.X.serialize() + attributeSeparator
+ this.Y.serialize() + attributeSeparator
+ this.Z.serialize() + (this.trailing ? attributeSeparator : "")
}
}
/** @template {IEntity} T */
class PinEntity extends IEntity {
static lookbehind = "Pin"
static #typeEntityMap = {
"bool": BooleanEntity,
"byte": ByteEntity,
"enum": EnumEntity,
"exec": StringEntity,
"float": NumberEntity,
"int": IntegerEntity,
"int64": Integer64Entity,
"name": StringEntity,
"real": NumberEntity,
"string": StringEntity,
[Configuration.paths.linearColor]: LinearColorEntity,
[Configuration.paths.niagaraBool]: BooleanEntity,
[Configuration.paths.niagaraFloat]: NumberEntity,
[Configuration.paths.niagaraPosition]: VectorEntity,
[Configuration.paths.rotator]: RotatorEntity,
[Configuration.paths.vector]: VectorEntity,
[Configuration.paths.vector2D]: Vector2DEntity,
[Configuration.paths.vector4f]: Vector4DEntity,
}
static #alternativeTypeEntityMap = {
"enum": EnumDisplayValueEntity,
"rg": RBSerializationVector2DEntity,
[Configuration.paths.niagaraPosition]: SimpleSerializationVectorEntity.flagAllowShortSerialization(),
[Configuration.paths.rotator]: SimpleSerializationRotatorEntity,
[Configuration.paths.vector]: SimpleSerializationVectorEntity,
[Configuration.paths.vector2D]: SimpleSerializationVector2DEntity,
[Configuration.paths.vector3f]: SimpleSerializationVectorEntity,
[Configuration.paths.vector4f]: SimpleSerializationVector4DEntity,
}
static attributes = {
PinId: GuidEntity.withDefault(),
PinName: StringEntity.withDefault(),
PinFriendlyName: AlternativesEntity.accepting(
LocalizedTextEntity,
FormatTextEntity,
InvariantTextEntity,
StringEntity
),
PinToolTip: StringEntity,
Direction: StringEntity,
PinType: PinTypeEntity.withDefault().flagInlined(),
LinkedTo: ArrayEntity.of(PinReferenceEntity).withDefault().flagSilent(),
SubPins: ArrayEntity.of(PinReferenceEntity),
ParentPin: PinReferenceEntity,
DefaultValue:
ComputedTypeEntity.from(
/** @param {PinEntity} pinEntity */
pinEntity => pinEntity.getEntityType(true)?.flagSerialized() ?? StringEntity
),
AutogeneratedDefaultValue: StringEntity,
DefaultObject: ObjectReferenceEntity,
PersistentGuid: GuidEntity,
bHidden: BooleanEntity,
bNotConnectable: BooleanEntity,
bDefaultValueIsReadOnly: BooleanEntity,
bDefaultValueIsIgnored: BooleanEntity,
bAdvancedView: BooleanEntity,
bOrphanedPin: BooleanEntity,
}
static grammar = this.createGrammar()
#recomputesNodeTitleOnChange = false
set recomputesNodeTitleOnChange(value) {
this.#recomputesNodeTitleOnChange = value;
}
get recomputesNodeTitleOnChange() {
return this.#recomputesNodeTitleOnChange
}
/** @type {ObjectEntity} */
#objectEntity = null
get objectEntity() {
try {
/*
* Why inside a try block ?
* It is because of this issue: https://stackoverflow.com/questions/61237153/access-private-method-in-an-overriden-method-called-from-the-base-class-construc
* super(values) will call IEntity constructor while this instance is not yet fully constructed
* IEntity will call computedEntity.compute(this) to initialize DefaultValue from this class
* Which in turn calls pinEntity.getEntityType(true)
* Which calls this.getType()
* Which calls this.objectEntity?.isPcg()
* Which would access #objectEntity through get objectEntity()
* And this would violate the private access rule (because this class is not yet constructed)
* If this issue in the future will be fixed in all the major browsers, please remove this try catch
*/
return this.#objectEntity
} catch (e) {
return null
}
}
set objectEntity(value) {
this.#objectEntity = value;
}
#pinIndex
get pinIndex() {
return this.#pinIndex
}
set pinIndex(value) {
this.#pinIndex = value;
}
constructor(values = {}) {
super(values);
/** @type {InstanceType} */ this.PinId;
/** @type {InstanceType} */ this.PinName;
/** @type {InstanceType} */ this.PinFriendlyName;
/** @type {InstanceType} */ this.PinToolTip;
/** @type {InstanceType} */ this.Direction;
/** @type {InstanceType} */ this.PinType;
/** @type {InstanceType} */ this.LinkedTo;
/** @type {T} */ this.DefaultValue;
/** @type {InstanceType} */ this.AutogeneratedDefaultValue;
/** @type {InstanceType} */ this.DefaultObject;
/** @type {InstanceType} */ this.PersistentGuid;
/** @type {InstanceType} */ this.bHidden;
/** @type {InstanceType} */ this.bNotConnectable;
/** @type {InstanceType} */ this.bDefaultValueIsReadOnly;
/** @type {InstanceType} */ this.bDefaultValueIsIgnored;
/** @type {InstanceType} */ this.bAdvancedView;
/** @type {InstanceType} */ this.bOrphanedPin;
/** @type {ObjectEntity} */ this.objectEntity;
}
/** @returns {P} */
static createGrammar() {
return Grammar.createEntityGrammar(this)
}
/** @param {ObjectEntity} objectEntity */
static fromLegacyObject(objectEntity) {
return new PinEntity(objectEntity)
}
/** @returns {String} */
getType() {
const category = this.PinType.PinCategory?.toString().toLocaleLowerCase();
if (["struct", "class", "object", "type", "statictype"].includes(category)) {
return this.PinType.PinSubCategoryObject?.path
}
if (this.isEnum()) {
return "enum"
}
if (this.objectEntity?.isPcg()) {
const pcgSuboject = this.objectEntity.getPcgSubobject();
const pinObjectReference = this.isInput()
? pcgSuboject.InputPins?.valueOf()[this.pinIndex]
: pcgSuboject.OutputPins?.valueOf()[this.pinIndex];
if (pinObjectReference) {
/** @type {ObjectEntity} */
const pinObject = pcgSuboject[Configuration.subObjectAttributeNameFromReference(pinObjectReference, true)];
let allowedTypes = pinObject.Properties?.AllowedTypes?.toString() ?? "";
if (allowedTypes == "") {
allowedTypes = this.PinType.PinCategory ?? "";
if (allowedTypes == "") {
allowedTypes = "Any";
}
}
if (allowedTypes) {
if (
pinObject.Properties.bAllowMultipleData?.valueOf() !== false
&& pinObject.Properties.bAllowMultipleConnections?.valueOf() !== false
) {
allowedTypes += "[]";
}
return allowedTypes
}
}
}
if (category === "optional") {
const subCategory = this.PinType.PinSubCategory?.toString();
switch (subCategory) {
case "red":
return "real"
case "rg":
return "rg"
case "rgb":
return Configuration.paths.vector
case "rgba":
return Configuration.paths.linearColor
default:
return subCategory
}
}
return category
}
/** @returns {typeof IEntity} */
getEntityType(alternative = false) {
const type = this.getType();
const entity = PinEntity.#typeEntityMap[type];
const alternativeEntity = PinEntity.#alternativeTypeEntityMap[type];
return alternative && alternativeEntity !== undefined
? alternativeEntity
: entity
}
pinTitle() {
return pinTitle(this)
}
/** @param {PinEntity} other */
copyTypeFrom(other) {
this.PinType = other.PinType;
}
getDefaultValue(maybeCreate = false) {
if (this.DefaultValue === undefined && maybeCreate) {
this.DefaultValue = /** @type {T} */(new (this.getEntityType(true))());
}
return this.DefaultValue
}
isEnum() {
const type = this.PinType.PinSubCategoryObject?.type;
return type === Configuration.paths.enum
|| type === Configuration.paths.userDefinedEnum
|| type?.toLowerCase() === "enum"
}
isExecution() {
return this.PinType.PinCategory.toString() === "exec"
|| this.getType() === Configuration.paths.niagaraParameterMap
}
isHidden() {
return this.bHidden?.valueOf()
}
isInput() {
return !this.isHidden() && this.Direction?.toString() != "EGPD_Output"
}
isOutput() {
return !this.isHidden() && this.Direction?.toString() == "EGPD_Output"
}
isLinked() {
return this.LinkedTo?.length > 0
}
/**
* @param {String} targetObjectName
* @param {PinEntity} targetPinEntity
* @returns true if it was not already linked to the tarket
*/
linkTo(targetObjectName, targetPinEntity) {
const linkFound = this.LinkedTo.values?.some(pinReferenceEntity =>
pinReferenceEntity.objectName.toString() == targetObjectName
&& pinReferenceEntity.pinGuid.toString() == targetPinEntity.PinId.toString()
);
if (!linkFound) {
this.LinkedTo.values.push(new PinReferenceEntity(new SymbolEntity(targetObjectName), targetPinEntity.PinId));
return true
}
return false // Already linked
}
/**
* @param {String} targetObjectName
* @param {PinEntity} targetPinEntity
* @returns true if it was linked to the target
*/
unlinkFrom(targetObjectName, targetPinEntity) {
const indexElement = this.LinkedTo.values?.findIndex(pinReferenceEntity => {
return pinReferenceEntity.objectName.toString() == targetObjectName
&& pinReferenceEntity.pinGuid.toString() == targetPinEntity.PinId.toString()
});
if (indexElement >= 0) {
this.LinkedTo.values.splice(indexElement, 1);
if (this.LinkedTo.length === 0 && PinEntity.attributes.LinkedTo.default === undefined) {
this.LinkedTo.values = [];
}
return true
}
return false
}
getSubCategory() {
return this.PinType.PinSubCategoryObject?.path
}
pinColor() {
return pinColor(this)
}
}
/** @param {PinEntity} pinEntity */
const indexFromUpperCaseLetterName = pinEntity =>
pinEntity.PinName?.toString().match(/^\s*([A-Z])\s*$/)?.[1]?.charCodeAt(0) - "A".charCodeAt(0);
/** @param {ObjectEntity} entity */
function nodeVariadic(entity) {
/** @type {() => PinEntity[]} */
let pinEntities;
/** @type {(pinEntity: PinEntity) => Number} */
let pinIndexFromEntity;
/** @type {(newPinIndex: Number, minIndex: Number, maxIndex: Number, newPin: PinEntity) => String} */
let pinNameFromIndex;
const type = entity.getType();
let prefix;
let name;
switch (type) {
case Configuration.paths.commutativeAssociativeBinaryOperator:
case Configuration.paths.promotableOperator:
name = entity.FunctionReference?.MemberName?.toString();
switch (name) {
default:
if (
!name?.startsWith("Add_")
&& !name?.startsWith("Subtract_")
&& !name?.startsWith("Multiply_")
&& !name?.startsWith("Divide_")
) {
break
}
case "And_Int64Int64":
case "And_IntInt":
case "BMax":
case "BMin":
case "BooleanAND":
case "BooleanNAND":
case "BooleanOR":
case "Concat_StrStr":
case "FMax":
case "FMin":
case "Max":
case "MaxInt64":
case "Min":
case "MinInt64":
case "Or_Int64Int64":
case "Or_IntInt":
pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isInput());
pinIndexFromEntity ??= indexFromUpperCaseLetterName;
pinNameFromIndex ??= (index, min = -1, max = -1) => {
const result = String.fromCharCode(index >= 0 ? index : max + "A".charCodeAt(0) + 1);
entity.NumAdditionalInputs = new NaturalNumberEntity(pinEntities().length - 1);
return result
};
break
}
break
case Configuration.paths.executionSequence:
prefix ??= "Then";
case Configuration.paths.multiGate:
prefix ??= "Out";
pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isOutput());
pinIndexFromEntity ??= pinEntity => Number(
pinEntity.PinName?.toString().match(new RegExp(String.raw`^\s*${prefix}[_\s]+(\d+)\s*$`, "i"))?.[1]
);
pinNameFromIndex ??= (index, min = -1, max = -1, newPin) =>
`${prefix} ${index >= 0 ? index : min > 0 ? `${prefix} 0` : max + 1}`;
break
// case Configuration.paths.niagaraNodeOp:
// pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isInput())
// pinIndexFromEntity ??= indexFromUpperCaseLetterName
// pinNameFromIndex ??= (index, min = -1, max = -1, newPin) => {
// const result = String.fromCharCode(index >= 0 ? index : max + "A".charCodeAt(0) + 1)
// entity.AddedPins ??= []
// entity.AddedPins.push(newPin)
// return result
// }
// break
case Configuration.paths.switchInteger:
pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isOutput());
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName?.toString().match(/^\s*(\d+)\s*$/)?.[1]);
pinNameFromIndex ??= (index, min = -1, max = -1, newPin) => (index < 0 ? max + 1 : index).toString();
break
case Configuration.paths.switchGameplayTag:
pinNameFromIndex ??= (index, min = -1, max = -1, newPin) => {
const result = `Case_${index >= 0 ? index : min > 0 ? "0" : max + 1}`;
entity.PinNames ??= new ArrayEntity();
entity.PinNames.valueOf().push(new StringEntity(result));
delete entity.PinTags.valueOf()[entity.PinTags.length - 1];
entity.PinTags.valueOf()[entity.PinTags.length] = null;
return result
};
case Configuration.paths.switchName:
case Configuration.paths.switchString:
pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isOutput());
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.toString().match(/^\s*Case[_\s]+(\d+)\s*$/i)?.[1]);
pinNameFromIndex ??= (index, min = -1, max = -1, newPin) => {
const result = `Case_${index >= 0 ? index : min > 0 ? "0" : max + 1}`;
entity.PinNames ??= new ArrayEntity();
entity.PinNames.valueOf().push(new StringEntity(result));
return result
};
break
}
if (pinEntities) {
return () => {
let min = Number.MAX_SAFE_INTEGER;
let max = Number.MIN_SAFE_INTEGER;
let values = [];
const modelPin = pinEntities().reduce(
(acc, cur) => {
const value = pinIndexFromEntity(cur);
if (!isNaN(value)) {
values.push(value);
min = Math.min(value, min);
if (value > max) {
max = value;
return cur
}
} else if (acc === undefined) {
return cur
}
return acc
},
undefined
);
if (min === Number.MAX_SAFE_INTEGER || max === Number.MIN_SAFE_INTEGER) {
min = undefined;
max = undefined;
}
if (!modelPin) {
return null
}
values.sort((a, b) => a < b ? -1 : a === b ? 0 : 1);
let prev = values[0];
let index = values.findIndex(
// Search for a gap
value => {
const result = value - prev > 1;
prev = value;
return result
}
);
const newPin = new PinEntity(modelPin);
newPin.PinId = new GuidEntity();
newPin.PinName = new StringEntity(pinNameFromIndex(index, min, max, newPin));
newPin.PinToolTip = undefined;
if (newPin.DefaultValue) {
// @ts-expect-error
newPin.DefaultValue = new (newPin.DefaultValue.constructor)();
}
entity.getCustomproperties(true).push(newPin);
return newPin
}
}
}
class MacroGraphReferenceEntity extends IEntity {
static attributes = {
...super.attributes,
MacroGraph: ObjectReferenceEntity,
GraphBlueprint: ObjectReferenceEntity,
GraphGuid: GuidEntity,
}
static grammar = this.createGrammar()
constructor(values) {
super(values);
/** @type {InstanceType} */ this.MacroGraph;
/** @type {InstanceType} */ this.GraphBlueprint;
/** @type {InstanceType} */ this.GraphGuid;
}
/** @returns {P} */
static createGrammar() {
return Grammar.createEntityGrammar(this)
}
getMacroName() {
const colonIndex = this.MacroGraph.path.search(":");
return this.MacroGraph.path.substring(colonIndex + 1)
}
}
class NullEntity extends IEntity {
static grammar = this.createGrammar()
/** @returns {P} */
static createGrammar() {
// @ts-expect-error
return Parsernostrum.reg(new RegExp(String.raw`\(${Parsernostrum.whitespaceInlineOpt.getParser().regexp.source}\)`))
.map(v => new this())
.label("NullEntity")
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor)
) {
let result = "()";
if (Self.serialized) {
result = `"${result}"`;
}
return result
}
}
class ScriptVariableEntity extends IEntity {
static attributes = {
...super.attributes,
ScriptVariable: ObjectReferenceEntity,
OriginalChangeId: GuidEntity,
}
static grammar = this.createGrammar()
constructor(values = {}) {
super(values);
/** @type {InstanceType} */ this.ScriptVariable;
/** @type {InstanceType} */ this.OriginalChangeId;
}
/** @returns {P} */
static createGrammar() {
return Grammar.createEntityGrammar(this).label("ScriptVariableEntity")
}
}
class UnknownPinEntity extends PinEntity {
static attributes = {
...super.attributes,
PinId: GuidEntity
}
static grammar = this.createGrammar()
/** @returns {P} */
static createGrammar() {
return Parsernostrum.seq(
// Lookbehind
Parsernostrum.reg(new RegExp(`(${Grammar.Regex.Symbol.source}\\s*)?\\(\\s*`), 1),
Grammar.createAttributeGrammar(this).sepBy(Grammar.commaSeparation),
Parsernostrum.reg(/\s*(?:,\s*)?\)/)
).map(([lookbehind, attributes, _2]) => {
lookbehind ??= "";
let values = {};
if (lookbehind.length) {
values.lookbehind = lookbehind;
}
attributes.forEach(attributeSetter => attributeSetter(values));
return new this(values)
}).label("UnknownPinEntity")
}
}
class VariableReferenceEntity extends IEntity {
static attributes = {
...super.attributes,
MemberScope: StringEntity,
MemberName: StringEntity.withDefault(),
MemberGuid: GuidEntity,
bSelfContext: BooleanEntity,
}
static grammar = this.createGrammar()
constructor(values) {
super(values);
/** @type {InstanceType} */ this.MemberScope;
/** @type {InstanceType} */ this.MemberName;
/** @type {InstanceType} */ this.MemberGuid;
/** @type {InstanceType} */ this.bSelfContext;
}
/** @returns {P} */
static createGrammar() {
return Grammar.createEntityGrammar(this).label("VariableReferenceEntity")
}
}
class ObjectEntity extends IEntity {
#exported = false
get exported() {
return this.#exported
}
set exported(value) {
this.#exported = value;
}
static #nameRegex = /^(\w+?)(?:_(\d+))?$/
/** @type {(k: String) => String} */
static printKey = k => !k.startsWith(Configuration.subObjectAttributeNamePrefix) ? k : ""
static attributeSeparator = "\n"
static wrap = this.notWrapped
static trailing = true
static attributes = {
...super.attributes,
Class: ObjectReferenceEntity,
Name: StringEntity,
Archetype: ObjectReferenceEntity,
ExportPath: MirroredEntity.of(ObjectReferenceEntity),
ObjectRef: ObjectReferenceEntity,
BlueprintElementType: ObjectReferenceEntity,
BlueprintElementInstance: ObjectReferenceEntity,
ConstA: MirroredEntity.of(NumberEntity),
ConstB: MirroredEntity.of(NumberEntity),
PinTags: ArrayEntity.of(NullEntity).flagInlined(),
PinNames: ArrayEntity.of(StringEntity).flagInlined(),
AxisKey: SymbolEntity,
InputAxisKey: SymbolEntity,
InputName: StringEntity,
InputType: SymbolEntity,
NumAdditionalInputs: NaturalNumberEntity,
bIsPureFunc: BooleanEntity,
bIsConstFunc: BooleanEntity,
bIsCaseSensitive: BooleanEntity,
VariableReference: VariableReferenceEntity,
SelfContextInfo: SymbolEntity,
DelegatePropertyName: StringEntity,
DelegateOwnerClass: ObjectReferenceEntity,
ComponentPropertyName: StringEntity,
EventReference: FunctionReferenceEntity,
FunctionReference: FunctionReferenceEntity,
FunctionScript: ObjectReferenceEntity,
CustomFunctionName: StringEntity,
TargetType: ObjectReferenceEntity,
MacroGraphReference: MacroGraphReferenceEntity,
Enum: ObjectReferenceEntity,
EnumEntries: ArrayEntity.of(StringEntity).flagInlined(),
InputKey: SymbolEntity,
OpName: StringEntity,
CachedChangeId: GuidEntity,
FunctionDisplayName: StringEntity,
AddedPins: ArrayEntity.of(UnknownPinEntity).withDefault().flagInlined().flagSilent(),
ChangeId: GuidEntity,
MaterialFunction: ObjectReferenceEntity,
bOverrideFunction: BooleanEntity,
bInternalEvent: BooleanEntity,
bConsumeInput: BooleanEntity,
bExecuteWhenPaused: BooleanEntity,
bOverrideParentBinding: BooleanEntity,
bControl: BooleanEntity,
bAlt: BooleanEntity,
bShift: BooleanEntity,
bCommand: BooleanEntity,
CommentColor: LinearColorEntity,
bCommentBubbleVisible_InDetailsPanel: BooleanEntity,
bColorCommentBubble: BooleanEntity,
ProxyFactoryFunctionName: StringEntity,
ProxyFactoryClass: ObjectReferenceEntity,
ProxyClass: ObjectReferenceEntity,
StructType: ObjectReferenceEntity,
MaterialExpression: ObjectReferenceEntity,
MaterialExpressionComment: ObjectReferenceEntity,
MoveMode: SymbolEntity,
TimelineName: StringEntity,
TimelineGuid: GuidEntity,
SizeX: MirroredEntity.of(IntegerEntity),
SizeY: MirroredEntity.of(IntegerEntity),
Text: MirroredEntity.of(StringEntity),
ParameterName: StringEntity,
ExpressionGUID: GuidEntity,
MaterialExpressionEditorX: MirroredEntity.of(IntegerEntity),
MaterialExpressionEditorY: MirroredEntity.of(IntegerEntity),
MaterialExpressionGuid: GuidEntity,
NodeTitle: StringEntity,
NodeTitleColor: LinearColorEntity,
PositionX: MirroredEntity.of(IntegerEntity),
PositionY: MirroredEntity.of(IntegerEntity),
SettingsInterface: ObjectReferenceEntity,
PCGNode: ObjectReferenceEntity,
HiGenGridSize: SymbolEntity,
Operation: SymbolEntity,
NodePosX: IntegerEntity,
NodePosY: IntegerEntity,
NodeHeight: IntegerEntity,
NodeWidth: IntegerEntity,
Graph: ObjectReferenceEntity,
SubgraphInstance: StringEntity,
InputPins: ArrayEntity.of(ObjectReferenceEntity).flagInlined(),
OutputPins: ArrayEntity.of(ObjectReferenceEntity).flagInlined(),
bExposeToLibrary: BooleanEntity,
bCanRenameNode: BooleanEntity,
bCommentBubblePinned: BooleanEntity,
bCommentBubbleVisible: BooleanEntity,
NodeComment: StringEntity,
AdvancedPinDisplay: SymbolEntity,
DelegateReference: VariableReferenceEntity,
EnabledState: SymbolEntity,
NodeGuid: GuidEntity,
ErrorType: IntegerEntity,
ErrorMsg: StringEntity,
ScriptVariables: ArrayEntity.flagInlined().of(ScriptVariableEntity),
Node: MirroredEntity.of(ObjectReferenceEntity),
ExportedNodes: StringEntity,
CustomProperties: ArrayEntity
.of(AlternativesEntity.accepting(PinEntity, UnknownPinEntity))
.withDefault()
.flagSilent(),
}
static customPropertyGrammar = Parsernostrum.seq(
Parsernostrum.reg(/CustomProperties\s+/),
this.attributes.CustomProperties.type.grammar,
).map(([_0, pin]) => values => {
/** @type {InstanceType} */(
values.CustomProperties ??= new (this.attributes.CustomProperties)()
).values.push(pin);
})
static inlinedArrayEntryGrammar = Parsernostrum.seq(
Parsernostrum.alt(
Grammar.symbolQuoted.map(v => [v, true]),
Grammar.symbol.map(v => [v, false]),
),
Parsernostrum.reg(new RegExp(String.raw`\s*\(\s*(\d+)\s*\)\s*\=\s*`), 1).map(Number) // Number in parentheses then equal
).chain(
/** @param {[[keyof ObjectEntity.attributes, Boolean], Number]} param */
([[symbol, quoted], index]) =>
(this.attributes[symbol]?.grammar ?? IEntity.unknownEntityGrammar).map(currentValue =>
values => {
if (values[symbol] === undefined) {
let arrayEntity = ArrayEntity;
if (quoted != arrayEntity.quoted) {
arrayEntity = arrayEntity.flagQuoted(quoted);
}
if (!arrayEntity.inlined) {
arrayEntity = arrayEntity.flagInlined();
}
values[symbol] = new arrayEntity();
}
/** @type {ArrayEntity} */
const target = values[symbol];
target.values[index] = currentValue;
}
)
)
static grammar = this.createGrammar()
static grammarMultipleObjects = Parsernostrum.seq(
Parsernostrum.whitespaceOpt,
this.grammar,
Parsernostrum.seq(
Parsernostrum.whitespace,
this.grammar,
)
.map(([_0, object]) => object)
.many(),
Parsernostrum.whitespaceOpt
).map(([_0, first, remaining, _4]) => [first, ...remaining])
constructor(values = {}) {
if (("NodePosX" in values) !== ("NodePosY" in values)) {
const entries = Object.entries(values);
const [key, position] = "NodePosX" in values
? ["NodePosY", Object.keys(values).indexOf("NodePosX") + 1]
: ["NodePosX", Object.keys(values).indexOf("NodePosY")];
entries.splice(position, 0, [key, new IntegerEntity(0)]);
values = Object.fromEntries(entries);
}
super(values);
// Attributes
/** @type {ArrayEntity} */ this.CustomProperties;
/** @type {InstanceType} */ this.AddedPins;
/** @type {InstanceType} */ this.AdvancedPinDisplay;
/** @type {InstanceType} */ this.Archetype;
/** @type {InstanceType} */ this.AxisKey;
/** @type {InstanceType} */ this.bIsPureFunc;
/** @type {InstanceType} */ this.BlueprintElementInstance;
/** @type {InstanceType} */ this.BlueprintElementType;
/** @type {InstanceType} */ this.Class;
/** @type {InstanceType} */ this.CommentColor;
/** @type {InstanceType} */ this.ComponentPropertyName;
/** @type {InstanceType} */ this.ConstA;
/** @type {InstanceType} */ this.ConstB;
/** @type {InstanceType} */ this.CustomFunctionName;
/** @type {InstanceType} */ this.DelegatePropertyName;
/** @type {InstanceType} */ this.DelegateReference;
/** @type {InstanceType} */ this.EnabledState;
/** @type {InstanceType} */ this.Enum;
/** @type {InstanceType} */ this.EnumEntries;
/** @type {InstanceType} */ this.EventReference;
/** @type {InstanceType} */ this.ExportedNodes;
/** @type {InstanceType} */ this.ExportPath;
/** @type {InstanceType} */ this.FunctionDisplayName;
/** @type {InstanceType} */ this.FunctionReference;
/** @type {InstanceType} */ this.FunctionScript;
/** @type {InstanceType} */ this.Graph;
/** @type {InstanceType} */ this.HiGenGridSize;
/** @type {InstanceType} */ this.InputAxisKey;
/** @type {InstanceType} */ this.InputKey;
/** @type {InstanceType} */ this.InputName;
/** @type {InstanceType} */ this.InputPins;
/** @type {InstanceType} */ this.InputType;
/** @type {InstanceType} */ this.MacroGraphReference;
/** @type {InstanceType} */ this.MaterialExpression;
/** @type {InstanceType} */ this.MaterialExpressionComment;
/** @type {InstanceType} */ this.MaterialExpressionEditorX;
/** @type {InstanceType} */ this.MaterialExpressionEditorY;
/** @type {InstanceType} */ this.MaterialFunction;
/** @type {InstanceType} */ this.Name;
/** @type {InstanceType} */ this.Node;
/** @type {InstanceType} */ this.NodeComment;
/** @type {InstanceType} */ this.NodeHeight;
/** @type {InstanceType} */ this.NodePosX;
/** @type {InstanceType} */ this.NodePosY;
/** @type {InstanceType} */ this.NodeTitle;
/** @type {InstanceType} */ this.NodeTitleColor;
/** @type {InstanceType} */ this.NodeWidth;
/** @type {InstanceType} */ this.NumAdditionalInputs;
/** @type {InstanceType} */ this.ObjectRef;
/** @type {InstanceType} */ this.Operation;
/** @type {InstanceType} */ this.OpName;
/** @type {InstanceType} */ this.OutputPins;
/** @type {InstanceType} */ this.ParameterName;
/** @type {InstanceType} */ this.PCGNode;
/** @type {InstanceType} */ this.PinNames;
/** @type {InstanceType} */ this.PinTags;
/** @type {InstanceType} */ this.PositionX;
/** @type {InstanceType} */ this.PositionY;
/** @type {InstanceType} */ this.ProxyFactoryFunctionName;
/** @type {InstanceType} */ this.ScriptVariables;
/** @type {InstanceType} */ this.SettingsInterface;
/** @type {InstanceType} */ this.SizeX;
/** @type {InstanceType} */ this.SizeY;
/** @type {InstanceType} */ this.StructType;
/** @type {InstanceType} */ this.SubgraphInstance;
/** @type {InstanceType} */ this.TargetType;
/** @type {InstanceType} */ this.Text;
/** @type {InstanceType