Various improvements (#13)

* Union lookbehind

* Fix quoted inline array
This commit is contained in:
barsdeveloper
2023-09-02 14:08:29 +02:00
committed by GitHub
parent 11f819e6d9
commit fd991b94b3
17 changed files with 313 additions and 144 deletions

View File

@@ -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)
})
})
})

View File

@@ -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", () => {

View File

@@ -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: {

View 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)
}
}

View 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)
}
}

View 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
View File

@@ -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 */

File diff suppressed because one or more lines are too long

View File

@@ -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
}

View File

@@ -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
})
: ""
}
}

View File

@@ -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) {

View File

@@ -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())

View File

@@ -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
View 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
}
}

View File

@@ -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]
}
}

View File

@@ -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)
}
}
}
)
)
)
)

View File

@@ -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 */