Refactoring entities (#23)

* Still WIP

* WIP

* ArrayEntity parsing fixed

* Fix format text entity

* Tests for various entity classes and update entity class implementations

* More tests and fixed

* More entities fixed

* Simple entities serialization fixed

* Entities tests fixed

* Remove serialization bits

* Fix Function reference

* CustomProperties creating fixed

* WIP

* Better typing for grammars

* Decoding code fixes

* Fixing still

* Several fixes

* rename toString to serialize

* Several fixes

* More fixes

* Moving more stuff out of Utility

* Several fixes

* Fixing Linear color entity print

* Serialization fixes

* Fix serialization

* Method to compute grammar

* Renaming fix

* Fix array grammar and equality check

* Fix inlined keys

* Fix type

* Several serialization fixes

* Fix undefined dereference

* Several fixes

* More fixes and cleanup

* Fix keys quoting mechanism

* Fix natural number assignment

* Fix Int64 toString()

* Fix quoted keys for inlined arrays

* Fix PG pins

* Fix several test cases

* Types fixes

* New pin default value empty

* Fix non existing DefaultValue for variadic nodes

* Smaller fixes for crashes

* Fix link color when attached to knot

* Linking test and more reliability operations for adding pins

* Improve issue 18 test

* More tests and fixes

* Fix enum pin entity

* Remove failing test
This commit is contained in:
barsdeveloper
2024-09-08 11:46:36 +02:00
committed by GitHub
parent 31a07b992d
commit 23ee628e28
129 changed files with 8888 additions and 8584 deletions

View File

@@ -646,7 +646,7 @@ ueb-node.ueb-node-style-conversion .ueb-node-wrapper::after {
grid-area: center;
align-self: center;
justify-self: center;
margin: 10px;
margin: 10px 10px 10px -6px;
width: 6px;
height: 6px;
border-radius: 3px;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

9019
dist/ueblueprint.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -4,6 +4,7 @@ import IElement from "./element/IElement.js"
import LinkElement from "./element/LinkElement.js"
import NodeElement from "./element/NodeElement.js"
import BlueprintEntity from "./entity/BlueprintEntity.js"
import BooleanEntity from "./entity/BooleanEntity.js"
import BlueprintTemplate from "./template/BlueprintTemplate.js"
/** @extends {IElement<BlueprintEntity, BlueprintTemplate>} */
@@ -14,19 +15,19 @@ export default class Blueprint extends IElement {
type: Boolean,
attribute: "data-selecting",
reflect: true,
converter: Utility.booleanConverter,
converter: BooleanEntity.booleanConverter,
},
scrolling: {
type: Boolean,
attribute: "data-scrolling",
reflect: true,
converter: Utility.booleanConverter,
converter: BooleanEntity.booleanConverter,
},
focused: {
type: Boolean,
attribute: "data-focused",
reflect: true,
converter: Utility.booleanConverter,
converter: BooleanEntity.booleanConverter,
},
zoom: {
type: Number,
@@ -411,15 +412,16 @@ export default class Blueprint extends IElement {
if (element instanceof NodeElement && !this.nodes.includes(element)) {
if (element.getType() == Configuration.paths.niagaraClipboardContent) {
this.entity = this.entity.mergeWith(element.entity)
const additionalSerialization = atob(element.entity.ExportedNodes)
const additionalSerialization = atob(element.entity.ExportedNodes.toString())
this.template.getPasteInputObject().pasted(additionalSerialization)
.forEach(node => node.entity.isExported = true)
.forEach(node => node.entity._exported = true)
continue
}
const name = element.entity.getObjectName()
const homonym = this.entity.getHomonymObjectEntity(element.entity)
if (homonym) {
homonym.Name = this.entity.takeFreeName(name)
homonym.Name.value = this.entity.takeFreeName(name)
homonym.Name = homonym.Name
}
this.nodes.push(element)
this.entity.addObjectEntity(element.entity)

View File

@@ -128,6 +128,7 @@ export default class Configuration {
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",
@@ -166,8 +167,10 @@ export default class Configuration {
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",
@@ -212,7 +215,7 @@ export default class Configuration {
vector4f: "/Script/CoreUObject.Vector4f",
whileLoop: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:WhileLoop",
}
static pinInputWrapWidth = 143 // px
static pinInputWrapWidth = 145 // px
static removeEventName = "ueb-element-delete"
static scale = {
[-12]: 0.133333,
@@ -265,6 +268,7 @@ export default class Configuration {
"KeepWorld",
"SnapToTarget",
],
[this.paths.eDrawDebugTrace]: ["None", "ForOneFrame", "ForDuration", "Persistent"],
[this.paths.eMaterialSamplerType]: [
"Color",
"Grayscale",
@@ -291,6 +295,14 @@ export default class Configuration {
["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"],
@@ -298,9 +310,6 @@ export default class Configuration {
["AdjustIfPossibleButDontSpawnIfColliding", "Try To Adjust Location, Don't Spawn If Still Colliding"],
["DontSpawnIfColliding", "Do Not Spawn"],
],
[this.paths.eSearchCase]: ["CaseSensitive", "IgnoreCase"],
[this.paths.eSearchDir]: ["FromStart", "FromEnd"],
[this.paths.eDrawDebugTrace]: ["None", "ForOneFrame", "ForDuration", "Persistent"],
[this.paths.eTextureMipValueMode]: [
"None (use computed mip level)",
"MipLevel (absolute, 0 is full resolution)",

View File

@@ -1,35 +1,7 @@
import ComputedType from "./entity/ComputedType.js"
import Configuration from "./Configuration.js"
import MirroredEntity from "./entity/MirroredEntity.js"
import Union from "./entity/Union.js"
export default class Utility {
static booleanConverter = {
fromAttribute: (value, type) => {
value ? "true" : "false"
},
toAttribute: (value, type) => {
if (value === true) {
return "true"
}
if (value === false) {
return "false"
}
return ""
}
}
/** @param {Number} x */
static sigmoid(x, curvature = 1.7) {
return 1 / (1 + (x / (1 - x) ** -curvature))
}
/** @param {Number} x */
static sigmoidPositive(x, curvature = 3.7, length = 1.1) {
return 1 - Math.exp(-((x / length) ** curvature))
}
/** @param {Number} value */
static clamp(value, min = -Infinity, max = Infinity) {
return Math.min(Math.max(value, min), max)
@@ -55,23 +27,6 @@ export default class Utility {
return num.toFixed(decimals)
}
/** @param {String} value */
static 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
}
}
/**
* @param {Number} num
* @param {Number} decimals
@@ -81,16 +36,6 @@ export default class Utility {
return Math.round(num * power) / power
}
/** @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)
}
/** @param {Number} num */
static printExponential(num) {
if (num == Number.POSITIVE_INFINITY) {
@@ -184,97 +129,6 @@ export default class Utility {
return false
}
/**
* @param {Attribute} a
* @param {Attribute} b
*/
static equals(a, b) {
while (a instanceof MirroredEntity) {
a = a.get()
}
while (b instanceof MirroredEntity) {
b = b.get()
}
// Here we cannot check both instanceof IEntity because this would introduce a circular include dependency
if (/** @type {IEntity?} */(a)?.equals && /** @type {IEntity?} */(b)?.equals) {
return /** @type {IEntity} */(a).equals(/** @type {IEntity} */(b))
}
a = Utility.sanitize(a)
b = Utility.sanitize(b)
if (a?.constructor === BigInt && b?.constructor === Number) {
b = BigInt(b)
} else if (a?.constructor === Number && b?.constructor === BigInt) {
a = BigInt(a)
}
if (a === b) {
return true
}
if (a instanceof Array && b instanceof Array) {
return a.length === b.length && a.every((value, i) => Utility.equals(value, b[i]))
}
return false
}
/**
* @template {Attribute | AttributeTypeDescription} T
* @param {T} value
* @returns {AttributeConstructor<T>}
*/
static getType(value) {
if (value === null) {
return null
}
if (value?.constructor === Object && /** @type {AttributeInformation} */(value)?.type instanceof Function) {
return /** @type {AttributeInformation} */(value).type
}
return /** @type {AttributeConstructor<any>} */(value?.constructor)
}
/**
* @template {Attribute} V
* @template {AttributeConstructor<V>} C
* @param {C} type
* @returns {value is InstanceType<C>}
*/
static isValueOfType(value, type, acceptNull = false) {
if (type instanceof MirroredEntity) {
type = type.getTargetType()
}
return (acceptNull && value === null) || value instanceof type || value?.constructor === type
}
/** @param {Attribute} value */
static sanitize(value, targetType = /** @type {AttributeTypeDescription } */(value?.constructor)) {
if (targetType instanceof Array) {
targetType = targetType[0]
}
if (targetType instanceof ComputedType) {
return value // The type is computed, can't say anything about it
}
if (targetType instanceof Union) {
let type = targetType.values.find(t => Utility.isValueOfType(value, t, false))
if (!type) {
type = targetType.values[0]
}
targetType = type
}
if (targetType instanceof MirroredEntity) {
if (value instanceof MirroredEntity) {
return value
}
return Utility.sanitize(value, targetType.getTargetType())
}
if (targetType && !Utility.isValueOfType(value, targetType, true)) {
value = targetType === BigInt
? BigInt(/** @type {Number} */(value))
: new /** @type {EntityConstructor} */(targetType)(value)
}
if (value instanceof Boolean || value instanceof Number || value instanceof String) {
value = /** @type {TerminalAttribute} */(value.valueOf()) // Get the relative primitive value
}
return value
}
/**
* @param {Number} x
* @param {Number} y
@@ -334,11 +188,14 @@ export default class Utility {
}
/** @param {String} value */
static escapeString(value) {
return value
.replaceAll(new RegExp(`(${Configuration.stringEscapedCharacters.source})`, "g"), '\\$1')
.replaceAll("\n", "\\n") // Replace newline with \n
.replaceAll("\t", "\\t") // Replace tab with \t
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 */
@@ -398,11 +255,6 @@ export default class Utility {
return pathValue.match(regex)?.[1] ?? ""
}
/** @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
* @param {Number} y

View File

@@ -8,23 +8,22 @@ export default function nodeColor(entity) {
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.materialExpressionFunctionInput:
return Configuration.nodeColors.red
case Configuration.paths.materialExpressionTextureSample:
return Configuration.nodeColors.darkTurquoise
case Configuration.paths.materialExpressionTextureCoordinate:
return Configuration.nodeColors.red
case Configuration.paths.pcgEditorGraphNodeInput:
case Configuration.paths.pcgEditorGraphNodeOutput:
return Configuration.nodeColors.red
}
switch (entity.getClass()) {
case Configuration.paths.callFunction:
return entity.bIsPureFunc
return entity.bIsPureFunc?.valueOf()
? Configuration.nodeColors.green
: Configuration.nodeColors.blue
case Configuration.paths.niagaraNodeFunctionCall:
@@ -74,7 +73,7 @@ export default function nodeColor(entity) {
return Configuration.nodeColors.intenseGreen
}
}
if (entity.bIsPureFunc) {
if (entity.bIsPureFunc?.valueOf()) {
return Configuration.nodeColors.green
}
return Configuration.nodeColors.blue

View File

@@ -20,7 +20,7 @@ export default function nodeTemplateClass(nodeEntity) {
|| nodeEntity.getClass() === Configuration.paths.callArrayFunction
) {
const memberParent = nodeEntity.FunctionReference?.MemberParent?.path ?? ""
const memberName = nodeEntity.FunctionReference?.MemberName
const memberName = nodeEntity.FunctionReference?.MemberName?.toString()
if (
memberName && (
memberParent === Configuration.paths.kismetMathLibrary
@@ -96,13 +96,15 @@ export default function nodeTemplateClass(nodeEntity) {
}
return MetasoundNodeTemplate
case Configuration.paths.niagaraNodeOp:
if ([
"Boolean::LogicEq",
"Boolean::LogicNEq",
"Numeric::Abs",
"Numeric::Add",
"Numeric::Mul",
].includes(nodeEntity.OpName)) {
if (
[
"Boolean::LogicEq",
"Boolean::LogicNEq",
"Numeric::Abs",
"Numeric::Add",
"Numeric::Mul",
].includes(nodeEntity.OpName?.toString())
) {
return VariableOperationNodeTemplate
}
break

View File

@@ -1,5 +1,9 @@
import Configuration from "../Configuration.js"
import Utility from "../Utility.js"
import BooleanEntity from "../entity/BooleanEntity.js"
import LinearColorEntity from "../entity/LinearColorEntity.js"
import MirroredEntity from "../entity/MirroredEntity.js"
import VectorEntity from "../entity/VectorEntity.js"
const sequencerScriptingNameRegex = /\/Script\/SequencerScripting\.MovieSceneScripting(.+)Channel/
const keyNameValue = {
@@ -31,43 +35,63 @@ const keyNameValue = {
"Tilde": "`",
}
/** @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 = Utility.numberFromText(value)?.toString()
result = numberFromText(value)?.toString()
if (result) {
return result
}
const match = value.match(/NumPad([a-zA-Z]+)/)
if (match) {
result = Utility.numberFromText(match[1]).toString()
result = numberFromText(match[1]).toString()
if (result) {
return "Num " + result
}
}
}
/** @param {ObjectEntity} entity */
/**
* @param {ObjectEntity} entity
* @returns {String}
*/
export default function nodeTitle(entity) {
let input
switch (entity.getType()) {
case Configuration.paths.asyncAction:
if (entity.ProxyFactoryFunctionName) {
return Utility.formatStringName(entity.ProxyFactoryFunctionName)
return Utility.formatStringName(entity.ProxyFactoryFunctionName?.toString())
}
case Configuration.paths.actorBoundEvent:
case Configuration.paths.componentBoundEvent:
return `${Utility.formatStringName(entity.DelegatePropertyName)} (${entity.ComponentPropertyName ?? "Unknown"})`
return `${Utility.formatStringName(entity.DelegatePropertyName?.toString())} (${entity.ComponentPropertyName?.toString() ?? "Unknown"})`
case Configuration.paths.callDelegate:
return `Call ${entity.DelegateReference?.MemberName ?? "None"}`
return `Call ${entity.DelegateReference?.MemberName?.toString() ?? "None"}`
case Configuration.paths.createDelegate:
return "Create Event"
case Configuration.paths.customEvent:
if (entity.CustomFunctionName) {
return entity.CustomFunctionName
return entity.CustomFunctionName?.toString()
}
case Configuration.paths.dynamicCast:
if (!entity.TargetType) {
@@ -77,7 +101,7 @@ export default function nodeTitle(entity) {
case Configuration.paths.enumLiteral:
return `Literal enum ${entity.Enum?.getName()}`
case Configuration.paths.event:
return `Event ${(entity.EventReference?.MemberName ?? "").replace(/^Receive/, "")}`
return `Event ${(entity.EventReference?.MemberName?.toString() ?? "").replace(/^Receive/, "")}`
case Configuration.paths.executionSequence:
return "Sequence"
case Configuration.paths.forEachElementInEnum:
@@ -85,9 +109,9 @@ export default function nodeTitle(entity) {
case Configuration.paths.forEachLoopWithBreak:
return "For Each Loop with Break"
case Configuration.paths.functionEntry:
return entity.FunctionReference?.MemberName === "UserConstructionScript"
return entity.FunctionReference?.MemberName?.toString() === "UserConstructionScript"
? "Construction Script"
: entity.FunctionReference?.MemberName
: entity.FunctionReference?.MemberName?.toString()
case Configuration.paths.functionResult:
return "Return Node"
case Configuration.paths.ifThenElse:
@@ -98,36 +122,32 @@ export default function nodeTitle(entity) {
}
case Configuration.paths.materialExpressionComponentMask: {
const materialObject = entity.getMaterialSubobject()
return `Mask ( ${Configuration.rgba
.filter(k => /** @type {MirroredEntity<any>} */(materialObject[k]).get() === true)
.map(v => v + " ")
.join("")})`
if (materialObject) {
return `Mask ( ${Configuration.rgba
.filter(k => /** @type {MirroredEntity<typeof BooleanEntity>} */(materialObject[k]).getter().value === true)
.map(v => v + " ")
.join("")})`
}
}
case Configuration.paths.materialExpressionConstant:
input ??= [entity.getCustomproperties().find(pinEntity => pinEntity.PinName == "Value")?.DefaultValue]
input ??= [entity.getCustomproperties().find(pinEntity => pinEntity.PinName.toString() == "Value")?.DefaultValue]
case Configuration.paths.materialExpressionConstant2Vector:
input ??= [
entity.getCustomproperties().find(pinEntity => pinEntity.PinName == "X")?.DefaultValue,
entity.getCustomproperties().find(pinEntity => pinEntity.PinName == "Y")?.DefaultValue,
entity.getCustomproperties().find(pinEntity => pinEntity.PinName?.toString() == "X")?.DefaultValue,
entity.getCustomproperties().find(pinEntity => pinEntity.PinName?.toString() == "Y")?.DefaultValue,
]
case Configuration.paths.materialExpressionConstant3Vector:
if (!input) {
/** @type {VectorEntity} */
const vector = entity.getCustomproperties()
.find(pinEntity => pinEntity.PinName == "Constant")
?.DefaultValue
input = [vector.X, vector.Y, vector.Z]
}
case Configuration.paths.materialExpressionConstant4Vector:
if (!input) {
/** @type {LinearColorEntity} */
const vector = entity.getCustomproperties()
.find(pinEntity => pinEntity.PinName == "Constant")
.find(pinEntity => pinEntity.PinName?.toString() == "Constant")
?.DefaultValue
input = [vector.R, vector.G, vector.B, vector.A].map(v => v.valueOf())
input = 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 (input.length > 0) {
return input.map(v => Utility.printExponential(v)).reduce((acc, cur) => acc + "," + cur)
return input.map(v => Utility.printExponential(v)).join(",")
}
break
case Configuration.paths.materialExpressionFunctionInput: {
@@ -150,6 +170,11 @@ export default function nodeTitle(entity) {
break
case Configuration.paths.materialExpressionSquareRoot:
return "Sqrt"
case Configuration.paths.materialExpressionSubtract:
const materialObject = entity.getMaterialSubobject()
if (materialObject) {
return `Subtract(${materialObject.ConstA ?? "1"},${materialObject.ConstB ?? "1"})`
}
case Configuration.paths.metasoundEditorGraphExternalNode: {
const name = entity["ClassName"]?.["Name"]
if (name) {
@@ -165,7 +190,7 @@ export default function nodeTitle(entity) {
return "Output"
case Configuration.paths.spawnActorFromClass:
let className = entity.getCustomproperties()
.find(pinEntity => pinEntity.PinName == "ReturnValue")
.find(pinEntity => pinEntity.PinName.toString() == "ReturnValue")
?.PinType
?.PinSubCategoryObject
?.getName()
@@ -190,7 +215,7 @@ export default function nodeTitle(entity) {
return `Switch on ${switchTarget}`
}
if (entity.isComment()) {
return entity.NodeComment
return entity.NodeComment.toString()
}
const keyNameSymbol = entity.getHIDAttribute()
if (keyNameSymbol) {
@@ -213,7 +238,7 @@ export default function nodeTitle(entity) {
}
if (entity.isPcg() && entity.getPcgSubobject()) {
let pcgSubobject = entity.getPcgSubobject()
let result = pcgSubobject.NodeTitle ? pcgSubobject.NodeTitle : nodeTitle(pcgSubobject)
let result = pcgSubobject.NodeTitle ? pcgSubobject.NodeTitle.toString() : nodeTitle(pcgSubobject)
return result
}
const subgraphObject = entity.getSubgraphObject()
@@ -232,7 +257,7 @@ export default function nodeTitle(entity) {
return Utility.formatStringName(settingsObject.BlueprintElementType.getName())
}
if (settingsObject.Operation) {
const match = settingsObject.Name.match(/PCGMetadata(\w+)Settings_\d+/)
const match = settingsObject.Name?.toString().match(/PCGMetadata(\w+)Settings_\d+/)
if (match) {
return Utility.formatStringName(match[1] + ": " + settingsObject.Operation)
}
@@ -242,7 +267,7 @@ export default function nodeTitle(entity) {
return settingsSubgraphObject.Graph.getName()
}
}
let memberName = entity.FunctionReference?.MemberName
let memberName = entity.FunctionReference?.MemberName?.toString()
if (memberName) {
const memberParent = entity.FunctionReference.MemberParent?.path ?? ""
switch (memberName) {
@@ -379,7 +404,7 @@ export default function nodeTitle(entity) {
return Utility.formatStringName(memberName)
}
if (entity.OpName) {
switch (entity.OpName) {
switch (entity.OpName.toString()) {
case "Boolean::LogicAnd": return "Logic AND"
case "Boolean::LogicEq": return "=="
case "Boolean::LogicNEq": return "!="
@@ -392,10 +417,10 @@ export default function nodeTitle(entity) {
case "Numeric::DistancePos": return "Distance"
case "Numeric::Mul": return String.fromCharCode(0x2a2f)
}
return Utility.formatStringName(entity.OpName).replaceAll("::", " ")
return Utility.formatStringName(entity.OpName.toString()).replaceAll("::", " ")
}
if (entity.FunctionDisplayName) {
return Utility.formatStringName(entity.FunctionDisplayName)
return Utility.formatStringName(entity.FunctionDisplayName.toString())
}
if (entity.ObjectRef) {
return entity.ObjectRef.getName()

View File

@@ -1,10 +1,13 @@
import Configuration from "../Configuration.js"
import ArrayEntity from "../entity/ArrayEntity.js"
import GuidEntity from "../entity/GuidEntity.js"
import NaturalNumberEntity from "../entity/NaturalNumberEntity.js"
import PinEntity from "../entity/PinEntity.js"
import StringEntity from "../entity/StringEntity.js"
/** @param {PinEntity} pinEntity */
const indexFromUpperCaseLetterName = pinEntity =>
pinEntity.PinName.match(/^\s*([A-Z])\s*$/)?.[1]?.charCodeAt(0) - "A".charCodeAt(0)
pinEntity.PinName?.toString().match(/^\s*([A-Z])\s*$/)?.[1]?.charCodeAt(0) - "A".charCodeAt(0)
/** @param {ObjectEntity} entity */
export default function nodeVariadic(entity) {
@@ -15,11 +18,12 @@ export default function nodeVariadic(entity) {
/** @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
name = entity.FunctionReference?.MemberName?.toString()
switch (name) {
default:
if (
@@ -50,17 +54,22 @@ export default function nodeVariadic(entity) {
pinIndexFromEntity ??= indexFromUpperCaseLetterName
pinNameFromIndex ??= (index, min = -1, max = -1) => {
const result = String.fromCharCode(index >= 0 ? index : max + "A".charCodeAt(0) + 1)
entity.NumAdditionalInputs = pinEntities().length - 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.match(/^\s*Out[_\s]+(\d+)\s*$/i)?.[1])
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) =>
`Out ${index >= 0 ? index : min > 0 ? "Out 0" : max + 1}`
`${prefix} ${index >= 0 ? index : min > 0 ? `${prefix} 0` : max + 1}`
break
// case Configuration.paths.niagaraNodeOp:
// pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isInput())
@@ -74,26 +83,26 @@ export default function nodeVariadic(entity) {
// break
case Configuration.paths.switchInteger:
pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isOutput())
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.match(/^\s*(\d+)\s*$/)?.[1])
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 ??= []
entity.PinNames.push(result)
delete entity.PinTags[entity.PinTags.length - 1]
entity.PinTags[entity.PinTags.length] = null
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.match(/^\s*Case[_\s]+(\d+)\s*$/i)?.[1])
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 ??= []
entity.PinNames.push(result)
entity.PinNames ??= new ArrayEntity()
entity.PinNames.valueOf().push(new StringEntity(result))
return result
}
break
@@ -138,9 +147,13 @@ export default function nodeVariadic(entity) {
}
)
const newPin = new PinEntity(modelPin)
newPin.PinId = GuidEntity.generateGuid()
newPin.PinName = pinNameFromIndex(index, min, max, newPin)
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
}

View File

@@ -52,17 +52,17 @@ const colors = {
const pinColorMaterial = css`120, 120, 120`
/** @param {PinEntity} entity */
/** @param {PinEntity<IEntity>} entity */
export default function pinColor(entity) {
if (entity.PinType.PinCategory == "mask") {
if (entity.PinType.PinCategory?.toString() === "mask") {
const result = colors[entity.PinType.PinSubCategory]
if (result) {
return result
}
} else if (entity.PinType.PinCategory == "optional") {
} else if (entity.PinType.PinCategory?.toString() === "optional") {
return pinColorMaterial
}
return colors[entity.getType()]
?? colors[entity.PinType.PinCategory.toLowerCase()]
?? colors[entity.PinType.PinCategory?.toString().toLowerCase()]
?? colors["default"]
}

View File

@@ -16,6 +16,16 @@ import Vector4DPinTemplate from "../template/pin/Vector4DPinTemplate.js"
import VectorPinTemplate from "../template/pin/VectorPinTemplate.js"
const inputPinTemplates = {
"bool": BoolPinTemplate,
"byte": IntPinTemplate,
"enum": EnumPinTemplate,
"int": IntPinTemplate,
"int64": Int64PinTemplate,
"MUTABLE_REFERENCE": ReferencePinTemplate,
"name": NamePinTemplate,
"real": RealPinTemplate,
"rg": Vector2DPinTemplate,
"string": StringPinTemplate,
[Configuration.paths.linearColor]: LinearColorPinTemplate,
[Configuration.paths.niagaraBool]: BoolPinTemplate,
[Configuration.paths.niagaraPosition]: VectorPinTemplate,
@@ -24,28 +34,19 @@ const inputPinTemplates = {
[Configuration.paths.vector2D]: Vector2DPinTemplate,
[Configuration.paths.vector3f]: VectorPinTemplate,
[Configuration.paths.vector4f]: Vector4DPinTemplate,
"bool": BoolPinTemplate,
"byte": IntPinTemplate,
"enum": EnumPinTemplate,
"int": IntPinTemplate,
"int64": Int64PinTemplate,
"MUTABLE_REFERENCE": ReferencePinTemplate,
"name": NamePinTemplate,
"rg": Vector2DPinTemplate,
"real": RealPinTemplate,
"string": StringPinTemplate,
}
/** @param {PinEntity} entity */
/** @param {PinEntity<IEntity>} entity */
export default function pinTemplate(entity) {
if (entity.PinType.ContainerType?.toString() === "Array") {
return PinTemplate
}
if (entity.PinType.bIsReference && !entity.PinType.bIsConst) {
if (entity.PinType.bIsReference?.valueOf() && !entity.PinType.bIsConst?.valueOf()) {
return inputPinTemplates["MUTABLE_REFERENCE"]
}
if (entity.getType() === "exec") {
const type = entity.getType()
if (type === "exec") {
return ExecPinTemplate
}
return (entity.isInput() ? inputPinTemplates[entity.getType()] : PinTemplate) ?? PinTemplate
return (entity.isInput() ? inputPinTemplates[type] : PinTemplate) ?? PinTemplate
}

View File

@@ -1,16 +1,12 @@
import Utility from "../Utility.js"
/** @param {PinEntity} entity */
/** @param {PinEntity<IEntity>} entity */
export default function pinTitle(entity) {
let result = entity.PinFriendlyName
? entity.PinFriendlyName.toString()
: Utility.formatStringName(entity.PinName ?? "")
: Utility.formatStringName(entity.PinName?.toString() ?? "")
let match
if (
entity.PinToolTip
// Match up until the first \n excluded or last character
&& (match = entity.PinToolTip.match(/\s*(.+?(?=\n)|.+\S)\s*/))
) {
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
}

View File

@@ -1,11 +1,11 @@
export default class ElementFactory {
/** @type {Map<String, AnyConstructor<IElement>>} */
/** @type {Map<String, IElementConstructor>} */
static #elementConstructors = new Map()
/**
* @param {String} tagName
* @param {AnyConstructor<IElement>} entityConstructor
* @param {IElementConstructor} entityConstructor
*/
static registerElement(tagName, entityConstructor) {
ElementFactory.#elementConstructors.set(tagName, entityConstructor)

View File

@@ -1,5 +1,5 @@
import Configuration from "../Configuration.js"
import Utility from "../Utility.js"
import BooleanEntity from "../entity/BooleanEntity.js"
import IDraggableElement from "./IDraggableElement.js"
/**
@@ -15,7 +15,7 @@ export default class ISelectableDraggableElement extends IDraggableElement {
type: Boolean,
attribute: "data-selected",
reflect: true,
converter: Utility.booleanConverter,
converter: BooleanEntity.booleanConverter,
},
}

View File

@@ -1,6 +1,6 @@
import IElement from "./IElement.js"
import BooleanEntity from "../entity/BooleanEntity.js"
import InputTemplate from "../template/pin/InputTemplate.js"
import Utility from "../Utility.js"
import IElement from "./IElement.js"
/** @extends {IElement<Object, InputTemplate>} */
export default class InputElement extends IElement {
@@ -10,19 +10,19 @@ export default class InputElement extends IElement {
singleLine: {
type: Boolean,
attribute: "data-single-line",
converter: Utility.booleanConverter,
converter: BooleanEntity.booleanConverter,
reflect: true,
},
selectOnFocus: {
type: Boolean,
attribute: "data-select-focus",
converter: Utility.booleanConverter,
converter: BooleanEntity.booleanConverter,
reflect: true,
},
blurOnEnter: {
type: Boolean,
attribute: "data-blur-enter",
converter: Utility.booleanConverter,
converter: BooleanEntity.booleanConverter,
reflect: true,
},
}

View File

@@ -1,9 +1,10 @@
import { html, nothing } from "lit"
import Configuration from "../Configuration.js"
import IFromToPositionedElement from "./IFromToPositionedElement.js"
import LinkTemplate from "../template/LinkTemplate.js"
import SVGIcon from "../SVGIcon.js"
import Utility from "../Utility.js"
import BooleanEntity from "../entity/BooleanEntity.js"
import LinkTemplate from "../template/LinkTemplate.js"
import IFromToPositionedElement from "./IFromToPositionedElement.js"
/** @extends {IFromToPositionedElement<Object, LinkTemplate>} */
export default class LinkElement extends IFromToPositionedElement {
@@ -13,7 +14,7 @@ export default class LinkElement extends IFromToPositionedElement {
dragging: {
type: Boolean,
attribute: "data-dragging",
converter: Utility.booleanConverter,
converter: BooleanEntity.booleanConverter,
reflect: true,
},
originatesFromInput: {
@@ -205,11 +206,16 @@ export default class LinkElement extends IFromToPositionedElement {
this.toY = location[1]
}
getInputPin() {
getInputPin(getSomething = false) {
if (this.source?.isInput()) {
return this.source
}
return this.destination
if (this.destination?.isInput()) {
return this.destination
}
if (getSomething) {
return this.source ?? this.destination
}
}
/** @param {PinElement} pin */
@@ -220,11 +226,16 @@ export default class LinkElement extends IFromToPositionedElement {
this.destination = pin
}
getOutputPin() {
getOutputPin(getSomething = false) {
if (this.source?.isOutput()) {
return this.source
}
if (this.destination?.isOutput()) {
return this.destination
}
return this.source
if (getSomething) {
return this.source ?? this.destination
}
}
/** @param {PinElement} pin */

View File

@@ -1,12 +1,11 @@
import Configuration from "../Configuration.js"
import Utility from "../Utility.js"
import nodeTemplateClass from "../decoding/nodeTemplate.js"
import nodeTitle from "../decoding/nodeTitle.js"
import IdentifierEntity from "../entity/IdentifierEntity.js"
import BooleanEntity from "../entity/BooleanEntity.js"
import ObjectEntity from "../entity/ObjectEntity.js"
import PinEntity from "../entity/PinEntity.js"
import PinReferenceEntity from "../entity/PinReferenceEntity.js"
import SerializerFactory from "../serialization/SerializerFactory.js"
import SymbolEntity from "../entity/SymbolEntity.js"
import NodeTemplate from "../template/node/NodeTemplate.js"
import ISelectableDraggableElement from "./ISelectableDraggableElement.js"
@@ -28,7 +27,7 @@ export default class NodeElement extends ISelectableDraggableElement {
advancedPinDisplay: {
type: String,
attribute: "data-advanced-display",
converter: IdentifierEntity.attributeConverter,
converter: SymbolEntity.attributeConverter,
reflect: true,
},
enabledState: {
@@ -42,7 +41,7 @@ export default class NodeElement extends ISelectableDraggableElement {
},
pureFunction: {
type: Boolean,
converter: Utility.booleanConverter,
converter: BooleanEntity.booleanConverter,
attribute: "data-pure-function",
reflect: true,
},
@@ -86,7 +85,7 @@ export default class NodeElement extends ISelectableDraggableElement {
/** @param {String} str */
static fromSerializedObject(str) {
str = str.trim()
let entity = SerializerFactory.getSerializer(ObjectEntity).read(str)
let entity = ObjectEntity.grammar.parse(str)
return NodeElement.newObject(/** @type {ObjectEntity} */(entity))
}
@@ -100,13 +99,17 @@ export default class NodeElement extends ISelectableDraggableElement {
return result
}
/** @param {String} name */
#redirectLinksAfterRename(name) {
for (let sourcePinElement of this.getPinElements()) {
for (let targetPinReference of sourcePinElement.getLinks()) {
this.blueprint.getPin(targetPinReference).redirectLink(sourcePinElement, new PinReferenceEntity({
objectName: name,
pinGuid: sourcePinElement.entity.PinId,
}))
this.blueprint.getPin(targetPinReference).redirectLink(
sourcePinElement,
new PinReferenceEntity(
new SymbolEntity(name),
sourcePinElement.entity.PinId,
)
)
}
}
}
@@ -117,7 +120,7 @@ export default class NodeElement extends ISelectableDraggableElement {
this.advancedPinDisplay = entity.AdvancedPinDisplay?.toString()
this.enabledState = entity.EnabledState
this.nodeDisplayName = nodeTitle(entity)
this.pureFunction = entity.bIsPureFunc
this.pureFunction = entity.bIsPureFunc?.valueOf()
this.dragLinkObjects = []
super.initialize(entity, template)
this.#pins = this.template.createPinElements()
@@ -128,11 +131,15 @@ export default class NodeElement extends ISelectableDraggableElement {
} else {
this.updateComplete.then(() => this.computeSizes())
}
entity.listenAttribute("Name", name => {
this.nodeTitle = entity.Name
this.nodeDisplayName = nodeTitle(entity)
this.#redirectLinksAfterRename(name)
})
entity.listenAttribute(
"Name",
/** @param {InstanceType<typeof ObjectEntity.attributes.Name>} newName */
newName => {
this.nodeTitle = newName.value
this.nodeDisplayName = nodeTitle(entity)
this.#redirectLinksAfterRename(newName.value)
}
)
}
async getUpdateComplete() {
@@ -223,7 +230,7 @@ export default class NodeElement extends ISelectableDraggableElement {
}
setShowAdvancedPinDisplay(value) {
this.entity.AdvancedPinDisplay = new IdentifierEntity(value ? "Shown" : "Hidden")
this.entity.AdvancedPinDisplay = new SymbolEntity(value ? "Shown" : "Hidden")
this.advancedPinDisplay = this.entity.AdvancedPinDisplay
}

View File

@@ -1,15 +1,17 @@
import Utility from "../Utility.js"
import pinTemplate from "../decoding/pinTemplate.js"
import ArrayEntity from "../entity/ArrayEntity.js"
import BooleanEntity from "../entity/BooleanEntity.js"
import GuidEntity from "../entity/GuidEntity.js"
import LinearColorEntity from "../entity/LinearColorEntity.js"
import PinEntity from "../entity/PinEntity.js"
import PinReferenceEntity from "../entity/PinReferenceEntity.js"
import SymbolEntity from "../entity/SymbolEntity.js"
import PinTemplate from "../template/pin/PinTemplate.js"
import ElementFactory from "./ElementFactory.js"
import IElement from "./IElement.js"
/**
* @template {TerminalAttribute} T
* @template {IEntity} T
* @extends {IElement<PinEntity<T>, PinTemplate>}
*/
export default class PinElement extends IElement {
@@ -42,7 +44,7 @@ export default class PinElement extends IElement {
fromAttribute: (value, type) => value
? LinearColorEntity.getLinearColorFromAnyFormat().parse(value)
: null,
toAttribute: (value, type) => value ? Utility.printLinearColor(value) : null,
toAttribute: (value, type) => value ? LinearColorEntity.printLinearColor(value) : null,
},
attribute: "data-color",
reflect: true,
@@ -53,7 +55,7 @@ export default class PinElement extends IElement {
},
isLinked: {
type: Boolean,
converter: Utility.booleanConverter,
converter: BooleanEntity.booleanConverter,
attribute: "data-linked",
reflect: true,
},
@@ -64,7 +66,7 @@ export default class PinElement extends IElement {
},
connectable: {
type: Boolean,
converter: Utility.booleanConverter,
converter: BooleanEntity.booleanConverter,
attribute: "data-connectable",
reflect: true,
}
@@ -89,9 +91,9 @@ export default class PinElement extends IElement {
nodeElement = undefined
) {
this.nodeElement = nodeElement
this.advancedView = entity.bAdvancedView
this.advancedView = entity.bAdvancedView?.valueOf()
this.isLinked = false
this.connectable = !entity.bNotConnectable
this.connectable = !entity.bNotConnectable?.valueOf()
super.initialize(entity, template)
this.pinType = this.entity.getType()
this.defaultValue = this.entity.getDefaultValue()
@@ -105,20 +107,15 @@ export default class PinElement extends IElement {
}
createPinReference() {
return new PinReferenceEntity({
objectName: this.nodeElement.getNodeName(),
pinGuid: this.getPinId(),
})
return new PinReferenceEntity(new SymbolEntity(this.nodeElement.getNodeName()), this.getPinId())
}
/** @return {GuidEntity} */
getPinId() {
return this.entity.PinId
}
/** @returns {String} */
getPinName() {
return this.entity.PinName
return this.entity.PinName?.toString() ?? ""
}
getPinDisplayName() {
@@ -147,7 +144,7 @@ export default class PinElement extends IElement {
}
getLinks() {
return this.entity.LinkedTo ?? []
return this.entity.LinkedTo?.valueOf() ?? []
}
getDefaultValue(maybeCreate = false) {
@@ -165,21 +162,23 @@ export default class PinElement extends IElement {
/** @param {IElement[]} nodesWhitelist */
sanitizeLinks(nodesWhitelist = []) {
this.entity.LinkedTo = this.entity.LinkedTo?.filter(pinReference => {
let pin = this.blueprint.getPin(pinReference)
if (pin) {
if (nodesWhitelist.length && !nodesWhitelist.includes(pin.nodeElement)) {
return false
this.entity.LinkedTo = new (PinEntity.attributes.LinkedTo)(
this.entity.LinkedTo?.valueOf().filter(pinReference => {
let pin = this.blueprint.getPin(pinReference)
if (pin) {
if (nodesWhitelist.length && !nodesWhitelist.includes(pin.nodeElement)) {
return false
}
let link = this.blueprint.getLink(this, pin)
if (!link) {
link = /** @type {LinkElementConstructor} */(ElementFactory.getConstructor("ueb-link"))
.newObject(this, pin)
this.blueprint.addGraphElement(link)
}
}
let link = this.blueprint.getLink(this, pin)
if (!link) {
link = /** @type {LinkElementConstructor} */(ElementFactory.getConstructor("ueb-link"))
.newObject(this, pin)
this.blueprint.addGraphElement(link)
}
}
return pin
})
return pin
})
)
this.isLinked = this.entity.isLinked()
}
@@ -231,7 +230,7 @@ export default class PinElement extends IElement {
redirectLink(originalPinElement, newReference) {
const index = this.getLinks().findIndex(pinReference =>
pinReference.objectName.toString() == originalPinElement.getNodeElement().getNodeName()
&& pinReference.pinGuid.valueOf() == originalPinElement.entity.PinId.valueOf()
&& pinReference.pinGuid.toString() == originalPinElement.entity.PinId.toString()
)
if (index >= 0) {
this.entity.LinkedTo[index] = newReference

View File

@@ -0,0 +1,38 @@
import P from "parsernostrum"
import IEntity from "./IEntity.js"
/** @template {(typeof IEntity)[]} T */
export default 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 P.alt(...grammars)
}
/**
* @template {(typeof IEntity)[]} Types
* @param {Types} types
*/
static accepting(...types) {
const result = /** @type {typeof AlternativesEntity<Types> & { alternatives: Types }} */(
this.asUniqueClass()
)
result.alternatives = types
result.grammar = result.createGrammar()
return result
}
}

106
js/entity/ArrayEntity.js Normal file
View File

@@ -0,0 +1,106 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import IEntity from "./IEntity.js"
/** @template {typeof IEntity} T */
export default class ArrayEntity extends IEntity {
/** @type {typeof IEntity} */
static type
static grammar = this.createGrammar()
get length() {
return this.values.length
}
/** @param {(ExtractType<T>)[]} values */
constructor(values = []) {
super()
this.values = values
}
/** @returns {P<ArrayEntity<typeof IEntity>>} */
static createGrammar(elementGrammar = this.type?.grammar ?? P.lazy(() => this.unknownEntityGrammar)) {
return this.inlined
? elementGrammar
: P.seq(
P.reg(/\(\s*/),
elementGrammar.sepBy(Grammar.commaSeparation).opt(),
P.reg(/\s*(,\s*)?\)/, 1),
).map(([_0, values, trailing]) => {
values = values instanceof Array ? values : []
const result = new this(values)
result.trailing = trailing !== undefined
return result
}).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<ArrayEntity>} */(result.createGrammar())
return result
}
/**
* @template {typeof IEntity} T
* @param {T} type
*/
static of(type) {
const result = /** @type {{type: T, grammar: P<ArrayEntity<T>> } & typeof ArrayEntity<T>} */(
this.asUniqueClass()
)
result.type = type
result.grammar = /** @type {P<ArrayEntity>} */(result.createGrammar())
return result
}
doSerialize(
insideString = false,
indentation = "",
Self = /** @type {typeof ArrayEntity<T>} */(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
}
}

View File

@@ -1,117 +0,0 @@
/**
* @template T
* @typedef {{
* type?: AttributeTypeDescription,
* default?: T,
* nullable?: Boolean,
* ignored?: Boolean,
* serialized?: Boolean,
* expected?: Boolean,
* inlined?: Boolean,
* quoted?: Boolean,
* silent?: Boolean,
* uninitialized?: Boolean,
* predicate?: (value: T) => Boolean,
* }} AttributeInfoSource
*/
/** @template T */
export default class AttributeInfo {
/** @typedef {keyof AttributeInfo<number>} AttributeKey */
static #default = {
nullable: false,
ignored: false, // Never serialize or deserialize
serialized: false, // Value is written and read as string
expected: false, // Must be there
inlined: false, // The key is a subobject or array and printed as inlined (A.B=123, A(0)=123)
quoted: false, // Key is serialized with quotes
silent: false, // Do not serialize if default
uninitialized: false, // Do not initialize with default
}
/** @param {AttributeInfoSource<T>} source */
constructor(source) {
this.type = source.type ?? source.default?.constructor
this.default = source.default
this.nullable = source.nullable ?? source.default === null
this.ignored = source.ignored
this.serialized = source.serialized
this.expected = source.expected
this.inlined = source.inlined
this.quoted = source.quoted
this.silent = source.silent
this.uninitialized = source.uninitialized
this.predicate = source.predicate
if (this.type === Array && this.default instanceof Array && this.default.length > 0) {
this.type = this.default
.map(v => v.constructor)
.reduce((acc, cur) => acc.includes(cur) ? acc : (acc.push(cur), acc), [])
}
}
/**
* @template {AttributeTypeDescription} D
* @param {D} type
* @returns {AttributeInfo<DescribedType<type>>}
*/
static createType(type) {
return new AttributeInfo({ type })
}
/**
* @template V
* @param {V} value
*/
static createValue(value) {
return new AttributeInfo({ default: value })
}
/**
* @param {IEntity | Object} source
* @param {String} attribute
* @param {AttributeKey} key
*/
static hasAttribute(source, attribute, key, type = /** @type {EntityConstructor} */(source.constructor)) {
const entity = /** @type {IEntity} */(source)
const result = entity.attributes[attribute]?.[key]
return /** @type {result} */(
result
?? type?.attributes?.[attribute]?.[key]
?? AttributeInfo.#default[key]
)
}
/**
* @template {IEntity | Object} S
* @template {EntityConstructor} C
* @template {keyof C["attributes"]} A
* @template {keyof C["attributes"][attribute]} K
* @param {S} source
* @param {A} attribute
* @param {K} key
* @param {C} type
* @returns {C["attributes"][attribute][key]}
*/
static getAttribute(source, attribute, key, type = /** @type {C} */(source.constructor)) {
let result = source["attributes"]?.[attribute]?.[key]
// Remember null is a valid asignment value for some attributes
if (result !== undefined) {
return result
}
result = /** @type {C["attributes"]} */(type?.attributes)?.[attribute]?.[key]
if (result !== undefined) {
return result
}
result = /** @type {C["attributes"][attribute]} */(AttributeInfo.#default)[key]
if (result !== undefined) {
return result
}
}
/** @param {AttributeKey} key */
get(key) {
return this[key] ?? AttributeInfo.#default[key]
}
}

View File

@@ -63,8 +63,8 @@ export default class BlueprintEntity extends ObjectEntity {
this.ScriptVariables = entity.ScriptVariables
}
let scriptVariables = Utility.mergeArrays(
this.ScriptVariables,
entity.ScriptVariables,
this.ScriptVariables.valueOf(),
entity.ScriptVariables.valueOf(),
(l, r) => l.OriginalChangeId.value == r.OriginalChangeId.value
)
if (scriptVariables.length === this.ScriptVariables.length) {

64
js/entity/BooleanEntity.js Executable file
View File

@@ -0,0 +1,64 @@
import P from "parsernostrum"
import IEntity from "./IEntity.js"
export default class BooleanEntity extends IEntity {
static grammar = this.createGrammar()
static booleanConverter = {
fromAttribute: (value, type) => {
value ? "true" : "false"
},
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
}
static createGrammar() {
return /** @type {P<BooleanEntity>} */(
P.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
}
}

View File

@@ -1,23 +1,24 @@
import Parsernostrum from "parsernostrum"
import AttributeInfo from "./AttributeInfo.js"
import P from "parsernostrum"
import IntegerEntity from "./IntegerEntity.js"
export default class ByteEntity extends IntegerEntity {
static attributes = {
...super.attributes,
value: new AttributeInfo({
...super.attributes.value,
predicate: v => v % 1 == 0 && v >= 0 && v < 1 << 8,
}),
}
static grammar = this.createGrammar()
static createGrammar() {
return Parsernostrum.numberByte.map(v => new this(v))
get value() {
return super.value
}
set value(value) {
value = Math.trunc(value)
if (value >= 0 && value < 1 << 8) {
super.value = value
}
}
constructor(values = 0) {
super(values)
createGrammar() {
return /** @type {P<ByteEntity>} */(
// @ts-expect-error
P.numberByte.map(v => new this(v))
)
}
}

View File

@@ -1,35 +1,34 @@
import Parsernostrum from "parsernostrum"
import AttributeInfo from "./AttributeInfo.js"
import P from "parsernostrum"
import IEntity from "./IEntity.js"
export default class ColorChannelEntity extends IEntity {
static attributes = {
...super.attributes,
value: AttributeInfo.createValue(0),
}
static grammar = this.createGrammar()
static createGrammar() {
return Parsernostrum.number.map(value => new this(value))
constructor(value = 0) {
super()
this.value = value
}
constructor(values = 0) {
if (values.constructor !== Object) {
// @ts-expect-error
values = {
value: values,
}
static createGrammar() {
return /** @type {P<ColorChannelEntity>} */(
P.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}"`
}
super(values)
/** @type {Number} */ this.value
return result
}
valueOf() {
return this.value
}
toString() {
return this.value.toFixed(6)
}
}

View File

@@ -1,14 +0,0 @@
export default class ComputedType {
#f
/** @param {Function} f */
constructor(f) {
this.#f = f
}
/** @param {IEntity} entity */
compute(entity) {
return this.#f(entity)
}
}

View File

@@ -0,0 +1,28 @@
import IEntity from "./IEntity.js"
import StringEntity from "./StringEntity.js"
export default 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)
}
}

View File

@@ -1,4 +1,4 @@
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import EnumEntity from "./EnumEntity.js"
@@ -7,6 +7,8 @@ export default class EnumDisplayValueEntity extends EnumEntity {
static grammar = this.createGrammar()
static createGrammar() {
return Parsernostrum.reg(Grammar.Regex.InsideString).map(v => new this(v))
return /** @type {P<EnumDisplayValueEntity>} */(
P.reg(Grammar.Regex.InsideString).map(v => new this(v))
)
}
}

View File

@@ -1,3 +1,4 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import SymbolEntity from "./SymbolEntity.js"
@@ -6,6 +7,8 @@ export default class EnumEntity extends SymbolEntity {
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.symbol.map(v => new this(v))
return /** @type {P<EnumEntity>} */(
Grammar.symbol.map(v => new this(v))
)
}
}

View File

@@ -1,56 +1,62 @@
import Parsernostrum from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import IEntity from "./IEntity.js"
import P from "parsernostrum"
import InvariantTextEntity from "./InvariantTextEntity.js"
import LocalizedTextEntity from "./LocalizedTextEntity.js"
import Union from "./Union.js"
import StringEntity from "./StringEntity.js"
import IEntity from "./IEntity.js"
export default class FormatTextEntity extends IEntity {
static attributes = {
...super.attributes,
value: new AttributeInfo({
type: [new Union(String, LocalizedTextEntity, InvariantTextEntity, FormatTextEntity)],
default: [],
}),
lookbehind: /** @type {AttributeInfo<Union<String[]>>} */(new AttributeInfo({
...super.attributes.lookbehind,
default: new Union("LOCGEN_FORMAT_NAMED", "LOCGEN_FORMAT_ORDERED"),
})),
}
static attributeSeparator = ", "
static lookbehind = ["LOCGEN_FORMAT_NAMED", "LOCGEN_FORMAT_ORDERED"]
static grammar = this.createGrammar()
static createGrammar() {
return Parsernostrum.seq(
Parsernostrum.reg(
// Resulting regex: /(LOCGEN_FORMAT_NAMED|LOCGEN_FORMAT_ORDERED)\s*/
new RegExp(`(${this.attributes.lookbehind.default.values.reduce((acc, cur) => acc + "|" + cur)})\\s*`),
1
),
Grammar.grammarFor(this.attributes.value)
)
.map(([lookbehind, values]) => {
const result = new this({
value: values,
lookbehind,
})
return result
})
/** @param {(StringEntity | LocalizedTextEntity | InvariantTextEntity | FormatTextEntity)[]} values */
constructor(values) {
super()
this.values = values
}
constructor(values) {
super(values)
/** @type {(String | LocalizedTextEntity | InvariantTextEntity | FormatTextEntity)[]} */ this.value
/** @returns {P<FormatTextEntity>} */
static createGrammar() {
return P.lazy(() => P.seq(
// Resulting regex: /(LOCGEN_FORMAT_NAMED|LOCGEN_FORMAT_ORDERED)\s*/
P.reg(new RegExp(String.raw`(${this.lookbehind.join("|")})\s*\(\s*`), 1),
P.alt(
...[StringEntity, LocalizedTextEntity, InvariantTextEntity, FormatTextEntity].map(type => type.grammar)
).sepBy(P.reg(/\s*\,\s*/)),
P.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.value?.[0]?.toString() // The pattern is always the first element of the array
const pattern = this.values?.[0]?.toString() // The pattern is always the first element of the array
if (!pattern) {
return ""
}
const values = this.value.slice(1).map(v => v.toString())
return this.lookbehind == "LOCGEN_FORMAT_NAMED"
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
@@ -65,5 +71,6 @@ export default class FormatTextEntity extends IEntity {
: substring
})
: ""
return result
}
}

View File

@@ -1,27 +1,29 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import GuidEntity from "./GuidEntity.js"
import IEntity from "./IEntity.js"
import ObjectReferenceEntity from "./ObjectReferenceEntity.js"
import StringEntity from "./StringEntity.js"
export default class FunctionReferenceEntity extends IEntity {
static attributes = {
...super.attributes,
MemberParent: AttributeInfo.createType(ObjectReferenceEntity),
MemberName: AttributeInfo.createType(String),
MemberGuid: AttributeInfo.createType(GuidEntity),
MemberParent: ObjectReferenceEntity,
MemberName: StringEntity,
MemberGuid: GuidEntity,
}
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.createEntityGrammar(this)
}
constructor(values) {
super(values)
/** @type {ObjectReferenceEntity} */ this.MemberParent
/** @type {String} */ this.MemberName
/** @type {GuidEntity} */ this.MemberGuid
/** @type {InstanceType<typeof FunctionReferenceEntity.attributes.MemberParent>} */ this.MemberParent
/** @type {InstanceType<typeof FunctionReferenceEntity.attributes.MemberName>} */ this.MemberName
/** @type {InstanceType<typeof FunctionReferenceEntity.attributes.MemberGuid>} */ this.MemberGuid
}
/** @returns {P<FunctionReferenceEntity>} */
static createGrammar() {
return Grammar.createEntityGrammar(this, Grammar.commaSeparation, false, 0)
}
}

View File

@@ -1,9 +1,9 @@
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import IEntity from "./IEntity.js"
import P from "parsernostrum"
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
@@ -11,43 +11,39 @@ if (typeof window === "undefined") {
export default class GuidEntity extends IEntity {
static attributes = {
...super.attributes,
value: AttributeInfo.createValue(""),
}
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.guid.map(v => new this(v))
}
static generateGuid(random = true) {
static generateGuid() {
let values = new Uint32Array(4)
if (random === true) {
crypto.getRandomValues(values)
}
crypto.getRandomValues(values)
let guid = ""
values.forEach(n => {
guid += ("0".repeat(8) + n.toString(16).toUpperCase()).slice(-8)
})
return new GuidEntity({ value: guid })
return guid
}
constructor(values) {
if (!values) {
values = GuidEntity.generateGuid().value
}
if (values.constructor !== Object) {
values = {
value: values,
}
}
super(values)
/** @type {String} */ this.value
constructor(value = GuidEntity.generateGuid()) {
super()
this.value = value
}
valueOf() {
return this.value
static createGrammar() {
return /** @type {P<GuidEntity>} */(
P.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() {

View File

@@ -1,232 +1,410 @@
import Configuration from "../Configuration.js"
import P from "parsernostrum"
import Utility from "../Utility.js"
import Serializable from "../serialization/Serializable.js"
import SerializerFactory from "../serialization/SerializerFactory.js"
import AttributeInfo from "./AttributeInfo.js"
import ComputedType from "./ComputedType.js"
import MirroredEntity from "./MirroredEntity.js"
import Union from "./Union.js"
/** @abstract */
export default class IEntity extends Serializable {
export default class IEntity {
/** @type {{ [attribute: String]: AttributeInfo }} */
static attributes = {
attributes: new AttributeInfo({
ignored: true,
}),
lookbehind: new AttributeInfo({
default: /** @type {String | Union<String[]>} */(""),
ignored: true,
uninitialized: true,
}),
}
/** @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 = P.lazy(() => this.createGrammar())
/** @type {P<IEntity>} */
static unknownEntityGrammar
static unknownEntity
/** @type {{ [key: String]: typeof IEntity }} */
static attributes = {}
/** @type {String | String[]} */
static lookbehind = ""
/** @type {(type: typeof IEntity) => InstanceType<typeof IEntity>} */
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
#keys
get keys() {
return this.#keys ?? Object.keys(this)
}
set _keys(keys) {
this.#_keys = keys
set keys(value) {
this.#keys = [... new Set(value)]
}
constructor(values = {}, suppressWarns = false) {
super()
const Self = /** @type {typeof IEntity} */(this.constructor)
/** @type {AttributeDeclarations?} */ this.attributes
/** @type {String} */ this.lookbehind
const valuesKeys = Object.keys(values)
const attributesKeys = values.attributes
? Utility.mergeArrays(Object.keys(values.attributes), Object.keys(Self.attributes))
: Object.keys(Self.attributes)
const allAttributesKeys = Utility.mergeArrays(valuesKeys, attributesKeys)
for (const key of allAttributesKeys) {
let value = values[key]
if (!suppressWarns && !(key in values)) {
if (!(key in Self.attributes) && !key.startsWith(Configuration.subObjectAttributeNamePrefix)) {
const typeName = value instanceof Array ? `[${value[0]?.constructor.name}]` : value.constructor.name
console.warn(
`UEBlueprint: Attribute ${key} (of type ${typeName}) in the serialized data is not defined in ${Self.name}.attributes`
)
}
}
if (!(key in Self.attributes)) {
// Remember attributeName can come from the values and be not defined in the attributes.
// In that case just assign it and skip the rest.
this[key] = value
continue
}
Self.attributes.lookbehind
const predicate = AttributeInfo.getAttribute(values, key, "predicate", Self)
const assignAttribute = !predicate
? v => this[key] = v
: v => {
Object.defineProperties(this, {
["#" + key]: {
writable: true,
enumerable: false,
},
[key]: {
enumerable: true,
get() {
return this["#" + key]
},
set(v) {
if (!predicate(v)) {
console.warn(
`UEBlueprint: Tried to assign attribute ${key} to ${Self.name} not satisfying the predicate`
)
return
}
this["#" + key] = v
}
},
})
this[key] = v
}
#lookbehind = /** @type {String} */(this.constructor.lookbehind)
get lookbehind() {
return this.#lookbehind.trim()
}
set lookbehind(value) {
this.#lookbehind = value
}
let defaultValue = AttributeInfo.getAttribute(values, key, "default", Self)
if (defaultValue instanceof Function) {
defaultValue = defaultValue(this)
}
let defaultType = AttributeInfo.getAttribute(values, key, "type", Self)
if (defaultType instanceof ComputedType) {
defaultType = defaultType.compute(this)
}
if (defaultType instanceof Array) {
defaultType = Array
}
if (defaultType === undefined) {
defaultType = Utility.getType(defaultValue)
}
#ignored = this.constructor.ignored
get ignored() {
return this.#ignored
}
set ignored(value) {
this.#ignored = value
}
if (value !== undefined) {
// Remember value can still be null
if (
value?.constructor === String
&& AttributeInfo.getAttribute(values, key, "serialized", Self)
&& defaultType !== String
) {
try {
value = SerializerFactory
.getSerializer(defaultType)
.read(/** @type {String} */(value))
} catch (e) {
assignAttribute(value)
continue
#quoted
get quoted() {
return /** @type {typeof IEntity} */(this.constructor).quoted ?? this.#quoted ?? false
}
set quoted(value) {
this.#quoted = value
}
#trailing = this.constructor.trailing
get trailing() {
return this.#trailing
}
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
}
}
assignAttribute(Utility.sanitize(value, /** @type {AttributeConstructor<Attribute>} */(defaultType)))
continue // We have a value, need nothing more
continue
}
if (defaultValue !== undefined && !AttributeInfo.getAttribute(values, key, "uninitialized", Self)) {
assignAttribute(defaultValue)
const attribute = attributes[key]
if (attribute.default !== undefined) {
this[key] = attribute.default(attribute)
continue
}
}
}
/** @param {AttributeTypeDescription} attributeType */
static defaultValueProviderFromType(attributeType) {
if (attributeType === Boolean) {
return false
} else if (attributeType === Number) {
return 0
} else if (attributeType === BigInt) {
return 0n
} else if (attributeType === String) {
return ""
} else if (attributeType === Array || attributeType instanceof Array) {
return () => []
} else if (attributeType instanceof Union) {
return this.defaultValueProviderFromType(attributeType.values[0])
} else if (attributeType instanceof MirroredEntity) {
return () => new MirroredEntity(attributeType.type, attributeType.getter)
} else if (attributeType instanceof ComputedType) {
return undefined
} else {
return () => new /** @type {AnyConstructor<Attribute>} */(attributeType)()
}
}
/**
* @template {new (...args: any) => any} C
* @param {C} type
* @returns {value is InstanceType<C>}
* @protected
* @returns {P<IEntity>}
*/
static isValueOfType(value, type) {
return value != null && (value instanceof type || value.constructor === type)
static createGrammar() {
return this.unknownEntityGrammar
}
static defineAttributes(object, attributes) {
Object.defineProperty(object, "attributes", {
writable: true,
configurable: false,
})
object.attributes = attributes
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() {
let result = this
if (this.name.length) {
// @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<T> | 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
}
/**
* @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} attribute
* @param {String} attributeName
* @param {(v: any) => void} callback
*/
listenAttribute(attribute, callback) {
const descriptor = Object.getOwnPropertyDescriptor(this, attribute)
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, { [attribute]: descriptor })
Object.defineProperties(this, { [attributeName]: descriptor })
} else if (descriptor.value) {
Object.defineProperties(this, {
["#" + attribute]: {
["#" + attributeName]: {
value: descriptor.value,
writable: true,
enumerable: false,
},
[attribute]: {
[attributeName]: {
enumerable: true,
get() {
return this["#" + attribute]
return this["#" + attributeName]
},
set(v) {
if (v == this["#" + attribute]) {
return
}
callback(v)
this["#" + attribute] = v
}
this["#" + attributeName] = v
},
},
})
}
}
getLookbehind() {
let lookbehind = this.lookbehind ?? AttributeInfo.getAttribute(this, "lookbehind", "default")
lookbehind = lookbehind instanceof Union ? lookbehind.values[0] : lookbehind
return lookbehind
/** @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,
) {
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 === true || value.quoted === true)) {
keyValue = `"${keyValue}"`
}
if (valueType.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 && this.trailing && result.length) {
result += attributeSeparator
}
return wrap(/** @type {IEntity} */(this), result)
}
unexpectedKeys() {
return Object.keys(this).length - Object.keys(/** @type {typeof IEntity} */(this.constructor).attributes).length
/** @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,
) {
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
}
/** @param {IEntity} other */
equals(other) {
const thisKeys = Object.keys(this)
const otherKeys = Object.keys(other)
if (thisKeys.length != otherKeys.length) {
if (!(other instanceof IEntity)) {
return false
}
for (const key of thisKeys) {
if (this[key] instanceof IEntity && !this[key].equals(other[key])) {
return false
} else if (!Utility.equals(this[key], other[key])) {
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
}

View File

@@ -1,38 +0,0 @@
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import IEntity from "./IEntity.js"
export default class IdentifierEntity extends IEntity {
static attributes = {
...super.attributes,
value: AttributeInfo.createValue(""),
}
static attributeConverter = {
fromAttribute: (value, type) => new IdentifierEntity(value),
toAttribute: (value, type) => value.toString()
}
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.symbol.map(v => new this(v))
}
constructor(values) {
if (values.constructor !== Object) {
values = {
value: values,
}
}
super(values)
/** @type {String} */ this.value
}
valueOf() {
return this.value
}
toString() {
return this.value
}
}

View File

@@ -1,34 +1,46 @@
import Parsernostrum from "parsernostrum"
import AttributeInfo from "./AttributeInfo.js"
import P from "parsernostrum"
import IEntity from "./IEntity.js"
export default class Integer64Entity extends IEntity {
static attributes = {
...super.attributes,
value: new AttributeInfo({
default: 0n,
predicate: v => v >= -(1n << 63n) && v < 1n << 63n,
}),
}
static grammar = this.createGrammar()
static createGrammar() {
return Parsernostrum.numberBigInteger.map(v => new this(v))
/**
* @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 | Object} values */
constructor(values = 0) {
if (values.constructor !== Object) {
values = {
value: values,
}
/** @param {bigint | Number} value */
constructor(value = 0n) {
super()
this.value = BigInt(value)
}
static createGrammar() {
return /** @type {P<Integer64Entity>} */(
P.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}"`
}
if (values.value === -0) {
values.value = 0n
}
super(values)
/** @type {BigInt} */ this.value
return result
}
valueOf() {

View File

@@ -1,42 +1,24 @@
import Parsernostrum from "parsernostrum"
import AttributeInfo from "./AttributeInfo.js"
import IEntity from "./IEntity.js"
import P from "parsernostrum"
import NumberEntity from "./NumberEntity.js"
export default class IntegerEntity extends IEntity {
export default class IntegerEntity extends NumberEntity {
static attributes = {
...super.attributes,
value: new AttributeInfo({
default: 0,
predicate: v => v % 1 == 0 && v > 1 << 31 && v < -(1 << 31),
}),
}
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
}
}
static createGrammar() {
return Parsernostrum.numberInteger.map(v => new this(v))
}
/** @param {Number | Object} values */
constructor(values = 0) {
if (values.constructor !== Object) {
values = {
value: values,
}
}
values.value = Math.floor(values.value)
if (values.value === -0) {
values.value = 0
}
super(values)
/** @type {Number} */ this.value
}
valueOf() {
return this.value
}
toString() {
return this.value.toString()
return /** @type {P<IntegerEntity>} */(
P.numberInteger.map(v => new this(v))
)
}
}

View File

@@ -1,44 +1,37 @@
import Parsernostrum from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import P from "parsernostrum"
import IEntity from "./IEntity.js"
export default class InvariantTextEntity extends IEntity {
static attributes = {
...super.attributes,
value: AttributeInfo.createValue(""),
lookbehind: new AttributeInfo({
...super.attributes.lookbehind,
default: "INVTEXT",
}),
}
static lookbehind = "INVTEXT"
static grammar = this.createGrammar()
constructor(value = "") {
super()
this.value = value
}
static createGrammar() {
return Parsernostrum.alt(
Parsernostrum.seq(
Parsernostrum.reg(new RegExp(`${this.attributes.lookbehind.default}\\s*\\(`)),
Grammar.grammarFor(this.attributes.value),
Parsernostrum.reg(/\s*\)/)
return /** @type {P<InvariantTextEntity>} */(
P.alt(
P.seq(
P.reg(new RegExp(`${this.lookbehind}\\s*\\(`)),
P.doubleQuotedString,
P.reg(/\s*\)/)
).map(([_0, value, _2]) => Number(value)),
P.reg(new RegExp(this.lookbehind)).map(() => 0) // InvariantTextEntity can not have arguments
)
.map(([_0, value, _2]) => value),
Parsernostrum.reg(new RegExp(this.attributes.lookbehind.default)) // InvariantTextEntity can not have arguments
.map(() => "")
).map(value => new this(value))
.map(value => new this(value))
.label("InvariantTextEntity")
)
}
constructor(values) {
if (values.constructor !== Object) {
values = {
value: values,
}
}
super(values)
/** @type {String} */ this.value
doSerialize() {
return this.lookbehind + "(" + this.value + ")"
}
toString() {
valueOf() {
return this.value
}
}

View File

@@ -1,38 +1,39 @@
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import BooleanEntity from "./BooleanEntity.js"
import IEntity from "./IEntity.js"
import IdentifierEntity from "./IdentifierEntity.js"
import StringEntity from "./StringEntity.js"
import SymbolEntity from "./SymbolEntity.js"
export default class KeyBindingEntity extends IEntity {
static attributes = {
...super.attributes,
ActionName: AttributeInfo.createValue(""),
bShift: AttributeInfo.createValue(false),
bCtrl: AttributeInfo.createValue(false),
bAlt: AttributeInfo.createValue(false),
bCmd: AttributeInfo.createValue(false),
Key: AttributeInfo.createType(IdentifierEntity),
ActionName: StringEntity,
bShift: BooleanEntity,
bCtrl: BooleanEntity,
bAlt: BooleanEntity,
bCmd: BooleanEntity,
Key: SymbolEntity,
}
static grammar = this.createGrammar()
static createGrammar() {
return Parsernostrum.alt(
IdentifierEntity.grammar.map(identifier => new this({
Key: identifier
})),
Grammar.createEntityGrammar(this)
)
constructor(values) {
super(values)
/** @type {InstanceType<typeof KeyBindingEntity.attributes.ActionName>} */ this.ActionName
/** @type {InstanceType<typeof KeyBindingEntity.attributes.bShift>} */ this.bShift
/** @type {InstanceType<typeof KeyBindingEntity.attributes.bCtrl>} */ this.bCtrl
/** @type {InstanceType<typeof KeyBindingEntity.attributes.bAlt>} */ this.bAlt
/** @type {InstanceType<typeof KeyBindingEntity.attributes.bCmd>} */ this.bCmd
/** @type {InstanceType<typeof KeyBindingEntity.attributes.Key>} */ this.Key
}
constructor(values = {}) {
super(values, true)
/** @type {String} */ this.ActionName
/** @type {Boolean} */ this.bShift
/** @type {Boolean} */ this.bCtrl
/** @type {Boolean} */ this.bAlt
/** @type {Boolean} */ this.bCmd
/** @type {IdentifierEntity} */ this.Key
static createGrammar() {
return /** @type {P<KeyBindingEntity>} */(
P.alt(
SymbolEntity.grammar.map(identifier => new this({ Key: identifier })),
Grammar.createEntityGrammar(this)
)
)
}
}

View File

@@ -1,8 +1,7 @@
import { css } from "lit"
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import Utility from "../Utility.js"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import ColorChannelEntity from "./ColorChannelEntity.js"
import IEntity from "./IEntity.js"
@@ -10,43 +9,65 @@ export default class LinearColorEntity extends IEntity {
static attributes = {
...super.attributes,
R: new AttributeInfo({
type: ColorChannelEntity,
default: () => new ColorChannelEntity(),
expected: true,
}),
G: new AttributeInfo({
type: ColorChannelEntity,
default: () => new ColorChannelEntity(),
expected: true,
}),
B: new AttributeInfo({
type: ColorChannelEntity,
default: () => new ColorChannelEntity(),
expected: true,
}),
A: new AttributeInfo({
type: ColorChannelEntity,
default: () => new ColorChannelEntity(1),
}),
H: new AttributeInfo({
type: ColorChannelEntity,
default: () => new ColorChannelEntity(),
ignored: true,
}),
S: new AttributeInfo({
type: ColorChannelEntity,
default: () => new ColorChannelEntity(),
ignored: true,
}),
V: new AttributeInfo({
type: ColorChannelEntity,
default: () => new ColorChannelEntity(),
ignored: true,
}),
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<typeof LinearColorEntity.attributes.R>} */ this.R
/** @type {InstanceType<typeof LinearColorEntity.attributes.G>} */ this.G
/** @type {InstanceType<typeof LinearColorEntity.attributes.B>} */ this.B
/** @type {InstanceType<typeof LinearColorEntity.attributes.A>} */ this.A
this.#updateHSV()
}
static createGrammar() {
return /** @type {P<LinearColorEntity>} */(
Grammar.createEntityGrammar(this).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) {
@@ -75,22 +96,19 @@ export default class LinearColorEntity extends IEntity {
static getWhite() {
return new LinearColorEntity({
R: 1,
G: 1,
B: 1,
R: new ColorChannelEntity(1),
G: new ColorChannelEntity(1),
B: new ColorChannelEntity(1),
})
}
static createGrammar() {
return Grammar.createEntityGrammar(this, false)
}
static getLinearColorFromHexGrammar() {
return Parsernostrum.regArray(new RegExp(
"#(" + Grammar.Regex.HexDigit.source + "{2})"
+ "(" + Grammar.Regex.HexDigit.source + "{2})"
+ "(" + Grammar.Regex.HexDigit.source + "{2})"
+ "(" + Grammar.Regex.HexDigit.source + "{2})?"
const hexDigit = /[0-9a-fA-F]/
return P.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,
@@ -100,12 +118,12 @@ export default class LinearColorEntity extends IEntity {
}
static getLinearColorRGBListGrammar() {
return Parsernostrum.seq(
Parsernostrum.numberByte,
return P.seq(
P.numberByte,
Grammar.commaSeparation,
Parsernostrum.numberByte,
P.numberByte,
Grammar.commaSeparation,
Parsernostrum.numberByte,
P.numberByte,
).map(([R, _1, G, _3, B]) => new this({
R: R / 255,
G: G / 255,
@@ -115,23 +133,23 @@ export default class LinearColorEntity extends IEntity {
}
static getLinearColorRGBGrammar() {
return Parsernostrum.seq(
Parsernostrum.reg(/rgb\s*\(\s*/),
return P.seq(
P.reg(/rgb\s*\(\s*/),
this.getLinearColorRGBListGrammar(),
Parsernostrum.reg(/\s*\)/)
P.reg(/\s*\)/)
).map(([_0, linearColor, _2]) => linearColor)
}
static getLinearColorRGBAGrammar() {
return Parsernostrum.seq(
Parsernostrum.reg(/rgba\s*\(\s*/),
return P.seq(
P.reg(/rgba\s*\(\s*/),
this.getLinearColorRGBListGrammar(),
Parsernostrum.reg(/\s*\)/)
P.reg(/\s*\)/)
).map(([_0, linearColor, _2]) => linearColor)
}
static getLinearColorFromAnyFormat() {
return Parsernostrum.alt(
return P.alt(
this.getLinearColorFromHexGrammar(),
this.getLinearColorRGBAGrammar(),
this.getLinearColorRGBGrammar(),
@@ -139,26 +157,6 @@ export default class LinearColorEntity extends IEntity {
)
}
constructor(values) {
if (values instanceof Array) {
values = {
R: values[0] ?? 0,
G: values[1] ?? 0,
B: values[2] ?? 0,
A: values[3] ?? 1,
}
}
super(values)
/** @type {ColorChannelEntity} */ this.R
/** @type {ColorChannelEntity} */ this.G
/** @type {ColorChannelEntity} */ this.B
/** @type {ColorChannelEntity} */ this.A
/** @type {ColorChannelEntity} */ this.H
/** @type {ColorChannelEntity} */ this.S
/** @type {ColorChannelEntity} */ this.V
this.#updateHSV()
}
#updateHSV() {
const r = this.R.value
const g = this.G.value
@@ -302,6 +300,11 @@ export default class LinearColorEntity extends IEntity {
+ 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
@@ -320,12 +323,7 @@ export default class LinearColorEntity extends IEntity {
this.#updateHSV()
}
/** @returns {[Number, Number, Number, Number]} */
toArray() {
return [this.R.value, this.G.value, this.B.value, this.A.value]
}
toString() {
return Utility.printLinearColor(this)
return LinearColorEntity.printLinearColor(this)
}
}

View File

@@ -1,47 +1,51 @@
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import Utility from "../Utility.js"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import IEntity from "./IEntity.js"
import StringEntity from "./StringEntity.js"
export default class LocalizedTextEntity extends IEntity {
static attributeSeparator = ", "
static printKey = k => ""
static lookbehind = "NSLOCTEXT"
static attributes = {
...super.attributes,
namespace: AttributeInfo.createValue(""),
key: AttributeInfo.createValue(""),
value: AttributeInfo.createValue(""),
lookbehind: new AttributeInfo({
...super.attributes.lookbehind,
default: "NSLOCTEXT",
}),
namespace: StringEntity.withDefault(),
key: StringEntity.withDefault(),
value: StringEntity.withDefault(),
}
static grammar = this.createGrammar()
static createGrammar() {
return Parsernostrum.regArray(new RegExp(
String.raw`${this.attributes.lookbehind.default}\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(matchResult => new this({
namespace: Utility.unescapeString(matchResult[1]),
key: Utility.unescapeString(matchResult[2]),
value: Utility.unescapeString(matchResult[3]),
}))
constructor(values = {}) {
super(values)
/** @type {InstanceType<typeof LocalizedTextEntity.attributes.namespace>} */ this.namespace
/** @type {InstanceType<typeof LocalizedTextEntity.attributes.key>} */ this.key
/** @type {InstanceType<typeof LocalizedTextEntity.attributes.value>} */ this.value
}
constructor(values) {
super(values)
/** @type {String} */ this.namespace
/** @type {String} */ this.key
/** @type {String} */ this.value
static createGrammar() {
return /** @type {P<LocalizedTextEntity>} */(
P.regArray(new RegExp(
String.raw`${LocalizedTextEntity.lookbehind}\s*\(`
+ String.raw`\s*"(?<namespace>${Grammar.Regex.InsideString.source})"\s*,`
+ String.raw`\s*"(?<key>${Grammar.Regex.InsideString.source})"\s*,`
+ String.raw`\s*"(?<value>${Grammar.Regex.InsideString.source})"\s*`
+ String.raw`(?<trailing>,\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)
return Utility.capitalFirstLetter(this.value.valueOf())
}
}

View File

@@ -1,5 +1,5 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import GuidEntity from "./GuidEntity.js"
import IEntity from "./IEntity.js"
import ObjectReferenceEntity from "./ObjectReferenceEntity.js"
@@ -8,30 +8,23 @@ export default class MacroGraphReferenceEntity extends IEntity {
static attributes = {
...super.attributes,
MacroGraph: new AttributeInfo({
type: ObjectReferenceEntity,
default: () => new ObjectReferenceEntity(),
}),
GraphBlueprint: new AttributeInfo({
type: ObjectReferenceEntity,
default: () => new ObjectReferenceEntity(),
}),
GraphGuid: new AttributeInfo({
type: GuidEntity,
default: () => new GuidEntity(),
}),
MacroGraph: ObjectReferenceEntity,
GraphBlueprint: ObjectReferenceEntity,
GraphGuid: GuidEntity,
}
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.createEntityGrammar(this)
}
constructor(values) {
super(values)
/** @type {ObjectReferenceEntity} */ this.MacroGraph
/** @type {ObjectReferenceEntity} */ this.GraphBlueprint
/** @type {GuidEntity} */ this.GuidEntity
/** @type {InstanceType<typeof MacroGraphReferenceEntity.attributes.MacroGraph>} */ this.MacroGraph
/** @type {InstanceType<typeof MacroGraphReferenceEntity.attributes.GraphBlueprint>} */ this.GraphBlueprint
/** @type {InstanceType<typeof MacroGraphReferenceEntity.attributes.GraphGuid>} */ this.GraphGuid
}
static createGrammar() {
return /** @type {P<MacroGraphReferenceEntity>} */(
Grammar.createEntityGrammar(this)
)
}
getMacroName() {

View File

@@ -1,36 +1,78 @@
import AttributeInfo from "./AttributeInfo.js"
import P from "parsernostrum"
import IEntity from "./IEntity.js"
/** @template {Attribute} T */
export default class MirroredEntity {
/** @template {typeof IEntity} T */
export default class MirroredEntity extends IEntity {
static attributes = {
type: new AttributeInfo({
ignored: true,
}),
getter: new AttributeInfo({
ignored: true,
}),
}
/** @type {typeof IEntity} */
static type
/**
* @param {ConstructorType<T>} type
* @param {() => T} getter
*/
constructor(type, getter = null) {
this.type = type
/** @param {() => InstanceType<T>} getter */
constructor(getter = null) {
super()
const self = /** @type {typeof MirroredEntity<T>} */(this.constructor)
getter ??= self.default !== undefined ? /** @type {MirroredEntity} */(self.default(self)).getter : getter
this.getter = getter
}
get() {
return this.getter()
static createGrammar(elementGrammar = this.type?.grammar ?? P.lazy(() => this.unknownEntityGrammar)) {
return this.type?.grammar.map(v => new this(() => v))
}
/** @returns {AttributeConstructor<Attribute>} */
getTargetType() {
const result = this.type
if (result instanceof MirroredEntity) {
return result.getTargetType()
}
/**
* @template {typeof IEntity} T
* @this {T}
* @param {(type: T) => (InstanceType<T> | 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<MirroredEntity<T>> } & typeof MirroredEntity<T>} */(
this.asUniqueClass()
)
result.type = type
result.grammar = result.createGrammar()
return result
}
doSerialize(
insideString = false,
indentation = "",
Self = /** @type {typeof MirroredEntity<T>} */(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)
}
valueOf() {
this.valueOf = this.getter().valueOf.bind(this.getter())
return this.valueOf()
}
toString() {
this.toString = this.getter().toString.bind(this.getter())
return this.toString()
}
}

View File

@@ -1,17 +1,22 @@
import IntegerEntity from "./IntegerEntity.js"
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import Utility from "../Utility.js"
import IntegerEntity from "./IntegerEntity.js"
export default class NaturalNumberEntity extends IntegerEntity {
static grammar = this.createGrammar()
static createGrammar() {
return Parsernostrum.numberNatural.map(v => new this(v))
get value() {
return super.value
}
set value(value) {
value = Math.round(Utility.clamp(value, 0))
super.value = value
}
constructor(values = 0) {
super(values)
this.value = Math.round(Utility.clamp(this.value, 0))
static createGrammar() {
return /** @type {P<NaturalNumberEntity>} */(
P.numberNatural.map(v => new this(v))
)
}
}

27
js/entity/NullEntity.js Normal file
View File

@@ -0,0 +1,27 @@
import P from "parsernostrum"
import IEntity from "./IEntity.js"
export default class NullEntity extends IEntity {
static grammar = this.createGrammar()
static createGrammar() {
return /** @type {P<NullEntity>} */(
// @ts-expect-error
P.reg(new RegExp(String.raw`\(${P.whitespaceInlineOpt.getParser().regexp.source}\)`))
.map(v => new this())
)
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor)
) {
let result = "()"
if (Self.serialized) {
result = `"${result}"`
}
return result
}
}

103
js/entity/NumberEntity.js Executable file
View File

@@ -0,0 +1,103 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import IEntity from "./IEntity.js"
import Utility from "../Utility.js"
export default 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)
}
}
static createGrammar() {
return /** @type {P<NumberEntity>} */(
P.regArray(
new RegExp(`(?<n>${this.numberRegexSource})|(?<posInf>\\+?inf)|(?<negInf>-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()
}
}

View File

@@ -1,345 +1,287 @@
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import Configuration from "../Configuration.js"
import Utility from "../Utility.js"
import nodeColor from "../decoding/nodeColor.js"
import nodeIcon from "../decoding/nodeIcon.js"
import nodeVariadic from "../decoding/nodeVariadic.js"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import Utility from "../Utility.js"
import AlternativesEntity from "./AlternativesEntity.js"
import ArrayEntity from "./ArrayEntity.js"
import BooleanEntity from "./BooleanEntity.js"
import FunctionReferenceEntity from "./FunctionReferenceEntity.js"
import GuidEntity from "./GuidEntity.js"
import IEntity from "./IEntity.js"
import IdentifierEntity from "./IdentifierEntity.js"
import IntegerEntity from "./IntegerEntity.js"
import LinearColorEntity from "./LinearColorEntity.js"
import MacroGraphReferenceEntity from "./MacroGraphReferenceEntity.js"
import MirroredEntity from "./MirroredEntity.js"
import NaturalNumberEntity from "./NaturalNumberEntity.js"
import NullEntity from "./NullEntity.js"
import NumberEntity from "./NumberEntity.js"
import ObjectReferenceEntity from "./ObjectReferenceEntity.js"
import PinEntity from "./PinEntity.js"
import ScriptVariableEntity from "./ScriptVariableEntity.js"
import StringEntity from "./StringEntity.js"
import SymbolEntity from "./SymbolEntity.js"
import Union from "./Union.js"
import UnknownPinEntity from "./UnknownPinEntity.js"
import VariableReferenceEntity from "./VariableReferenceEntity.js"
import nodeVariadic from "../decoding/nodeVariadic.js"
export default 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,
isExported: new AttributeInfo({
type: Boolean,
ignored: true,
}),
Class: AttributeInfo.createType(ObjectReferenceEntity),
Name: AttributeInfo.createType(String),
Archetype: AttributeInfo.createType(ObjectReferenceEntity),
ExportPath: AttributeInfo.createType(ObjectReferenceEntity),
ObjectRef: AttributeInfo.createType(ObjectReferenceEntity),
BlueprintElementType: AttributeInfo.createType(ObjectReferenceEntity),
BlueprintElementInstance: AttributeInfo.createType(ObjectReferenceEntity),
PinTags: new AttributeInfo({
type: [null],
inlined: true,
}),
PinNames: new AttributeInfo({
type: [String],
inlined: true,
}),
AxisKey: AttributeInfo.createType(SymbolEntity),
InputAxisKey: AttributeInfo.createType(SymbolEntity),
InputName: AttributeInfo.createType(String),
InputType: AttributeInfo.createType(SymbolEntity),
NumAdditionalInputs: AttributeInfo.createType(Number),
bIsPureFunc: AttributeInfo.createType(Boolean),
bIsConstFunc: AttributeInfo.createType(Boolean),
bIsCaseSensitive: AttributeInfo.createType(Boolean),
VariableReference: AttributeInfo.createType(VariableReferenceEntity),
SelfContextInfo: AttributeInfo.createType(SymbolEntity),
DelegatePropertyName: AttributeInfo.createType(String),
DelegateOwnerClass: AttributeInfo.createType(ObjectReferenceEntity),
ComponentPropertyName: AttributeInfo.createType(String),
EventReference: AttributeInfo.createType(FunctionReferenceEntity),
FunctionReference: AttributeInfo.createType(FunctionReferenceEntity),
FunctionScript: AttributeInfo.createType(ObjectReferenceEntity),
CustomFunctionName: AttributeInfo.createType(String),
TargetType: AttributeInfo.createType(ObjectReferenceEntity),
MacroGraphReference: AttributeInfo.createType(MacroGraphReferenceEntity),
Enum: AttributeInfo.createType(ObjectReferenceEntity),
EnumEntries: new AttributeInfo({
type: [String],
inlined: true,
}),
InputKey: AttributeInfo.createType(SymbolEntity),
OpName: AttributeInfo.createType(String),
CachedChangeId: AttributeInfo.createType(GuidEntity),
FunctionDisplayName: AttributeInfo.createType(String),
AddedPins: new AttributeInfo({
type: [UnknownPinEntity],
default: () => [],
inlined: true,
silent: true,
}),
ChangeId: AttributeInfo.createType(GuidEntity),
MaterialFunction: AttributeInfo.createType(ObjectReferenceEntity),
bOverrideFunction: AttributeInfo.createType(Boolean),
bInternalEvent: AttributeInfo.createType(Boolean),
bConsumeInput: AttributeInfo.createType(Boolean),
bExecuteWhenPaused: AttributeInfo.createType(Boolean),
bOverrideParentBinding: AttributeInfo.createType(Boolean),
bControl: AttributeInfo.createType(Boolean),
bAlt: AttributeInfo.createType(Boolean),
bShift: AttributeInfo.createType(Boolean),
bCommand: AttributeInfo.createType(Boolean),
CommentColor: AttributeInfo.createType(LinearColorEntity),
bCommentBubbleVisible_InDetailsPanel: AttributeInfo.createType(Boolean),
bColorCommentBubble: AttributeInfo.createType(Boolean),
ProxyFactoryFunctionName: AttributeInfo.createType(String),
ProxyFactoryClass: AttributeInfo.createType(ObjectReferenceEntity),
ProxyClass: AttributeInfo.createType(ObjectReferenceEntity),
StructType: AttributeInfo.createType(ObjectReferenceEntity),
MaterialExpression: AttributeInfo.createType(ObjectReferenceEntity),
MaterialExpressionComment: AttributeInfo.createType(ObjectReferenceEntity),
MoveMode: AttributeInfo.createType(SymbolEntity),
TimelineName: AttributeInfo.createType(String),
TimelineGuid: AttributeInfo.createType(GuidEntity),
SizeX: AttributeInfo.createType(new MirroredEntity(IntegerEntity)),
SizeY: AttributeInfo.createType(new MirroredEntity(IntegerEntity)),
Text: AttributeInfo.createType(new MirroredEntity(String)),
MaterialExpressionEditorX: AttributeInfo.createType(new MirroredEntity(IntegerEntity)),
MaterialExpressionEditorY: AttributeInfo.createType(new MirroredEntity(IntegerEntity)),
NodeTitle: AttributeInfo.createType(String),
NodeTitleColor: AttributeInfo.createType(LinearColorEntity),
PositionX: AttributeInfo.createType(new MirroredEntity(IntegerEntity)),
PositionY: AttributeInfo.createType(new MirroredEntity(IntegerEntity)),
SettingsInterface: AttributeInfo.createType(ObjectReferenceEntity),
PCGNode: AttributeInfo.createType(ObjectReferenceEntity),
HiGenGridSize: AttributeInfo.createType(SymbolEntity),
Operation: AttributeInfo.createType(SymbolEntity),
NodePosX: AttributeInfo.createType(IntegerEntity),
NodePosY: AttributeInfo.createType(IntegerEntity),
NodeHeight: AttributeInfo.createType(IntegerEntity),
NodeWidth: AttributeInfo.createType(IntegerEntity),
Graph: AttributeInfo.createType(ObjectReferenceEntity),
SubgraphInstance: AttributeInfo.createType(String),
InputPins: new AttributeInfo({
type: [ObjectReferenceEntity],
inlined: true,
}),
OutputPins: new AttributeInfo({
type: [ObjectReferenceEntity],
inlined: true,
}),
bExposeToLibrary: AttributeInfo.createType(Boolean),
bCanRenameNode: AttributeInfo.createType(Boolean),
bCommentBubblePinned: AttributeInfo.createType(Boolean),
bCommentBubbleVisible: AttributeInfo.createType(Boolean),
NodeComment: AttributeInfo.createType(String),
AdvancedPinDisplay: AttributeInfo.createType(IdentifierEntity),
DelegateReference: AttributeInfo.createType(VariableReferenceEntity),
EnabledState: AttributeInfo.createType(IdentifierEntity),
NodeGuid: AttributeInfo.createType(GuidEntity),
ErrorType: AttributeInfo.createType(IntegerEntity),
ErrorMsg: AttributeInfo.createType(String),
ScriptVariables: new AttributeInfo({
type: [ScriptVariableEntity],
inlined: true,
}),
Node: AttributeInfo.createType(new MirroredEntity(ObjectReferenceEntity)),
ExportedNodes: AttributeInfo.createType(String),
CustomProperties: AttributeInfo.createType([new Union(PinEntity, UnknownPinEntity)]),
Class: ObjectReferenceEntity,
Name: StringEntity,
Archetype: ObjectReferenceEntity,
ExportPath: 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),
MaterialExpressionEditorX: MirroredEntity.of(IntegerEntity),
MaterialExpressionEditorY: MirroredEntity.of(IntegerEntity),
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.of(ScriptVariableEntity),
Node: MirroredEntity.of(ObjectReferenceEntity),
ExportedNodes: StringEntity,
CustomProperties: ArrayEntity.of(AlternativesEntity.accepting(PinEntity, UnknownPinEntity)).withDefault().flagSilent(),
}
static nameRegex = /^(\w+?)(?:_(\d+))?$/
static customPropertyGrammar = Parsernostrum.seq(
Parsernostrum.reg(/CustomProperties\s+/),
Grammar.grammarFor(
undefined,
this.attributes.CustomProperties.type[0]
),
static customPropertyGrammar = P.seq(
P.reg(/CustomProperties\s+/),
this.attributes.CustomProperties.type.grammar,
).map(([_0, pin]) => values => {
if (!values.CustomProperties) {
values.CustomProperties = []
}
values.CustomProperties.push(pin)
/** @type {InstanceType<typeof this.attributes.CustomProperties>} */(
values.CustomProperties ??= new (this.attributes.CustomProperties)()
).values.push(pin)
})
static inlinedArrayEntryGrammar = Parsernostrum.seq(
Parsernostrum.alt(
static inlinedArrayEntryGrammar = P.seq(
P.alt(
Grammar.symbolQuoted.map(v => [v, true]),
Grammar.symbol.map(v => [v, false]),
),
Parsernostrum.reg(
new RegExp(`\\s*\\(\\s*(\\d+)\\s*\\)\\s*\\=\\s*`),
1
).map(Number)
P.reg(new RegExp(String.raw`\s*\(\s*(\d+)\s*\)\s*\=\s*`), 1).map(Number)
)
.chain(
/** @param {[[String, Boolean], Number]} param */
/** @param {[[keyof ObjectEntity.attributes, Boolean], Number]} param */
([[symbol, quoted], index]) =>
Grammar.grammarFor(this.attributes[symbol])
.map(currentValue =>
values => {
(values[symbol] ??= [])[index] = currentValue
Utility.objectSet(values, ["attributes", symbol, "quoted"], quoted)
if (!this.attributes[symbol]?.inlined) {
if (!values.attributes) {
IEntity.defineAttributes(values, {})
}
Utility.objectSet(values, ["attributes", symbol, "type"], [currentValue.constructor])
Utility.objectSet(values, ["attributes", symbol, "inlined"], true)
(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 createSubObjectGrammar() {
return Parsernostrum.lazy(() => this.grammar)
.map(object =>
values => values[Configuration.subObjectAttributeNameFromEntity(object)] = object
)
}
static createGrammar() {
return Parsernostrum.seq(
Parsernostrum.reg(/Begin +Object/),
Parsernostrum.seq(
Parsernostrum.whitespace,
Parsernostrum.alt(
this.createSubObjectGrammar(),
this.customPropertyGrammar,
Grammar.createAttributeGrammar(this, Parsernostrum.reg(Grammar.Regex.MultipleWordsSymbols)),
Grammar.createAttributeGrammar(this, Grammar.attributeNameQuoted, undefined, (obj, k, v) =>
Utility.objectSet(obj, ["attributes", ...k, "quoted"], true)
),
this.inlinedArrayEntryGrammar,
)
)
.map(([_0, entry]) => entry)
.many(),
Parsernostrum.reg(/\s+End +Object/),
)
.map(([_0, attributes, _2]) => {
const values = {}
attributes.forEach(attributeSetter => attributeSetter(values))
return new this(values)
})
}
static getMultipleObjectsGrammar() {
return Parsernostrum.seq(
Parsernostrum.whitespaceOpt,
static grammarMultipleObjects = P.seq(
P.whitespaceOpt,
this.grammar,
P.seq(
P.whitespace,
this.grammar,
Parsernostrum.seq(
Parsernostrum.whitespace,
this.grammar,
)
.map(([_0, object]) => object)
.many(),
Parsernostrum.whitespaceOpt
)
.map(([_0, first, remaining, _4]) => [first, ...remaining])
}
.map(([_0, object]) => object)
.many(),
P.whitespaceOpt
).map(([_0, first, remaining, _4]) => [first, ...remaining])
/** @type {String} */
#class
constructor(values = {}, suppressWarns = false) {
if ("NodePosX" in values !== "NodePosY" in values) {
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")]
const entry = [key, new (AttributeInfo.getAttribute(values, key, "type", ObjectEntity))()]
entries.splice(position, 0, entry)
entries.splice(position, 0, [key, new IntegerEntity(0)])
values = Object.fromEntries(entries)
}
super(values, suppressWarns)
// Attributes not assigned a strong type in attributes because the names are too generic
/** @type {Number | MirroredEntity<Boolean>} */ this.R
/** @type {Number | MirroredEntity<Boolean>} */ this.G
/** @type {Number | MirroredEntity<Boolean>} */ this.B
/** @type {Number | MirroredEntity<Boolean>} */ this.A
super(values)
// Attributes
/** @type {(PinEntity | UnknownPinEntity)[]} */ this.CustomProperties
/** @type {Boolean} */ this.bIsPureFunc
/** @type {Boolean} */ this.isExported
/** @type {FunctionReferenceEntity} */ this.ComponentPropertyName
/** @type {FunctionReferenceEntity} */ this.EventReference
/** @type {FunctionReferenceEntity} */ this.FunctionReference
/** @type {IdentifierEntity} */ this.AdvancedPinDisplay
/** @type {IdentifierEntity} */ this.EnabledState
/** @type {IntegerEntity} */ this.NodeHeight
/** @type {IntegerEntity} */ this.NodePosX
/** @type {IntegerEntity} */ this.NodePosY
/** @type {IntegerEntity} */ this.NodeWidth
/** @type {LinearColorEntity} */ this.CommentColor
/** @type {LinearColorEntity} */ this.NodeTitleColor
/** @type {MacroGraphReferenceEntity} */ this.MacroGraphReference
/** @type {MirroredEntity} */ this.MaterialExpressionEditorX
/** @type {MirroredEntity} */ this.MaterialExpressionEditorY
/** @type {MirroredEntity} */ this.SizeX
/** @type {MirroredEntity} */ this.SizeY
/** @type {MirroredEntity} */ this.Text
/** @type {MirroredEntity<IntegerEntity>} */ this.PositionX
/** @type {MirroredEntity<IntegerEntity>} */ this.PositionY
/** @type {MirroredEntity<ObjectReferenceEntity>} */ this.Node
/** @type {null[]} */ this.PinTags
/** @type {Number} */ this.NumAdditionalInputs
/** @type {ObjectReferenceEntity[]} */ this.InputPins
/** @type {ObjectReferenceEntity[]} */ this.OutputPins
/** @type {ObjectReferenceEntity} */ this.Archetype
/** @type {ObjectReferenceEntity} */ this.BlueprintElementInstance
/** @type {ObjectReferenceEntity} */ this.BlueprintElementType
/** @type {ObjectReferenceEntity} */ this.Class
/** @type {ObjectReferenceEntity} */ this.Enum
/** @type {ObjectReferenceEntity} */ this.ExportPath
/** @type {ObjectReferenceEntity} */ this.FunctionScript
/** @type {ObjectReferenceEntity} */ this.Graph
/** @type {ObjectReferenceEntity} */ this.MaterialExpression
/** @type {ObjectReferenceEntity} */ this.MaterialExpressionComment
/** @type {ObjectReferenceEntity} */ this.MaterialFunction
/** @type {ObjectReferenceEntity} */ this.ObjectRef
/** @type {ObjectReferenceEntity} */ this.PCGNode
/** @type {ObjectReferenceEntity} */ this.SettingsInterface
/** @type {ObjectReferenceEntity} */ this.StructType
/** @type {ObjectReferenceEntity} */ this.TargetType
/** @type {ScriptVariableEntity[]} */ this.ScriptVariables
/** @type {String[]} */ this.EnumEntries
/** @type {String[]} */ this.PinNames
/** @type {String} */ this.CustomFunctionName
/** @type {String} */ this.DelegatePropertyName
/** @type {String} */ this.ExportedNodes
/** @type {String} */ this.FunctionDisplayName
/** @type {String} */ this.InputName
/** @type {String} */ this.Name
/** @type {String} */ this.NodeComment
/** @type {String} */ this.NodeTitle
/** @type {String} */ this.Operation
/** @type {String} */ this.OpName
/** @type {String} */ this.ProxyFactoryFunctionName
/** @type {String} */ this.SubgraphInstance
/** @type {String} */ this.Text
/** @type {SymbolEntity} */ this.AxisKey
/** @type {SymbolEntity} */ this.HiGenGridSize
/** @type {SymbolEntity} */ this.InputAxisKey
/** @type {SymbolEntity} */ this.InputKey
/** @type {SymbolEntity} */ this.InputType
/** @type {UnknownPinEntity[]} */ this.AddedPins
/** @type {VariableReferenceEntity} */ this.DelegateReference
/** @type {VariableReferenceEntity} */ this.VariableReference
/** @type {InstanceType<typeof ObjectEntity.attributes.AddedPins>} */ this.AddedPins
/** @type {InstanceType<typeof ObjectEntity.attributes.AdvancedPinDisplay>} */ this.AdvancedPinDisplay
/** @type {InstanceType<typeof ObjectEntity.attributes.Archetype>} */ this.Archetype
/** @type {InstanceType<typeof ObjectEntity.attributes.AxisKey>} */ this.AxisKey
/** @type {InstanceType<typeof ObjectEntity.attributes.bIsPureFunc>} */ this.bIsPureFunc
/** @type {InstanceType<typeof ObjectEntity.attributes.BlueprintElementInstance>} */ this.BlueprintElementInstance
/** @type {InstanceType<typeof ObjectEntity.attributes.ConstA>} */ this.ConstA
/** @type {InstanceType<typeof ObjectEntity.attributes.ConstB>} */ this.ConstB
/** @type {InstanceType<typeof ObjectEntity.attributes.BlueprintElementType>} */ this.BlueprintElementType
/** @type {InstanceType<typeof ObjectEntity.attributes.Class>} */ this.Class
/** @type {InstanceType<typeof ObjectEntity.attributes.CommentColor>} */ this.CommentColor
/** @type {InstanceType<typeof ObjectEntity.attributes.ComponentPropertyName>} */ this.ComponentPropertyName
/** @type {InstanceType<typeof ObjectEntity.attributes.CustomFunctionName>} */ this.CustomFunctionName
/** @type {ArrayEntity<typeof PinEntity | typeof UnknownPinEntity>} */ this.CustomProperties
/** @type {InstanceType<typeof ObjectEntity.attributes.DelegatePropertyName>} */ this.DelegatePropertyName
/** @type {InstanceType<typeof ObjectEntity.attributes.DelegateReference>} */ this.DelegateReference
/** @type {InstanceType<typeof ObjectEntity.attributes.EnabledState>} */ this.EnabledState
/** @type {InstanceType<typeof ObjectEntity.attributes.Enum>} */ this.Enum
/** @type {InstanceType<typeof ObjectEntity.attributes.EnumEntries>} */ this.EnumEntries
/** @type {InstanceType<typeof ObjectEntity.attributes.EventReference>} */ this.EventReference
/** @type {InstanceType<typeof ObjectEntity.attributes.ExportedNodes>} */ this.ExportedNodes
/** @type {InstanceType<typeof ObjectEntity.attributes.ExportPath>} */ this.ExportPath
/** @type {InstanceType<typeof ObjectEntity.attributes.FunctionDisplayName>} */ this.FunctionDisplayName
/** @type {InstanceType<typeof ObjectEntity.attributes.FunctionReference>} */ this.FunctionReference
/** @type {InstanceType<typeof ObjectEntity.attributes.FunctionScript>} */ this.FunctionScript
/** @type {InstanceType<typeof ObjectEntity.attributes.Graph>} */ this.Graph
/** @type {InstanceType<typeof ObjectEntity.attributes.HiGenGridSize>} */ this.HiGenGridSize
/** @type {InstanceType<typeof ObjectEntity.attributes.InputAxisKey>} */ this.InputAxisKey
/** @type {InstanceType<typeof ObjectEntity.attributes.InputKey>} */ this.InputKey
/** @type {InstanceType<typeof ObjectEntity.attributes.InputName>} */ this.InputName
/** @type {InstanceType<typeof ObjectEntity.attributes.InputPins>} */ this.InputPins
/** @type {InstanceType<typeof ObjectEntity.attributes.InputType>} */ this.InputType
/** @type {InstanceType<typeof ObjectEntity.attributes.MacroGraphReference>} */ this.MacroGraphReference
/** @type {InstanceType<typeof ObjectEntity.attributes.MaterialExpression>} */ this.MaterialExpression
/** @type {InstanceType<typeof ObjectEntity.attributes.MaterialExpressionComment>} */ this.MaterialExpressionComment
/** @type {InstanceType<typeof ObjectEntity.attributes.MaterialExpressionEditorX>} */ this.MaterialExpressionEditorX
/** @type {InstanceType<typeof ObjectEntity.attributes.MaterialExpressionEditorY>} */ this.MaterialExpressionEditorY
/** @type {InstanceType<typeof ObjectEntity.attributes.MaterialFunction>} */ this.MaterialFunction
/** @type {InstanceType<typeof ObjectEntity.attributes.Name>} */ this.Name
/** @type {InstanceType<typeof ObjectEntity.attributes.Node>} */ this.Node
/** @type {InstanceType<typeof ObjectEntity.attributes.NodeComment>} */ this.NodeComment
/** @type {InstanceType<typeof ObjectEntity.attributes.NodeHeight>} */ this.NodeHeight
/** @type {InstanceType<typeof ObjectEntity.attributes.NodePosX>} */ this.NodePosX
/** @type {InstanceType<typeof ObjectEntity.attributes.NodePosY>} */ this.NodePosY
/** @type {InstanceType<typeof ObjectEntity.attributes.NodeTitle>} */ this.NodeTitle
/** @type {InstanceType<typeof ObjectEntity.attributes.NodeTitleColor>} */ this.NodeTitleColor
/** @type {InstanceType<typeof ObjectEntity.attributes.NodeWidth>} */ this.NodeWidth
/** @type {InstanceType<typeof ObjectEntity.attributes.NumAdditionalInputs>} */ this.NumAdditionalInputs
/** @type {InstanceType<typeof ObjectEntity.attributes.ObjectRef>} */ this.ObjectRef
/** @type {InstanceType<typeof ObjectEntity.attributes.Operation>} */ this.Operation
/** @type {InstanceType<typeof ObjectEntity.attributes.OpName>} */ this.OpName
/** @type {InstanceType<typeof ObjectEntity.attributes.OutputPins>} */ this.OutputPins
/** @type {InstanceType<typeof ObjectEntity.attributes.PCGNode>} */ this.PCGNode
/** @type {InstanceType<typeof ObjectEntity.attributes.PinTags>} */ this.PinTags
/** @type {InstanceType<typeof ObjectEntity.attributes.PinNames>} */ this.PinNames
/** @type {InstanceType<typeof ObjectEntity.attributes.PositionX>} */ this.PositionX
/** @type {InstanceType<typeof ObjectEntity.attributes.PositionY>} */ this.PositionY
/** @type {InstanceType<typeof ObjectEntity.attributes.ProxyFactoryFunctionName>} */ this.ProxyFactoryFunctionName
/** @type {InstanceType<typeof ObjectEntity.attributes.ScriptVariables>} */ this.ScriptVariables
/** @type {InstanceType<typeof ObjectEntity.attributes.SettingsInterface>} */ this.SettingsInterface
/** @type {InstanceType<typeof ObjectEntity.attributes.SizeX>} */ this.SizeX
/** @type {InstanceType<typeof ObjectEntity.attributes.SizeY>} */ this.SizeY
/** @type {InstanceType<typeof ObjectEntity.attributes.StructType>} */ this.StructType
/** @type {InstanceType<typeof ObjectEntity.attributes.SubgraphInstance>} */ this.SubgraphInstance
/** @type {InstanceType<typeof ObjectEntity.attributes.TargetType>} */ this.TargetType
/** @type {InstanceType<typeof ObjectEntity.attributes.Text>} */ this.Text
/** @type {InstanceType<typeof ObjectEntity.attributes.Text>} */ this.Text
/** @type {InstanceType<typeof ObjectEntity.attributes.VariableReference>} */ this.VariableReference
// Legacy nodes pins
if (this["Pins"] instanceof Array) {
this["Pins"].forEach(
if (this["Pins"] instanceof ArrayEntity) {
this["Pins"].valueOf().forEach(
/** @param {ObjectReferenceEntity} objectReference */
objectReference => {
const pinObject = this[Configuration.subObjectAttributeNameFromReference(objectReference, true)]
if (pinObject) {
const pinEntity = PinEntity.fromLegacyObject(pinObject)
pinEntity.LinkedTo = []
pinEntity.LinkedTo = new (PinEntity.attributes.LinkedTo)()
this.getCustomproperties(true).push(pinEntity)
Utility.objectSet(this, ["attributes", "CustomProperties", "ignored"], true)
this.CustomProperties.ignored = true
}
}
)
@@ -354,24 +296,37 @@ export default class ObjectEntity extends IEntity {
obj.MaterialExpressionEditorX && (obj.MaterialExpressionEditorX.getter = () => this.NodePosX)
obj.MaterialExpressionEditorY && (obj.MaterialExpressionEditorY.getter = () => this.NodePosY)
if (this.getType() === Configuration.paths.materialExpressionComponentMask) {
// The following attributes are too generic therefore not assigned a MirroredEntity
const rgbaPins = Configuration.rgba.map(pinName =>
this.getPinEntities().find(pin => pin.PinName === pinName && (pin.recomputesNodeTitleOnChange = true))
)
const attribute = {}
obj.R = new MirroredEntity(Boolean, () => rgbaPins[0].DefaultValue)
obj.G = new MirroredEntity(Boolean, () => rgbaPins[1].DefaultValue)
obj.B = new MirroredEntity(Boolean, () => rgbaPins[2].DefaultValue)
obj.A = new MirroredEntity(Boolean, () => rgbaPins[3].DefaultValue)
Utility.objectSet(obj, ["attributes", "R", "default"], false)
Utility.objectSet(obj, ["attributes", "R", "silent"], true)
Utility.objectSet(obj, ["attributes", "G", "default"], false)
Utility.objectSet(obj, ["attributes", "G", "silent"], true)
Utility.objectSet(obj, ["attributes", "B", "default"], false)
Utility.objectSet(obj, ["attributes", "B", "silent"], true)
Utility.objectSet(obj, ["attributes", "A", "default"], false)
Utility.objectSet(obj, ["attributes", "A", "silent"], true)
obj._keys = [...Configuration.rgba, ...Object.keys(obj).filter(k => !Configuration.rgba.includes(k))]
const rgbaPins = Configuration.rgba.map(pinName => {
const result = this.getPinEntities().find(pin => pin.PinName.toString() === pinName)
result.recomputesNodeTitleOnChange = true
return result
})
// Reorder keys so that the added ones stay first
obj.keys = [...Configuration.rgba, ...obj.keys]
const silentBool = MirroredEntity.of(BooleanEntity).withDefault().flagSilent()
obj["R"] = new silentBool(() => rgbaPins[0].DefaultValue)
obj["G"] = new silentBool(() => rgbaPins[1].DefaultValue)
obj["B"] = new silentBool(() => rgbaPins[2].DefaultValue)
obj["A"] = new silentBool(() => rgbaPins[3].DefaultValue)
} else if (this.getType() === Configuration.paths.materialExpressionSubtract) {
const silentNumber = MirroredEntity
.of(NumberEntity.withPrecision(6))
.withDefault(() => new MirroredEntity(() => new NumberEntity(1)))
.flagSilent()
const pinA = this.getCustomproperties().find(pin => pin.PinName?.toString() === "A")
const pinB = this.getCustomproperties().find(pin => pin.PinName?.toString() === "B")
if (pinA || pinB) {
// Reorder keys so that the added ones stay first
obj.keys = ["ConstA", "ConstB", ...obj.keys]
if (pinA) {
pinA.recomputesNodeTitleOnChange = true
obj.ConstA = new silentNumber(() => pinA.DefaultValue)
}
if (pinB) {
pinB.recomputesNodeTitleOnChange = true
obj.ConstB = new silentNumber(() => pinB.DefaultValue)
}
}
}
}
/** @type {ObjectEntity} */
@@ -383,15 +338,15 @@ export default class ObjectEntity extends IEntity {
/** @param {ObjectEntity} obj */
obj => {
if (obj.Node !== undefined) {
const nodeRef = obj.Node.get()
const nodeRef = obj.Node.getter()
if (
nodeRef.type === this.PCGNode.type
&& nodeRef.path === `${this.Name}.${this.PCGNode.path}`
) {
obj.Node.getter = () => new ObjectReferenceEntity({
type: this.PCGNode.type,
path: `${this.Name}.${this.PCGNode.path}`,
})
obj.Node.getter = () => new ObjectReferenceEntity(
this.PCGNode.type,
`${this.Name}.${this.PCGNode.path}`,
)
}
}
}
@@ -400,7 +355,7 @@ export default class ObjectEntity extends IEntity {
}
let inputIndex = 0
let outputIndex = 0
this.CustomProperties?.forEach((pinEntity, i) => {
this.getCustomproperties().forEach((pinEntity, i) => {
pinEntity.objectEntity = this
pinEntity.pinIndex = pinEntity.isInput()
? inputIndex++
@@ -410,10 +365,55 @@ export default class ObjectEntity extends IEntity {
})
}
/** @returns {P<ObjectEntity>} */
static createGrammar() {
return P.seq(
P.reg(/Begin +Object/),
P.seq(
P.whitespace,
P.alt(
this.createSubObjectGrammar(),
this.customPropertyGrammar,
Grammar.createAttributeGrammar(this, P.reg(Grammar.Regex.MultipleWordsSymbols)),
Grammar.createAttributeGrammar(
this,
Grammar.attributeNameQuoted,
undefined,
(values, attributeKey, attributeValue) => {
Utility.objectSet(values, [...attributeKey, "quoted"], true)
},
),
this.inlinedArrayEntryGrammar,
)
)
.map(([_0, entry]) => entry)
.many(),
P.reg(/\s+End +Object/),
)
.map(([_0, attributes, _2]) => {
const values = {}
attributes.forEach(attributeSetter => attributeSetter(values))
return new this(values)
})
.label("ObjectEntity")
}
static createSubObjectGrammar() {
return P.lazy(() => this.grammar)
.map(object =>
values => {
object.trailing = false
values[Configuration.subObjectAttributeNameFromEntity(object)] = object
}
)
}
/** @type {String} */
#class
getClass() {
if (!this.#class) {
this.#class = (this.Class?.path ? this.Class.path : this.Class?.type)
?? (this.ExportPath?.path ? this.ExportPath.path : this.ExportPath?.type)
?? this.ExportPath.type
?? ""
if (this.#class && !this.#class.startsWith("/")) {
// Old path names did not start with /Script or /Engine, check tests/resources/LegacyNodes.js
@@ -441,12 +441,12 @@ export default class ObjectEntity extends IEntity {
if (dropCounter) {
return this.getNameAndCounter()[0]
}
return this.Name
return this.Name.toString()
}
/** @returns {[String, Number]} */
getNameAndCounter() {
const result = this.getObjectName().match(ObjectEntity.nameRegex)
const result = this.getObjectName().match(ObjectEntity.#nameRegex)
let name = ""
let counter = null
return result
@@ -509,10 +509,7 @@ export default class ObjectEntity extends IEntity {
}
getCustomproperties(canCreate = false) {
if (canCreate && !this.CustomProperties) {
this.CustomProperties = []
}
return this.CustomProperties ?? []
return this.CustomProperties.values
}
/** @returns {PinEntity[]} */
@@ -587,7 +584,7 @@ export default class ObjectEntity extends IEntity {
isPcg() {
return this.getClass() === Configuration.paths.pcgEditorGraphNode
|| this.getPcgSubobject()
|| this.getPcgSubobject() != null
}
isNiagara() {
@@ -629,7 +626,7 @@ export default class ObjectEntity extends IEntity {
}
getDelegatePin() {
return this.getCustomproperties().find(pin => pin.PinType.PinCategory === "delegate")
return this.getCustomproperties().find(pin => pin.PinType.PinCategory.toString() === "delegate")
}
nodeColor() {
@@ -643,4 +640,70 @@ export default class ObjectEntity extends IEntity {
additionalPinInserter() {
return nodeVariadic(this)
}
/** @param {String} key */
showProperty(key) {
switch (key) {
case "Class":
case "Name":
case "Archetype":
case "ExportPath":
case "CustomProperties":
// Serielized separately, check doWrite()
return false
}
return super.showProperty(key)
}
/** @param {typeof ObjectEntity} Self */
doSerialize(
insideString = false,
indentation = "",
Self = /** @type {typeof ObjectEntity} */(this.constructor),
printKey = Self.printKey,
keySeparator = Self.keySeparator,
attributeSeparator = Self.attributeSeparator,
wrap = Self.wrap,
) {
const deeperIndentation = indentation + Configuration.indentation
const content = super.doSerialize(insideString, deeperIndentation, Self, printKey, keySeparator, attributeSeparator, wrap)
let result = indentation + "Begin Object"
+ ((this.Class?.type || this.Class?.path)
// && Self.attributes.Class.ignored !== true
// && this.Class.ignored !== true
? ` Class${keySeparator}${this.Class.serialize(insideString)}`
: ""
)
+ (this.Name
// && Self.attributes.Name.ignored !== true
// && this.Name.ignored !== true
? ` Name${keySeparator}${this.Name.serialize(insideString)}`
: ""
)
+ (this.Archetype
// && Self.attributes.Archetype.ignored !== true
// && this.Archetype.ignored !== true
? ` Archetype${keySeparator}${this.Archetype.serialize(insideString)}`
: ""
)
+ ((this.ExportPath?.type || this.ExportPath?.path)
// && Self.attributes.ExportPath.ignored !== true
// && this.ExportPath.ignored !== true
? ` ExportPath${keySeparator}${this.ExportPath.serialize(insideString)}`
: ""
)
+ (content ? attributeSeparator + content : "")
+ (Self.attributes.CustomProperties.ignored !== true && this.CustomProperties.ignored !== true
? this.getCustomproperties().map(pin =>
deeperIndentation
+ printKey("CustomProperties ")
+ pin.serialize(insideString)
).join(Self.attributeSeparator)
: ""
)
+ attributeSeparator
+ indentation + "End Object"
+ (this.trailing ? attributeSeparator : "")
return result
}
}

View File

@@ -1,80 +1,116 @@
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import Utility from "../Utility.js"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import IEntity from "./IEntity.js"
export default class ObjectReferenceEntity extends IEntity {
static attributes = {
...super.attributes,
type: new AttributeInfo({
default: "",
serialized: true,
}),
path: new AttributeInfo({
default: "",
serialized: true,
}),
_full: new AttributeInfo({
ignored: true,
}),
}
static quoted = Parsernostrum.regArray(new RegExp(
/** @protected */
static _quotedParser = P.regArray(new RegExp(
`'"(${Grammar.Regex.InsideString.source})"'`
+ "|"
+ `'(${Grammar.Regex.InsideSingleQuotedString.source})'`
)).map(([_0, a, b]) => a ?? b)
static path = this.quoted.getParser().parser.regexp.source + "|" + Grammar.Regex.Path.source
static typeReference = Parsernostrum.reg(
static typeReference = P.reg(
// @ts-expect-error
new RegExp(Grammar.Regex.Path.source + "|" + Grammar.symbol.getParser().regexp.source)
)
static fullReferenceGrammar = Parsernostrum.regArray(
new RegExp(
"(" + this.typeReference.getParser().regexp.source + ")"
+ "(?:" + this.quoted.getParser().parser.regexp.source + ")"
)
).map(([_full, type, ...path]) => new this({ type, path: path.find(v => v), _full }))
static fullReferenceSerializedGrammar = Parsernostrum.regArray(
new RegExp(
'"(' + Grammar.Regex.InsideString.source + "?)"
+ "(?:'(" + Grammar.Regex.InsideSingleQuotedString.source + `?)')?"`
)
).map(([_full, type, path]) => new this({ type, path, _full }))
static typeReferenceGrammar = this.typeReference.map(v => new this({ type: v, path: "", _full: v }))
static fullReferenceGrammar = this.createFullReferenceGrammar()
static grammar = this.createGrammar()
constructor(values = {}) {
if (values.constructor === String) {
values = {
path: values
}
}
super(values)
if (!values._full || values._full.length === 0) {
this._full = `"${this.type + (this.path ? (`'${this.path}'`) : "")}"`
}
/** @type {String} */ this.type
/** @type {String} */ this.path
#type
get type() {
return this.#type
}
set type(value) {
this.#type = value
}
#path
get path() {
return this.#path
}
set path(value) {
this.#path = value
}
#fullEscaped
/** @type {String} */
#full
get full() {
return this.#full
}
set full(value) {
this.#full = value
}
constructor(type = "None", path = "", full = null) {
super()
this.#type = type
this.#path = path
this.#full = full ?? `"${this.type + (this.path ? (`'${this.path}'`) : "")}"`
}
/** @returns {P<ObjectReferenceEntity>} */
static createGrammar() {
return Parsernostrum.alt(
this.fullReferenceSerializedGrammar,
this.fullReferenceGrammar,
this.typeReferenceGrammar,
)
return P.alt(
this.createFullReferenceSerializedGrammar(),
this.createFullReferenceGrammar(),
this.createTypeReferenceGrammar(),
).label("ObjectReferenceEntity")
}
/** @returns {P<ObjectReferenceEntity>} */
static createFullReferenceGrammar() {
return P.regArray(
new RegExp(
// @ts-expect-error
"(" + this.typeReference.getParser().regexp.source + ")"
// @ts-expect-error
+ "(?:" + this._quotedParser.getParser().parser.regexp.source + ")"
)
).map(([full, type, ...path]) => new this(type, path.find(v => v), full))
}
/** @returns {P<ObjectReferenceEntity>} */
static createFullReferenceSerializedGrammar() {
return P.regArray(
new RegExp(
'"(' + Grammar.Regex.InsideString.source + "?)"
+ "(?:'(" + Grammar.Regex.InsideSingleQuotedString.source + `?)')?"`
)
).map(([full, type, path]) => new this(type, path, full))
}
/** @returns {P<ObjectReferenceEntity>} */
static createTypeReferenceGrammar() {
return this.typeReference.map(v => new this(v, "", v))
}
static createNoneInstance() {
return new ObjectReferenceEntity({ type: "None", path: "" })
return new ObjectReferenceEntity("None")
}
getName(dropCounter = false) {
return Utility.getNameFromPath(this.path.replace(/_C$/, ""), dropCounter)
}
toString() {
return this._full
doSerialize(insideString = false) {
if (insideString) {
if (this.#fullEscaped === undefined) {
this.#fullEscaped = Utility.escapeString(this.#full, false)
}
return this.#fullEscaped
}
return this.full
}
/** @param {IEntity} other */
equals(other) {
if (!(other instanceof ObjectReferenceEntity)) {
return false
}
return this.type == other.type && this.path == other.path
}
}

View File

@@ -1,36 +0,0 @@
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import IEntity from "./IEntity.js"
export default class PathSymbolEntity extends IEntity {
static attributes = {
...super.attributes,
value: new AttributeInfo({
default: "",
}),
}
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.symbol.map(v => new this(v))
}
constructor(values) {
if (values.constructor !== Object) {
values = {
value: values,
}
}
super(values)
/** @type {String} */ this.value
}
valueOf() {
return this.value
}
toString() {
return this.value
}
}

View File

@@ -1,10 +1,13 @@
import P from "parsernostrum"
import Configuration from "../Configuration.js"
import pinColor from "../decoding/pinColor.js"
import pinTitle from "../decoding/pinTitle.js"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import AlternativesEntity from "./AlternativesEntity.js"
import ArrayEntity from "./ArrayEntity.js"
import BooleanEntity from "./BooleanEntity.js"
import ByteEntity from "./ByteEntity.js"
import ComputedType from "./ComputedType.js"
import ComputedTypeEntity from "./ComputedTypeEntity.js"
import EnumDisplayValueEntity from "./EnumDisplayValueEntity.js"
import EnumEntity from "./EnumEntity.js"
import FormatTextEntity from "./FormatTextEntity.js"
@@ -15,6 +18,7 @@ import IntegerEntity from "./IntegerEntity.js"
import InvariantTextEntity from "./InvariantTextEntity.js"
import LinearColorEntity from "./LinearColorEntity.js"
import LocalizedTextEntity from "./LocalizedTextEntity.js"
import NumberEntity from "./NumberEntity.js"
import ObjectReferenceEntity from "./ObjectReferenceEntity.js"
import PinReferenceEntity from "./PinReferenceEntity.js"
import PinTypeEntity from "./PinTypeEntity.js"
@@ -24,33 +28,37 @@ import SimpleSerializationRotatorEntity from "./SimpleSerializationRotatorEntity
import SimpleSerializationVector2DEntity from "./SimpleSerializationVector2DEntity.js"
import SimpleSerializationVector4DEntity from "./SimpleSerializationVector4DEntity.js"
import SimpleSerializationVectorEntity from "./SimpleSerializationVectorEntity.js"
import Union from "./Union.js"
import StringEntity from "./StringEntity.js"
import SymbolEntity from "./SymbolEntity.js"
import Vector2DEntity from "./Vector2DEntity.js"
import Vector4DEntity from "./Vector4DEntity.js"
import VectorEntity from "./VectorEntity.js"
/** @template {TerminalAttribute} T */
/** @template {IEntity} T */
export default class PinEntity extends IEntity {
static lookbehind = "Pin"
static #typeEntityMap = {
"bool": BooleanEntity,
"byte": ByteEntity,
"enum": EnumEntity,
"exec": StringEntity,
"int": IntegerEntity,
"int64": Integer64Entity,
"name": StringEntity,
"real": NumberEntity,
"string": StringEntity,
[Configuration.paths.linearColor]: LinearColorEntity,
[Configuration.paths.niagaraPosition]: VectorEntity,
[Configuration.paths.rotator]: RotatorEntity,
[Configuration.paths.vector]: VectorEntity,
[Configuration.paths.vector2D]: Vector2DEntity,
[Configuration.paths.vector4f]: Vector4DEntity,
"bool": Boolean,
"byte": ByteEntity,
"enum": EnumEntity,
"exec": String,
"int": IntegerEntity,
"int64": Integer64Entity,
"name": String,
"real": Number,
"string": String,
}
static #alternativeTypeEntityMap = {
"enum": EnumDisplayValueEntity,
"rg": RBSerializationVector2DEntity,
[Configuration.paths.niagaraPosition]: SimpleSerializationVectorEntity.flagAllowShortSerialization(),
[Configuration.paths.rotator]: SimpleSerializationRotatorEntity,
[Configuration.paths.vector]: SimpleSerializationVectorEntity,
[Configuration.paths.vector2D]: SimpleSerializationVector2DEntity,
@@ -58,50 +66,34 @@ export default class PinEntity extends IEntity {
[Configuration.paths.vector4f]: SimpleSerializationVector4DEntity,
}
static attributes = {
...super.attributes,
lookbehind: new AttributeInfo({
default: "Pin",
ignored: true,
}),
objectEntity: new AttributeInfo({
ignored: true,
}),
pinIndex: new AttributeInfo({
type: Number,
ignored: true,
}),
PinId: new AttributeInfo({
type: GuidEntity,
default: () => new GuidEntity()
}),
PinName: AttributeInfo.createValue(""),
PinFriendlyName: AttributeInfo.createType(new Union(LocalizedTextEntity, FormatTextEntity, InvariantTextEntity, String)),
PinToolTip: AttributeInfo.createType(String),
Direction: AttributeInfo.createType(String),
PinType: new AttributeInfo({
type: PinTypeEntity,
default: () => new PinTypeEntity(),
inlined: true,
}),
LinkedTo: AttributeInfo.createType([PinReferenceEntity]),
SubPins: AttributeInfo.createType([PinReferenceEntity]),
ParentPin: AttributeInfo.createType(PinReferenceEntity),
DefaultValue: new AttributeInfo({
type: new ComputedType(
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) ?? String
pinEntity => pinEntity.getEntityType(true)?.flagSerialized() ?? StringEntity
),
serialized: true,
}),
AutogeneratedDefaultValue: AttributeInfo.createType(String),
DefaultObject: AttributeInfo.createType(ObjectReferenceEntity),
PersistentGuid: AttributeInfo.createType(GuidEntity),
bHidden: AttributeInfo.createValue(false),
bNotConnectable: AttributeInfo.createValue(false),
bDefaultValueIsReadOnly: AttributeInfo.createValue(false),
bDefaultValueIsIgnored: AttributeInfo.createValue(false),
bAdvancedView: AttributeInfo.createValue(false),
bOrphanedPin: AttributeInfo.createValue(false),
AutogeneratedDefaultValue: StringEntity,
DefaultObject: ObjectReferenceEntity,
PersistentGuid: GuidEntity,
bHidden: BooleanEntity.withDefault(),
bNotConnectable: BooleanEntity.withDefault(),
bDefaultValueIsReadOnly: BooleanEntity.withDefault(),
bDefaultValueIsIgnored: BooleanEntity.withDefault(),
bAdvancedView: BooleanEntity.withDefault(),
bOrphanedPin: BooleanEntity.withDefault(),
}
static grammar = this.createGrammar()
@@ -113,42 +105,76 @@ export default class PinEntity extends IEntity {
return this.#recomputesNodeTitleOnChange
}
static createGrammar() {
return Grammar.createEntityGrammar(this)
/** @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
}
constructor(values = {}, suppressWarns = false) {
super(values, suppressWarns)
/** @type {ObjectEntity} */ this.objectEntity
/** @type {Number} */ this.pinIndex
/** @type {GuidEntity} */ this.PinId
/** @type {String} */ this.PinName
/** @type {LocalizedTextEntity | String} */ this.PinFriendlyName
/** @type {String} */ this.PinToolTip
/** @type {String} */ this.Direction
/** @type {PinTypeEntity} */ this.PinType
/** @type {PinReferenceEntity[]} */ this.LinkedTo
#pinIndex
get pinIndex() {
return this.#pinIndex
}
set pinIndex(value) {
this.#pinIndex = value
}
constructor(values = {}) {
super(values)
/** @type {InstanceType<typeof PinEntity.attributes.PinId>} */ this.PinId
/** @type {InstanceType<typeof PinEntity.attributes.PinName>} */ this.PinName
/** @type {InstanceType<typeof PinEntity.attributes.PinFriendlyName>} */ this.PinFriendlyName
/** @type {InstanceType<typeof PinEntity.attributes.PinToolTip>} */ this.PinToolTip
/** @type {InstanceType<typeof PinEntity.attributes.Direction>} */ this.Direction
/** @type {InstanceType<typeof PinEntity.attributes.PinType>} */ this.PinType
/** @type {InstanceType<typeof PinEntity.attributes.LinkedTo>} */ this.LinkedTo
/** @type {T} */ this.DefaultValue
/** @type {String} */ this.AutogeneratedDefaultValue
/** @type {ObjectReferenceEntity} */ this.DefaultObject
/** @type {GuidEntity} */ this.PersistentGuid
/** @type {Boolean} */ this.bHidden
/** @type {Boolean} */ this.bNotConnectable
/** @type {Boolean} */ this.bDefaultValueIsReadOnly
/** @type {Boolean} */ this.bDefaultValueIsIgnored
/** @type {Boolean} */ this.bAdvancedView
/** @type {Boolean} */ this.bOrphanedPin
/** @type {InstanceType<typeof PinEntity.attributes.AutogeneratedDefaultValue>} */ this.AutogeneratedDefaultValue
/** @type {InstanceType<typeof PinEntity.attributes.DefaultObject>} */ this.DefaultObject
/** @type {InstanceType<typeof PinEntity.attributes.PersistentGuid>} */ this.PersistentGuid
/** @type {InstanceType<typeof PinEntity.attributes.bHidden>} */ this.bHidden
/** @type {InstanceType<typeof PinEntity.attributes.bNotConnectable>} */ this.bNotConnectable
/** @type {InstanceType<typeof PinEntity.attributes.bDefaultValueIsReadOnly>} */ this.bDefaultValueIsReadOnly
/** @type {InstanceType<typeof PinEntity.attributes.bDefaultValueIsIgnored>} */ this.bDefaultValueIsIgnored
/** @type {InstanceType<typeof PinEntity.attributes.bAdvancedView>} */ this.bAdvancedView
/** @type {InstanceType<typeof PinEntity.attributes.bOrphanedPin>} */ this.bOrphanedPin
/** @type {ObjectEntity} */ this.objectEntity
}
static createGrammar() {
return /** @type {P<PinEntity>} */(
Grammar.createEntityGrammar(this)
)
}
/** @param {ObjectEntity} objectEntity */
static fromLegacyObject(objectEntity) {
return new PinEntity(objectEntity, true)
return new PinEntity(objectEntity)
}
getType() {
const category = this.PinType.PinCategory.toLocaleLowerCase()
const category = this.PinType.PinCategory?.toString().toLocaleLowerCase()
if (category === "struct" || category === "class" || category === "object" || category === "type") {
return this.PinType.PinSubCategoryObject.path
return this.PinType.PinSubCategoryObject?.path
}
if (this.isEnum()) {
return "enum"
@@ -156,8 +182,8 @@ export default class PinEntity extends IEntity {
if (this.objectEntity?.isPcg()) {
const pcgSuboject = this.objectEntity.getPcgSubobject()
const pinObjectReference = this.isInput()
? pcgSuboject.InputPins?.[this.pinIndex]
: pcgSuboject.OutputPins?.[this.pinIndex]
? pcgSuboject.InputPins?.valueOf()[this.pinIndex]
: pcgSuboject.OutputPins?.valueOf()[this.pinIndex]
if (pinObjectReference) {
/** @type {ObjectEntity} */
const pinObject = pcgSuboject[Configuration.subObjectAttributeNameFromReference(pinObjectReference, true)]
@@ -170,8 +196,8 @@ export default class PinEntity extends IEntity {
}
if (allowedTypes) {
if (
pinObject.Properties.bAllowMultipleData !== false
&& pinObject.Properties.bAllowMultipleConnections !== false
pinObject.Properties.bAllowMultipleData?.valueOf() !== false
&& pinObject.Properties.bAllowMultipleConnections?.valueOf() !== false
) {
allowedTypes += "[]"
}
@@ -180,7 +206,8 @@ export default class PinEntity extends IEntity {
}
}
if (category === "optional") {
switch (this.PinType.PinSubCategory) {
const subCategory = this.PinType.PinSubCategory?.toString()
switch (subCategory) {
case "red":
return "real"
case "rg":
@@ -190,16 +217,17 @@ export default class PinEntity extends IEntity {
case "rgba":
return Configuration.paths.linearColor
default:
return this.PinType.PinSubCategory
return subCategory
}
}
return category
}
/** @returns {typeof IEntity} */
getEntityType(alternative = false) {
const typeString = this.getType()
const entity = PinEntity.#typeEntityMap[typeString]
const alternativeEntity = PinEntity.#alternativeTypeEntityMap[typeString]
const type = this.getType()
const entity = PinEntity.#typeEntityMap[type]
const alternativeEntity = PinEntity.#alternativeTypeEntityMap[type]
return alternative && alternativeEntity !== undefined
? alternativeEntity
: entity
@@ -211,48 +239,37 @@ export default class PinEntity extends IEntity {
/** @param {PinEntity} other */
copyTypeFrom(other) {
this.PinType.PinCategory = other.PinType.PinCategory
this.PinType.PinSubCategory = other.PinType.PinSubCategory
this.PinType.PinSubCategoryObject = other.PinType.PinSubCategoryObject
this.PinType.PinSubCategoryMemberReference = other.PinType.PinSubCategoryMemberReference
this.PinType.PinValueType = other.PinType.PinValueType
this.PinType.ContainerType = other.PinType.ContainerType
this.PinType.bIsReference = other.PinType.bIsReference
this.PinType.bIsConst = other.PinType.bIsConst
this.PinType.bIsWeakPointer = other.PinType.bIsWeakPointer
this.PinType.bIsUObjectWrapper = other.PinType.bIsUObjectWrapper
this.PinType.bSerializeAsSinglePrecisionFloat = other.PinType.bSerializeAsSinglePrecisionFloat
this.PinType = other.PinType
}
getDefaultValue(maybeCreate = false) {
if (this.DefaultValue === undefined && maybeCreate) {
// @ts-expect-error
this.DefaultValue = new (this.getEntityType(true))()
this.DefaultValue = /** @type {T} */(new (this.getEntityType(true))())
}
return this.DefaultValue
}
isEnum() {
const type = this.PinType.PinSubCategoryObject.type
const type = this.PinType.PinSubCategoryObject?.type
return type === Configuration.paths.enum
|| type === Configuration.paths.userDefinedEnum
|| type.toLowerCase() === "enum"
|| type?.toLowerCase() === "enum"
}
isExecution() {
return this.PinType.PinCategory === "exec"
return this.PinType.PinCategory.toString() === "exec"
}
isHidden() {
return this.bHidden
return this.bHidden?.valueOf()
}
isInput() {
return !this.bHidden && this.Direction != "EGPD_Output"
return !this.isHidden() && this.Direction?.toString() != "EGPD_Output"
}
isOutput() {
return !this.bHidden && this.Direction == "EGPD_Output"
return !this.isHidden() && this.Direction?.toString() == "EGPD_Output"
}
isLinked() {
@@ -265,15 +282,12 @@ export default class PinEntity extends IEntity {
* @returns true if it was not already linked to the tarket
*/
linkTo(targetObjectName, targetPinEntity) {
const linkFound = this.LinkedTo?.some(pinReferenceEntity =>
const linkFound = this.LinkedTo.values?.some(pinReferenceEntity =>
pinReferenceEntity.objectName.toString() == targetObjectName
&& pinReferenceEntity.pinGuid.valueOf() == targetPinEntity.PinId.valueOf()
&& pinReferenceEntity.pinGuid.toString() == targetPinEntity.PinId.toString()
)
if (!linkFound) {
(this.LinkedTo ??= []).push(new PinReferenceEntity({
objectName: targetObjectName,
pinGuid: targetPinEntity.PinId,
}))
this.LinkedTo.values.push(new PinReferenceEntity(new SymbolEntity(targetObjectName), targetPinEntity.PinId))
return true
}
return false // Already linked
@@ -285,14 +299,14 @@ export default class PinEntity extends IEntity {
* @returns true if it was linked to the target
*/
unlinkFrom(targetObjectName, targetPinEntity) {
const indexElement = this.LinkedTo?.findIndex(pinReferenceEntity => {
const indexElement = this.LinkedTo.values?.findIndex(pinReferenceEntity => {
return pinReferenceEntity.objectName.toString() == targetObjectName
&& pinReferenceEntity.pinGuid.valueOf() == targetPinEntity.PinId.valueOf()
&& pinReferenceEntity.pinGuid.toString() == targetPinEntity.PinId.toString()
})
if (indexElement >= 0) {
this.LinkedTo.splice(indexElement, 1)
this.LinkedTo.values.splice(indexElement, 1)
if (this.LinkedTo.length === 0 && PinEntity.attributes.LinkedTo.default === undefined) {
this.LinkedTo = undefined
this.LinkedTo.values = []
}
return true
}

View File

@@ -1,34 +1,35 @@
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import GuidEntity from "./GuidEntity.js"
import IEntity from "./IEntity.js"
import PathSymbolEntity from "./PathSymbolEntity.js"
import AttributeInfo from "./AttributeInfo.js"
import SymbolEntity from "./SymbolEntity.js"
export default class PinReferenceEntity extends IEntity {
static attributes = {
...super.attributes,
objectName: AttributeInfo.createType(PathSymbolEntity),
pinGuid: AttributeInfo.createType(GuidEntity),
}
static grammar = this.createGrammar()
/**
* @param {SymbolEntity} objectName
* @param {GuidEntity} pinGuid
*/
constructor(objectName = null, pinGuid = null) {
super()
this.objectName = objectName
this.pinGuid = pinGuid
}
static createGrammar() {
return Parsernostrum.seq(
PathSymbolEntity.grammar,
Parsernostrum.whitespace,
GuidEntity.grammar
).map(
([objectName, _1, pinGuid]) => new this({
objectName: objectName,
pinGuid: pinGuid,
})
return /** @type {P<PinReferenceEntity>} */(
P.seq(
SymbolEntity.grammar,
P.whitespace,
GuidEntity.grammar
)
.map(([objectName, _1, pinGuid]) => new this(objectName, pinGuid))
.label("PinReferenceEntity")
)
}
constructor(values) {
super(values)
/** @type {PathSymbolEntity} */ this.objectName
/** @type {GuidEntity} */ this.pinGuid
doSerialize() {
return this.objectName.serialize() + " " + this.pinGuid.serialize()
}
}

View File

@@ -1,69 +1,56 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import BooleanEntity from "./BooleanEntity.js"
import FunctionReferenceEntity from "./FunctionReferenceEntity.js"
import IEntity from "./IEntity.js"
import ObjectReferenceEntity from "./ObjectReferenceEntity.js"
import PathSymbolEntity from "./PathSymbolEntity.js"
import StringEntity from "./StringEntity.js"
import SymbolEntity from "./SymbolEntity.js"
export default class PinTypeEntity extends IEntity {
static attributes = {
...super.attributes,
PinCategory: AttributeInfo.createValue(""),
PinSubCategory: AttributeInfo.createValue(""),
PinSubCategoryObject: new AttributeInfo({
type: ObjectReferenceEntity,
default: () => ObjectReferenceEntity.createNoneInstance(),
}),
PinSubCategoryMemberReference: new AttributeInfo({
type: FunctionReferenceEntity,
default: null,
}),
PinValueType: new AttributeInfo({
type: PinTypeEntity,
default: null,
}),
ContainerType: AttributeInfo.createType(PathSymbolEntity),
bIsReference: AttributeInfo.createValue(false),
bIsConst: AttributeInfo.createValue(false),
bIsWeakPointer: AttributeInfo.createValue(false),
bIsUObjectWrapper: AttributeInfo.createValue(false),
bSerializeAsSinglePrecisionFloat: AttributeInfo.createValue(false),
PinCategory: StringEntity.withDefault(),
PinSubCategory: StringEntity.withDefault(),
PinSubCategoryObject: ObjectReferenceEntity.withDefault(),
PinSubCategoryMemberReference: FunctionReferenceEntity.withDefault(),
ContainerType: SymbolEntity,
bIsReference: BooleanEntity.withDefault(),
bIsConst: BooleanEntity.withDefault(),
bIsWeakPointer: BooleanEntity.withDefault(),
bIsUObjectWrapper: BooleanEntity.withDefault(),
bSerializeAsSinglePrecisionFloat: BooleanEntity.withDefault(),
}
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.createEntityGrammar(this)
constructor(values = {}) {
super(values)
/** @type {InstanceType<typeof PinTypeEntity.attributes.PinCategory>} */ this.PinCategory
/** @type {InstanceType<typeof PinTypeEntity.attributes.PinSubCategory>} */ this.PinSubCategory
/** @type {InstanceType<typeof PinTypeEntity.attributes.PinSubCategoryObject>} */ this.PinSubCategoryObject
/** @type {InstanceType<typeof PinTypeEntity.attributes.PinSubCategoryMemberReference>} */ this.PinSubCategoryMemberReference
/** @type {InstanceType<typeof PinTypeEntity.attributes.ContainerType>} */ this.ContainerType
/** @type {InstanceType<typeof PinTypeEntity.attributes.bIsReference>} */ this.bIsReference
/** @type {InstanceType<typeof PinTypeEntity.attributes.bIsConst>} */ this.bIsConst
/** @type {InstanceType<typeof PinTypeEntity.attributes.bIsWeakPointer>} */ this.bIsWeakPointer
/** @type {InstanceType<typeof PinTypeEntity.attributes.bIsUObjectWrapper>} */ this.bIsUObjectWrapper
/** @type {InstanceType<typeof PinTypeEntity.attributes.bIsUObjectWrapper>} */ this.bIsUObjectWrapper
/** @type {InstanceType<typeof PinTypeEntity.attributes.bSerializeAsSinglePrecisionFloat>} */ this.bSerializeAsSinglePrecisionFloat
}
constructor(values = {}, suppressWarns = false) {
super(values, suppressWarns)
/** @type {String} */ this.PinCategory
/** @type {String} */ this.PinSubCategory
/** @type {ObjectReferenceEntity} */ this.PinSubCategoryObject
/** @type {FunctionReferenceEntity} */ this.PinSubCategoryMemberReference
/** @type {PinTypeEntity} */ this.PinValueType
/** @type {PathSymbolEntity} */ this.ContainerType
/** @type {Boolean} */ this.bIsReference
/** @type {Boolean} */ this.bIsConst
/** @type {Boolean} */ this.bIsWeakPointer
/** @type {Boolean} */ this.bIsUObjectWrapper
/** @type {Boolean} */ this.bIsUObjectWrapper
/** @type {Boolean} */ this.bSerializeAsSinglePrecisionFloat
static createGrammar() {
return /** @type {P<PinTypeEntity>} */(
Grammar.createEntityGrammar(this).label("PinTypeEntity")
)
}
/** @param {PinTypeEntity} other */
copyTypeFrom(other) {
this.PinCategory = other.PinCategory
this.PinSubCategory = other.PinSubCategory
this.PinSubCategoryObject = other.PinSubCategoryObject
this.PinSubCategoryMemberReference = other.PinSubCategoryMemberReference
this.PinValueType = other.PinValueType
this.ContainerType = other.ContainerType
this.bIsReference = other.bIsReference
this.bIsConst = other.bIsConst
this.bIsWeakPointer = other.bIsWeakPointer
this.bIsUObjectWrapper = other.bIsUObjectWrapper
this.bSerializeAsSinglePrecisionFloat = other.bSerializeAsSinglePrecisionFloat
for (const key of this.keys) {
if (other[key] !== undefined) {
this[key] = other[key]
}
}
}
}

View File

@@ -1,4 +1,5 @@
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import Vector2DEntity from "./Vector2DEntity.js"
export default class RBSerializationVector2DEntity extends Vector2DEntity {
@@ -6,16 +7,19 @@ export default class RBSerializationVector2DEntity extends Vector2DEntity {
static grammar = this.createGrammar()
static createGrammar() {
return Parsernostrum.alt(
Parsernostrum.regArray(new RegExp(
/X\s*=\s*/.source + "(?<x>" + Parsernostrum.number.getParser().parser.regexp.source + ")"
return /** @type {P<RBSerializationVector2DEntity>} */(P.alt(
P.regArray(new RegExp(
/X\s*=\s*/.source + "(?<x>" + Grammar.numberRegexSource + ")"
+ "\\s+"
+ /Y\s*=\s*/.source + "(?<y>" + Parsernostrum.number.getParser().parser.regexp.source + ")"
+ /Y\s*=\s*/.source + "(?<y>" + Grammar.numberRegexSource + ")"
)).map(({ groups: { x, y } }) => new this({
X: Number(x),
Y: Number(y),
X: new (Vector2DEntity.attributes.X)(x),
Y: new (Vector2DEntity.attributes.Y)(y),
})),
Vector2DEntity.grammar
)
Vector2DEntity.grammar.map(v => new this({
X: v.X,
Y: v.Y,
}))
).label("RBSerializationVector2DEntity"))
}
}

View File

@@ -1,35 +1,29 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import IEntity from "./IEntity.js"
import NumberEntity from "./NumberEntity.js"
export default class RotatorEntity extends IEntity {
static attributes = {
...super.attributes,
R: new AttributeInfo({
default: 0,
expected: true,
}),
P: new AttributeInfo({
default: 0,
expected: true,
}),
Y: new AttributeInfo({
default: 0,
expected: true,
}),
R: NumberEntity.withDefault(),
P: NumberEntity.withDefault(),
Y: NumberEntity.withDefault(),
}
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.createEntityGrammar(this, false)
}
constructor(values) {
super(values)
/** @type {Number} */ this.R
/** @type {Number} */ this.P
/** @type {Number} */ this.Y
/** @type {InstanceType<typeof RotatorEntity.attributes.R>} */ this.R
/** @type {InstanceType<typeof RotatorEntity.attributes.P>} */ this.P
/** @type {InstanceType<typeof RotatorEntity.attributes.Y>} */ this.Y
}
static createGrammar() {
return /** @type {P<RotatorEntity>} */(
Grammar.createEntityGrammar(this, Grammar.commaSeparation, true).label("RotatorEntity")
)
}
getRoll() {

View File

@@ -1,5 +1,5 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import GuidEntity from "./GuidEntity.js"
import IEntity from "./IEntity.js"
import ObjectReferenceEntity from "./ObjectReferenceEntity.js"
@@ -8,18 +8,20 @@ export default class ScriptVariableEntity extends IEntity {
static attributes = {
...super.attributes,
ScriptVariable: AttributeInfo.createType(ObjectReferenceEntity),
OriginalChangeId: AttributeInfo.createType(GuidEntity),
ScriptVariable: ObjectReferenceEntity,
OriginalChangeId: GuidEntity,
}
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.createEntityGrammar(this)
constructor(values = {}) {
super(values)
/** @type {InstanceType<typeof ScriptVariableEntity.attributes.ScriptVariable>} */ this.ScriptVariable
/** @type {InstanceType<typeof ScriptVariableEntity.attributes.OriginalChangeId>} */ this.OriginalChangeId
}
constructor(values = {}, suppressWarns = false) {
super(values, suppressWarns)
/** @type {ObjectReferenceEntity} */ this.ScriptVariable
/** @type {GuidEntity} */ this.OriginalChangeId
static createGrammar() {
return /** @type {P<ScriptVariableEntity>} */(
Grammar.createEntityGrammar(this).label("ScriptVariableEntity")
)
}
}

View File

@@ -1,25 +1,41 @@
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import NumberEntity from "./NumberEntity.js"
import RotatorEntity from "./RotatorEntity.js"
export default class SimpleSerializationRotatorEntity extends RotatorEntity {
static attributeSeparator = ", "
static grammar = this.createGrammar()
static createGrammar() {
const number = Parsernostrum.number.getParser().parser.regexp.source
return Parsernostrum.alt(
Parsernostrum.regArray(new RegExp(
"(" + number + ")"
+ "\\s*,\\s*"
+ "(" + number + ")"
+ "\\s*,\\s*"
+ "(" + number + ")"
)).map(([_, p, y, r]) => new this({
R: Number(r),
P: Number(p),
Y: Number(y),
})),
RotatorEntity.grammar
return /** @type {P<SimpleSerializationRotatorEntity>} */(
P.alt(
P.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 : "")
}
}

View File

@@ -1,22 +1,36 @@
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import NumberEntity from "./NumberEntity.js"
import Vector2DEntity from "./Vector2DEntity.js"
export default class SimpleSerializationVector2DEntity extends Vector2DEntity {
static attributeSeparator = ", "
static grammar = this.createGrammar()
static createGrammar() {
const number = Parsernostrum.number.getParser().parser.regexp.source
return Parsernostrum.alt(
Parsernostrum.regArray(new RegExp(
"(" + number + ")"
+ "\\s*,\\s*"
+ "(" + number + ")"
)).map(([_, x, y]) => new this({
X: Number(x),
Y: Number(y),
})),
Vector2DEntity.grammar
return /** @type {P<SimpleSerializationVector2DEntity>} */(
P.alt(
P.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 : "")
}
}

View File

@@ -1,27 +1,29 @@
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import Vector4DEntity from "./Vector4DEntity.js"
export default class SimpleSerializationVector4DEntity extends Vector4DEntity {
static grammar = this.createGrammar()
/** @returns {P<SimpleSerializationVector4DEntity> } */
static createGrammar() {
const number = Parsernostrum.number.getParser().parser.regexp.source
return Parsernostrum.alt(
Parsernostrum.regArray(new RegExp(
"(" + number + ")"
+ "\\s*,\\s*"
+ "(" + number + ")"
+ "\\s*,\\s*"
+ "(" + number + ")"
+ "\\s*,\\s*"
+ "(" + number + ")"
const number = Grammar.numberRegexSource
return P.alt(
P.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: Number(x),
Y: Number(y),
Z: Number(z),
W: Number(w),
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
)

View File

@@ -1,26 +1,60 @@
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import NumberEntity from "./NumberEntity.js"
import VectorEntity from "./VectorEntity.js"
export default class SimpleSerializationVectorEntity extends VectorEntity {
static allowShortSerialization = false
static attributeSeparator = ", "
static grammar = this.createGrammar()
static createGrammar() {
const number = Parsernostrum.number.getParser().parser.regexp.source
return Parsernostrum.alt(
Parsernostrum.regArray(new RegExp(
"(" + number + ")"
+ "\\s*,\\s*"
+ "(" + number + ")"
+ "\\s*,\\s*"
+ "(" + number + ")"
))
.map(([_0, x, y, z]) => new this({
X: Number(x),
Y: Number(y),
Z: Number(z),
})),
VectorEntity.grammar
return /** @type {P<SimpleSerializationVectorEntity>} */(
P.alt(
P.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 : "")
}
}

37
js/entity/StringEntity.js Executable file
View File

@@ -0,0 +1,37 @@
import P from "parsernostrum"
import Utility from "../Utility.js"
import IEntity from "./IEntity.js"
export default class StringEntity extends IEntity {
static grammar = this.createGrammar()
constructor(value = "") {
super()
this.value = value
}
static createGrammar() {
return /** @type {P<StringEntity>} */(
P.doubleQuotedString
.map(insideString => new this(Utility.unescapeString(insideString)))
.label("StringEntity")
)
}
doSerialize(insideString = false) {
let result = `"${Utility.escapeString(this.value)}"`
if (insideString) {
result = Utility.escapeString(result, false)
}
return result
}
valueOf() {
return this.value
}
toString() {
return this.value
}
}

View File

@@ -1,32 +1,36 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import IEntity from "./IEntity.js"
export default class SymbolEntity extends IEntity {
static attributes = {
...super.attributes,
value: AttributeInfo.createValue(""),
static attributeConverter = {
fromAttribute: (value, type) => new this(value),
toAttribute: (value, type) => value.toString()
}
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.symbol.map(v => new this(v))
return /** @type {P<SymbolEntity>} */(
Grammar.symbol.map(v => new this(v)).label("SymbolEntity")
)
}
/** @param {String | Object} values */
constructor(values) {
if (values.constructor !== Object) {
values = {
value: values,
}
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}"`
}
super(values)
/** @type {String} */ this.value
}
valueOf() {
return this.value
return result
}
toString() {

View File

@@ -1,16 +1,20 @@
import AttributeInfo from "./AttributeInfo.js"
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import BooleanEntity from "./BooleanEntity.js"
import IEntity from "./IEntity.js"
import StringEntity from "./StringEntity.js"
export default class TerminalTypeEntity extends IEntity {
static attributes = {
...super.attributes,
TerminalCategory: AttributeInfo.createType(String),
TerminalSubCategory: AttributeInfo.createType(String),
bTerminalIsConst: AttributeInfo.createType(Boolean),
bTerminalIsWeakPointer: AttributeInfo.createType(Boolean),
bTerminalIsUObjectWrapper: AttributeInfo.createType(Boolean),
TerminalCategory: StringEntity,
TerminalSubCategory: StringEntity,
bTerminalIsConst: BooleanEntity,
bTerminalIsWeakPointer: BooleanEntity,
bTerminalIsUObjectWrapper: BooleanEntity,
}
static grammar = this.createGrammar()
constructor(values) {
super(values)
@@ -20,4 +24,10 @@ export default class TerminalTypeEntity extends IEntity {
/** @type {Boolean} */ this.bTerminalIsWeakPointer
/** @type {Boolean} */ this.bTerminalIsUObjectWrapper
}
static createGrammar() {
return /** @type {P<TerminalTypeEntity>} */(
Grammar.createEntityGrammar(this)
)
}
}

View File

@@ -1,14 +0,0 @@
/** @template {any[]} T */
export default class Union {
/** @type {T} */
#values
get values() {
return this.#values
}
/** @param {T} values */
constructor(...values) {
this.#values = values
}
}

View File

@@ -1,40 +1,37 @@
import Parsernostrum from "parsernostrum"
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import IEntity from "./IEntity.js"
export default class UnknownKeysEntity extends IEntity {
static grammar = this.createGrammar()
static createGrammar() {
return Parsernostrum.seq(
// Lookbehind
Parsernostrum.reg(
new RegExp(`(${Grammar.Regex.Path.source}|${Grammar.Regex.Symbol.source}\\s*)?\\(\\s*`),
1
),
Parsernostrum.seq(Grammar.attributeName, Grammar.equalSeparation).map(([attribute, equal]) => attribute)
.chain(attributeName =>
Grammar.unknownValue.map(attributeValue =>
values => values[attributeName] = attributeValue
)
)
.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)
})
static {
IEntity.unknownEntity = this
}
constructor(values) {
super(values, true)
static createGrammar() {
return /** @type {P<UnknownKeysEntity>} */(
P.seq(
// Lookbehind
P.reg(new RegExp(`(${Grammar.Regex.Path.source}|${Grammar.Regex.Symbol.source}\\s*)?\\(\\s*`), 1),
P.seq(Grammar.attributeName, Grammar.equalSeparation).map(([attribute, equal]) => attribute)
.chain(attributeName =>
this.unknownEntityGrammar.map(attributeValue =>
values => values[attributeName] = attributeValue
)
)
.sepBy(Grammar.commaSeparation),
P.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("UnknownKeysEntity")
)
}
}

View File

@@ -1,5 +1,5 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import Parsernostrum from "parsernostrum"
import PinEntity from "./PinEntity.js"
export default class UnknownPinEntity extends PinEntity {
@@ -7,25 +7,20 @@ export default class UnknownPinEntity extends PinEntity {
static grammar = this.createGrammar()
static createGrammar() {
return Parsernostrum.seq(
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)
})
}
constructor(values = {}) {
super(values, true)
return /** @type {P<UnknownPinEntity>} */(
P.seq(
P.reg(new RegExp(`(${Grammar.Regex.Symbol.source})\\s*\\(\\s*`), 1),
Grammar.createAttributeGrammar(this).sepBy(Grammar.commaSeparation),
P.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")
)
}
}

View File

@@ -1,27 +1,32 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import BooleanEntity from "./BooleanEntity.js"
import GuidEntity from "./GuidEntity.js"
import IEntity from "./IEntity.js"
import StringEntity from "./StringEntity.js"
export default class VariableReferenceEntity extends IEntity {
static attributes = {
...super.attributes,
MemberScope: AttributeInfo.createType(String),
MemberName: AttributeInfo.createValue(""),
MemberGuid: AttributeInfo.createType(GuidEntity),
bSelfContext: AttributeInfo.createType(Boolean),
MemberScope: StringEntity,
MemberName: StringEntity.withDefault(),
MemberGuid: GuidEntity,
bSelfContext: BooleanEntity,
}
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.createEntityGrammar(this)
}
constructor(values) {
super(values)
/** @type {String} */ this.MemberName
/** @type {GuidEntity} */ this.GuidEntity
/** @type {Boolean} */ this.bSelfContext
/** @type {InstanceType<typeof VariableReferenceEntity.attributes.MemberScope>} */ this.MemberScope
/** @type {InstanceType<typeof VariableReferenceEntity.attributes.MemberName>} */ this.MemberName
/** @type {InstanceType<typeof VariableReferenceEntity.attributes.MemberGuid>} */ this.MemberGuid
/** @type {InstanceType<typeof VariableReferenceEntity.attributes.bSelfContext>} */ this.bSelfContext
}
static createGrammar() {
return /** @type {P<VariableReferenceEntity>} */(
Grammar.createEntityGrammar(this).label("VariableReferenceEntity")
)
}
}

View File

@@ -1,34 +1,30 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import IEntity from "./IEntity.js"
import NumberEntity from "./NumberEntity.js"
export default class Vector2DEntity extends IEntity {
static attributes = {
...super.attributes,
X: new AttributeInfo({
default: 0,
expected: true,
}),
Y: new AttributeInfo({
default: 0,
expected: true,
}),
X: NumberEntity.withDefault(),
Y: NumberEntity.withDefault(),
}
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.createEntityGrammar(this, false)
}
constructor(values) {
super(values)
/** @type {Number} */ this.X
/** @type {Number} */ this.Y
/** @type {InstanceType<typeof Vector2DEntity.attributes.X>} */ this.X
/** @type {InstanceType<typeof Vector2DEntity.attributes.Y>} */ this.Y
}
/** @returns {P<Vector2DEntity>} */
static createGrammar() {
return Grammar.createEntityGrammar(this, Grammar.commaSeparation, true).label("Vector2DEntity")
}
/** @returns {[Number, Number]} */
toArray() {
return [this.X, this.Y]
return [this.X.valueOf(), this.Y.valueOf()]
}
}

View File

@@ -1,44 +1,35 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import IEntity from "./IEntity.js"
import NumberEntity from "./NumberEntity.js"
export default class Vector4DEntity extends IEntity {
static attributes = {
...super.attributes,
X: new AttributeInfo({
default: 0,
expected: true,
}),
Y: new AttributeInfo({
default: 0,
expected: true,
}),
Z: new AttributeInfo({
default: 0,
expected: true,
}),
W: new AttributeInfo({
default: 0,
expected: true,
}),
X: NumberEntity.withDefault(),
Y: NumberEntity.withDefault(),
Z: NumberEntity.withDefault(),
W: NumberEntity.withDefault(),
}
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.createEntityGrammar(Vector4DEntity, false)
}
constructor(values) {
super(values)
/** @type {Number} */ this.X
/** @type {Number} */ this.Y
/** @type {Number} */ this.Z
/** @type {Number} */ this.W
/** @type {InstanceType<typeof Vector4DEntity.attributes.X>} */ this.X
/** @type {InstanceType<typeof Vector4DEntity.attributes.Y>} */ this.Y
/** @type {InstanceType<typeof Vector4DEntity.attributes.Z>} */ this.Z
/** @type {InstanceType<typeof Vector4DEntity.attributes.W>} */ this.W
}
static createGrammar() {
return /** @type {P<Vector4DEntity>} */(
Grammar.createEntityGrammar(this, Grammar.commaSeparation, true).label("Vector4DEntity")
)
}
/** @returns {[Number, Number, Number, Number]} */
toArray() {
return [this.X, this.Y, this.Z, this.W]
return [this.X.valueOf(), this.Y.valueOf(), this.Z.valueOf(), this.W.valueOf()]
}
}

View File

@@ -1,39 +1,33 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import AttributeInfo from "./AttributeInfo.js"
import IEntity from "./IEntity.js"
import NumberEntity from "./NumberEntity.js"
export default class VectorEntity extends IEntity {
static attributes = {
...super.attributes,
X: new AttributeInfo({
default: 0,
expected: true,
}),
Y: new AttributeInfo({
default: 0,
expected: true,
}),
Z: new AttributeInfo({
default: 0,
expected: true,
}),
X: NumberEntity.withDefault(),
Y: NumberEntity.withDefault(),
Z: NumberEntity.withDefault(),
}
static grammar = this.createGrammar()
static createGrammar() {
return Grammar.createEntityGrammar(VectorEntity, false)
}
constructor(values) {
super(values)
/** @type {Number} */ this.X
/** @type {Number} */ this.Y
/** @type {Number} */ this.Z
/** @type {InstanceType<typeof VectorEntity.attributes.X>} */ this.X
/** @type {InstanceType<typeof VectorEntity.attributes.Y>} */ this.Y
/** @type {InstanceType<typeof VectorEntity.attributes.X>} */ this.Z
}
c
static createGrammar() {
return /** @type {P<VectorEntity>} */(
Grammar.createEntityGrammar(this, Grammar.commaSeparation, true).label("VectorEntity")
)
}
/** @returns {[Number, Number, Number]} */
toArray() {
return [this.X, this.Y, this.Z]
return [this.X.valueOf(), this.Y.valueOf(), this.Z.valueOf()]
}
}

View File

@@ -11,23 +11,21 @@ export default class KnotEntity extends ObjectEntity {
*/
constructor(values = {}, pinReferenceForType = undefined) {
values.Class = new ObjectReferenceEntity(Configuration.paths.knot)
values.Name = "K2Node_Knot"
values.Name = new (ObjectEntity.attributes.Name)("K2Node_Knot")
const inputPinEntity = new PinEntity(
{ PinName: "InputPin" },
true
{ PinName: new (PinEntity.attributes.PinName)("InputPin") },
)
const outputPinEntity = new PinEntity(
{
PinName: "OutputPin",
Direction: "EGPD_Output",
PinName: new (PinEntity.attributes.PinName)("OutputPin"),
Direction: new (PinEntity.attributes.Direction)("EGPD_Output"),
},
true
)
if (pinReferenceForType) {
inputPinEntity.copyTypeFrom(pinReferenceForType)
outputPinEntity.copyTypeFrom(pinReferenceForType)
}
values["CustomProperties"] = [inputPinEntity, outputPinEntity]
super(values, true)
values["CustomProperties"] = new (ObjectEntity.attributes.CustomProperties)([inputPinEntity, outputPinEntity])
super(values)
}
}

View File

@@ -1,4 +1,3 @@
import ObjectSerializer from "../../serialization/ObjectSerializer.js"
import IInput from "../IInput.js"
/**
@@ -10,8 +9,6 @@ import IInput from "../IInput.js"
export default class Copy extends IInput {
static #serializer = new ObjectSerializer()
/** @type {(e: ClipboardEvent) => void} */
#copyHandler
@@ -33,11 +30,11 @@ export default class Copy extends IInput {
getSerializedText() {
const allNodes = this.blueprint.getNodes(true).map(n => n.entity)
const exported = allNodes.filter(n => n.isExported).map(n => Copy.#serializer.write(n, false))
const result = allNodes.filter(n => !n.isExported).map(n => Copy.#serializer.write(n, false))
const exported = allNodes.filter(n => n.exported).map(n => n.serialize())
const result = allNodes.filter(n => !n.exported).map(n => n.serialize())
if (exported.length) {
this.blueprint.entity.ExportedNodes = btoa(exported.join(""))
result.splice(0, 0, Copy.#serializer.write(this.blueprint.entity, false))
this.blueprint.entity.ExportedNodes.value = btoa(exported.join(""))
result.splice(0, 0, this.blueprint.entity.serialize(false))
delete this.blueprint.entity.ExportedNodes
}
return result.join("")

View File

@@ -1,4 +1,3 @@
import ObjectSerializer from "../../serialization/ObjectSerializer.js"
import IInput from "../IInput.js"
/**
@@ -10,8 +9,6 @@ import IInput from "../IInput.js"
export default class Cut extends IInput {
static #serializer = new ObjectSerializer()
/** @type {(e: ClipboardEvent) => void} */
#cutHandler
@@ -39,7 +36,7 @@ export default class Cut extends IInput {
getSerializedText() {
return this.blueprint
.getNodes(true)
.map(node => Cut.#serializer.write(node.entity, false))
.map(node => node.entity.serialize())
.join("")
}

View File

@@ -1,6 +1,6 @@
import ElementFactory from "../../element/ElementFactory.js"
import ObjectEntity from "../../entity/ObjectEntity.js"
import IInput from "../IInput.js"
import ObjectSerializer from "../../serialization/ObjectSerializer.js"
/**
* @typedef {import("../IInput.js").Options & {
@@ -11,8 +11,6 @@ import ObjectSerializer from "../../serialization/ObjectSerializer.js"
export default class Paste extends IInput {
static #serializer = new ObjectSerializer()
/** @type {(e: ClipboardEvent) => void} */
#pasteHandle
@@ -42,7 +40,7 @@ export default class Paste extends IInput {
let top = 0
let left = 0
let count = 0
let nodes = Paste.#serializer.readMultiple(value).map(entity => {
let nodes = ObjectEntity.grammarMultipleObjects.parse(value).map(entity => {
let node = /** @type {NodeElementConstructor} */(ElementFactory.getConstructor("ueb-node"))
.newObject(entity)
top += node.locationY

View File

@@ -64,9 +64,12 @@ export default class KeyboardShortcut extends IInput {
this.#activationKeys = this.options.activationKeys ?? []
const wantsShift = keyEntry => keyEntry.bShift || keyEntry.Key == "LeftShift" || keyEntry.Key == "RightShift"
const wantsCtrl = keyEntry => keyEntry.bCtrl || keyEntry.Key == "LeftControl" || keyEntry.Key == "RightControl"
const wantsAlt = keyEntry => keyEntry.bAlt || keyEntry.Key == "LeftAlt" || keyEntry.Key == "RightAlt"
/** @param {KeyBindingEntity} keyEntry */
const wantsShift = keyEntry => keyEntry.bShift?.valueOf() || keyEntry.Key.valueOf() == "LeftShift" || keyEntry.Key.valueOf() == "RightShift"
/** @param {KeyBindingEntity} keyEntry */
const wantsCtrl = keyEntry => keyEntry.bCtrl?.valueOf() || keyEntry.Key.valueOf() == "LeftControl" || keyEntry.Key.valueOf() == "RightControl"
/** @param {KeyBindingEntity} keyEntry */
const wantsAlt = keyEntry => keyEntry.bAlt?.valueOf() || keyEntry.Key.valueOf() == "LeftAlt" || keyEntry.Key.valueOf() == "RightAlt"
let self = this
/** @param {KeyboardEvent} e */
@@ -94,10 +97,10 @@ export default class KeyboardShortcut extends IInput {
this.keyUpHandler = e => {
if (
self.#activationKeys.some(keyEntry =>
keyEntry.bShift && e.key == "Shift"
|| keyEntry.bCtrl && e.key == "Control"
|| keyEntry.bAlt && e.key == "Alt"
|| keyEntry.bCmd && e.key == "Meta"
keyEntry.bShift?.valueOf() && e.key == "Shift"
|| keyEntry.bCtrl?.valueOf() && e.key == "Control"
|| keyEntry.bAlt?.valueOf() && e.key == "Alt"
|| keyEntry.bCmd?.valueOf() && e.key == "Meta"
|| Configuration.Keys[keyEntry.Key.value] == e.code
)
) {

View File

@@ -43,7 +43,7 @@ export default class MouseCreateLink extends IMouseClickDrag {
this.link.setMessageReplaceOutputLink()
this.linkValid = true
} else if (
(a.entity.PinType.PinCategory != "object" || b.entity.PinType.PinCategory != "object")
(a.entity.PinType.PinCategory.valueOf() != "object" || b.entity.PinType.PinCategory.valueOf() != "object")
&& a.pinType != b.pinType
) {
this.link.setMessageTypesIncompatible(a, b)
@@ -113,6 +113,7 @@ export default class MouseCreateLink extends IMouseClickDrag {
})
this.#listenedPins = null
if (this.enteredPin && this.linkValid) {
// Knot can use wither the input or output (by default) part indifferently, check if a switch is needed
if (this.#knotPin) {
const otherPin = this.#knotPin !== this.link.source ? this.link.source : this.enteredPin
// Knot pin direction correction
@@ -125,7 +126,11 @@ export default class MouseCreateLink extends IMouseClickDrag {
}
}
} else if (this.enteredPin.nodeElement.getType() === Configuration.paths.knot) {
this.enteredPin = /** @type {KnotPinTemplate} */(this.enteredPin.template).getOppositePin()
this.#knotPin = this.enteredPin
if (this.link.source.isOutput()) {
// Knot uses by default the output pin, let's switch to keep it coherent with the source node we have
this.enteredPin = /** @type {KnotPinTemplate} */(this.enteredPin.template).getOppositePin()
}
}
if (!this.link.source.getLinks().find(ref => ref.equals(this.enteredPin.createPinReference()))) {
this.blueprint.addGraphElement(this.link)

View File

@@ -1,29 +0,0 @@
import Serializer from "./Serializer.js"
/**
* @template {AttributeConstructor<Attribute>} T
* @extends {Serializer<T>}
*/
export default class CustomSerializer extends Serializer {
#objectWriter
/**
* @param {(v: ConstructedType<T>, insideString: Boolean) => String} objectWriter
* @param {T} entityType
*/
constructor(objectWriter, entityType) {
super(entityType)
this.#objectWriter = objectWriter
}
/**
* @param {ConstructedType<T>} entity
* @param {Boolean} insideString
* @returns {String}
*/
doWrite(entity, insideString, indentation = "") {
let result = this.#objectWriter(entity, insideString)
return result
}
}

View File

@@ -1,14 +1,15 @@
import Parsernostrum from "parsernostrum"
import Configuration from "../Configuration.js"
import Utility from "../Utility.js"
import AttributeInfo from "../entity/AttributeInfo.js"
import AlternativesEntity from "../entity/AlternativesEntity.js"
import IEntity from "../entity/IEntity.js"
import MirroredEntity from "../entity/MirroredEntity.js"
import Union from "../entity/Union.js"
import Serializable from "./Serializable.js"
export default 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 + ")"
@@ -36,10 +37,11 @@ export default class Grammar {
static null = Parsernostrum.reg(/\(\s*\)/).map(() => null)
static true = Parsernostrum.reg(/true/i).map(() => true)
static false = Parsernostrum.reg(/false/i).map(() => false)
static boolean = Parsernostrum.regArray(/(true)|false/i).map(v => v[1] ? true : 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
@@ -68,173 +70,84 @@ export default class Grammar {
/* --- Factory --- */
/**
* @template T
* @param {AttributeInfo<T>} attribute
* @param {Parsernostrum<any>} defaultGrammar
* @returns {Parsernostrum<T>}
*/
static grammarFor(attribute, type = attribute?.type, defaultGrammar = this.unknownValue) {
let result = defaultGrammar
if (type === Array || type instanceof Array) {
if (attribute?.inlined) {
return this.grammarFor(undefined, type[0])
}
result = Parsernostrum.seq(
Parsernostrum.reg(/\(\s*/),
this.grammarFor(undefined, type[0]).sepBy(this.commaSeparation).opt(),
Parsernostrum.reg(/\s*(?:,\s*)?\)/),
).map(([_0, values, _3]) => values instanceof Array ? values : [])
} else if (type instanceof Union) {
result = type.values
.map(v => this.grammarFor(undefined, v))
.reduce((acc, cur) => !cur || cur === this.unknownValue || acc === this.unknownValue
? this.unknownValue
: Parsernostrum.alt(acc, cur)
)
} else if (type instanceof MirroredEntity) {
// @ts-expect-error
return this.grammarFor(undefined, type.getTargetType())
.map(v => new MirroredEntity(type.type, () => v))
} else if (attribute?.constructor === Object) {
result = this.grammarFor(undefined, type)
} else {
switch (type) {
case Boolean:
result = this.boolean
break
case null:
result = this.null
break
case Number:
result = this.number
break
case BigInt:
result = this.bigInt
break
case String:
result = this.string
break
default:
if (/** @type {AttributeConstructor<any>} */(type)?.prototype instanceof Serializable) {
result = /** @type {typeof Serializable} */(type).grammar
}
}
}
if (attribute) {
if (attribute.serialized && type.constructor !== String) {
if (result == this.unknownValue) {
result = this.string
} else {
result = Parsernostrum.seq(Parsernostrum.str('"'), result, Parsernostrum.str('"')).map(([_0, value, _2]) => value)
}
}
if (attribute.nullable) {
result = Parsernostrum.alt(result, this.null)
}
}
return result
}
/**
* @template {AttributeConstructor<Attribute>} T
* @param {T} entityType
* @param {typeof IEntity} entityType
* @param {String[]} key
* @returns {AttributeInfo}
* @returns {typeof IEntity}
*/
static getAttribute(entityType, key) {
let result
let type
if (entityType instanceof Union) {
for (let t of entityType.values) {
if (result = this.getAttribute(t, key)) {
return result
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 (entityType instanceof IEntity.constructor) {
// @ts-expect-error
result = entityType.attributes[key[0]]
type = result?.type
} else if (entityType instanceof Array) {
result = entityType[key[0]]
type = result
if (keys.length > 0) {
return this.getAttribute(attribute, keys)
}
if (key.length > 1) {
return this.getAttribute(type, key.slice(1))
}
return result
return attribute
}
/** @param {typeof IEntity} entityType */
static createAttributeGrammar(
entityType,
attributeName = this.attributeName,
attributeNameGrammar = this.attributeName,
valueSeparator = this.equalSeparation,
handleObjectSet = (obj, k, v) => { }
handleObjectSet = (values, attributeKey, attributeValue) => { },
) {
return Parsernostrum.seq(
attributeName,
attributeNameGrammar,
valueSeparator,
).chain(([attributeName, _1]) => {
const attributeKey = attributeName.split(Configuration.keysSeparator)
const attributeValue = this.getAttribute(entityType, attributeKey)
return this
.grammarFor(attributeValue)
.map(attributeValue =>
values => {
handleObjectSet(values, attributeKey, attributeValue)
Utility.objectSet(values, attributeKey, attributeValue)
}
)
const grammar = attributeValue ? attributeValue.grammar : IEntity.unknownEntityGrammar
return grammar.map(attributeValue =>
values => {
Utility.objectSet(values, attributeKey, attributeValue)
handleObjectSet(values, attributeKey, attributeValue)
}
)
})
}
/**
* @template {IEntity} T
* @param {(new (...args: any) => T) & EntityConstructor} entityType
* @param {Boolean | Number} acceptUnknownKeys Number to specify the limit or true, to let it be a reasonable value
* @template {typeof IEntity & (new (...values: any) => InstanceType<T>)} T
* @param {T} entityType
* @return {Parsernostrum<InstanceType<T>>}
*/
static createEntityGrammar(entityType, acceptUnknownKeys = true, entriesSeparator = this.commaSeparation) {
const lookbehind = entityType.attributes.lookbehind.default
static createEntityGrammar(entityType, entriesSeparator = this.commaSeparation, complete = false, minKeys = 1) {
const lookbehind = entityType.lookbehind instanceof Array ? entityType.lookbehind.join("|") : entityType.lookbehind
return Parsernostrum.seq(
Parsernostrum.reg(
lookbehind instanceof Union
? new RegExp(`(${lookbehind.values.reduce((acc, cur) => acc + "|" + cur)})\\s*\\(\\s*`)
: lookbehind.constructor == String && lookbehind.length > 0
? new RegExp(`(${lookbehind})\\s*\\(\\s*`)
: /()\(\s*/,
1
),
this.createAttributeGrammar(entityType).sepBy(entriesSeparator),
Parsernostrum.reg(/\s*(?:,\s*)?\)/), // trailing comma
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, _2]) => {
.map(([lookbehind, attributes, trailing]) => {
let values = {}
attributes.forEach(attributeSetter => attributeSetter(values))
if (lookbehind.length) {
values.lookbehind = lookbehind
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 => {
let totalKeys = Object.keys(values)
let missingKey
// Check missing values
if (
Object.keys(/** @type {AttributeDeclarations} */(entityType.attributes))
.filter(key => entityType.attributes[key].expected)
.find(key => !totalKeys.includes(key) && (missingKey = key))
) {
return Parsernostrum.failure()
if (entityType.lookbehind instanceof Array || entityType.lookbehind !== lookbehind) {
entityType = entityType.withLookbehind(lookbehind)
}
const unknownKeys = Object.keys(values).filter(key => !(key in entityType.attributes)).length
if (!acceptUnknownKeys && unknownKeys > 0) {
return Parsernostrum.failure()
}
return Parsernostrum.success().map(() => new entityType(values))
const keys = Object.keys(values)
return complete
? Parsernostrum.success()
.assert(v => Object.keys(entityType.attributes).every(k => keys.includes(k)))
.map(() => new entityType(values))
: Parsernostrum.success().map(() => new entityType(values))
})
}
/** @type {Parsernostrum<any>} */
static unknownValue // Defined in initializeSerializerFactor to avoid circular include
}

View File

@@ -1,104 +0,0 @@
import Configuration from "../Configuration.js"
import AttributeInfo from "../entity/AttributeInfo.js"
import ObjectEntity from "../entity/ObjectEntity.js"
import PinEntity from "../entity/PinEntity.js"
import Grammar from "./Grammar.js"
import Serializer from "./Serializer.js"
import SerializerFactory from "./SerializerFactory.js"
/** @extends Serializer<ObjectEntityConstructor> */
export default class ObjectSerializer extends Serializer {
constructor(entityType = ObjectEntity) {
super(entityType, undefined, "\n", true, undefined, Serializer.same)
}
showProperty(entity, key) {
switch (key) {
case "Class":
case "Name":
case "Archetype":
case "ExportPath":
case "CustomProperties":
// Serielized separately, check doWrite()
return false
}
return super.showProperty(entity, key)
}
/** @param {ObjectEntity} value */
write(value, insideString = false) {
return this.doWrite(value, insideString) + "\n"
}
/** @param {String} value */
doRead(value) {
return Grammar.grammarFor(undefined, this.entityType).parse(value)
}
/**
* @param {String} value
* @returns {ObjectEntity[]}
*/
readMultiple(value) {
return ObjectEntity.getMultipleObjectsGrammar().parse(value)
}
/**
* @param {ObjectEntity} entity
* @param {Boolean} insideString
* @returns {String}
*/
doWrite(
entity,
insideString,
indentation = "",
wrap = this.wrap,
attributeSeparator = this.attributeSeparator,
trailingSeparator = this.trailingSeparator,
attributeValueConjunctionSign = this.attributeValueConjunctionSign,
attributeKeyPrinter = this.attributeKeyPrinter,
) {
const moreIndentation = indentation + Configuration.indentation
if (!(entity instanceof ObjectEntity)) {
return super.doWrite(
entity,
insideString,
indentation,
wrap,
attributeSeparator,
trailingSeparator,
attributeValueConjunctionSign,
// @ts-expect-error
key => entity[key] instanceof ObjectEntity ? "" : attributeKeyPrinter(key)
)
}
let result = indentation + "Begin Object"
+ (entity.Class?.type || entity.Class?.path ? ` Class=${this.doWriteValue(entity.Class, insideString)}` : "")
+ (entity.Name ? ` Name=${this.doWriteValue(entity.Name, insideString)}` : "")
+ (entity.Archetype ? ` Archetype=${this.doWriteValue(entity.Archetype, insideString)}` : "")
+ (entity.ExportPath?.type || entity.ExportPath?.path ? ` ExportPath=${this.doWriteValue(entity.ExportPath, insideString)}` : "")
+ "\n"
+ super.doWrite(
entity,
insideString,
moreIndentation,
wrap,
attributeSeparator,
true,
attributeValueConjunctionSign,
key => entity[key] instanceof ObjectEntity ? "" : attributeKeyPrinter(key)
)
+ (!AttributeInfo.getAttribute(entity, "CustomProperties", "ignored")
? entity.getCustomproperties().map(pin =>
moreIndentation
+ attributeKeyPrinter("CustomProperties ")
+ SerializerFactory.getSerializer(PinEntity).doWrite(pin, insideString)
+ this.attributeSeparator
).join("")
: ""
)
+ indentation + "End Object"
return result
}
}

View File

@@ -1,11 +0,0 @@
import Parsernostrum from "parsernostrum"
export default class Serializable {
static grammar = this.createGrammar()
/** @protected */
static createGrammar() {
return /** @type {Parsernostrum<any>} */(Parsernostrum.failure())
}
}

View File

@@ -1,168 +0,0 @@
import Utility from "../Utility.js"
import AttributeInfo from "../entity/AttributeInfo.js"
import IEntity from "../entity/IEntity.js"
import Grammar from "./Grammar.js"
import SerializerFactory from "./SerializerFactory.js"
/** @template {AttributeConstructor<Attribute>} T */
export default class Serializer {
/** @type {(v: String) => String} */
static same = v => v
/** @type {(entity: Attribute, serialized: String) => String} */
static notWrapped = (entity, serialized) => serialized
/** @type {(entity: Attribute, serialized: String) => String} */
static bracketsWrapped = (entity, serialized) => `(${serialized})`
/** @param {T} entityType */
constructor(
entityType,
/** @type {(entity: ConstructedType<T>, serialized: String) => String} */
wrap = (entity, serialized) => serialized,
attributeSeparator = ",",
trailingSeparator = false,
attributeValueConjunctionSign = "=",
attributeKeyPrinter = Serializer.same
) {
this.entityType = entityType
this.wrap = wrap
this.attributeSeparator = attributeSeparator
this.trailingSeparator = trailingSeparator
this.attributeValueConjunctionSign = attributeValueConjunctionSign
this.attributeKeyPrinter = attributeKeyPrinter
}
/**
* @param {String} value
* @returns {ConstructedType<T>}
*/
read(value) {
return this.doRead(value.trim())
}
/** @param {ConstructedType<T>} value */
write(value, insideString = false) {
return this.doWrite(value, insideString)
}
/**
* @param {String} value
* @returns {ConstructedType<T>}
*/
doRead(value) {
let grammar = Grammar.grammarFor(undefined, this.entityType)
const parseResult = grammar.run(value)
if (!parseResult.status) {
throw new Error(
this.entityType
? `Error when trying to parse the entity ${this.entityType.prototype.constructor.name}`
: "Error when trying to parse null"
)
}
return parseResult.value
}
/**
* @param {ConstructedType<T>} entity
* @param {Boolean} insideString
* @returns {String}
*/
doWrite(
entity,
insideString = false,
indentation = "",
wrap = this.wrap,
attributeSeparator = this.attributeSeparator,
trailingSeparator = this.trailingSeparator,
attributeValueConjunctionSign = this.attributeValueConjunctionSign,
attributeKeyPrinter = this.attributeKeyPrinter
) {
let result = ""
const keys = entity._keys ?? Object.keys(entity)
let first = true
for (const key of keys) {
const value = entity[key]
if (value !== undefined && this.showProperty(entity, key)) {
let keyValue = entity instanceof Array ? `(${key})` : key
if (AttributeInfo.getAttribute(entity, key, "quoted")) {
keyValue = `"${keyValue}"`
}
const isSerialized = AttributeInfo.getAttribute(entity, key, "serialized")
if (first) {
first = false
} else {
result += attributeSeparator
}
if (AttributeInfo.getAttribute(entity, key, "inlined")) {
result += this.doWrite(
value,
insideString,
indentation,
Serializer.notWrapped,
attributeSeparator,
false,
attributeValueConjunctionSign,
AttributeInfo.getAttribute(entity, key, "type") instanceof Array
? k => attributeKeyPrinter(`${keyValue}${k}`)
: k => attributeKeyPrinter(`${keyValue}.${k}`)
)
continue
}
const keyPrinted = attributeKeyPrinter(keyValue)
const indentationPrinted = attributeSeparator.includes("\n") ? indentation : ""
result += (
keyPrinted.length
? (indentationPrinted + keyPrinted + this.attributeValueConjunctionSign)
: ""
)
+ (
isSerialized
? `"${this.doWriteValue(value, true, indentation)}"`
: this.doWriteValue(value, insideString, indentation)
)
}
}
if (trailingSeparator && result.length) {
// append separator at the end if asked and there was printed content
result += attributeSeparator
}
return wrap(entity, result)
}
/** @param {Boolean} insideString */
doWriteValue(value, insideString, indentation = "") {
const type = Utility.getType(value)
const serializer = SerializerFactory.getSerializer(type)
if (!serializer) {
throw new Error(
`Unknown value type "${type.name}", a serializer must be registered in the SerializerFactory class, `
+ "check initializeSerializerFactory.js"
)
}
return serializer.doWrite(value, insideString, indentation)
}
/**
* @param {IEntity} entity
* @param {String} key
*/
showProperty(entity, key) {
if (entity instanceof IEntity) {
if (AttributeInfo.getAttribute(entity, key, "ignored")) {
return false
}
if (AttributeInfo.getAttribute(entity, key, "silent")) {
let defaultValue = AttributeInfo.getAttribute(entity, key, "default")
if (defaultValue instanceof Function) {
defaultValue = defaultValue(entity)
}
if (Utility.equals(entity[key], defaultValue)) {
return false
}
}
}
return true
}
}

View File

@@ -1,22 +0,0 @@
export default class SerializerFactory {
static #serializers = new Map()
/**
* @template {AttributeConstructor<Attribute>} T
* @param {T} type
* @param {Serializer<T>} object
*/
static registerSerializer(type, object) {
SerializerFactory.#serializers.set(type, object)
}
/**
* @template {AttributeConstructor<Attribute>} T
* @param {T} type
* @returns {Serializer<T>}
*/
static getSerializer(type) {
return SerializerFactory.#serializers.get(type)
}
}

View File

@@ -1,28 +0,0 @@
import Utility from "../Utility.js"
import Serializer from "./Serializer.js"
/**
* @template {AttributeConstructor<Attribute>} T
* @extends {Serializer<T>}
*/
export default class ToStringSerializer extends Serializer {
/** @param {T} entityType */
constructor(entityType, escape = true) {
super(entityType)
if (escape) {
this.wrap = (entity, serialized) => Utility.escapeString(serialized)
}
}
/**
* @param {ConstructedType<T>} entity
* @param {Boolean} insideString
*/
doWrite(entity, insideString, indentation = "") {
return !insideString && entity.constructor === String
? `"${this.wrap(entity, entity.toString())}"` // String will have quotes if not inside a string already
: this.wrap(entity, entity.toString())
}
}

View File

@@ -1,342 +1,49 @@
import Parsernostrum from "parsernostrum"
import Utility from "../Utility.js"
import BlueprintEntity from "../entity/BlueprintEntity.js"
import ByteEntity from "../entity/ByteEntity.js"
import ColorChannelEntity from "../entity/ColorChannelEntity.js"
import EnumDisplayValueEntity from "../entity/EnumDisplayValueEntity.js"
import EnumEntity from "../entity/EnumEntity.js"
import AlternativesEntity from "../entity/AlternativesEntity.js"
import ArrayEntity from "../entity/ArrayEntity.js"
import BooleanEntity from "../entity/BooleanEntity.js"
import FormatTextEntity from "../entity/FormatTextEntity.js"
import FunctionReferenceEntity from "../entity/FunctionReferenceEntity.js"
import GuidEntity from "../entity/GuidEntity.js"
import IdentifierEntity from "../entity/IdentifierEntity.js"
import Integer64Entity from "../entity/Integer64Entity.js"
import IntegerEntity from "../entity/IntegerEntity.js"
import IEntity from "../entity/IEntity.js"
import InvariantTextEntity from "../entity/InvariantTextEntity.js"
import KeyBindingEntity from "../entity/KeyBindingEntity.js"
import LinearColorEntity from "../entity/LinearColorEntity.js"
import LocalizedTextEntity from "../entity/LocalizedTextEntity.js"
import MacroGraphReferenceEntity from "../entity/MacroGraphReferenceEntity.js"
import MirroredEntity from "../entity/MirroredEntity.js"
import ObjectEntity from "../entity/ObjectEntity.js"
import NullEntity from "../entity/NullEntity.js"
import NumberEntity from "../entity/NumberEntity.js"
import ObjectReferenceEntity from "../entity/ObjectReferenceEntity.js"
import PathSymbolEntity from "../entity/PathSymbolEntity.js"
import PinEntity from "../entity/PinEntity.js"
import PinReferenceEntity from "../entity/PinReferenceEntity.js"
import PinTypeEntity from "../entity/PinTypeEntity.js"
import RBSerializationVector2DEntity from "../entity/RBSerializationVector2DEntity.js"
import RotatorEntity from "../entity/RotatorEntity.js"
import ScriptVariableEntity from "../entity/ScriptVariableEntity.js"
import SimpleSerializationRotatorEntity from "../entity/SimpleSerializationRotatorEntity.js"
import SimpleSerializationVector2DEntity from "../entity/SimpleSerializationVector2DEntity.js"
import SimpleSerializationVector4DEntity from "../entity/SimpleSerializationVector4DEntity.js"
import SimpleSerializationVectorEntity from "../entity/SimpleSerializationVectorEntity.js"
import StringEntity from "../entity/StringEntity.js"
import SymbolEntity from "../entity/SymbolEntity.js"
import TerminalTypeEntity from "../entity/TerminalTypeEntity.js"
import Union from "../entity/Union.js"
import UnknownKeysEntity from "../entity/UnknownKeysEntity.js"
import VariableReferenceEntity from "../entity/VariableReferenceEntity.js"
import Vector2DEntity from "../entity/Vector2DEntity.js"
import Vector4DEntity from "../entity/Vector4DEntity.js"
import VectorEntity from "../entity/VectorEntity.js"
import CustomSerializer from "./CustomSerializer.js"
import Grammar from "./Grammar.js"
import ObjectSerializer from "./ObjectSerializer.js"
import Serializer from "./Serializer.js"
import SerializerFactory from "./SerializerFactory.js"
import ToStringSerializer from "./ToStringSerializer.js"
Grammar.unknownValue =
Parsernostrum.alt(
// Remember to keep the order, otherwise parsing might fail
Grammar.boolean,
GuidEntity.grammar,
Parsernostrum.str("None").map(() => new ObjectReferenceEntity({ type: "None" })),
Grammar.null,
Grammar.number,
ObjectReferenceEntity.fullReferenceGrammar,
Grammar.string,
LocalizedTextEntity.grammar,
InvariantTextEntity.grammar,
FormatTextEntity.grammar,
PinReferenceEntity.grammar,
Vector4DEntity.grammar,
VectorEntity.grammar,
RotatorEntity.grammar,
LinearColorEntity.grammar,
Vector2DEntity.grammar,
UnknownKeysEntity.grammar,
SymbolEntity.grammar,
Grammar.grammarFor(undefined, [PinReferenceEntity]),
Grammar.grammarFor(undefined, [new Union(Number, String, SymbolEntity)]),
Parsernostrum.lazy(() => Grammar.grammarFor(undefined, [undefined])),
)
export default function initializeSerializerFactory() {
SerializerFactory.registerSerializer(
null,
new CustomSerializer(
(nullValue, insideString) => "()",
null
IEntity.unknownEntityGrammar =
Parsernostrum.alt(
// Remember to keep the order, otherwise parsing might fail
BooleanEntity.grammar,
GuidEntity.grammar,
Parsernostrum.str("None").map(() => ObjectReferenceEntity.createNoneInstance()),
NullEntity.grammar,
NumberEntity.grammar,
ObjectReferenceEntity.fullReferenceGrammar,
StringEntity.grammar,
LocalizedTextEntity.grammar,
InvariantTextEntity.grammar,
FormatTextEntity.grammar,
PinReferenceEntity.grammar,
Vector4DEntity.grammar,
VectorEntity.grammar,
Vector2DEntity.grammar,
RotatorEntity.grammar,
LinearColorEntity.grammar,
UnknownKeysEntity.grammar,
SymbolEntity.grammar,
ArrayEntity.of(PinReferenceEntity).grammar,
ArrayEntity.of(AlternativesEntity.accepting(NumberEntity, StringEntity, SymbolEntity)).grammar,
Parsernostrum.lazy(() => ArrayEntity.createGrammar(IEntity.unknownEntityGrammar)),
)
)
SerializerFactory.registerSerializer(
Array,
new CustomSerializer(
(array, insideString) =>
`(${array
.map(v => SerializerFactory.getSerializer(Utility.getType(v)).write(v, insideString))
.join(",")
})`,
Array
)
)
SerializerFactory.registerSerializer(
BigInt,
new ToStringSerializer(BigInt)
)
SerializerFactory.registerSerializer(
BlueprintEntity,
new ObjectSerializer(BlueprintEntity),
)
SerializerFactory.registerSerializer(
Boolean,
new CustomSerializer(
/** @param {Boolean} boolean */
(boolean, insideString) => boolean
? insideString
? "true"
: "True"
: insideString
? "false"
: "False",
Boolean
)
)
SerializerFactory.registerSerializer(
ByteEntity,
new ToStringSerializer(ByteEntity)
)
SerializerFactory.registerSerializer(
ColorChannelEntity,
new ToStringSerializer(ColorChannelEntity)
)
SerializerFactory.registerSerializer(
EnumDisplayValueEntity,
new ToStringSerializer(EnumDisplayValueEntity)
)
SerializerFactory.registerSerializer(
EnumEntity,
new ToStringSerializer(EnumEntity)
)
SerializerFactory.registerSerializer(
FormatTextEntity,
new CustomSerializer(
(v, insideString) => {
let result = v.getLookbehind() + "("
+ v.value.map(v =>
SerializerFactory.getSerializer(Utility.getType(v)).write(v, insideString)
).join(", ")
+ ")"
return result
},
FormatTextEntity)
)
SerializerFactory.registerSerializer(
FunctionReferenceEntity,
new Serializer(FunctionReferenceEntity, Serializer.bracketsWrapped)
)
SerializerFactory.registerSerializer(
GuidEntity,
new ToStringSerializer(GuidEntity)
)
SerializerFactory.registerSerializer(
IdentifierEntity,
new ToStringSerializer(IdentifierEntity)
)
SerializerFactory.registerSerializer(
Integer64Entity,
new ToStringSerializer(Integer64Entity)
)
SerializerFactory.registerSerializer(
IntegerEntity,
new ToStringSerializer(IntegerEntity)
)
SerializerFactory.registerSerializer(
InvariantTextEntity,
new Serializer(InvariantTextEntity, (entity, v) => `${entity.getLookbehind()}(${v})`, ", ", false, "", () => "")
)
SerializerFactory.registerSerializer(
KeyBindingEntity,
new Serializer(KeyBindingEntity, Serializer.bracketsWrapped)
)
SerializerFactory.registerSerializer(
LinearColorEntity,
new Serializer(LinearColorEntity, Serializer.bracketsWrapped)
)
SerializerFactory.registerSerializer(
LocalizedTextEntity,
new Serializer(LocalizedTextEntity, (entity, v) => `${entity.getLookbehind()}(${v})`, ", ", false, "", () => "")
)
SerializerFactory.registerSerializer(
MacroGraphReferenceEntity,
new Serializer(MacroGraphReferenceEntity, Serializer.bracketsWrapped)
)
SerializerFactory.registerSerializer(
MirroredEntity,
new CustomSerializer(
(v, insideString) => SerializerFactory.getSerializer(v.getTargetType()).write(v.get(), insideString),
MirroredEntity
)
)
SerializerFactory.registerSerializer(
Number,
new ToStringSerializer(Number)
)
SerializerFactory.registerSerializer(
ObjectEntity,
new ObjectSerializer()
)
SerializerFactory.registerSerializer(
ObjectReferenceEntity,
new ToStringSerializer(ObjectReferenceEntity, false)
)
SerializerFactory.registerSerializer(
PathSymbolEntity,
new ToStringSerializer(PathSymbolEntity)
)
SerializerFactory.registerSerializer(
PinEntity,
new Serializer(PinEntity, (entity, v) => `${entity.getLookbehind()} (${v})`, ",", true)
)
SerializerFactory.registerSerializer(
PinReferenceEntity,
new Serializer(PinReferenceEntity, undefined, " ", false, "", () => "")
)
SerializerFactory.registerSerializer(
PinTypeEntity,
new Serializer(PinTypeEntity)
)
SerializerFactory.registerSerializer(
TerminalTypeEntity,
new Serializer(TerminalTypeEntity, Serializer.bracketsWrapped)
)
SerializerFactory.registerSerializer(
RBSerializationVector2DEntity,
new CustomSerializer(
(value, insideString) => `X=${value.X} Y=${value.Y}`,
RBSerializationVector2DEntity
)
)
SerializerFactory.registerSerializer(
RotatorEntity,
new Serializer(RotatorEntity, Serializer.bracketsWrapped)
)
SerializerFactory.registerSerializer(
ScriptVariableEntity,
new Serializer(ScriptVariableEntity, Serializer.bracketsWrapped)
)
SerializerFactory.registerSerializer(
String,
new CustomSerializer(
(value, insideString) => insideString
? Utility.escapeString(value)
: `"${Utility.escapeString(value)}"`,
String
)
)
SerializerFactory.registerSerializer(
SimpleSerializationRotatorEntity,
new CustomSerializer(
(value, insideString) => `${value.P}, ${value.Y}, ${value.R}`,
SimpleSerializationRotatorEntity
)
)
SerializerFactory.registerSerializer(
SimpleSerializationVector2DEntity,
new CustomSerializer(
(value, insideString) => `${value.X}, ${value.Y}`,
SimpleSerializationVector2DEntity
)
)
SerializerFactory.registerSerializer(
SimpleSerializationVectorEntity,
new CustomSerializer(
(value, insideString) => `${value.X}, ${value.Y}, ${value.Z}`,
SimpleSerializationVectorEntity
)
)
SerializerFactory.registerSerializer(
SimpleSerializationVector4DEntity,
new CustomSerializer(
(value, insideString) => `${value.X}, ${value.Y}, ${value.Z}, ${value.W}`,
SimpleSerializationVector4DEntity
)
)
SerializerFactory.registerSerializer(
SymbolEntity,
new ToStringSerializer(SymbolEntity)
)
SerializerFactory.registerSerializer(
UnknownKeysEntity,
new Serializer(UnknownKeysEntity, (entity, string) => `${entity.getLookbehind() ?? ""}(${string})`)
)
SerializerFactory.registerSerializer(
VariableReferenceEntity,
new Serializer(VariableReferenceEntity, Serializer.bracketsWrapped)
)
SerializerFactory.registerSerializer(
Vector2DEntity,
new Serializer(Vector2DEntity, Serializer.bracketsWrapped)
)
SerializerFactory.registerSerializer(
VectorEntity,
new Serializer(VectorEntity, Serializer.bracketsWrapped)
)
SerializerFactory.registerSerializer(
Vector4DEntity,
new Serializer(Vector4DEntity, Serializer.bracketsWrapped)
)
}

View File

@@ -1,17 +1,23 @@
import { html, nothing } from "lit"
import Configuration from "../Configuration.js"
import Shortcuts from "../Shortcuts.js"
import Utility from "../Utility.js"
import ElementFactory from "../element/ElementFactory.js"
import LinearColorEntity from "../entity/LinearColorEntity.js"
import KnotEntity from "../entity/objects/KnotEntity.js"
import KeyboardShortcut from "../input/keyboard/KeyboardShortcut.js"
import MouseClick from "../input/mouse/MouseClick.js"
import MouseDbClick from "../input/mouse/MouseDbClick.js"
import Shortcuts from "../Shortcuts.js"
import Utility from "../Utility.js"
import IFromToPositionedTemplate from "./IFromToPositionedTemplate.js"
/** @extends {IFromToPositionedTemplate<LinkElement>} */
export default class LinkTemplate extends IFromToPositionedTemplate {
/** @param {Number} x */
static sigmoidPositive(x, curvature = 3.7, length = 1.1) {
return 1 - Math.exp(-((x / length) ** curvature))
}
/**
* Returns a function providing the inverse multiplication y = a / x + q. The value of a and q are calculated using
* the derivative of that function y' = -a / x^2 at the point p (x = p[0] and y = p[1]). This means
@@ -157,7 +163,7 @@ export default class LinkTemplate extends IFromToPositionedTemplate {
const aspectRatio = dy / Math.max(30, dx)
const c2 =
LinkTemplate.c2Clamped(dx)
* Utility.sigmoidPositive(fillRatio * 1.2 + aspectRatio * 0.5, 1.5, 1.8)
* LinkTemplate.sigmoidPositive(fillRatio * 1.2 + aspectRatio * 0.5, 1.5, 1.8)
+ this.element.startPercentage
this.element.svgPathD = Configuration.linkRightSVGPath(this.element.startPercentage, c1, c2)
}
@@ -168,9 +174,9 @@ export default class LinkTemplate extends IFromToPositionedTemplate {
if (changedProperties.has("originatesFromInput")) {
this.element.style.setProperty("--ueb-from-input", this.element.originatesFromInput ? "1" : "0")
}
const referencePin = this.element.source ?? this.element.destination
const referencePin = this.element.getOutputPin(true)
if (referencePin) {
this.element.style.setProperty("--ueb-link-color-rgb", Utility.printLinearColor(referencePin.color))
this.element.style.setProperty("--ueb-link-color-rgb", LinearColorEntity.printLinearColor(referencePin.color))
}
this.element.style.setProperty("--ueb-y-reflected", `${this.element.fromY > this.element.toY ? 1 : 0}`)
this.element.style.setProperty("--ueb-start-percentage", `${Math.round(this.element.startPercentage)}%`)

View File

@@ -26,7 +26,7 @@ export default class CommentNodeTemplate extends IResizeableTemplate {
<div class="ueb-node-border">
<div class="ueb-node-wrapper">
<div class="ueb-node-top"
.innerText="${Utility.encodeHTMLWhitespace(this.element.entity.NodeComment)}">
.innerText="${Utility.encodeHTMLWhitespace(this.element.entity.NodeComment?.toString())}">
</div>
</div>
</div>

View File

@@ -40,7 +40,7 @@ export default class EventNodeTemplate extends NodeTemplate {
createDelegatePinElement() {
const pin = /** @type {PinElementConstructor} */(ElementFactory.getConstructor("ueb-pin")).newObject(
this.element.getPinEntities().find(v => !v.isHidden() && v.PinType.PinCategory === "delegate"),
this.element.getPinEntities().find(v => !v.isHidden() && v.PinType.PinCategory?.toString() === "delegate"),
new MinimalPinTemplate(),
this.element
)
@@ -50,7 +50,7 @@ export default class EventNodeTemplate extends NodeTemplate {
createPinElements() {
return this.element.getPinEntities()
.filter(v => !v.isHidden() && v.PinType.PinCategory !== "delegate")
.filter(v => !v.isHidden() && v.PinType.PinCategory?.toString() !== "delegate")
.map(pinEntity => /** @type {PinElementConstructor} */(ElementFactory.getConstructor("ueb-pin"))
.newObject(pinEntity, undefined, this.element)
)

View File

@@ -11,7 +11,7 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
#hasSubtitle = false
/** @type {() => PinEntity} */
/** @type {() => PinEntity<IEntity>} */
pinInserter
/** @type {HTMLElement} */
@@ -41,7 +41,7 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
this.element.updateComplete.then(() => this.element.acknowledgeReflow())
}
/** @param {PinEntity} pinEntity */
/** @param {PinEntity<IEntity>} pinEntity */
createPinElement(pinEntity) {
const pinElement = /** @type {PinElementConstructor} */(ElementFactory.getConstructor("ueb-pin"))
.newObject(pinEntity, undefined, this.element)
@@ -165,7 +165,7 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
.filter(v => !v.isHidden())
.map(pinEntity => {
this.#hasSubtitle = this.#hasSubtitle
|| pinEntity.PinName === "self" && pinEntity.pinTitle() === "Target"
|| pinEntity.PinName.toString() === "self" && pinEntity.pinTitle() === "Target"
return this.createPinElement(pinEntity)
})
}

View File

@@ -1,14 +1,19 @@
import { html } from "lit"
import BooleanEntity from "../../entity/BooleanEntity.js"
import MouseIgnore from "../../input/mouse/MouseIgnore.js"
import PinTemplate from "./PinTemplate.js"
/** @extends PinTemplate<Boolean> */
/** @extends PinTemplate<BooleanEntity> */
export default class BoolPinTemplate extends PinTemplate {
/** @type {HTMLInputElement?} */
#input
#onChangeHandler = () => this.element.setDefaultValue(this.#input.checked)
#onChangeHandler = () => {
const entity = this.element.getDefaultValue()
entity.value = this.#input.checked
this.element.setDefaultValue(entity)
}
/** @param {PropertyValues} changedProperties */
firstUpdated(changedProperties) {
@@ -35,7 +40,7 @@ export default class BoolPinTemplate extends PinTemplate {
renderInput() {
return html`
<input type="checkbox" class="ueb-pin-input-wrapper ueb-pin-input" ?checked="${this.element.defaultValue === true}" />
<input type="checkbox" class="ueb-pin-input-wrapper ueb-pin-input" ?checked="${this.element.defaultValue?.valueOf() === true}" />
`
}
}

View File

@@ -1,5 +1,6 @@
import { html } from "lit"
import Configuration from "../../Configuration.js"
import StringEntity from "../../entity/StringEntity.js"
import Utility from "../../Utility.js"
import IInputPinTemplate from "./IInputPinTemplate.js"
@@ -15,11 +16,11 @@ export default class EnumPinTemplate extends IInputPinTemplate {
setup() {
super.setup()
const enumEntries = this.element.nodeElement.entity.EnumEntries
const enumEntries = this.element.nodeElement.entity.EnumEntries?.valueOf()
this.#dropdownEntries =
enumEntries?.map(k => {
if (k === "") {
k = "None"
if (k.valueOf() === "") {
k = new StringEntity("None")
}
return [
k,
@@ -42,12 +43,11 @@ export default class EnumPinTemplate extends IInputPinTemplate {
}
renderInput() {
const entity = this.element.nodeElement.entity
return html`
<ueb-dropdown
class="ueb-pin-input-wrapper ueb-pin-input"
.options="${this.#dropdownEntries}"
.selectedOption="${this.element.defaultValue.value}"
.selectedOption="${this.element.defaultValue}"
>
</ueb-dropdown>
`
@@ -62,4 +62,16 @@ export default class EnumPinTemplate extends IInputPinTemplate {
getInputs() {
return [this.#dropdownElement.getValue()]
}
/**
* @this {EnumPinTemplate}
* @param {String[]} values
* @param {String[]} rawValues
*/
setDefaultValue(values = [], rawValues) {
const value = this.element.getDefaultValue()
value.value = values[0]
this.element.setDefaultValue(value)
this.element.requestUpdate()
}
}

View File

@@ -9,7 +9,7 @@ export default class ExecPinTemplate extends PinTemplate {
}
renderName() {
let pinName = this.element.entity.PinName
let pinName = this.element.entity.PinName?.toString()
if (this.element.entity.PinFriendlyName) {
pinName = this.element.entity.PinFriendlyName.toString()
} else if (pinName === "execute" || pinName === "then") {

View File

@@ -4,7 +4,7 @@ import Utility from "../../Utility.js"
import PinTemplate from "./PinTemplate.js"
/**
* @template {TerminalAttribute} T
* @template {IEntity} T
* @extends PinTemplate<T>
*/
export default class IInputPinTemplate extends PinTemplate {
@@ -41,6 +41,10 @@ export default class IInputPinTemplate extends PinTemplate {
/** @param {HTMLElement} inputElement*/
#updateWrapClass(inputElement) {
if (this.element.querySelector(".ueb-pin-name")?.getBoundingClientRect().width < 20) {
// Do not wrap if the pin name is just a letter (like A, B, V, ...)
return
}
const width = this.blueprint.scaleCorrect(this.#inputWrapper.getBoundingClientRect().width) + this.nameWidth
const inputWrapped = this.element.classList.contains("ueb-pin-input-wrap")
if (!inputWrapped && width > Configuration.pinInputWrapWidth) {

View File

@@ -1,14 +1,17 @@
import IInputPinTemplate from "./IInputPinTemplate.js"
/**
* @template {TerminalAttribute} T
* @template {IEntity} T
* @extends IInputPinTemplate<T>
*/
export default class INumericPinTemplate extends IInputPinTemplate {
static singleLineInput = true
/** @param {String[]} values */
/**
* @this {INumericPinTemplate<NumberEntity>}
* @param {String[]} values
*/
setInputs(values = [], updateDefaultValue = false) {
if (!values || values.length == 0) {
values = [this.getInput()]
@@ -29,11 +32,14 @@ export default class INumericPinTemplate extends IInputPinTemplate {
}
/**
* @this {INumericPinTemplate<NumberEntity>}
* @param {Number[]} values
* @param {String[]} rawValues
*/
setDefaultValue(values = [], rawValues) {
this.element.setDefaultValue(/** @type {T} */(values[0]))
const value = this.element.getDefaultValue()
value.value = values[0]
this.element.setDefaultValue(value)
this.element.requestUpdate()
}
}

View File

@@ -10,7 +10,9 @@ export default class Int64PinTemplate extends INumericPinTemplate {
* @param {String[]} rawValues
*/
setDefaultValue(values = [], rawValues) {
this.element.setDefaultValue(new Integer64Entity(values[0]))
const value = this.element.getDefaultValue()
value.value = BigInt(values[0])
this.element.setDefaultValue(value)
this.element.requestUpdate()
}

View File

@@ -5,15 +5,6 @@ import IntegerEntity from "../../entity/IntegerEntity.js"
/** @extends INumericPinTemplate<IntegerEntity> */
export default class IntPinTemplate extends INumericPinTemplate {
/**
* @param {Number[]} values
* @param {String[]} rawValues
*/
setDefaultValue(values = [], rawValues) {
this.element.setDefaultValue(new IntegerEntity(values[0]))
this.element.requestUpdate()
}
renderInput() {
return html`
<div class="ueb-pin-input-wrapper ueb-pin-input">

View File

@@ -2,7 +2,7 @@ import { html } from "lit"
import PinTemplate from "./PinTemplate.js"
/**
* @template {TerminalAttribute} T
* @template {IEntity} T
* @extends PinTemplate<T>
*/
export default class MinimalPinTemplate extends PinTemplate {

View File

@@ -9,12 +9,12 @@ import VariableConversionNodeTemplate from "../node/VariableConversionNodeTempla
import VariableOperationNodeTemplate from "../node/VariableOperationNodeTemplate.js"
/**
* @template {TerminalAttribute} T
* @template {IEntity} T
* @typedef {import("../../element/PinElement.js").default<T>} PinElement
*/
/**
* @template {TerminalAttribute} T
* @template {IEntity} T
* @extends ITemplate<PinElement<T>>
*/
export default class PinTemplate extends ITemplate {
@@ -110,12 +110,12 @@ export default class PinTemplate extends ITemplate {
return SVGIcon.pcgStackPin
}
}
switch (this.element.entity.PinType?.ContainerType?.toString()) {
switch (this.element.entity.PinType?.ContainerType?.serialize()) {
case "Array": return SVGIcon.arrayPin
case "Set": return SVGIcon.setPin
case "Map": return SVGIcon.mapPin
}
if (this.element.entity.PinType?.PinCategory?.toLocaleLowerCase() === "delegate") {
if (this.element.entity.PinType?.PinCategory?.toString().toLocaleLowerCase() === "delegate") {
return SVGIcon.delegate
}
if (this.element.nodeElement?.template instanceof VariableOperationNodeTemplate) {
@@ -141,8 +141,8 @@ export default class PinTemplate extends ITemplate {
isInputRendered() {
return this.element.isInput()
&& !this.element.entity.bDefaultValueIsIgnored
&& !this.element.entity.PinType.bIsReference
&& !this.element.entity.bDefaultValueIsIgnored?.valueOf()
&& !this.element.entity.PinType.bIsReference?.valueOf()
}
renderInput() {
@@ -170,6 +170,7 @@ export default class PinTemplate extends ITemplate {
getLinkLocation() {
const rect = this.iconElement.getBoundingClientRect()
/** @type {[Number, Number]} */
const boundingLocation = [this.element.isInput() ? rect.left : rect.right + 1, (rect.top + rect.bottom) / 2]
const location = Utility.convertLocation(boundingLocation, this.blueprint.template.gridElement)
return this.blueprint.compensateTranslation(location[0], location[1])

View File

@@ -1,22 +1,18 @@
import { html } from "lit"
import Utility from "../../Utility.js"
import NumberEntity from "../../entity/NumberEntity.js"
import INumericPinTemplate from "./INumericPinTemplate.js"
/**
* @template {Number} T
* @template {NumberEntity} T
* @extends INumericPinTemplate<T>
*/
export default class RealPinTemplate extends INumericPinTemplate {
setDefaultValue(values = [], rawValues = values) {
this.element.setDefaultValue(values[0])
}
renderInput() {
return html`
<div class="ueb-pin-input-wrapper ueb-pin-input">
<ueb-input .singleLine="${true}"
.innerText="${Utility.printNumber(this.element.getDefaultValue() ?? 0)}">
.innerText="${NumberEntity.printNumber(this.element.getDefaultValue()?.valueOf() ?? 0)}">
</ueb-input>
</div>
`

View File

@@ -1,5 +1,5 @@
import { html } from "lit"
import Utility from "../../Utility.js"
import NumberEntity from "../../entity/NumberEntity.js"
import RotatorEntity from "../../entity/RotatorEntity.js"
import INumericPinTemplate from "./INumericPinTemplate.js"
@@ -7,15 +7,15 @@ import INumericPinTemplate from "./INumericPinTemplate.js"
export default class RotatorPinTemplate extends INumericPinTemplate {
#getR() {
return Utility.printNumber(this.element.getDefaultValue()?.R ?? 0)
return NumberEntity.printNumber(this.element.getDefaultValue()?.R.valueOf() ?? 0)
}
#getP() {
return Utility.printNumber(this.element.getDefaultValue()?.P ?? 0)
return NumberEntity.printNumber(this.element.getDefaultValue()?.P.valueOf() ?? 0)
}
#getY() {
return Utility.printNumber(this.element.getDefaultValue()?.Y ?? 0)
return NumberEntity.printNumber(this.element.getDefaultValue()?.Y.valueOf() ?? 0)
}
setDefaultValue(values = [], rawValues = values) {
@@ -23,9 +23,9 @@ export default class RotatorPinTemplate extends INumericPinTemplate {
if (!(rotator instanceof RotatorEntity)) {
throw new TypeError("Expected DefaultValue to be a RotatorEntity")
}
rotator.R = values[0] // Roll
rotator.P = values[1] // Pitch
rotator.Y = values[2] // Yaw
rotator.R.value = values[0] // Roll
rotator.P.value = values[1] // Pitch
rotator.Y.value = values[2] // Yaw
this.element.requestUpdate("DefaultValue", rotator)
}

Some files were not shown because too many files have changed in this diff Show More