mirror of
https://github.com/barsdeveloper/ueblueprint.git
synced 2026-02-03 23:55:04 +08:00
Various improvements (#13)
* Union lookbehind * Fix quoted inline array
This commit is contained in:
@@ -7,10 +7,15 @@ import Entity3 from "../fixtures/Entity3.js"
|
||||
import entity3Value from "../fixtures/serializedEntity3.js"
|
||||
import Entity4 from "../fixtures/Entity4.js"
|
||||
import entity4Value from "../fixtures/serializedEntity4.js"
|
||||
import Entity5 from "../fixtures/Entity5.js"
|
||||
import entity5Value1 from "../fixtures/serializedEntity5-1.js"
|
||||
import EntityF from "../fixtures/EntityF.js"
|
||||
import Grammar from "../../js/serialization/Grammar.js"
|
||||
import initializeSerializerFactory from "../../js/serialization/initializeSerializerFactory.js"
|
||||
import ObjectSerializer from "../../js/serialization/ObjectSerializer.js"
|
||||
import Serializer from "../../js/serialization/Serializer.js"
|
||||
import SerializerFactory from "../../js/serialization/SerializerFactory.js"
|
||||
import UnknownKeysEntity from "../../js/entity/UnknownKeysEntity.js"
|
||||
|
||||
describe("Entity initialization", () => {
|
||||
before(() => {
|
||||
@@ -298,4 +303,32 @@ describe("Entity initialization", () => {
|
||||
expect(SerializerFactory.getSerializer(Entity4).write(entity)).to.equal(entity4Value)
|
||||
)
|
||||
})
|
||||
|
||||
context("Entity5", () => {
|
||||
let entity = new Entity5()
|
||||
before(() => {
|
||||
initializeSerializerFactory()
|
||||
SerializerFactory.registerSerializer(
|
||||
Entity5,
|
||||
new ObjectSerializer()
|
||||
)
|
||||
SerializerFactory.registerSerializer(
|
||||
EntityF,
|
||||
new Serializer(UnknownKeysEntity, (entity, string) => `${entity.lookbehind ?? ""}(${string})`)
|
||||
)
|
||||
})
|
||||
it("can serialize/deserialize", () => {
|
||||
expect(entity = SerializerFactory.getSerializer(Entity5).read(entity5Value1)).to.deep.equal({
|
||||
Name: "",
|
||||
key1: "Value 1",
|
||||
key2: {
|
||||
lookbehind: "Foo",
|
||||
arg1: 55,
|
||||
arg2: "Argument 2",
|
||||
},
|
||||
})
|
||||
expect(entity.key2).to.be.instanceof(UnknownKeysEntity)
|
||||
expect(SerializerFactory.getSerializer(Entity5).write(entity)).to.equal(entity5Value1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -40,6 +40,20 @@ describe("Serializer", () => {
|
||||
serializer.read(`LOCGEN_FORMAT_NAMED(NSLOCTEXT("KismetSchema", "SplitPinFriendlyNameFormat", "{PinDisplayName} {ProtoPinDisplayName}"), "PinDisplayName", "Out Hit", "ProtoPinDisplayName", "Hit Bone Name")`)
|
||||
.toString()
|
||||
).to.be.equal("Out Hit Hit Bone Name"))
|
||||
it("Test 3", () => expect(
|
||||
serializer.read(String.raw`LOCGEN_FORMAT_ORDERED(
|
||||
NSLOCTEXT(
|
||||
"PCGSettings",
|
||||
"OverridableParamPinTooltip",
|
||||
"{0}Attribute type is \"{1}\" and its exact name is \"{2}\""
|
||||
),
|
||||
"If InRangeMin = InRangeMax, then that density value is mapped to the average of OutRangeMin and OutRangeMax\n",
|
||||
"float",
|
||||
"InRangeMin"
|
||||
)`)
|
||||
.toString()
|
||||
|
||||
).to.be.equal(`If InRangeMin = InRangeMax, then that density value is mapped to the average of OutRangeMin and OutRangeMax\nAttribute type is "float" and its exact name is "InRangeMin"`))
|
||||
})
|
||||
|
||||
context("GuidEntity", () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Entity1 from "./Entity1.js"
|
||||
import Entity2 from "./Entity2.js"
|
||||
import IEntity from "../../js/entity/IEntity.js"
|
||||
import UnionType from "../../js/entity/UnionType.js"
|
||||
import Union from "../../js/entity/Union.js"
|
||||
|
||||
export default class Entity3 extends IEntity {
|
||||
|
||||
@@ -50,11 +50,11 @@ export default class Entity3 extends IEntity {
|
||||
type: String,
|
||||
},
|
||||
mike: {
|
||||
type: new UnionType(Number, String, Array),
|
||||
type: new Union(Number, String, Array),
|
||||
default: "Bar",
|
||||
},
|
||||
november: {
|
||||
type: new UnionType(Number, String, Array),
|
||||
type: new Union(Number, String, Array),
|
||||
default: 0,
|
||||
},
|
||||
oscar: {
|
||||
|
||||
18
cypress/fixtures/Entity5.js
Normal file
18
cypress/fixtures/Entity5.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import EntityF from "./EntityF.js"
|
||||
import IEntity from "../../js/entity/IEntity.js"
|
||||
|
||||
export default class Entity5 extends IEntity {
|
||||
|
||||
static attributes = {
|
||||
key1: {
|
||||
type: String,
|
||||
},
|
||||
key2: {
|
||||
type: EntityF,
|
||||
},
|
||||
}
|
||||
|
||||
static {
|
||||
this.cleanupAttributes(this.attributes)
|
||||
}
|
||||
}
|
||||
19
cypress/fixtures/EntityF.js
Normal file
19
cypress/fixtures/EntityF.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import IEntity from "../../js/entity/IEntity.js"
|
||||
import Union from "../../js/entity/Union.js"
|
||||
|
||||
export default class EntityF extends IEntity {
|
||||
|
||||
static lookbehind = new Union("Foo", "Bar")
|
||||
static attributes = {
|
||||
arg1: {
|
||||
type: Number,
|
||||
},
|
||||
arg2: {
|
||||
type: String,
|
||||
},
|
||||
}
|
||||
|
||||
constructor(values = {}) {
|
||||
super(values)
|
||||
}
|
||||
}
|
||||
5
cypress/fixtures/serializedEntity5-1.js
Normal file
5
cypress/fixtures/serializedEntity5-1.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export default `Begin Object
|
||||
key1="Value 1"
|
||||
key2=Foo(arg1=55,arg2="Argument 2")
|
||||
End Object
|
||||
`
|
||||
166
dist/ueblueprint.js
vendored
166
dist/ueblueprint.js
vendored
@@ -582,22 +582,21 @@ class SerializerFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {import("./IEntity.js").AnyValueConstructor<*>} AnyValueConstructor */
|
||||
/**
|
||||
* @template {any[]} U
|
||||
* @template {[...U]} T
|
||||
*/
|
||||
class Union {
|
||||
|
||||
class UnionType {
|
||||
|
||||
#types
|
||||
get types() {
|
||||
return this.#types
|
||||
/** @type {T} */
|
||||
#values
|
||||
get values() {
|
||||
return this.#values
|
||||
}
|
||||
|
||||
/** @param {...AnyValueConstructor} types */
|
||||
constructor(...types) {
|
||||
this.#types = types;
|
||||
}
|
||||
|
||||
getFirstType() {
|
||||
return this.#types[0]
|
||||
/** @param {T} values */
|
||||
constructor(...values) {
|
||||
this.#values = values;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -861,10 +860,10 @@ class Utility {
|
||||
if (targetType instanceof ComputedType) {
|
||||
return value // The type is computed, can't say anything about it
|
||||
}
|
||||
if (targetType instanceof UnionType) {
|
||||
let type = targetType.types.find(t => Utility.isValueOfType(value, t, false));
|
||||
if (targetType instanceof Union) {
|
||||
let type = targetType.values.find(t => Utility.isValueOfType(value, t, false));
|
||||
if (!type) {
|
||||
type = targetType.getFirstType();
|
||||
type = targetType.values[0];
|
||||
}
|
||||
targetType = type;
|
||||
}
|
||||
@@ -1102,7 +1101,7 @@ class Utility {
|
||||
* @typedef {IEntity | MirroredEntity | String | Number | BigInt | Boolean} AnySimpleValue
|
||||
* @typedef {AnySimpleValue | AnySimpleValue[]} AnyValue
|
||||
* @typedef {(entity: IEntity) => AnyValue} ValueSupplier
|
||||
* @typedef {AnyValueConstructor<AnyValue> | AnyValueConstructor<AnyValue>[] | UnionType | UnionType[] | ComputedType | MirroredEntity} AttributeType
|
||||
* @typedef {AnyValueConstructor<AnyValue> | AnyValueConstructor<AnyValue>[] | Union | Union[] | ComputedType | MirroredEntity} AttributeType
|
||||
* @typedef {{
|
||||
* type?: AttributeType,
|
||||
* default?: AnyValue | ValueSupplier,
|
||||
@@ -1270,8 +1269,8 @@ class IEntity {
|
||||
return ""
|
||||
} else if (attributeType === Array || attributeType instanceof Array) {
|
||||
return () => []
|
||||
} else if (attributeType instanceof UnionType) {
|
||||
return this.defaultValueProviderFromType(attributeType.getFirstType())
|
||||
} else if (attributeType instanceof Union) {
|
||||
return this.defaultValueProviderFromType(attributeType.values[0])
|
||||
} else if (attributeType instanceof MirroredEntity) {
|
||||
return () => new MirroredEntity(attributeType.type, attributeType.key, attributeType.getter)
|
||||
} else if (attributeType instanceof ComputedType) {
|
||||
@@ -1515,10 +1514,10 @@ class LocalizedTextEntity extends IEntity {
|
||||
|
||||
class FormatTextEntity extends IEntity {
|
||||
|
||||
static lookbehind = "LOCGEN_FORMAT_NAMED"
|
||||
static lookbehind = new Union("LOCGEN_FORMAT_NAMED", "LOCGEN_FORMAT_ORDERED")
|
||||
static attributes = {
|
||||
value: {
|
||||
type: [new UnionType(String, LocalizedTextEntity, InvariantTextEntity, FormatTextEntity)],
|
||||
type: [new Union(String, LocalizedTextEntity, InvariantTextEntity, FormatTextEntity)],
|
||||
default: [],
|
||||
},
|
||||
}
|
||||
@@ -1538,12 +1537,21 @@ class FormatTextEntity extends IEntity {
|
||||
return ""
|
||||
}
|
||||
const values = this.value.slice(1).map(v => v.toString());
|
||||
return pattern.replaceAll(/\{([a-zA-Z]\w*)\}/g, (substring, arg) => {
|
||||
const argLocation = values.indexOf(arg) + 1;
|
||||
return argLocation > 0 && argLocation < values.length
|
||||
? values[argLocation]
|
||||
: substring
|
||||
})
|
||||
return this.lookbehind == "LOCGEN_FORMAT_NAMED"
|
||||
? pattern.replaceAll(/\{([a-zA-Z]\w*)\}/g, (substring, arg) => {
|
||||
const argLocation = values.indexOf(arg) + 1;
|
||||
return argLocation > 0 && argLocation < values.length
|
||||
? values[argLocation]
|
||||
: substring
|
||||
})
|
||||
: this.lookbehind == "LOCGEN_FORMAT_ORDERED"
|
||||
? pattern.replaceAll(/\{(\d+)\}/g, (substring, arg) => {
|
||||
const argValue = Number(arg);
|
||||
return argValue < values.length
|
||||
? values[argValue]
|
||||
: substring
|
||||
})
|
||||
: ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2320,7 +2328,7 @@ class PinEntity extends IEntity {
|
||||
default: "",
|
||||
},
|
||||
PinFriendlyName: {
|
||||
type: new UnionType(LocalizedTextEntity, FormatTextEntity, String),
|
||||
type: new Union(LocalizedTextEntity, FormatTextEntity, String),
|
||||
},
|
||||
PinToolTip: {
|
||||
type: String,
|
||||
@@ -2984,9 +2992,12 @@ class ObjectEntity extends IEntity {
|
||||
ObjectRef: {
|
||||
type: ObjectReferenceEntity,
|
||||
},
|
||||
PinTags: {
|
||||
type: [null],
|
||||
inlined: true,
|
||||
},
|
||||
PinNames: {
|
||||
type: [String],
|
||||
default: undefined, // To keep the order, may be defined in additionalPinInserter()
|
||||
inlined: true,
|
||||
},
|
||||
AxisKey: {
|
||||
@@ -2997,7 +3008,6 @@ class ObjectEntity extends IEntity {
|
||||
},
|
||||
NumAdditionalInputs: {
|
||||
type: Number,
|
||||
default: undefined, // To keep the order, may be defined in additionalPinInserter()
|
||||
},
|
||||
bIsPureFunc: {
|
||||
type: Boolean,
|
||||
@@ -3175,7 +3185,7 @@ class ObjectEntity extends IEntity {
|
||||
type: String,
|
||||
},
|
||||
CustomProperties: {
|
||||
type: [new UnionType(PinEntity, UnknownPinEntity)],
|
||||
type: [new Union(PinEntity, UnknownPinEntity)],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3256,6 +3266,7 @@ class ObjectEntity extends IEntity {
|
||||
/** @type {String} */ this.Name;
|
||||
/** @type {ObjectReferenceEntity?} */ this.ExportPath;
|
||||
/** @type {ObjectReferenceEntity?} */ this.ObjectRef;
|
||||
/** @type {null[]} */ this.PinTags;
|
||||
/** @type {String[]} */ this.PinNames;
|
||||
/** @type {SymbolEntity?} */ this.AxisKey;
|
||||
/** @type {SymbolEntity?} */ this.InputAxisKey;
|
||||
@@ -3944,13 +3955,23 @@ class ObjectEntity extends IEntity {
|
||||
case Configuration.paths.multiGate:
|
||||
pinEntities ??= () => this.getPinEntities().filter(pinEntity => pinEntity.isOutput());
|
||||
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.match(/^\s*Out[_\s]+(\d+)\s*$/i)?.[1]);
|
||||
pinNameFromIndex ??= (index, min = -1, max = -1) => `Out ${index >= 0 ? index : min > 0 ? "Out 0" : max + 1}`;
|
||||
pinNameFromIndex ??= (index, min = -1, max = -1) =>
|
||||
`Out ${index >= 0 ? index : min > 0 ? "Out 0" : max + 1}`;
|
||||
break
|
||||
case Configuration.paths.switchInteger:
|
||||
pinEntities ??= () => this.getPinEntities().filter(pinEntity => pinEntity.isOutput());
|
||||
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.match(/^\s*(\d+)\s*$/)?.[1]);
|
||||
pinNameFromIndex ??= (index, min = -1, max = -1) => (index < 0 ? max + 1 : index).toString();
|
||||
break
|
||||
case Configuration.paths.switchGameplayTag:
|
||||
pinNameFromIndex ??= (index, min = -1, max = -1) => {
|
||||
const result = `Case_${index >= 0 ? index : min > 0 ? "0" : max + 1}`;
|
||||
this.PinNames ??= [];
|
||||
this.PinNames.push(result);
|
||||
delete this.PinTags[this.PinTags.length - 1];
|
||||
this.PinTags[this.PinTags.length] = null;
|
||||
return result
|
||||
};
|
||||
case Configuration.paths.switchName:
|
||||
case Configuration.paths.switchString:
|
||||
pinEntities ??= () => this.getPinEntities().filter(pinEntity => pinEntity.isOutput());
|
||||
@@ -4176,6 +4197,7 @@ class Grammar {
|
||||
static symbol = P.regex(Grammar.Regex.Symbol)
|
||||
static symbolQuoted = Grammar.regexMap(
|
||||
new RegExp('"(' + Grammar.Regex.Symbol.source + ')"'),
|
||||
/** @type {(_0: String, v: String) => String} */
|
||||
([_0, v]) => v
|
||||
)
|
||||
static attributeName = P.regex(Grammar.Regex.DotSeparatedSymbols)
|
||||
@@ -4229,8 +4251,8 @@ class Grammar {
|
||||
this.grammarFor(undefined, type[0]).sepBy(this.commaSeparation),
|
||||
P.regex(/\s*(?:,\s*)?\)/),
|
||||
).map(([_0, values, _3]) => values);
|
||||
} else if (type instanceof UnionType) {
|
||||
result = type.types
|
||||
} 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
|
||||
@@ -4371,8 +4393,8 @@ class Grammar {
|
||||
static getAttribute(entityType, key) {
|
||||
let result;
|
||||
let type;
|
||||
if (entityType instanceof UnionType) {
|
||||
for (let t of entityType.types) {
|
||||
if (entityType instanceof Union) {
|
||||
for (let t of entityType.values) {
|
||||
if (result = this.getAttribute(t, key)) {
|
||||
return result
|
||||
}
|
||||
@@ -4421,15 +4443,23 @@ class Grammar {
|
||||
*/
|
||||
static createEntityGrammar = (entityType, acceptUnknownKeys = true) =>
|
||||
P.seq(
|
||||
entityType.lookbehind.length
|
||||
? P.regex(new RegExp(`${entityType.lookbehind}\\s*\\(\\s*`))
|
||||
: P.regex(/\(\s*/),
|
||||
this.regexMap(
|
||||
entityType.lookbehind instanceof Union
|
||||
? new RegExp(`(${entityType.lookbehind.values.reduce((acc, cur) => acc + "|" + cur)})\\s*\\(\\s*`)
|
||||
: entityType.lookbehind.constructor == String && entityType.lookbehind.length
|
||||
? new RegExp(`(${entityType.lookbehind})\\s*\\(\\s*`)
|
||||
: /()\(\s*/,
|
||||
result => result[1]
|
||||
),
|
||||
this.createAttributeGrammar(entityType).sepBy1(this.commaSeparation),
|
||||
P.regex(/\s*(?:,\s*)?\)/),
|
||||
P.regex(/\s*(?:,\s*)?\)/), // trailing comma
|
||||
)
|
||||
.map(([_0, attributes, _2]) => {
|
||||
.map(([lookbehind, attributes, _2]) => {
|
||||
let values = {};
|
||||
attributes.forEach(attributeSetter => attributeSetter(values));
|
||||
if (lookbehind.length) {
|
||||
values.lookbehind = lookbehind;
|
||||
}
|
||||
return values
|
||||
})
|
||||
// Decide if we accept the entity or not. It is accepted if it doesn't have too many unexpected keys
|
||||
@@ -4465,10 +4495,17 @@ class Grammar {
|
||||
|
||||
static formatTextEntity = P.lazy(() =>
|
||||
P.seq(
|
||||
P.regex(new RegExp(`${FormatTextEntity.lookbehind}\\s*`)),
|
||||
this.regexMap(
|
||||
new RegExp(`(${FormatTextEntity.lookbehind.values.reduce((acc, cur) => acc + "|" + cur)})\\s*`),
|
||||
result => result[1]
|
||||
),
|
||||
this.grammarFor(FormatTextEntity.attributes.value)
|
||||
)
|
||||
.map(([_0, values]) => new FormatTextEntity(values))
|
||||
.map(([lookbehind, values]) => {
|
||||
const result = new FormatTextEntity(values);
|
||||
result.lookbehind = lookbehind;
|
||||
return result
|
||||
})
|
||||
)
|
||||
|
||||
static functionReferenceEntity = P.lazy(() => this.createEntityGrammar(FunctionReferenceEntity))
|
||||
@@ -4513,12 +4550,13 @@ class Grammar {
|
||||
+ String.raw`\s*"(${Grammar.Regex.InsideString.source})"\s*,`
|
||||
+ String.raw`\s*"(${Grammar.Regex.InsideString.source})"\s*`
|
||||
+ String.raw`(?:,\s+)?`
|
||||
+ String.raw`\)`
|
||||
+ String.raw`\)`,
|
||||
"m"
|
||||
),
|
||||
matchResult => new LocalizedTextEntity({
|
||||
namespace: matchResult[1],
|
||||
key: matchResult[2],
|
||||
value: matchResult[3]
|
||||
namespace: Utility.unescapeString(matchResult[1]),
|
||||
key: Utility.unescapeString(matchResult[2]),
|
||||
value: Utility.unescapeString(matchResult[3]),
|
||||
})
|
||||
)
|
||||
)
|
||||
@@ -4705,7 +4743,7 @@ class Grammar {
|
||||
this.unknownKeysEntity,
|
||||
this.symbolEntity,
|
||||
this.grammarFor(undefined, [PinReferenceEntity]),
|
||||
this.grammarFor(undefined, [new UnionType(Number, String, SymbolEntity)]),
|
||||
this.grammarFor(undefined, [new Union(Number, String, SymbolEntity)]),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -4724,28 +4762,30 @@ class Grammar {
|
||||
static inlinedArrayEntry = P.lazy(() =>
|
||||
P.seq(
|
||||
P.alt(
|
||||
this.symbolQuoted.map(v => [v, true]),
|
||||
this.symbol.map(v => [v, false]),
|
||||
this.symbolQuoted.map(v => [v, true])
|
||||
),
|
||||
this.regexMap(
|
||||
new RegExp(`\\s*\\(\\s*(\\d+)\\s*\\)\\s*\\=\\s*`),
|
||||
v => v[1]
|
||||
v => Number(v[1])
|
||||
)
|
||||
)
|
||||
.chain(([[symbol, quoted], index]) =>
|
||||
this.grammarFor(ObjectEntity.attributes[symbol])
|
||||
.map(currentValue =>
|
||||
values => {
|
||||
(values[symbol] ??= [])[index] = currentValue;
|
||||
if (!ObjectEntity.attributes[symbol]?.inlined) {
|
||||
if (!values.attributes) {
|
||||
IEntity.defineAttributes(values, {});
|
||||
}
|
||||
Utility.objectSet(values, ["attributes", symbol, "inlined"], true, true);
|
||||
.chain(
|
||||
/** @param {[[String, Boolean], Number]} param */
|
||||
([[symbol, quoted], index]) =>
|
||||
this.grammarFor(ObjectEntity.attributes[symbol])
|
||||
.map(currentValue =>
|
||||
values => {
|
||||
(values[symbol] ??= [])[index] = currentValue;
|
||||
Utility.objectSet(values, ["attributes", symbol, "quoted"], quoted, true);
|
||||
if (!ObjectEntity.attributes[symbol]?.inlined) {
|
||||
if (!values.attributes) {
|
||||
IEntity.defineAttributes(values, {});
|
||||
}
|
||||
Utility.objectSet(values, ["attributes", symbol, "inlined"], true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -4916,7 +4956,7 @@ class Serializer {
|
||||
* @returns {T}
|
||||
*/
|
||||
read(value) {
|
||||
return this.doRead(value)
|
||||
return this.doRead(value.trim())
|
||||
}
|
||||
|
||||
/** @param {T} value */
|
||||
|
||||
6
dist/ueblueprint.min.js
vendored
6
dist/ueblueprint.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
import ComputedType from "./entity/ComputedType.js"
|
||||
import Configuration from "./Configuration.js"
|
||||
import MirroredEntity from "./entity/MirroredEntity.js"
|
||||
import UnionType from "./entity/UnionType.js"
|
||||
import Union from "./entity/Union.js"
|
||||
|
||||
/**
|
||||
* @typedef {import("./Blueprint.js").default} Blueprint
|
||||
@@ -264,10 +264,10 @@ export default class Utility {
|
||||
if (targetType instanceof ComputedType) {
|
||||
return value // The type is computed, can't say anything about it
|
||||
}
|
||||
if (targetType instanceof UnionType) {
|
||||
let type = targetType.types.find(t => Utility.isValueOfType(value, t, false))
|
||||
if (targetType instanceof Union) {
|
||||
let type = targetType.values.find(t => Utility.isValueOfType(value, t, false))
|
||||
if (!type) {
|
||||
type = targetType.getFirstType()
|
||||
type = targetType.values[0]
|
||||
}
|
||||
targetType = type
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import IEntity from "./IEntity.js"
|
||||
import InvariantTextEntity from "./InvariantTextEntity.js"
|
||||
import LocalizedTextEntity from "./LocalizedTextEntity.js"
|
||||
import UnionType from "./UnionType.js"
|
||||
import Union from "./Union.js"
|
||||
|
||||
export default class FormatTextEntity extends IEntity {
|
||||
|
||||
static lookbehind = "LOCGEN_FORMAT_NAMED"
|
||||
static lookbehind = new Union("LOCGEN_FORMAT_NAMED", "LOCGEN_FORMAT_ORDERED")
|
||||
static attributes = {
|
||||
value: {
|
||||
type: [new UnionType(String, LocalizedTextEntity, InvariantTextEntity, FormatTextEntity)],
|
||||
type: [new Union(String, LocalizedTextEntity, InvariantTextEntity, FormatTextEntity)],
|
||||
default: [],
|
||||
},
|
||||
}
|
||||
@@ -28,11 +28,20 @@ export default class FormatTextEntity extends IEntity {
|
||||
return ""
|
||||
}
|
||||
const values = this.value.slice(1).map(v => v.toString())
|
||||
return pattern.replaceAll(/\{([a-zA-Z]\w*)\}/g, (substring, arg) => {
|
||||
const argLocation = values.indexOf(arg) + 1
|
||||
return argLocation > 0 && argLocation < values.length
|
||||
? values[argLocation]
|
||||
: substring
|
||||
})
|
||||
return this.lookbehind == "LOCGEN_FORMAT_NAMED"
|
||||
? pattern.replaceAll(/\{([a-zA-Z]\w*)\}/g, (substring, arg) => {
|
||||
const argLocation = values.indexOf(arg) + 1
|
||||
return argLocation > 0 && argLocation < values.length
|
||||
? values[argLocation]
|
||||
: substring
|
||||
})
|
||||
: this.lookbehind == "LOCGEN_FORMAT_ORDERED"
|
||||
? pattern.replaceAll(/\{(\d+)\}/g, (substring, arg) => {
|
||||
const argValue = Number(arg)
|
||||
return argValue < values.length
|
||||
? values[argValue]
|
||||
: substring
|
||||
})
|
||||
: ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ComputedType from "./ComputedType.js"
|
||||
import MirroredEntity from "./MirroredEntity.js"
|
||||
import SerializerFactory from "../serialization/SerializerFactory.js"
|
||||
import UnionType from "./UnionType.js"
|
||||
import Union from "./Union.js"
|
||||
import Utility from "../Utility.js"
|
||||
|
||||
/**
|
||||
@@ -14,7 +14,7 @@ import Utility from "../Utility.js"
|
||||
* @typedef {IEntity | MirroredEntity | String | Number | BigInt | Boolean} AnySimpleValue
|
||||
* @typedef {AnySimpleValue | AnySimpleValue[]} AnyValue
|
||||
* @typedef {(entity: IEntity) => AnyValue} ValueSupplier
|
||||
* @typedef {AnyValueConstructor<AnyValue> | AnyValueConstructor<AnyValue>[] | UnionType | UnionType[] | ComputedType | MirroredEntity} AttributeType
|
||||
* @typedef {AnyValueConstructor<AnyValue> | AnyValueConstructor<AnyValue>[] | Union | Union[] | ComputedType | MirroredEntity} AttributeType
|
||||
* @typedef {{
|
||||
* type?: AttributeType,
|
||||
* default?: AnyValue | ValueSupplier,
|
||||
@@ -182,8 +182,8 @@ export default class IEntity {
|
||||
return ""
|
||||
} else if (attributeType === Array || attributeType instanceof Array) {
|
||||
return () => []
|
||||
} else if (attributeType instanceof UnionType) {
|
||||
return this.defaultValueProviderFromType(attributeType.getFirstType())
|
||||
} else if (attributeType instanceof Union) {
|
||||
return this.defaultValueProviderFromType(attributeType.values[0])
|
||||
} else if (attributeType instanceof MirroredEntity) {
|
||||
return () => new MirroredEntity(attributeType.type, attributeType.key, attributeType.getter)
|
||||
} else if (attributeType instanceof ComputedType) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import ObjectReferenceEntity from "./ObjectReferenceEntity.js"
|
||||
import PinEntity from "./PinEntity.js"
|
||||
import SVGIcon from "../SVGIcon.js"
|
||||
import SymbolEntity from "./SymbolEntity.js"
|
||||
import UnionType from "./UnionType.js"
|
||||
import Union from "./Union.js"
|
||||
import UnknownPinEntity from "./UnknownPinEntity.js"
|
||||
import Utility from "../Utility.js"
|
||||
import VariableReferenceEntity from "./VariableReferenceEntity.js"
|
||||
@@ -33,9 +33,12 @@ export default class ObjectEntity extends IEntity {
|
||||
ObjectRef: {
|
||||
type: ObjectReferenceEntity,
|
||||
},
|
||||
PinTags: {
|
||||
type: [null],
|
||||
inlined: true,
|
||||
},
|
||||
PinNames: {
|
||||
type: [String],
|
||||
default: undefined, // To keep the order, may be defined in additionalPinInserter()
|
||||
inlined: true,
|
||||
},
|
||||
AxisKey: {
|
||||
@@ -46,7 +49,6 @@ export default class ObjectEntity extends IEntity {
|
||||
},
|
||||
NumAdditionalInputs: {
|
||||
type: Number,
|
||||
default: undefined, // To keep the order, may be defined in additionalPinInserter()
|
||||
},
|
||||
bIsPureFunc: {
|
||||
type: Boolean,
|
||||
@@ -224,7 +226,7 @@ export default class ObjectEntity extends IEntity {
|
||||
type: String,
|
||||
},
|
||||
CustomProperties: {
|
||||
type: [new UnionType(PinEntity, UnknownPinEntity)],
|
||||
type: [new Union(PinEntity, UnknownPinEntity)],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -305,6 +307,7 @@ export default class ObjectEntity extends IEntity {
|
||||
/** @type {String} */ this.Name
|
||||
/** @type {ObjectReferenceEntity?} */ this.ExportPath
|
||||
/** @type {ObjectReferenceEntity?} */ this.ObjectRef
|
||||
/** @type {null[]} */ this.PinTags
|
||||
/** @type {String[]} */ this.PinNames
|
||||
/** @type {SymbolEntity?} */ this.AxisKey
|
||||
/** @type {SymbolEntity?} */ this.InputAxisKey
|
||||
@@ -993,13 +996,23 @@ export default class ObjectEntity extends IEntity {
|
||||
case Configuration.paths.multiGate:
|
||||
pinEntities ??= () => this.getPinEntities().filter(pinEntity => pinEntity.isOutput())
|
||||
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.match(/^\s*Out[_\s]+(\d+)\s*$/i)?.[1])
|
||||
pinNameFromIndex ??= (index, min = -1, max = -1) => `Out ${index >= 0 ? index : min > 0 ? "Out 0" : max + 1}`
|
||||
pinNameFromIndex ??= (index, min = -1, max = -1) =>
|
||||
`Out ${index >= 0 ? index : min > 0 ? "Out 0" : max + 1}`
|
||||
break
|
||||
case Configuration.paths.switchInteger:
|
||||
pinEntities ??= () => this.getPinEntities().filter(pinEntity => pinEntity.isOutput())
|
||||
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.match(/^\s*(\d+)\s*$/)?.[1])
|
||||
pinNameFromIndex ??= (index, min = -1, max = -1) => (index < 0 ? max + 1 : index).toString()
|
||||
break
|
||||
case Configuration.paths.switchGameplayTag:
|
||||
pinNameFromIndex ??= (index, min = -1, max = -1) => {
|
||||
const result = `Case_${index >= 0 ? index : min > 0 ? "0" : max + 1}`
|
||||
this.PinNames ??= []
|
||||
this.PinNames.push(result)
|
||||
delete this.PinTags[this.PinTags.length - 1]
|
||||
this.PinTags[this.PinTags.length] = null
|
||||
return result
|
||||
}
|
||||
case Configuration.paths.switchName:
|
||||
case Configuration.paths.switchString:
|
||||
pinEntities ??= () => this.getPinEntities().filter(pinEntity => pinEntity.isOutput())
|
||||
|
||||
@@ -17,7 +17,7 @@ import RotatorEntity from "./RotatorEntity.js"
|
||||
import SimpleSerializationRotatorEntity from "./SimpleSerializationRotatorEntity.js"
|
||||
import SimpleSerializationVector2DEntity from "./SimpleSerializationVector2DEntity.js"
|
||||
import SimpleSerializationVectorEntity from "./SimpleSerializationVectorEntity.js"
|
||||
import UnionType from "./UnionType.js"
|
||||
import Union from "./Union.js"
|
||||
import Utility from "../Utility.js"
|
||||
import Vector2DEntity from "./Vector2DEntity.js"
|
||||
import VectorEntity from "./VectorEntity.js"
|
||||
@@ -62,7 +62,7 @@ export default class PinEntity extends IEntity {
|
||||
default: "",
|
||||
},
|
||||
PinFriendlyName: {
|
||||
type: new UnionType(LocalizedTextEntity, FormatTextEntity, String),
|
||||
type: new Union(LocalizedTextEntity, FormatTextEntity, String),
|
||||
},
|
||||
PinToolTip: {
|
||||
type: String,
|
||||
|
||||
17
js/entity/Union.js
Normal file
17
js/entity/Union.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @template {any[]} U
|
||||
* @template {[...U]} T
|
||||
*/
|
||||
export default class Union {
|
||||
|
||||
/** @type {T} */
|
||||
#values
|
||||
get values() {
|
||||
return this.#values
|
||||
}
|
||||
|
||||
/** @param {T} values */
|
||||
constructor(...values) {
|
||||
this.#values = values
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/** @typedef {import("./IEntity.js").AnyValueConstructor<*>} AnyValueConstructor */
|
||||
|
||||
export default class UnionType {
|
||||
|
||||
#types
|
||||
get types() {
|
||||
return this.#types
|
||||
}
|
||||
|
||||
/** @param {...AnyValueConstructor} types */
|
||||
constructor(...types) {
|
||||
this.#types = types
|
||||
}
|
||||
|
||||
getFirstType() {
|
||||
return this.#types[0]
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import SimpleSerializationVector2DEntity from "../entity/SimpleSerializationVect
|
||||
import SimpleSerializationVectorEntity from "../entity/SimpleSerializationVectorEntity.js"
|
||||
import SymbolEntity from "../entity/SymbolEntity.js"
|
||||
import TerminalTypeEntity from "../entity/TerminalTypeEntity.js"
|
||||
import UnionType from "../entity/UnionType.js"
|
||||
import Union from "../entity/Union.js"
|
||||
import UnknownKeysEntity from "../entity/UnknownKeysEntity.js"
|
||||
import UnknownPinEntity from "../entity/UnknownPinEntity.js"
|
||||
import Utility from "../Utility.js"
|
||||
@@ -130,6 +130,7 @@ export default class Grammar {
|
||||
static symbol = P.regex(Grammar.Regex.Symbol)
|
||||
static symbolQuoted = Grammar.regexMap(
|
||||
new RegExp('"(' + Grammar.Regex.Symbol.source + ')"'),
|
||||
/** @type {(_0: String, v: String) => String} */
|
||||
([_0, v]) => v
|
||||
)
|
||||
static attributeName = P.regex(Grammar.Regex.DotSeparatedSymbols)
|
||||
@@ -183,8 +184,8 @@ export default class Grammar {
|
||||
this.grammarFor(undefined, type[0]).sepBy(this.commaSeparation),
|
||||
P.regex(/\s*(?:,\s*)?\)/),
|
||||
).map(([_0, values, _3]) => values)
|
||||
} else if (type instanceof UnionType) {
|
||||
result = type.types
|
||||
} 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
|
||||
@@ -325,8 +326,8 @@ export default class Grammar {
|
||||
static getAttribute(entityType, key) {
|
||||
let result
|
||||
let type
|
||||
if (entityType instanceof UnionType) {
|
||||
for (let t of entityType.types) {
|
||||
if (entityType instanceof Union) {
|
||||
for (let t of entityType.values) {
|
||||
if (result = this.getAttribute(t, key)) {
|
||||
return result
|
||||
}
|
||||
@@ -375,15 +376,23 @@ export default class Grammar {
|
||||
*/
|
||||
static createEntityGrammar = (entityType, acceptUnknownKeys = true) =>
|
||||
P.seq(
|
||||
entityType.lookbehind.length
|
||||
? P.regex(new RegExp(`${entityType.lookbehind}\\s*\\(\\s*`))
|
||||
: P.regex(/\(\s*/),
|
||||
this.regexMap(
|
||||
entityType.lookbehind instanceof Union
|
||||
? new RegExp(`(${entityType.lookbehind.values.reduce((acc, cur) => acc + "|" + cur)})\\s*\\(\\s*`)
|
||||
: entityType.lookbehind.constructor == String && entityType.lookbehind.length
|
||||
? new RegExp(`(${entityType.lookbehind})\\s*\\(\\s*`)
|
||||
: /()\(\s*/,
|
||||
result => result[1]
|
||||
),
|
||||
this.createAttributeGrammar(entityType).sepBy1(this.commaSeparation),
|
||||
P.regex(/\s*(?:,\s*)?\)/),
|
||||
P.regex(/\s*(?:,\s*)?\)/), // trailing comma
|
||||
)
|
||||
.map(([_0, attributes, _2]) => {
|
||||
.map(([lookbehind, attributes, _2]) => {
|
||||
let values = {}
|
||||
attributes.forEach(attributeSetter => attributeSetter(values))
|
||||
if (lookbehind.length) {
|
||||
values.lookbehind = lookbehind
|
||||
}
|
||||
return values
|
||||
})
|
||||
// Decide if we accept the entity or not. It is accepted if it doesn't have too many unexpected keys
|
||||
@@ -419,10 +428,17 @@ export default class Grammar {
|
||||
|
||||
static formatTextEntity = P.lazy(() =>
|
||||
P.seq(
|
||||
P.regex(new RegExp(`${FormatTextEntity.lookbehind}\\s*`)),
|
||||
this.regexMap(
|
||||
new RegExp(`(${FormatTextEntity.lookbehind.values.reduce((acc, cur) => acc + "|" + cur)})\\s*`),
|
||||
result => result[1]
|
||||
),
|
||||
this.grammarFor(FormatTextEntity.attributes.value)
|
||||
)
|
||||
.map(([_0, values]) => new FormatTextEntity(values))
|
||||
.map(([lookbehind, values]) => {
|
||||
const result = new FormatTextEntity(values)
|
||||
result.lookbehind = lookbehind
|
||||
return result
|
||||
})
|
||||
)
|
||||
|
||||
static functionReferenceEntity = P.lazy(() => this.createEntityGrammar(FunctionReferenceEntity))
|
||||
@@ -467,12 +483,13 @@ export default class Grammar {
|
||||
+ String.raw`\s*"(${Grammar.Regex.InsideString.source})"\s*,`
|
||||
+ String.raw`\s*"(${Grammar.Regex.InsideString.source})"\s*`
|
||||
+ String.raw`(?:,\s+)?`
|
||||
+ String.raw`\)`
|
||||
+ String.raw`\)`,
|
||||
"m"
|
||||
),
|
||||
matchResult => new LocalizedTextEntity({
|
||||
namespace: matchResult[1],
|
||||
key: matchResult[2],
|
||||
value: matchResult[3]
|
||||
namespace: Utility.unescapeString(matchResult[1]),
|
||||
key: Utility.unescapeString(matchResult[2]),
|
||||
value: Utility.unescapeString(matchResult[3]),
|
||||
})
|
||||
)
|
||||
)
|
||||
@@ -659,7 +676,7 @@ export default class Grammar {
|
||||
this.unknownKeysEntity,
|
||||
this.symbolEntity,
|
||||
this.grammarFor(undefined, [PinReferenceEntity]),
|
||||
this.grammarFor(undefined, [new UnionType(Number, String, SymbolEntity)]),
|
||||
this.grammarFor(undefined, [new Union(Number, String, SymbolEntity)]),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -678,28 +695,30 @@ export default class Grammar {
|
||||
static inlinedArrayEntry = P.lazy(() =>
|
||||
P.seq(
|
||||
P.alt(
|
||||
this.symbolQuoted.map(v => [v, true]),
|
||||
this.symbol.map(v => [v, false]),
|
||||
this.symbolQuoted.map(v => [v, true])
|
||||
),
|
||||
this.regexMap(
|
||||
new RegExp(`\\s*\\(\\s*(\\d+)\\s*\\)\\s*\\=\\s*`),
|
||||
v => v[1]
|
||||
v => Number(v[1])
|
||||
)
|
||||
)
|
||||
.chain(([[symbol, quoted], index]) =>
|
||||
this.grammarFor(ObjectEntity.attributes[symbol])
|
||||
.map(currentValue =>
|
||||
values => {
|
||||
(values[symbol] ??= [])[index] = currentValue
|
||||
if (!ObjectEntity.attributes[symbol]?.inlined) {
|
||||
if (!values.attributes) {
|
||||
IEntity.defineAttributes(values, {})
|
||||
}
|
||||
Utility.objectSet(values, ["attributes", symbol, "inlined"], true, true)
|
||||
.chain(
|
||||
/** @param {[[String, Boolean], Number]} param */
|
||||
([[symbol, quoted], index]) =>
|
||||
this.grammarFor(ObjectEntity.attributes[symbol])
|
||||
.map(currentValue =>
|
||||
values => {
|
||||
(values[symbol] ??= [])[index] = currentValue
|
||||
Utility.objectSet(values, ["attributes", symbol, "quoted"], quoted, true)
|
||||
if (!ObjectEntity.attributes[symbol]?.inlined) {
|
||||
if (!values.attributes) {
|
||||
IEntity.defineAttributes(values, {})
|
||||
}
|
||||
Utility.objectSet(values, ["attributes", symbol, "inlined"], true, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import SerializerFactory from "./SerializerFactory.js"
|
||||
import Utility from "../Utility.js"
|
||||
|
||||
/**
|
||||
* @typedef {import("../entity/IEntity.js").EntityConstructor} EntityConstructor
|
||||
* @typedef {import("../entity/IEntity.js").AnyValue} AnyValue
|
||||
* @typedef {import("../entity/IEntity.js").AnyValueConstructor<*>} AnyValueConstructor
|
||||
* @typedef {import("../entity/IEntity.js").EntityConstructor} EntityConstructor
|
||||
*/
|
||||
|
||||
/** @template {AnyValue} T */
|
||||
@@ -44,7 +44,7 @@ export default class Serializer {
|
||||
* @returns {T}
|
||||
*/
|
||||
read(value) {
|
||||
return this.doRead(value)
|
||||
return this.doRead(value.trim())
|
||||
}
|
||||
|
||||
/** @param {T} value */
|
||||
|
||||
Reference in New Issue
Block a user