} parser */
- constructor(parser) {
- super();
- this.#parser = parser;
- }
-
- /**
- * @param {PathNode} path
- * @param {Number} index
- */
- makePath(path, index) {
- return path
- }
-
- /**
- * @param {Context} context
- * @param {PathNode} path
- */
- isHighlighted(context, path) {
- if (super.isHighlighted(context, path)) {
- // If LazyParser is highlighted, then highlight its child
- const childrenPath = { parent: path, parser: this.#resolvedPraser, index: 0 };
- context.highlighted = context.highlighted instanceof Parser ? this.#resolvedPraser : childrenPath;
- }
- return false
- }
-
- resolve() {
- if (!this.#resolvedPraser) {
- this.#resolvedPraser = this.#parser().getParser();
- }
- return this.#resolvedPraser
- }
-
- /**
- * @param {Context} context
- * @param {Number} position
- * @param {PathNode} path
- * @param {Number} index
- */
- parse(context, position, path, index) {
- this.resolve();
- this.parse = this.#resolvedPraser.parse.bind(this.#resolvedPraser);
- return this.parse(context, position, path, index)
- }
-
- /**
- * @protected
- * @param {Context} context
- * @param {String} indentation
- * @param {PathNode} path
- * @param {Number} index
- */
- doToString(context, indentation, path, index) {
- this.resolve();
- this.doToString = this.#resolvedPraser.toString.bind(this.#resolvedPraser);
- return this.doToString(context, indentation, path, index)
- }
-}
-
-/** @template {Parser} T */
-class Lookahead extends Parser {
-
- #parser
- get parser() {
- return this.#parser
- }
-
- #type
- get type() {
- return this.#type
- }
-
- /**
- * @readonly
- * @enum {String}
- */
- static Type = {
- NEGATIVE_AHEAD: "?!",
- NEGATIVE_BEHIND: "? String.raw`[^${character}\\]*(?:\\.[^${character}\\]*)*`
- static #numberRegex = /[-\+]?(?:\d*\.)?\d+/
- static common = {
- number: new RegExp(this.#numberRegex.source + String.raw`(?!\.)`),
- numberInteger: /[\-\+]?\d+(?!\.\d)/,
- numberNatural: /\d+/,
- numberExponential: new RegExp(this.#numberRegex.source + String.raw`(?:[eE][\+\-]?\d+)?(?!\.)`),
- numberUnit: /\+?(?:0(?:\.\d+)?|1(?:\.0+)?)(?![\.\d])/,
- numberByte: /0*(?:25[0-5]|2[0-4]\d|1?\d?\d)(?!\d|\.)/,
- whitespace: /\s+/,
- whitespaceOpt: /\s*/,
- whitespaceInline: /[^\S\n]+/,
- whitespaceInlineOpt: /[^\S\n]*/,
- whitespaceMultiline: /\s*?\n\s*/,
- doubleQuotedString: new RegExp(`"(${this.#createEscapeable('"')})"`),
- singleQuotedString: new RegExp(`'(${this.#createEscapeable("'")})'`),
- backtickQuotedString: new RegExp("`(" + this.#createEscapeable("`") + ")`"),
- }
-
-
- /**
- * @param {RegExp} regexp
- * @param {(match: RegExpExecArray) => T} matchMapper
- */
- constructor(regexp, matchMapper) {
- super();
- this.#regexp = regexp;
- this.#anchoredRegexp = new RegExp(`^(?:${regexp.source})`, regexp.flags);
- this.#matchMapper = matchMapper;
- }
-
- /**
- * @param {Context} context
- * @param {Number} position
- * @param {PathNode} path
- * @param {Number} index
- */
- parse(context, position, path, index) {
- path = this.makePath(path, index);
- const match = this.#anchoredRegexp.exec(context.input.substring(position));
- if (match) {
- position += match[0].length;
- }
- const result = match
- ? Reply.makeSuccess(position, this.#matchMapper(match), path, position)
- : Reply.makeFailure();
- return result
- }
-
- /**
- * @protected
- * @param {Context} context
- * @param {String} indentation
- * @param {PathNode} path
- * @param {Number} index
- */
- doToString(context, indentation, path, index) {
- let result = "/" + this.#regexp.source + "/";
- const shortname = Object.entries(RegExpParser.common).find(([k, v]) => v.source === this.#regexp.source)?.[0];
- if (shortname) {
- result = "P." + shortname;
- }
- return result
- }
-}
-
-/**
- * @template {Parser} T
- * @template P
- */
-class MapParser extends Parser {
-
- #parser
- get parser() {
- return this.#parser
- }
-
- #mapper
- get mapper() {
- return this.#mapper
- }
-
- /**
- * @param {T} parser
- * @param {(v: ParserValue) => P} mapper
- */
- constructor(parser, mapper) {
- super();
- this.#parser = parser;
- this.#mapper = mapper;
- }
-
- /**
- * @param {Context} context
- * @param {PathNode} path
- */
- isHighlighted(context, path) {
- if (super.isHighlighted(context, path)) {
- // If MapParser is highlighted, then highlight its child
- const childrenPath = { parent: path, parser: this.#parser, index: 0 };
- context.highlighted = context.highlighted instanceof Parser ? this.#parser : childrenPath;
- }
- return false
- }
-
- /**
- * @param {Context} context
- * @param {Number} position
- * @param {PathNode} path
- * @param {Number} index
- * @returns {Result
}
- */
- parse(context, position, path, index) {
- path = this.makePath(path, index);
- const result = this.#parser.parse(context, position, path, 0);
- if (result.status) {
- result.value = this.#mapper(result.value);
- }
- return result
- }
-
- /**
- * @protected
- * @param {Context} context
- * @param {String} indentation
- * @param {PathNode} path
- * @param {Number} index
- */
- doToString(context, indentation, path, index) {
- let result = this.#parser.toString(context, indentation, path, 0);
- if (this.#parser instanceof RegExpParser) {
- if (Object.values(RegExpParser.common).includes(this.#parser.regexp)) {
- if (
- this.#parser.regexp === RegExpParser.common.numberInteger
- && this.#mapper === /** @type {(v: any) => BigInt} */(BigInt)
- ) {
- return "P.numberBigInteger"
- }
- return result
- }
- }
- let serializedMapper = this.#mapper.toString();
- if (serializedMapper.length > 60 || serializedMapper.includes("\n")) {
- serializedMapper = "(...) => { ... }";
- }
- result += ` -> map<${serializedMapper}>`;
- return result
- }
-}
-
-/** @extends {RegExpParser} */
-class RegExpArrayParser extends RegExpParser {
-
- /** @param {RegExpExecArray} match */
- static #mapper = match => match
-
- /** @param {RegExp} regexp */
- constructor(regexp) {
- super(regexp, RegExpArrayParser.#mapper);
- }
-}
-
-/** @extends {RegExpParser} */
-class RegExpValueParser extends RegExpParser {
-
- /** @param {RegExp} regexp */
- constructor(regexp, group = 0) {
- super(
- regexp,
- /** @param {RegExpExecArray} match */
- match => match[group]
- );
- }
-}
-
-/** @template {Parser[]} T */
-class SequenceParser extends Parser {
-
- #parsers
- get parsers() {
- return this.#parsers
- }
-
- /** @param {T} parsers */
- constructor(...parsers) {
- super();
- this.#parsers = parsers;
- }
-
- /**
- * @param {Context} context
- * @param {Number} position
- * @param {PathNode} path
- * @param {Number} index
- */
- parse(context, position, path, index) {
- path = this.makePath(path, index);
- const value = /** @type {ParserValue} */(new Array(this.#parsers.length));
- const result = Reply.makeSuccess(position, value);
- for (let i = 0; i < this.#parsers.length; ++i) {
- const outcome = this.#parsers[i].parse(context, result.position, path, i);
- if (outcome.bestPosition > result.bestPosition) {
- result.bestParser = outcome.bestParser;
- result.bestPosition = outcome.bestPosition;
- }
- if (!outcome.status) {
- result.status = false;
- result.value = null;
- break
- }
- result.value[i] = outcome.value;
- result.position = outcome.position;
- }
- return result
- }
-
- /**
- * @protected
- * @param {Context} context
- * @param {String} indentation
- * @param {PathNode} path
- * @param {Number} index
- */
- doToString(context, indentation, path, index) {
- const deeperIndentation = indentation + Parser.indentation;
- const result = "SEQ<\n"
- + deeperIndentation
- + this.#parsers
- .map((parser, index) => parser.toString(context, deeperIndentation, path, index))
- .join("\n" + deeperIndentation)
- + "\n" + indentation + ">";
- return result
- }
-}
-
-/** @template {Parser} T */
-class TimesParser extends Parser {
-
- #parser
- get parser() {
- return this.#parser
- }
-
- #min
- get min() {
- return this.#min
- }
-
- #max
- get max() {
- return this.#max
- }
-
- /** @param {T} parser */
- constructor(parser, min = 0, max = Number.POSITIVE_INFINITY) {
- super();
- if (min > max) {
- throw new Error("Min is greater than max")
- }
- this.#parser = parser;
- this.#min = min;
- this.#max = max;
- }
-
- /**
- * @param {Context} context
- * @param {Number} position
- * @param {PathNode} path
- * @param {Number} index
- */
- parse(context, position, path, index) {
- path = this.makePath(path, index);
- const value = /** @type {ParserValue[]} */([]);
- const result = Reply.makeSuccess(position, value, path);
- for (let i = 0; i < this.#max; ++i) {
- const outcome = this.#parser.parse(context, result.position, path, 0);
- if (outcome.bestPosition > result.bestPosition) {
- result.bestParser = outcome.bestParser;
- result.bestPosition = outcome.bestPosition;
- }
- if (!outcome.status) {
- if (i < this.#min) {
- result.status = false;
- result.value = null;
- }
- break
- }
- result.value.push(outcome.value);
- result.position = outcome.position;
- }
- return result
- }
-
- /**
- * @protected
- * @param {Context} context
- * @param {String} indentation
- * @param {PathNode} path
- * @param {Number} index
- */
- doToString(context, indentation, path, index) {
- let result = this.parser.toString(context, indentation, path, 0);
- const serialized =
- this.#min === 0 && this.#max === 1 ? "?"
- : this.#min === 0 && this.#max === Number.POSITIVE_INFINITY ? "*"
- : this.#min === 1 && this.#max === Number.POSITIVE_INFINITY ? "+"
- : "{"
- + this.#min
- + (this.#min !== this.#max ? "," + this.#max : "")
- + "}";
- result += serialized;
- return result
- }
-}
-
-/** @template {Parser} T */
-class Parsernostrum {
-
- #parser
-
- /** @type {(new (parser: Parser) => Parsernostrum) & typeof Parsernostrum} */
- Self
-
- static lineColumnFromOffset(string, offset) {
- const lines = string.substring(0, offset).split('\n');
- const line = lines.length;
- const column = lines[lines.length - 1].length + 1;
- return { line, column }
- }
- /** @param {[any, ...any]|RegExpExecArray} param0 */
- static #firstElementGetter = ([v, _]) => v
- /** @param {[any, any, ...any]|RegExpExecArray} param0 */
- static #secondElementGetter = ([_, v]) => v
- static #arrayFlatter = ([first, rest]) => [first, ...rest]
- /**
- * @template T
- * @param {T} v
- * @returns {T extends Array ? String : T}
- */
- // @ts-expect-error
- static #joiner = v => v instanceof Array ? v.join("") : v
- static #createEscapeable = character => String.raw`[^${character}\\]*(?:\\.[^${character}\\]*)*`
-
- // Prefedined parsers
-
- /** Parser accepting any valid decimal, possibly signed number */
- static number = this.reg(RegExpParser.common.number).map(Number)
-
- /** Parser accepting any digits only number */
- static numberInteger = this.reg(RegExpParser.common.numberInteger).map(Number)
-
- /** Parser accepting any digits only number and returns a BigInt */
- static numberBigInteger = this.reg(this.numberInteger.getParser().parser.regexp).map(BigInt)
-
- /** Parser accepting any digits only number */
- static numberNatural = this.reg(RegExpParser.common.numberNatural).map(Number)
-
- /** Parser accepting any valid decimal, possibly signed, possibly in the exponential form number */
- static numberExponential = this.reg(RegExpParser.common.numberExponential).map(Number)
-
- /** Parser accepting any valid decimal number between 0 and 1 */
- static numberUnit = this.reg(RegExpParser.common.numberUnit).map(Number)
-
- /** Parser accepting any integer between 0 and 255 */
- static numberByte = this.reg(RegExpParser.common.numberByte).map(Number)
-
- /** Parser accepting whitespace */
- static whitespace = this.reg(RegExpParser.common.whitespace)
-
- /** Parser accepting whitespace */
- static whitespaceOpt = this.reg(RegExpParser.common.whitespaceOpt)
-
- /** Parser accepting whitespace that spans on a single line */
- static whitespaceInline = this.reg(RegExpParser.common.whitespaceInline)
-
- /** Parser accepting whitespace that spans on a single line */
- static whitespaceInlineOpt = this.reg(RegExpParser.common.whitespaceInlineOpt)
-
- /** Parser accepting whitespace that contains a list a newline */
- static whitespaceMultiline = this.reg(RegExpParser.common.whitespaceMultiline)
-
- /** Parser accepting a double quoted string and returns the content */
- static doubleQuotedString = this.reg(RegExpParser.common.doubleQuotedString, 1)
-
- /** Parser accepting a single quoted string and returns the content */
- static singleQuotedString = this.reg(RegExpParser.common.singleQuotedString, 1)
-
- /** Parser accepting a backtick quoted string and returns the content */
- static backtickQuotedString = this.reg(RegExpParser.common.backtickQuotedString, 1)
-
- /** @param {T} parser */
- constructor(parser, optimized = false) {
- this.#parser = parser;
- }
-
- /** @param {PathNode} path */
- static #simplifyPath(path) {
- /** @type {PathNode[]} */
- const array = [];
- while (path) {
- array.push(path);
- path = path.parent;
- }
- array.reverse();
- /** @type {Map} */
- let visited = new Map();
- for (let i = 1; i < array.length; ++i) {
- const existing = visited.get(array[i].current);
- if (existing !== undefined) {
- if (array[i + 1]) {
- array[i + 1].parent = array[existing];
- }
- visited = new Map([...visited.entries()].filter(([parser, index]) => index <= existing || index > i));
- visited.set(array[i].current, existing);
- array.splice(existing + 1, i - existing);
- i = existing;
- } else {
- visited.set(array[i].current, i);
- }
- }
- return array[array.length - 1]
- }
-
- getParser() {
- return this.#parser
- }
-
- /**
- * @param {String} input
- * @returns {Result>}
- */
- run(input) {
- const result = this.#parser.parse(Reply.makeContext(this, input), 0, Reply.makePathNode(), 0);
- if (result.position !== input.length) {
- result.status = false;
- }
- return result
- }
-
- /**
- * @param {String} input
- * @throws {Error} when the parser fails to match
- */
- parse(input, printParser = true) {
- const result = this.run(input);
- if (result.status) {
- return result.value
- }
- const chunkLength = 60;
- const chunkRange = /** @type {[Number, Number]} */(
- [Math.ceil(chunkLength / 2), Math.floor(chunkLength / 2)]
- );
- const position = Parsernostrum.lineColumnFromOffset(input, result.bestPosition);
- let bestPosition = result.bestPosition;
- const inlineInput = input.replaceAll(
- /^(\s)+|\s{6,}|\s*?\n\s*/g,
- (m, startingSpace, offset) => {
- let replaced = startingSpace ? "..." : " ... ";
- if (offset <= result.bestPosition) {
- if (result.bestPosition < offset + m.length) {
- bestPosition -= result.bestPosition - offset;
- } else {
- bestPosition -= m.length - replaced.length;
- }
- }
- return replaced
- }
- );
- const string = inlineInput.substring(0, chunkLength).trimEnd();
- const leadingWhitespaceLength = Math.min(
- input.substring(result.bestPosition - chunkRange[0]).match(/^\s*/)[0].length,
- chunkRange[0] - 1,
- );
- let offset = Math.min(bestPosition, chunkRange[0] - leadingWhitespaceLength);
- chunkRange[0] = Math.max(0, bestPosition - chunkRange[0]) + leadingWhitespaceLength;
- chunkRange[1] = Math.min(input.length, chunkRange[0] + chunkLength);
- let segment = inlineInput.substring(...chunkRange);
- if (chunkRange[0] > 0) {
- segment = "..." + segment;
- offset += 3;
- }
- if (chunkRange[1] < inlineInput.length - 1) {
- segment = segment + "...";
- }
- const bestParser = this.toString(Parser.indentation, true, Parsernostrum.#simplifyPath(result.bestParser));
- throw new Error(
- `Could not parse: ${string}\n\n`
- + `Input: ${segment}\n`
- + " " + " ".repeat(offset)
- + `^ From here (line: ${position.line}, `
- + `column: ${position.column}, `
- + `offset: ${result.bestPosition})${result.bestPosition === input.length ? ", end of string" : ""}\n`
- + (printParser
- ? "\n"
- + (result.bestParser ? "Last valid parser matched:" : "No parser matched:")
- + bestParser
- + "\n"
- : ""
- )
- )
- }
-
- // Parsers
-
- /**
- * @template {String} S
- * @param {S} value
- */
- static str(value) {
- return new this(new StringParser(value))
- }
-
- /** @param {RegExp} value */
- static reg(value, group = 0) {
- return new this(new RegExpValueParser(value, group))
- }
-
- /** @param {RegExp} value */
- static regArray(value) {
- return new this(new RegExpArrayParser(value))
- }
-
- static success() {
- return new this(SuccessParser.instance)
- }
-
- static failure() {
- return new this(FailureParser.instance)
- }
-
- // Combinators
-
- /**
- * @template {[Parsernostrum, Parsernostrum, ...Parsernostrum[]]} P
- * @param {P} parsers
- * @returns {Parsernostrum>>}
- */
- static seq(...parsers) {
- const results = new this(new SequenceParser(...parsers.map(p => p.getParser())));
- // @ts-expect-error
- return results
- }
-
- /**
- * @template {Parsernostrum[]} P
- * @param {P} parsers
- * @returns {Parsernostrum>>}
- */
- static alt(...parsers) {
- // @ts-expect-error
- return new this(new AlternativeParser(...parsers.map(p => p.getParser())))
- }
-
- /**
- * @template {Parsernostrum} P
- * @param {P} parser
- */
- static lookahead(parser) {
- return new this(new Lookahead(parser.getParser(), Lookahead.Type.POSITIVE_AHEAD))
- }
-
- /**
- * @template {Parsernostrum} P
- * @param {() => P} parser
- * @returns {Parsernostrum>>}
- */
- static lazy(parser) {
- return new this(new LazyParser(parser))
- }
-
- /** @param {Number} min */
- times(min, max = min) {
- return new Parsernostrum(new TimesParser(this.#parser, min, max))
- }
-
- many() {
- return this.times(0, Number.POSITIVE_INFINITY)
- }
-
- /** @param {Number} n */
- atLeast(n) {
- return this.times(n, Number.POSITIVE_INFINITY)
- }
-
- /** @param {Number} n */
- atMost(n) {
- return this.times(0, n)
- }
-
- /** @returns {Parsernostrum} */
- opt() {
- // @ts-expect-error
- return Parsernostrum.alt(this, Parsernostrum.success())
- }
-
- /**
- * @template {Parsernostrum} P
- * @param {P} separator
- */
- sepBy(separator, allowTrailing = false) {
- const results = Parsernostrum.seq(
- this,
- Parsernostrum.seq(separator, this).map(Parsernostrum.#secondElementGetter).many()
- )
- .map(Parsernostrum.#arrayFlatter);
- return results
- }
-
- skipSpace() {
- return Parsernostrum.seq(this, Parsernostrum.whitespaceOpt).map(Parsernostrum.#firstElementGetter)
- }
-
- /**
- * @template P
- * @param {(v: ParserValue) => P} fn
- * @returns {Parsernostrum>}
- */
- map(fn) {
- // @ts-expect-error
- return new Parsernostrum(new MapParser(this.#parser, fn))
- }
-
- /**
- * @template {Parsernostrum} P
- * @param {(v: ParserValue, input: String, position: Number) => P} fn
- */
- chain(fn) {
- return new Parsernostrum(new ChainedParser(this.#parser, fn))
- }
-
- /**
- * @param {(v: ParserValue, input: String, position: Number) => boolean} fn
- * @return {Parsernostrum}
- */
- assert(fn) {
- // @ts-expect-error
- return this.chain((v, input, position) => fn(v, input, position)
- ? Parsernostrum.success().map(() => v)
- : Parsernostrum.failure()
- )
- }
-
- join(value = "") {
- return this.map(Parsernostrum.#joiner)
- }
-
- label(value = "") {
- return new Parsernostrum(new Label(this.#parser, value))
- }
-
- /** @param {Parsernostrum | Parser | PathNode} highlight */
- toString(indentation = "", newline = false, highlight = null) {
- if (highlight instanceof Parsernostrum) {
- highlight = highlight.getParser();
- }
- const context = Reply.makeContext(this, "");
- context.highlighted = highlight;
- const path = Reply.makePathNode();
- return (newline ? "\n" + indentation : "") + this.#parser.toString(context, indentation, path)
- }
-}
-
-class Serializable {
-
- static grammar = this.createGrammar()
-
- /** @protected */
- static createGrammar() {
- return /** @type {Parsernostrum} */(Parsernostrum.failure())
- }
-}
-
-class SerializerFactory {
-
- static #serializers = new Map()
-
- /**
- * @template {AttributeConstructor} T
- * @param {T} type
- * @param {Serializer} object
- */
- static registerSerializer(type, object) {
- SerializerFactory.#serializers.set(type, object);
- }
-
- /**
- * @template {AttributeConstructor} T
- * @param {T} type
- * @returns {Serializer}
- */
- static getSerializer(type) {
- return SerializerFactory.#serializers.get(type)
- }
-}
-
-/** @abstract */
-class IEntity extends Serializable {
-
- /** @type {{ [attribute: String]: AttributeInfo }} */
- static attributes = {
- attributes: new AttributeInfo({
- ignored: true,
- }),
- lookbehind: new AttributeInfo({
- default: /** @type {String | Union} */(""),
- ignored: true,
- uninitialized: true,
- }),
- }
-
- /** @type {String[]} */
- #_keys
- get _keys() {
- return this.#_keys
- }
- set _keys(keys) {
- this.#_keys = keys;
- }
-
- 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;
- };
-
- 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);
- }
-
- 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
- }
- }
- assignAttribute(Utility.sanitize(value, /** @type {AttributeConstructor} */(defaultType)));
- continue // We have a value, need nothing more
- }
- if (defaultValue !== undefined && !AttributeInfo.getAttribute(values, key, "uninitialized", Self)) {
- assignAttribute(defaultValue);
- }
- }
- }
-
- /** @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} */(attributeType)()
- }
- }
-
- /**
- * @template {new (...args: any) => any} C
- * @param {C} type
- * @returns {value is InstanceType}
- */
- static isValueOfType(value, type) {
- return value != null && (value instanceof type || value.constructor === type)
- }
-
- static defineAttributes(object, attributes) {
- Object.defineProperty(object, "attributes", {
- writable: true,
- configurable: false,
- });
- object.attributes = attributes;
- }
-
- /**
- *
- * @param {String} attribute
- * @param {(v: any) => void} callback
- */
- listenAttribute(attribute, callback) {
- const descriptor = Object.getOwnPropertyDescriptor(this, attribute);
- const setter = descriptor.set;
- if (setter) {
- descriptor.set = v => {
- setter(v);
- callback(v);
- };
- Object.defineProperties(this, { [attribute]: descriptor });
- } else if (descriptor.value) {
- Object.defineProperties(this, {
- ["#" + attribute]: {
- value: descriptor.value,
- writable: true,
- enumerable: false,
- },
- [attribute]: {
- enumerable: true,
- get() {
- return this["#" + attribute]
- },
- set(v) {
- if (v == this["#" + attribute]) {
- return
- }
- callback(v);
- this["#" + attribute] = v;
- }
- },
- });
- }
- }
-
- getLookbehind() {
- let lookbehind = this.lookbehind ?? AttributeInfo.getAttribute(this, "lookbehind", "default");
- lookbehind = lookbehind instanceof Union ? lookbehind.values[0] : lookbehind;
- return lookbehind
- }
-
- unexpectedKeys() {
- return Object.keys(this).length - Object.keys(/** @type {typeof IEntity} */(this.constructor).attributes).length
- }
-
- /** @param {IEntity} other */
- equals(other) {
- const thisKeys = Object.keys(this);
- const otherKeys = Object.keys(other);
- if (thisKeys.length != otherKeys.length) {
- 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])) {
- return false
- }
- }
- return true
- }
-}
-
-class Grammar {
-
- static separatedBy = (source, separator, min = 1) =>
- new RegExp(
- source + "(?:" + separator + source + ")"
- + (min === 1 ? "*" : min === 2 ? "+" : `{${min},}`)
- )
-
- static Regex = class {
- static HexDigit = /[0-9a-fA-F]/
- static InsideString = /(?:[^"\\]|\\.)*/
- static InsideSingleQuotedString = /(?:[^'\\]|\\.)*/
- static Integer = /[\-\+]?\d+(?!\d|\.)/
- static Number = /[-\+]?(?:\d*\.)?\d+(?!\d|\.)/
- static RealUnit = /\+?(?:0(?:\.\d+)?|1(?:\.0+)?)(?![\.\d])/ // A number between 0 and 1 included
- static Word = Grammar.separatedBy("[a-zA-Z]", "_")
- static Symbol = /[a-zA-Z_]\w*/
- static DotSeparatedSymbols = Grammar.separatedBy(this.Symbol.source, "\\.")
- static MultipleWordsSymbols = Grammar.separatedBy(this.Symbol.source, "(?:\\.|\\ +)")
- static PathFragment = Grammar.separatedBy(this.Symbol.source, "[\\.:]")
- static PathSpaceFragment = Grammar.separatedBy(this.Symbol.source, "[\\.:\\ ]")
- static Path = new RegExp(`(?:\\/${this.PathFragment.source}){2,}`) // Multiple (2+) /PathFragment
- }
-
- /* --- Primitive --- */
-
- static null = Parsernostrum.reg(/\(\s*\)/).map(() => null)
- static true = Parsernostrum.reg(/true/i).map(() => true)
- static false = Parsernostrum.reg(/false/i).map(() => false)
- static boolean = Parsernostrum.regArray(/(true)|false/i).map(v => v[1] ? true : false)
- static number = Parsernostrum.regArray(
- 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)
- static bigInt = Parsernostrum.reg(new RegExp(Parsernostrum.number.getParser().parser.regexp.source)).map(BigInt)
- .map(result =>
- result[2] !== undefined
- ? Number.POSITIVE_INFINITY
- : result[3] !== undefined
- ? Number.NEGATIVE_INFINITY
- : Number(result[1])
- )
- static naturalNumber = Parsernostrum.lazy(() => Parsernostrum.reg(/\d+/).map(Number))
- static string = Parsernostrum.doubleQuotedString.map(insideString => Utility.unescapeString(insideString))
-
- /* --- Fragment --- */
-
- static colorValue = Parsernostrum.numberByte
- static word = Parsernostrum.reg(Grammar.Regex.Word)
- static symbol = Parsernostrum.reg(Grammar.Regex.Symbol)
- static symbolQuoted = Parsernostrum.reg(new RegExp('"(' + Grammar.Regex.Symbol.source + ')"'), 1)
- static attributeName = Parsernostrum.reg(Grammar.Regex.DotSeparatedSymbols)
- static attributeNameQuoted = Parsernostrum.reg(new RegExp('"(' + Grammar.Regex.InsideString.source + ')"'), 1)
- static guid = Parsernostrum.reg(new RegExp(`${Grammar.Regex.HexDigit.source}{32}`))
- static commaSeparation = Parsernostrum.reg(/\s*,\s*(?!\))/)
- static commaOrSpaceSeparation = Parsernostrum.reg(/\s*,\s*(?!\))|\s+/)
- static equalSeparation = Parsernostrum.reg(/\s*=\s*/)
- static hexColorChannel = Parsernostrum.reg(new RegExp(Grammar.Regex.HexDigit.source + "{2}"))
-
- /* --- Factory --- */
-
- /**
- * @template T
- * @param {AttributeInfo} attribute
- * @param {Parsernostrum} defaultGrammar
- * @returns {Parsernostrum}
- */
- 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} */(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} T
- * @param {T} entityType
- * @param {String[]} key
- * @returns {AttributeInfo}
- */
- 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
- }
- }
- }
- 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 (key.length > 1) {
- return this.getAttribute(type, key.slice(1))
- }
- return result
- }
-
- static createAttributeGrammar(
- entityType,
- attributeName = this.attributeName,
- valueSeparator = this.equalSeparation,
- handleObjectSet = (obj, k, v) => { }
- ) {
- return Parsernostrum.seq(
- attributeName,
- 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);
- }
- )
- })
- }
-
- /**
- * @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
- */
- static createEntityGrammar(entityType, acceptUnknownKeys = true, entriesSeparator = this.commaSeparation) {
- const lookbehind = entityType.attributes.lookbehind.default;
- 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
- )
- .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
- .chain(values => {
- let totalKeys = Object.keys(values);
- // Check missing values
- if (
- Object.keys(/** @type {AttributeDeclarations} */(entityType.attributes))
- .filter(key => entityType.attributes[key].expected)
- .find(key => !totalKeys.includes(key) && (key))
- ) {
- return Parsernostrum.failure()
- }
- 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))
- })
- }
-
- /** @type {Parsernostrum} */
- static unknownValue // Defined in initializeSerializerFactor to avoid circular include
-}
-
-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(values = 0) {
- if (values.constructor !== Object) {
- // @ts-expect-error
- values = {
- value: values,
- };
- }
- super(values);
- /** @type {Number} */ this.value;
- }
-
- valueOf() {
- return this.value
- }
-
- toString() {
- return this.value.toFixed(6)
- }
-}
-
-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,
- }),
- }
- static grammar = this.createGrammar()
-
- /** @param {Number} x */
- static linearToSRGB(x) {
- if (x <= 0) {
- return 0
- } else if (x >= 1) {
- return 1
- } else if (x < 0.0031308) {
- return x * 12.92
- } else {
- return Math.pow(x, 1 / 2.4) * 1.055 - 0.055
- }
- }
-
- /** @param {Number} x */
- static sRGBtoLinear(x) {
- if (x <= 0) {
- return 0
- } else if (x >= 1) {
- return 1
- } else if (x < 0.04045) {
- return x / 12.92
- } else {
- return Math.pow((x + 0.055) / 1.055, 2.4)
- }
- }
-
- static getWhite() {
- return new LinearColorEntity({
- R: 1,
- G: 1,
- B: 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})?"
- )).map(([m, R, G, B, A]) => new this({
- R: parseInt(R, 16) / 255,
- G: parseInt(G, 16) / 255,
- B: parseInt(B, 16) / 255,
- A: parseInt(A ?? "FF", 16) / 255,
- }))
- }
-
- static getLinearColorRGBListGrammar() {
- return Parsernostrum.seq(
- Parsernostrum.numberByte,
- Grammar.commaSeparation,
- Parsernostrum.numberByte,
- Grammar.commaSeparation,
- Parsernostrum.numberByte,
- ).map(([R, _1, G, _3, B]) => new this({
- R: R / 255,
- G: G / 255,
- B: B / 255,
- A: 1,
- }))
- }
-
- static getLinearColorRGBGrammar() {
- return Parsernostrum.seq(
- Parsernostrum.reg(/rgb\s*\(\s*/),
- this.getLinearColorRGBListGrammar(),
- Parsernostrum.reg(/\s*\)/)
- ).map(([_0, linearColor, _2]) => linearColor)
- }
-
- static getLinearColorRGBAGrammar() {
- return Parsernostrum.seq(
- Parsernostrum.reg(/rgba\s*\(\s*/),
- this.getLinearColorRGBListGrammar(),
- Parsernostrum.reg(/\s*\)/)
- ).map(([_0, linearColor, _2]) => linearColor)
- }
-
- static getLinearColorFromAnyFormat() {
- return Parsernostrum.alt(
- this.getLinearColorFromHexGrammar(),
- this.getLinearColorRGBAGrammar(),
- this.getLinearColorRGBGrammar(),
- this.getLinearColorRGBListGrammar(),
- )
- }
-
- 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;
- const b = this.B.value;
- if (Utility.approximatelyEqual(r, g) && Utility.approximatelyEqual(r, b) && Utility.approximatelyEqual(g, b)) {
- this.S.value = 0;
- this.V.value = r;
- return
- }
- const max = Math.max(r, g, b);
- const min = Math.min(r, g, b);
- const d = max - min;
- let h;
- switch (max) {
- case min:
- h = 0;
- break
- case r:
- h = (g - b) / d + (g < b ? 6 : 0);
- break
- case g:
- h = (b - r) / d + 2;
- break
- case b:
- h = (r - g) / d + 4;
- break
- }
- h /= 6;
- this.H.value = h;
- this.S.value = max == 0 ? 0 : d / max;
- this.V.value = max;
- }
-
- /**
- * @param {Number} r
- * @param {Number} g
- * @param {Number} b
- * @param {Number} a
- */
- setFromRGBA(r, g, b, a = 1) {
- this.R.value = r;
- this.G.value = g;
- this.B.value = b;
- this.A.value = a;
- this.#updateHSV();
- }
-
- /**
- * @param {Number} h
- * @param {Number} s
- * @param {Number} v
- * @param {Number} a
- */
- setFromHSVA(h, s, v, a = 1) {
- const i = Math.floor(h * 6);
- const f = h * 6 - i;
- const p = v * (1 - s);
- const q = v * (1 - f * s);
- const t = v * (1 - (1 - f) * s);
- const values = [v, q, p, p, t, v];
- const [r, g, b] = [values[i % 6], values[(i + 4) % 6], values[(i + 2) % 6]];
- this.R.value = r;
- this.G.value = g;
- this.B.value = b;
- this.A.value = a;
- this.H.value = h;
- this.S.value = s;
- this.V.value = v;
- }
-
- /**
- * @param {Number} x
- * @param {Number} y
- * @param {Number} v
- * @param {Number} a
- */
- setFromWheelLocation(x, y, v, a) {
- const [r, theta] = Utility.getPolarCoordinates(x, y, true);
- this.setFromHSVA(1 - theta / (2 * Math.PI), r, v, a);
- }
-
- toDimmedColor(minV = 0) {
- const result = new LinearColorEntity();
- result.setFromRGBANumber(this.toNumber());
- result.setFromHSVA(
- result.H.value,
- result.S.value * 0.6,
- Math.pow(result.V.value + minV, 0.55) * 0.7
- );
- return result
- }
-
- toCSSRGBValues() {
- const r = Math.round(this.R.value * 255);
- const g = Math.round(this.G.value * 255);
- const b = Math.round(this.B.value * 255);
- return i$3`${r}, ${g}, ${b}`
- }
-
- toRGBA() {
- return [
- Math.round(this.R.value * 255),
- Math.round(this.G.value * 255),
- Math.round(this.B.value * 255),
- Math.round(this.A.value * 255),
- ]
- }
-
- toSRGBA() {
- return [
- Math.round(LinearColorEntity.linearToSRGB(this.R.value) * 255),
- Math.round(LinearColorEntity.linearToSRGB(this.G.value) * 255),
- Math.round(LinearColorEntity.linearToSRGB(this.B.value) * 255),
- Math.round(this.A.value * 255),
- ]
- }
-
- toRGBAString() {
- return this
- .toRGBA()
- .map(v => v.toString(16).toUpperCase().padStart(2, "0"))
- .join("")
- }
-
- toSRGBAString() {
- return this
- .toSRGBA()
- .map(v => v.toString(16).toUpperCase().padStart(2, "0"))
- .join("")
- }
-
- toHSVA() {
- return [this.H.value, this.S.value, this.V.value, this.A.value]
- }
-
- toNumber() {
- return (
- Math.round(this.R.value * 0xff) << 24)
- + (Math.round(this.G.value * 0xff) << 16)
- + (Math.round(this.B.value * 0xff) << 8)
- + Math.round(this.A.value * 0xff)
- }
-
- /** @param {Number} number */
- setFromRGBANumber(number) {
- this.A.value = (number & 0xff) / 0xff;
- this.B.value = ((number >> 8) & 0xff) / 0xff;
- this.G.value = ((number >> 16) & 0xff) / 0xff;
- this.R.value = ((number >> 24) & 0xff) / 0xff;
- this.#updateHSV();
- }
-
- /** @param {Number} number */
- setFromSRGBANumber(number) {
- this.A.value = (number & 0xff) / 0xff;
- this.B.value = LinearColorEntity.sRGBtoLinear(((number >> 8) & 0xff) / 0xff);
- this.G.value = LinearColorEntity.sRGBtoLinear(((number >> 16) & 0xff) / 0xff);
- this.R.value = LinearColorEntity.sRGBtoLinear(((number >> 24) & 0xff) / 0xff);
- this.#updateHSV();
- }
-
- /** @returns {[Number, Number, Number, Number]} */
- toArray() {
- return [this.R.value, this.G.value, this.B.value, this.A.value]
- }
-
- toString() {
- return Utility.printLinearColor(this)
- }
-}
-
-/** @param {ObjectEntity} entity */
-function nodeColor(entity) {
- switch (entity.getType()) {
- case Configuration.paths.materialExpressionConstant2Vector:
- case Configuration.paths.materialExpressionConstant3Vector:
- case Configuration.paths.materialExpressionConstant4Vector:
- return Configuration.nodeColors.yellow
- case Configuration.paths.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
- ? Configuration.nodeColors.green
- : Configuration.nodeColors.blue
- case Configuration.paths.niagaraNodeFunctionCall:
- return Configuration.nodeColors.darkerBlue
- case Configuration.paths.dynamicCast:
- return Configuration.nodeColors.turquoise
- case Configuration.paths.inputDebugKey:
- case Configuration.paths.inputKey:
- return Configuration.nodeColors.red
- case Configuration.paths.createDelegate:
- case Configuration.paths.enumLiteral:
- case Configuration.paths.makeArray:
- case Configuration.paths.makeMap:
- case Configuration.paths.materialGraphNode:
- case Configuration.paths.select:
- return Configuration.nodeColors.green
- case Configuration.paths.executionSequence:
- case Configuration.paths.ifThenElse:
- case Configuration.paths.macro:
- case Configuration.paths.multiGate:
- return Configuration.nodeColors.gray
- case Configuration.paths.functionEntry:
- case Configuration.paths.functionResult:
- return Configuration.nodeColors.violet
- case Configuration.paths.timeline:
- return Configuration.nodeColors.yellow
- }
- if (entity.switchTarget()) {
- return Configuration.nodeColors.lime
- }
- if (entity.isEvent()) {
- return Configuration.nodeColors.red
- }
- if (entity.isComment()) {
- return (entity.CommentColor ? entity.CommentColor : LinearColorEntity.getWhite())
- .toDimmedColor()
- .toCSSRGBValues()
- }
- const pcgSubobject = entity.getPcgSubobject();
- if (pcgSubobject) {
- if (pcgSubobject.NodeTitleColor) {
- return pcgSubobject.NodeTitleColor.toDimmedColor(0.1).toCSSRGBValues()
- }
- switch (entity.PCGNode?.getName(true)) {
- case "Branch":
- case "Select":
- return Configuration.nodeColors.intenseGreen
- }
- }
- if (entity.bIsPureFunc) {
- return Configuration.nodeColors.green
- }
- return Configuration.nodeColors.blue
-}
-
class SVGIcon {
static arrayPin = x`
@@ -3913,6 +1327,2612 @@ class SVGIcon {
`
}
+class Reply {
+
+ /**
+ * @template T
+ * @param {Number} position
+ * @param {T} value
+ * @param {PathNode} bestPath
+ * @returns {Result}
+ */
+ static makeSuccess(position, value, bestPath = null, bestPosition = 0) {
+ return {
+ status: true,
+ value: value,
+ position: position,
+ bestParser: bestPath,
+ bestPosition: bestPosition,
+ }
+ }
+
+ /**
+ * @param {PathNode} bestPath
+ * @returns {Result}
+ */
+ static makeFailure(position = 0, bestPath = null, bestPosition = 0) {
+ return {
+ status: false,
+ value: null,
+ position,
+ bestParser: bestPath,
+ bestPosition: bestPosition,
+ }
+ }
+
+ /** @param {Parsernostrum} parsernostrum */
+ static makeContext(parsernostrum = null, input = "") {
+ return /** @type {Context} */({
+ parsernostrum,
+ input,
+ highlighted: null,
+ })
+ }
+
+ static makePathNode(parser, index = 0, previous = null) {
+ return /** @type {PathNode} */({
+ parent: previous,
+ current: parser,
+ index,
+ })
+ }
+}
+
+/** @template T */
+class Parser {
+
+ static indentation = " "
+ static highlight = "Last valid parser"
+
+ /** @type {(new (...args: any) => Parser) & typeof Parser} */
+ Self
+
+ /** @param {String} value */
+ static frame(value, label = "", indentation = "") {
+ label = value ? "[ " + label + " ]" : "";
+ let rows = value.split("\n");
+ const width = Math.max(...rows.map(r => r.length));
+ const rightPadding = width < label.length ? " ".repeat(label.length - width) : "";
+ for (let i = 0; i < rows.length; ++i) {
+ rows[i] =
+ indentation
+ + "| "
+ + rows[i]
+ + " ".repeat(width - rows[i].length)
+ + rightPadding
+ + " |";
+ }
+ if (label.length < width) {
+ label = label + "─".repeat(width - label.length);
+ }
+ const rowA = "┌─" + label + "─┐";
+ const rowB = indentation + "└─" + "─".repeat(label.length) + "─┘";
+ rows = [rowA, ...rows, rowB];
+ return rows.join("\n")
+ }
+
+ /**
+ * @param {PathNode} path
+ * @param {Number} index
+ * @returns {PathNode}
+ */
+ makePath(path, index) {
+ return { current: this, parent: path, index }
+ }
+
+ /**
+ * @param {Context} context
+ * @param {PathNode} path
+ */
+ isHighlighted(context, path) {
+ if (context.highlighted instanceof Parser) {
+ return context.highlighted === this
+ }
+ if (!context.highlighted || !path?.current) {
+ return false
+ }
+ let a, b;
+ for (
+ a = path,
+ b = /** @type {PathNode} */(context.highlighted);
+ a.current && b.current;
+ a = a.parent,
+ b = b.parent
+ ) {
+ if (a.current !== b.current || a.index !== b.index) {
+ return false
+ }
+ }
+ return !a.current && !b.current
+ }
+
+ /** @param {PathNode?} path */
+ isVisited(path) {
+ if (!path) {
+ return false
+ }
+ for (path = path.parent; path != null; path = path.parent) {
+ if (path.current === this) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * @param {Context} context
+ * @param {Number} position
+ * @param {PathNode} path
+ * @param {Number} index
+ * @returns {Result}
+ */
+ parse(context, position, path, index) {
+ return null
+ }
+
+ /** @param {PathNode} path */
+ toString(context = Reply.makeContext(null, ""), indentation = "", path = null, index = 0) {
+ path = this.makePath(path, index);
+ if (this.isVisited(path)) {
+ return "<...>"
+ }
+ const isVisited = this.isVisited(path);
+ const isHighlighted = this.isHighlighted(context, path);
+ let result = isVisited ? "<...>" : this.doToString(context, isHighlighted ? "" : indentation, path, index);
+ if (isHighlighted) {
+ /** @type {String[]} */
+ result = Parser.frame(result, Parser.highlight, indentation);
+ }
+ return result
+ }
+
+ /**
+ * @protected
+ * @param {Context} context
+ * @param {String} indentation
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ doToString(context, indentation, path, index) {
+ return `${this.constructor.name} does not implement toString()`
+ }
+}
+
+/** @template {String} T */
+class StringParser extends Parser {
+
+ #value
+ get value() {
+ return this.#value
+ }
+
+ /** @param {T} value */
+ constructor(value) {
+ super();
+ this.#value = value;
+ }
+
+ /**
+ * @param {Context} context
+ * @param {Number} position
+ * @param {PathNode} path
+ * @param {Number} index
+ * @returns {Result}
+ */
+ parse(context, position, path, index) {
+ path = this.makePath(path, index);
+ const end = position + this.#value.length;
+ const value = context.input.substring(position, end);
+ const result = this.#value === value
+ ? Reply.makeSuccess(end, this.#value, path, end)
+ : Reply.makeFailure();
+ return result
+ }
+
+ /**
+ * @protected
+ * @param {Context} context
+ * @param {String} indentation
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ doToString(context, indentation, path, index) {
+ return `"${this.value.replaceAll("\n", "\\n").replaceAll('"', '\\"')}"`
+ }
+}
+
+/** @extends Parser<""> */
+class SuccessParser extends Parser {
+
+ static instance = new SuccessParser()
+
+ /**
+ * @param {Context} context
+ * @param {Number} position
+ * @param {PathNode} path
+ * @param {Number} index
+ * @returns {Result<"">}
+ */
+ parse(context, position, path, index) {
+ path = this.makePath(path, index);
+ return Reply.makeSuccess(position, "", path, 0)
+ }
+
+ /**
+ * @protected
+ * @param {Context} context
+ * @param {String} indentation
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ doToString(context, indentation, path, index) {
+ return ""
+ }
+}
+
+/**
+ * @template {any[]} T
+ * @typedef {T extends [infer A] ? A
+ * : T extends [infer A, ...infer B] ? A | Union
+ * : never
+ * } Union
+ */
+
+/**
+ * @template {any[]} T
+ * @extends Parser>
+ */
+class AlternativeParser extends Parser {
+
+ #parsers
+ get parsers() {
+ return this.#parsers
+ }
+
+ /** @param {Parser[]} parsers */
+ constructor(...parsers) {
+ super();
+ this.#parsers = parsers;
+ }
+
+ /**
+ * @param {Context} context
+ * @param {Number} position
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ parse(context, position, path, index) {
+ path = this.makePath(path, index);
+ const result = /** @type {Result>} */(Reply.makeSuccess(0, ""));
+ for (let i = 0; i < this.#parsers.length; ++i) {
+ const outcome = this.#parsers[i].parse(context, position, path, i);
+ if (outcome.bestPosition > result.bestPosition) {
+ result.bestParser = outcome.bestParser;
+ result.bestPosition = outcome.bestPosition;
+ }
+ if (outcome.status) {
+ result.value = outcome.value;
+ result.position = outcome.position;
+ return result
+ }
+ }
+ result.status = false;
+ result.value = null;
+ return result
+ }
+
+ /**
+ * @protected
+ * @param {Context} context
+ * @param {String} indentation
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ doToString(context, indentation, path, index) {
+ // Short syntax for optional parser
+ if (this.#parsers.length === 2 && this.#parsers[1] instanceof SuccessParser) {
+ let result = this.#parsers[0].toString(context, indentation, path, 0);
+ if (!(this.#parsers[0] instanceof StringParser)) {
+ result = "<" + result + ">";
+ }
+ result += "?";
+ return result
+ }
+ const deeperIndentation = indentation + Parser.indentation;
+ let result = "ALT<\n"
+ + deeperIndentation
+ + this.#parsers
+ .map((parser, i) => parser.toString(
+ context,
+ deeperIndentation + " ".repeat(i === 0 ? 0 : Parser.indentation.length - 2),
+ path,
+ i,
+ ))
+ .join("\n" + deeperIndentation + "| ")
+ + "\n" + indentation + ">";
+ return result
+ }
+}
+
+/**
+ * @template S
+ * @template T
+ * @extends Parser
+ */
+class ChainedParser extends Parser {
+
+ #parser
+ get parser() {
+ return this.#parser
+ }
+
+ #fn
+
+ /**
+ * @param {Parser} parser
+ * @param {(v: S, input: String, position: Number) => Parsernostrum} chained
+ */
+ constructor(parser, chained) {
+ super();
+ this.#parser = parser;
+ this.#fn = chained;
+ }
+
+ /**
+ * @param {Context} context
+ * @param {Number} position
+ * @param {PathNode} path
+ * @param {Number} index
+ * @returns {Result}
+ */
+ parse(context, position, path, index) {
+ path = this.makePath(path, index);
+ const outcome = this.#parser.parse(context, position, path, 0);
+ if (!outcome.status) {
+ // @ts-expect-error
+ return outcome
+ }
+ const result = this.#fn(outcome.value, context.input, outcome.position)
+ .getParser()
+ .parse(context, outcome.position, path, 0);
+ if (outcome.bestPosition > result.bestPosition) {
+ result.bestParser = outcome.bestParser;
+ result.bestPosition = outcome.bestPosition;
+ }
+ return result
+ }
+
+ /**
+ * @protected
+ * @param {Context} context
+ * @param {String} indentation
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ doToString(context, indentation, path, index) {
+ const result = this.#parser.toString(context, indentation, path, 0) + " => chained";
+ return result
+ }
+}
+
+/** @extends Parser */
+class FailureParser extends Parser {
+
+ static instance = new FailureParser()
+
+ /**
+ * @param {Context} context
+ * @param {Number} position
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ parse(context, position, path, index) {
+ return Reply.makeFailure()
+ }
+
+ /**
+ * @protected
+ * @param {Context} context
+ * @param {String} indentation
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ doToString(context, indentation, path, index) {
+ return ""
+ }
+}
+
+/**
+ * @template T
+ * @extends Parser
+ */
+class Label extends Parser {
+
+ #parser
+ get parser() {
+ return this.#parser
+ }
+
+ #label = ""
+
+ /**
+ * @param {Parser} parser
+ * @param {String} label
+ */
+ constructor(parser, label) {
+ super();
+ this.#parser = parser;
+ this.#label = label;
+ }
+
+ /**
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ makePath(path, index) {
+ return path // Label does not alter the path
+ }
+
+ /**
+ * @param {Context} context
+ * @param {Number} position
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ parse(context, position, path, index) {
+ this.parse = this.#parser.parse.bind(this.#parser);
+ return this.parse(context, position, path, index)
+ }
+
+ /**
+ * @protected
+ * @param {Context} context
+ * @param {String} indentation
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ doToString(context, indentation, path, index) {
+ let result = this.#parser.toString(context, "", path, index);
+ result = Parser.frame(result, this.#label, indentation);
+ return result
+ }
+}
+
+/**
+ * @template T
+ * @extends Parser
+ */
+class LazyParser extends Parser {
+
+ #parser
+
+ /** @type {Parser} */
+ #resolvedPraser
+
+ /** @param {() => Parsernostrum} parser */
+ constructor(parser) {
+ super();
+ this.#parser = parser;
+ }
+
+ /**
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ makePath(path, index) {
+ return path
+ }
+
+ /**
+ * @param {Context} context
+ * @param {PathNode} path
+ */
+ isHighlighted(context, path) {
+ if (super.isHighlighted(context, path)) {
+ // If LazyParser is highlighted, then highlight its child
+ const childrenPath = { parent: path, parser: this.#resolvedPraser, index: 0 };
+ context.highlighted = context.highlighted instanceof Parser ? this.#resolvedPraser : childrenPath;
+ }
+ return false
+ }
+
+ resolve() {
+ if (!this.#resolvedPraser) {
+ this.#resolvedPraser = this.#parser().getParser();
+ }
+ return this.#resolvedPraser
+ }
+
+ /**
+ * @param {Context} context
+ * @param {Number} position
+ * @param {PathNode} path
+ * @param {Number} index
+ * @returns {Result}
+ */
+ parse(context, position, path, index) {
+ this.resolve();
+ this.parse = this.#resolvedPraser.parse.bind(this.#resolvedPraser);
+ return this.parse(context, position, path, index)
+ }
+
+ /**
+ * @protected
+ * @param {Context} context
+ * @param {String} indentation
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ doToString(context, indentation, path, index) {
+ this.resolve();
+ this.doToString = this.#resolvedPraser.toString.bind(this.#resolvedPraser);
+ return this.doToString(context, indentation, path, index)
+ }
+}
+
+/** @extends Parser<""> */
+class Lookahead extends Parser {
+
+ #parser
+ get parser() {
+ return this.#parser
+ }
+
+ #type
+ get type() {
+ return this.#type
+ }
+
+ /**
+ * @readonly
+ * @enum {String}
+ */
+ static Type = {
+ NEGATIVE_AHEAD: "?!",
+ NEGATIVE_BEHIND: "?}
+ */
+ parse(context, position, path, index) {
+ path = this.makePath(path, index);
+ let result = this.#parser.parse(context, position, path, 0);
+ result = result.status == (this.#type === Lookahead.Type.POSITIVE_AHEAD)
+ ? Reply.makeSuccess(position, "", path, position)
+ : Reply.makeFailure();
+ return result
+ }
+
+ /**
+ * @protected
+ * @param {Context} context
+ * @param {String} indentation
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ doToString(context, indentation, path, index) {
+ return "(" + this.#type + this.#parser.toString(context, indentation, path, 0) + ")"
+ }
+}
+
+/**
+ * @template T
+ * @extends Parser
+ */
+class RegExpParser extends Parser {
+
+ /** @type {RegExp} */
+ #regexp
+ get regexp() {
+ return this.#regexp
+ }
+ /** @type {RegExp} */
+ #anchoredRegexp
+ #matchMapper
+
+ static #createEscapeable = character => String.raw`[^${character}\\]*(?:\\.[^${character}\\]*)*`
+ static #numberRegex = /[-\+]?(?:\d*\.)?\d+/
+ static common = {
+ number: new RegExp(this.#numberRegex.source + String.raw`(?!\.)`),
+ numberInteger: /[\-\+]?\d+(?!\.\d)/,
+ numberNatural: /\d+/,
+ numberExponential: new RegExp(this.#numberRegex.source + String.raw`(?:[eE][\+\-]?\d+)?(?!\.)`),
+ numberUnit: /\+?(?:0(?:\.\d+)?|1(?:\.0+)?)(?![\.\d])/,
+ numberByte: /0*(?:25[0-5]|2[0-4]\d|1?\d?\d)(?!\d|\.)/,
+ whitespace: /\s+/,
+ whitespaceOpt: /\s*/,
+ whitespaceInline: /[^\S\n]+/,
+ whitespaceInlineOpt: /[^\S\n]*/,
+ whitespaceMultiline: /\s*?\n\s*/,
+ doubleQuotedString: new RegExp(`"(${this.#createEscapeable('"')})"`),
+ singleQuotedString: new RegExp(`'(${this.#createEscapeable("'")})'`),
+ backtickQuotedString: new RegExp("`(" + this.#createEscapeable("`") + ")`"),
+ }
+
+ /**
+ * @param {RegExp} regexp
+ * @param {(match: RegExpExecArray) => T} matchMapper
+ */
+ constructor(regexp, matchMapper) {
+ super();
+ this.#regexp = regexp;
+ this.#anchoredRegexp = new RegExp(`^(?:${regexp.source})`, regexp.flags);
+ this.#matchMapper = matchMapper;
+ }
+
+ /**
+ * @param {Context} context
+ * @param {Number} position
+ * @param {PathNode} path
+ * @param {Number} index
+ * @returns {Result}
+ */
+ parse(context, position, path, index) {
+ path = this.makePath(path, index);
+ const match = this.#anchoredRegexp.exec(context.input.substring(position));
+ if (match) {
+ position += match[0].length;
+ }
+ const result = match
+ ? Reply.makeSuccess(position, this.#matchMapper(match), path, position)
+ : Reply.makeFailure();
+ return result
+ }
+
+ /**
+ * @protected
+ * @param {Context} context
+ * @param {String} indentation
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ doToString(context, indentation, path, index) {
+ let result = "/" + this.#regexp.source + "/";
+ const shortname = Object.entries(RegExpParser.common).find(([k, v]) => v.source === this.#regexp.source)?.[0];
+ if (shortname) {
+ result = "P." + shortname;
+ }
+ return result
+ }
+}
+
+/**
+ * @template S
+ * @template T
+ * @extends Parser
+ */
+class MapParser extends Parser {
+
+ #parser
+ get parser() {
+ return this.#parser
+ }
+
+ #mapper
+ get mapper() {
+ return this.#mapper
+ }
+
+ /**
+ * @param {Parser} parser
+ * @param {(v: S) => T} mapper
+ */
+ constructor(parser, mapper) {
+ super();
+ this.#parser = parser;
+ this.#mapper = mapper;
+ }
+
+ /**
+ * @param {Context} context
+ * @param {PathNode} path
+ */
+ isHighlighted(context, path) {
+ if (super.isHighlighted(context, path)) {
+ // If MapParser is highlighted, then highlight its child
+ const childrenPath = { parent: path, parser: this.#parser, index: 0 };
+ context.highlighted = context.highlighted instanceof Parser ? this.#parser : childrenPath;
+ }
+ return false
+ }
+
+ /**
+ * @param {Context} context
+ * @param {Number} position
+ * @param {PathNode} path
+ * @param {Number} index
+ * @returns {Result}
+ */
+ parse(context, position, path, index) {
+ path = this.makePath(path, index);
+ // @ts-expect-error
+ const result = /** @type {Result} */(this.#parser.parse(context, position, path, 0));
+ if (result.status) {
+ // @ts-expect-error
+ result.value = this.#mapper(result.value);
+ }
+ return result
+ }
+
+ /**
+ * @protected
+ * @param {Context} context
+ * @param {String} indentation
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ doToString(context, indentation, path, index) {
+ let result = this.#parser.toString(context, indentation, path, 0);
+ if (this.#parser instanceof RegExpParser) {
+ if (Object.values(RegExpParser.common).includes(this.#parser.regexp)) {
+ if (
+ this.#parser.regexp === RegExpParser.common.numberInteger
+ && this.#mapper === /** @type {(v: any) => BigInt} */(BigInt)
+ ) {
+ return "P.numberBigInteger"
+ }
+ return result
+ }
+ }
+ let serializedMapper = this.#mapper.toString();
+ if (serializedMapper.length > 60 || serializedMapper.includes("\n")) {
+ serializedMapper = "(...) => { ... }";
+ }
+ result += ` -> map<${serializedMapper}>`;
+ return result
+ }
+}
+
+/** @extends {RegExpParser} */
+class RegExpArrayParser extends RegExpParser {
+
+ /** @param {RegExpExecArray} match */
+ static #mapper = match => match
+
+ /** @param {RegExp} regexp */
+ constructor(regexp) {
+ super(regexp, RegExpArrayParser.#mapper);
+ }
+}
+
+/** @extends {RegExpParser} */
+class RegExpValueParser extends RegExpParser {
+
+ /** @param {RegExp} regexp */
+ constructor(regexp, group = 0) {
+ super(regexp, match => match[group]);
+ }
+}
+
+/**
+ * @template {any[]} T
+ * @extends Parser
+ */
+class SequenceParser extends Parser {
+
+ #parsers
+ get parsers() {
+ return this.#parsers
+ }
+
+ /** @param {Parser[]} parsers */
+ constructor(...parsers) {
+ super();
+ this.#parsers = parsers;
+ }
+
+ /**
+ * @param {Context} context
+ * @param {Number} position
+ * @param {PathNode} path
+ * @param {Number} index
+ * @returns {Result}
+ */
+ parse(context, position, path, index) {
+ path = this.makePath(path, index);
+ const value = /** @type {ParserValue} */(new Array(this.#parsers.length));
+ const result = Reply.makeSuccess(position, value);
+ for (let i = 0; i < this.#parsers.length; ++i) {
+ const outcome = this.#parsers[i].parse(context, result.position, path, i);
+ if (outcome.bestPosition > result.bestPosition) {
+ result.bestParser = outcome.bestParser;
+ result.bestPosition = outcome.bestPosition;
+ }
+ if (!outcome.status) {
+ result.status = false;
+ result.value = null;
+ break
+ }
+ result.value[i] = outcome.value;
+ result.position = outcome.position;
+ }
+ return result
+ }
+
+ /**
+ * @protected
+ * @param {Context} context
+ * @param {String} indentation
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ doToString(context, indentation, path, index) {
+ const deeperIndentation = indentation + Parser.indentation;
+ const result = "SEQ<\n"
+ + deeperIndentation
+ + this.#parsers
+ .map((parser, index) => parser.toString(context, deeperIndentation, path, index))
+ .join("\n" + deeperIndentation)
+ + "\n" + indentation + ">";
+ return result
+ }
+}
+
+/**
+ * @template T
+ * @extends Parser
+ */
+class TimesParser extends Parser {
+
+ #parser
+ get parser() {
+ return this.#parser
+ }
+
+ #min
+ get min() {
+ return this.#min
+ }
+
+ #max
+ get max() {
+ return this.#max
+ }
+
+ /** @param {Parser} parser */
+ constructor(parser, min = 0, max = Number.POSITIVE_INFINITY) {
+ super();
+ if (min > max) {
+ throw new Error("Min is greater than max")
+ }
+ this.#parser = parser;
+ this.#min = min;
+ this.#max = max;
+ }
+
+ /**
+ * @param {Context} context
+ * @param {Number} position
+ * @param {PathNode} path
+ * @param {Number} index
+ * @returns {Result}
+ */
+ parse(context, position, path, index) {
+ path = this.makePath(path, index);
+ const value = /** @type {ParserValue[]} */([]);
+ const result = Reply.makeSuccess(position, value, path);
+ for (let i = 0; i < this.#max; ++i) {
+ const outcome = this.#parser.parse(context, result.position, path, 0);
+ if (outcome.bestPosition > result.bestPosition) {
+ result.bestParser = outcome.bestParser;
+ result.bestPosition = outcome.bestPosition;
+ }
+ if (!outcome.status) {
+ if (i < this.#min) {
+ result.status = false;
+ result.value = null;
+ }
+ break
+ }
+ // @ts-expect-error
+ result.value.push(outcome.value);
+ result.position = outcome.position;
+ }
+ // @ts-expect-error
+ return result
+ }
+
+ /**
+ * @protected
+ * @param {Context} context
+ * @param {String} indentation
+ * @param {PathNode} path
+ * @param {Number} index
+ */
+ doToString(context, indentation, path, index) {
+ let result = this.parser.toString(context, indentation, path, 0);
+ const serialized =
+ this.#min === 0 && this.#max === 1 ? "?"
+ : this.#min === 0 && this.#max === Number.POSITIVE_INFINITY ? "*"
+ : this.#min === 1 && this.#max === Number.POSITIVE_INFINITY ? "+"
+ : "{"
+ + this.#min
+ + (this.#min !== this.#max ? "," + this.#max : "")
+ + "}";
+ result += serialized;
+ return result
+ }
+}
+
+/** @template T */
+class Parsernostrum {
+
+ #parser
+
+ /** @type {(new (parser: Parser) => Parsernostrum) & typeof Parsernostrum} */
+ Self
+
+ static lineColumnFromOffset(string, offset) {
+ const lines = string.substring(0, offset).split('\n');
+ const line = lines.length;
+ const column = lines[lines.length - 1].length + 1;
+ return { line, column }
+ }
+ /** @param {[any, ...any] | RegExpExecArray} param0 */
+ static #firstElementGetter = ([v, _]) => v
+ /** @param {[any, any, ...any] | RegExpExecArray} param0 */
+ static #secondElementGetter = ([_, v]) => v
+ static #arrayFlatter = ([first, rest]) => [first, ...rest]
+ /**
+ * @template T
+ * @param {T} v
+ * @returns {T extends Array ? String : T}
+ */
+ // @ts-expect-error
+ static #joiner = v => v instanceof Array ? v.join("") : v
+
+ // Prefedined parsers
+
+ /** Parser accepting any valid decimal, possibly signed number */
+ static number = this.reg(RegExpParser.common.number).map(Number)
+
+ /** Parser accepting any digits only number */
+ static numberInteger = this.reg(RegExpParser.common.numberInteger).map(Number)
+
+ /** Parser accepting any digits only number and returns a BigInt */
+ // @ts-expect-error
+ static numberBigInteger = this.reg(this.numberInteger.getParser().parser.regexp).map(BigInt)
+
+ /** Parser accepting any digits only number */
+ static numberNatural = this.reg(RegExpParser.common.numberNatural).map(Number)
+
+ /** Parser accepting any valid decimal, possibly signed, possibly in the exponential form number */
+ static numberExponential = this.reg(RegExpParser.common.numberExponential).map(Number)
+
+ /** Parser accepting any valid decimal number between 0 and 1 */
+ static numberUnit = this.reg(RegExpParser.common.numberUnit).map(Number)
+
+ /** Parser accepting any integer between 0 and 255 */
+ static numberByte = this.reg(RegExpParser.common.numberByte).map(Number)
+
+ /** Parser accepting whitespace */
+ static whitespace = this.reg(RegExpParser.common.whitespace)
+
+ /** Parser accepting whitespace */
+ static whitespaceOpt = this.reg(RegExpParser.common.whitespaceOpt)
+
+ /** Parser accepting whitespace that spans on a single line */
+ static whitespaceInline = this.reg(RegExpParser.common.whitespaceInline)
+
+ /** Parser accepting whitespace that spans on a single line */
+ static whitespaceInlineOpt = this.reg(RegExpParser.common.whitespaceInlineOpt)
+
+ /** Parser accepting whitespace that contains a list a newline */
+ static whitespaceMultiline = this.reg(RegExpParser.common.whitespaceMultiline)
+
+ /** Parser accepting a double quoted string and returns the content */
+ static doubleQuotedString = this.reg(RegExpParser.common.doubleQuotedString, 1)
+
+ /** Parser accepting a single quoted string and returns the content */
+ static singleQuotedString = this.reg(RegExpParser.common.singleQuotedString, 1)
+
+ /** Parser accepting a backtick quoted string and returns the content */
+ static backtickQuotedString = this.reg(RegExpParser.common.backtickQuotedString, 1)
+
+ /** @param {Parser} parser */
+ constructor(parser, optimized = false) {
+ this.#parser = parser;
+ }
+
+ /** @param {PathNode} path */
+ static #simplifyPath(path) {
+ /** @type {PathNode[]} */
+ const array = [];
+ while (path) {
+ array.push(path);
+ path = path.parent;
+ }
+ array.reverse();
+ /** @type {Map} */
+ let visited = new Map();
+ for (let i = 1; i < array.length; ++i) {
+ const existing = visited.get(array[i].current);
+ if (existing !== undefined) {
+ if (array[i + 1]) {
+ array[i + 1].parent = array[existing];
+ }
+ visited = new Map([...visited.entries()].filter(([parser, index]) => index <= existing || index > i));
+ visited.set(array[i].current, existing);
+ array.splice(existing + 1, i - existing);
+ i = existing;
+ } else {
+ visited.set(array[i].current, i);
+ }
+ }
+ return array[array.length - 1]
+ }
+
+ getParser() {
+ return this.#parser
+ }
+
+ /** @param {String} input */
+ run(input) {
+ const result = this.#parser.parse(Reply.makeContext(this, input), 0, Reply.makePathNode(), 0);
+ if (result.position !== input.length) {
+ result.status = false;
+ }
+ return /** @type {Result} */(result)
+ }
+
+ /**
+ * @param {String} input
+ * @throws {Error} when the parser fails to match
+ */
+ parse(input, printParser = true) {
+ const result = this.run(input);
+ if (result.status) {
+ return result.value
+ }
+ const chunkLength = 60;
+ const chunkRange = /** @type {[Number, Number]} */(
+ [Math.ceil(chunkLength / 2), Math.floor(chunkLength / 2)]
+ );
+ const position = Parsernostrum.lineColumnFromOffset(input, result.bestPosition);
+ let bestPosition = result.bestPosition;
+ const inlineInput = input.replaceAll(
+ /^(\s)+|\s{6,}|\s*?\n\s*/g,
+ (m, startingSpace, offset) => {
+ let replaced = startingSpace ? "..." : " ... ";
+ if (offset <= result.bestPosition) {
+ if (result.bestPosition < offset + m.length) {
+ bestPosition -= result.bestPosition - offset;
+ } else {
+ bestPosition -= m.length - replaced.length;
+ }
+ }
+ return replaced
+ }
+ );
+ const string = inlineInput.substring(0, chunkLength).trimEnd();
+ const leadingWhitespaceLength = Math.min(
+ input.substring(result.bestPosition - chunkRange[0]).match(/^\s*/)[0].length,
+ chunkRange[0] - 1,
+ );
+ let offset = Math.min(bestPosition, chunkRange[0] - leadingWhitespaceLength);
+ chunkRange[0] = Math.max(0, bestPosition - chunkRange[0]) + leadingWhitespaceLength;
+ chunkRange[1] = Math.min(input.length, chunkRange[0] + chunkLength);
+ let segment = inlineInput.substring(...chunkRange);
+ if (chunkRange[0] > 0) {
+ segment = "..." + segment;
+ offset += 3;
+ }
+ if (chunkRange[1] < inlineInput.length - 1) {
+ segment = segment + "...";
+ }
+ const bestParser = this.toString(Parser.indentation, true, Parsernostrum.#simplifyPath(result.bestParser));
+ throw new Error(
+ `Could not parse: ${string}\n\n`
+ + `Input: ${segment}\n`
+ + " " + " ".repeat(offset)
+ + `^ From here (line: ${position.line}, `
+ + `column: ${position.column}, `
+ + `offset: ${result.bestPosition})${result.bestPosition === input.length ? ", end of string" : ""}\n`
+ + (printParser
+ ? "\n"
+ + (result.bestParser ? "Last valid parser matched:" : "No parser matched:")
+ + bestParser
+ + "\n"
+ : ""
+ )
+ )
+ }
+
+ // Parsers
+
+ /**
+ * @template {String} S
+ * @param {S} value
+ */
+ static str(value) {
+ return new this(new StringParser(value))
+ }
+
+ /** @param {RegExp} value */
+ static reg(value, group = 0) {
+ return new this(new RegExpValueParser(value, group))
+ }
+
+ /** @param {RegExp} value */
+ static regArray(value) {
+ return new this(new RegExpArrayParser(value))
+ }
+
+ static success() {
+ return new this(SuccessParser.instance)
+ }
+
+ static failure() {
+ return new this(FailureParser.instance)
+ }
+
+ // Combinators
+
+ /**
+ * @template {Parsernostrum[]} P
+ * @param {P} parsers
+ * @returns {Parsernostrum>}
+ */
+ static seq(...parsers) {
+ return new this(new SequenceParser(...parsers.map(p => p.getParser())))
+ }
+
+ /**
+ * @template {Parsernostrum[]} P
+ * @param {P} parsers
+ * @returns {Parsernostrum>>}
+ */
+ static alt(...parsers) {
+ return new this(new AlternativeParser(...parsers.map(p => p.getParser())))
+ }
+
+ /**
+ * @template {Parsernostrum} P
+ * @param {P} parser
+ */
+ static lookahead(parser) {
+ return new this(new Lookahead(parser.getParser(), Lookahead.Type.POSITIVE_AHEAD))
+ }
+
+ /**
+ * @template {Parsernostrum} P
+ * @param {() => P} parser
+ * @returns {Parsernostrum>}
+ */
+ static lazy(parser) {
+ return new this(new LazyParser(parser))
+ }
+
+ /** @param {Number} min */
+ times(min, max = min) {
+ return new Parsernostrum(new TimesParser(this.#parser, min, max))
+ }
+
+ many() {
+ return this.times(0, Number.POSITIVE_INFINITY)
+ }
+
+ /** @param {Number} n */
+ atLeast(n) {
+ return this.times(n, Number.POSITIVE_INFINITY)
+ }
+
+ /** @param {Number} n */
+ atMost(n) {
+ return this.times(0, n)
+ }
+
+ /**
+ * @param {any} emptyResult
+ * @returns {Parsernostrum}
+ */
+ opt(emptyResult = "") {
+ let success = Parsernostrum.success();
+ if (emptyResult !== "") {
+ success = success.map(() => emptyResult);
+ }
+ // @ts-expect-error
+ return Parsernostrum.alt(this, success)
+ }
+
+ /**
+ * @template {Parsernostrum} P
+ * @param {P} separator
+ */
+ sepBy(separator, atLeast = 1, allowTrailing = false) {
+ let result = Parsernostrum.seq(
+ this,
+ Parsernostrum.seq(separator, this).map(Parsernostrum.#secondElementGetter).atLeast(atLeast - 1),
+ ...(allowTrailing ? [separator.opt([])] : [])
+ ).map(Parsernostrum.#arrayFlatter);
+ if (atLeast === 0) {
+ result = result.opt([]);
+ }
+ return result
+ }
+
+ skipSpace() {
+ return Parsernostrum.seq(this, Parsernostrum.whitespaceOpt).map(Parsernostrum.#firstElementGetter)
+ }
+
+ /**
+ * @template R
+ * @param {(v: T) => R} fn
+ * @returns {Parsernostrum}
+ */
+ map(fn) {
+ return new Parsernostrum(new MapParser(this.#parser, fn))
+ }
+
+ /**
+ * @template {Parsernostrum} P
+ * @param {(v: T, input: String, position: Number) => P} fn
+ * @returns {P}
+ */
+ chain(fn) {
+ // @ts-expect-error
+ return new Parsernostrum(new ChainedParser(this.#parser, fn))
+ }
+
+ /**
+ * @param {(v: T, input: String, position: Number) => boolean} fn
+ * @return {Parsernostrum}
+ */
+ assert(fn) {
+ return this.chain((v, input, position) => fn(v, input, position)
+ ? Parsernostrum.success().map(() => v)
+ : Parsernostrum.failure()
+ )
+ }
+
+ join(value = "") {
+ return this.map(Parsernostrum.#joiner)
+ }
+
+ /** @return {Parsernostrum} */
+ label(value = "") {
+ return new Parsernostrum(new Label(this.#parser, value))
+ }
+
+ /** @param {Parsernostrum | Parser | PathNode} highlight */
+ toString(indentation = "", newline = false, highlight = null) {
+ if (highlight instanceof Parsernostrum) {
+ highlight = highlight.getParser();
+ }
+ const context = Reply.makeContext(this, "");
+ context.highlighted = highlight;
+ const path = Reply.makePathNode();
+ return (newline ? "\n" + indentation : "") + this.#parser.toString(context, indentation, path)
+ }
+}
+
+/** @abstract */
+class IEntity {
+
+ /** @type {(v: String) => String} */
+ static same = v => v
+ /** @type {(entity: IEntity, serialized: String) => String} */
+ static notWrapped = (entity, serialized) => serialized
+ /** @type {(entity: IEntity, serialized: String) => String} */
+ static defaultWrapped = (entity, serialized) => `${entity.#lookbehind}(${serialized})`
+ static wrap = this.defaultWrapped
+ static attributeSeparator = ","
+ static keySeparator = "="
+ /** @type {(k: String) => String} */
+ static printKey = k => k
+ static grammar = Parsernostrum.lazy(() => this.createGrammar())
+ /** @type {P} */
+ static unknownEntityGrammar
+ static unknownEntity
+ /** @type {{ [key: String]: typeof IEntity }} */
+ static attributes = {}
+ /** @type {String | String[]} */
+ static lookbehind = ""
+ /** @type {(type: typeof IEntity) => InstanceType} */
+ static default
+ static nullable = false
+ static ignored = false // Never serialize or deserialize
+ static serialized = false // Value is written and read as string
+ static expected = false // Must be there
+ static inlined = false // The key is a subobject or array and printed as inlined (A.B=123, A(0)=123)
+ /** @type {Boolean} */
+ static quoted // Key is serialized with quotes
+ static silent = false // Do not serialize if default
+ static trailing = false // Add attribute separator after the last attribute when serializing
+
+ /** @type {String[]} */
+ #keys
+ get keys() {
+ return this.#keys ?? Object.keys(this)
+ }
+ set keys(value) {
+ this.#keys = [... new Set(value)];
+ }
+
+ #lookbehind = /** @type {String} */(this.constructor.lookbehind)
+ get lookbehind() {
+ return this.#lookbehind.trim()
+ }
+ set lookbehind(value) {
+ this.#lookbehind = value;
+ }
+
+ #ignored = this.constructor.ignored
+ get ignored() {
+ return this.#ignored
+ }
+ set ignored(value) {
+ this.#ignored = value;
+ }
+
+ #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;
+ }
+ }
+ continue
+ }
+ const attribute = attributes[key];
+ if (attribute.default !== undefined) {
+ this[key] = attribute.default(attribute);
+ continue
+ }
+ }
+ }
+
+ /**
+ * @protected
+ * @returns {P}
+ */
+ static createGrammar() {
+ return this.unknownEntityGrammar
+ }
+
+ static actualClass() {
+ let self = this;
+ while (!self.name) {
+ self = Object.getPrototypeOf(self);
+ }
+ return self
+ }
+
+ static className() {
+ return this.actualClass().name
+ }
+
+ /**
+ * @protected
+ * @template {typeof IEntity} T
+ * @this {T}
+ * @returns {T}
+ */
+ static asUniqueClass() {
+ 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 | 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} attributeName
+ * @param {(v: any) => void} callback
+ */
+ listenAttribute(attributeName, callback) {
+ const descriptor = Object.getOwnPropertyDescriptor(this, attributeName);
+ const setter = descriptor.set;
+ if (setter) {
+ descriptor.set = v => {
+ setter(v);
+ callback(v);
+ };
+ Object.defineProperties(this, { [attributeName]: descriptor });
+ } else if (descriptor.value) {
+
+ Object.defineProperties(this, {
+ ["#" + attributeName]: {
+ value: descriptor.value,
+ writable: true,
+ enumerable: false,
+ },
+ [attributeName]: {
+ enumerable: true,
+ get() {
+ return this["#" + attributeName]
+ },
+ set(v) {
+ callback(v);
+ this["#" + attributeName] = v;
+ },
+ },
+ });
+ }
+ }
+
+ /** @this {IEntity | Array} */
+ doSerialize(
+ insideString = false,
+ indentation = "",
+ Self = /** @type {typeof IEntity} */(this.constructor),
+ printKey = Self.printKey,
+ keySeparator = Self.keySeparator,
+ attributeSeparator = Self.attributeSeparator,
+ wrap = Self.wrap,
+ ) {
+ 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)
+ }
+
+ /** @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
+ }
+
+ equals(other) {
+ if (!(other instanceof IEntity)) {
+ return false
+ }
+ const thisKeys = Object.keys(this);
+ const otherKeys = Object.keys(other);
+ const thisType = /** @type {typeof IEntity} */(this.constructor).actualClass();
+ const otherType = /** @type {typeof IEntity} */(other.constructor).actualClass();
+ if (
+ thisKeys.length !== otherKeys.length
+ || this.lookbehind != other.lookbehind
+ || !(other instanceof thisType) && !(this instanceof otherType)
+ ) {
+ return false
+ }
+ for (let i = 0; i < thisKeys.length; ++i) {
+ const k = thisKeys[i];
+ if (!otherKeys.includes(k)) {
+ return false
+ }
+ const a = this[k];
+ const b = other[k];
+ if (a instanceof IEntity) {
+ if (!a.equals(b)) {
+ return false
+ }
+ } else if (a instanceof Array && b instanceof Array) {
+ if (a.length !== b.length) {
+ return false
+ }
+ for (let j = 0; j < a.length; ++j) {
+ if (!(a[j] instanceof IEntity && a[j].equals(b[j])) && a[j] !== b[j]) {
+ return false
+ }
+ }
+ } else {
+ if (a !== b) {
+ return false
+ }
+ }
+ }
+ return true
+ }
+}
+
+class BooleanEntity extends IEntity {
+
+ static grammar = this.createGrammar()
+ static booleanConverter = {
+ fromAttribute: (value, type) => {
+ },
+ toAttribute: (value, type) => {
+ if (value === true) {
+ return "true"
+ }
+ if (value === false) {
+ return "false"
+ }
+ return ""
+ }
+ }
+
+ #uppercase = true
+ get uppercase() {
+ return this.#uppercase
+ }
+ set uppercase(value) {
+ this.#uppercase = value;
+ }
+
+ static createGrammar() {
+ return /** @type {P} */(
+ Parsernostrum.regArray(/(true)|(True)|(false)|(False)/)
+ .map(v => {
+ const result = (v[1] ?? v[2]) ? new this(true) : new this(false);
+ result.uppercase = (v[2] ?? v[4]) !== undefined;
+ return result
+ })
+ .label("BooleanEntity")
+ )
+ }
+
+ constructor(value = false) {
+ super();
+ this.value = value;
+ }
+
+ serialize(
+ insideString = false,
+ indentation = "",
+ Self = /** @type {typeof IEntity} */(this.constructor),
+ ) {
+ let result = this.value
+ ? this.#uppercase ? "True" : "true"
+ : this.#uppercase ? "False" : "false";
+ if (Self.serialized) {
+ result = `"${result}"`;
+ }
+ return result
+ }
+
+ valueOf() {
+ return this.value
+ }
+}
+
+class ElementFactory {
+
+ /** @type {Map} */
+ static #elementConstructors = new Map()
+
+ /**
+ * @param {String} tagName
+ * @param {IElementConstructor} entityConstructor
+ */
+ static registerElement(tagName, entityConstructor) {
+ ElementFactory.#elementConstructors.set(tagName, entityConstructor);
+ }
+
+ /** @param {String} tagName */
+ static getConstructor(tagName) {
+ return ElementFactory.#elementConstructors.get(tagName)
+ }
+}
+
+/** @template {(typeof IEntity)[]} T */
+class AlternativesEntity extends IEntity {
+
+ /** @type {(typeof IEntity)[]} */
+ static alternatives = []
+
+ static className() {
+ let result = super.className();
+ if (this.alternatives.length) {
+ result += ".accepting(" + this.alternatives.map(v => v.className()).join(", ") + ")";
+ }
+ return result
+ }
+
+ static createGrammar() {
+ const grammars = this.alternatives.map(entity => entity.grammar);
+ if (this.alternatives.length == 0 || grammars.includes(this.unknownEntityGrammar)) {
+ return this.unknownEntityGrammar
+ }
+ return Parsernostrum.alt(...grammars)
+ }
+
+ /**
+ * @template {(typeof IEntity)[]} Types
+ * @param {Types} types
+ */
+ static accepting(...types) {
+ const result = /** @type {typeof AlternativesEntity & { alternatives: Types }} */(
+ this.asUniqueClass()
+ );
+ result.alternatives = types;
+ result.grammar = result.createGrammar();
+ return result
+ }
+}
+
+class Grammar {
+
+ /** @type {String} */
+ // @ts-expect-error
+ static numberRegexSource = Parsernostrum.number.getParser().parser.regexp.source
+
+ static separatedBy = (source, separator, min = 1) =>
+ new RegExp(
+ source + "(?:" + separator + source + ")"
+ + (min === 1 ? "*" : min === 2 ? "+" : `{${min},}`)
+ )
+
+ static Regex = class {
+ static HexDigit = /[0-9a-fA-F]/
+ static InsideString = /(?:[^"\\]|\\.)*/
+ static InsideSingleQuotedString = /(?:[^'\\]|\\.)*/
+ static Integer = /[\-\+]?\d+(?!\d|\.)/
+ static Number = /[-\+]?(?:\d*\.)?\d+(?!\d|\.)/
+ static RealUnit = /\+?(?:0(?:\.\d+)?|1(?:\.0+)?)(?![\.\d])/ // A number between 0 and 1 included
+ static Word = Grammar.separatedBy("[a-zA-Z]", "_")
+ static Symbol = /[a-zA-Z_]\w*/
+ static DotSeparatedSymbols = Grammar.separatedBy(this.Symbol.source, "\\.")
+ static MultipleWordsSymbols = Grammar.separatedBy(this.Symbol.source, "(?:\\.|\\ +)")
+ static PathFragment = Grammar.separatedBy(this.Symbol.source, "[\\.:]")
+ static PathSpaceFragment = Grammar.separatedBy(this.Symbol.source, "[\\.:\\ ]")
+ static Path = new RegExp(`(?:\\/${this.PathFragment.source}){2,}`) // Multiple (2+) /PathFragment
+ }
+
+ /* --- Primitive --- */
+
+ static null = Parsernostrum.reg(/\(\s*\)/).map(() => null)
+ static true = Parsernostrum.reg(/true/i).map(() => true)
+ static false = Parsernostrum.reg(/false/i).map(() => false)
+ static number = Parsernostrum.regArray(
+ // @ts-expect-error
+ new RegExp(`(${Parsernostrum.number.getParser().parser.regexp.source})|(\\+?inf)|(-inf)`)
+ ).map(([_0, n, plusInf, minusInf]) => n ? Number(n) : plusInf ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY)
+ // @ts-expect-error
+ static bigInt = Parsernostrum.reg(new RegExp(Parsernostrum.number.getParser().parser.regexp.source)).map(BigInt)
+ .map(result =>
+ result[2] !== undefined
+ ? Number.POSITIVE_INFINITY
+ : result[3] !== undefined
+ ? Number.NEGATIVE_INFINITY
+ : Number(result[1])
+ )
+ static naturalNumber = Parsernostrum.lazy(() => Parsernostrum.reg(/\d+/).map(Number))
+ static string = Parsernostrum.doubleQuotedString.map(insideString => Utility.unescapeString(insideString))
+
+ /* --- Fragment --- */
+
+ static colorValue = Parsernostrum.numberByte
+ static word = Parsernostrum.reg(Grammar.Regex.Word)
+ static symbol = Parsernostrum.reg(Grammar.Regex.Symbol)
+ static symbolQuoted = Parsernostrum.reg(new RegExp('"(' + Grammar.Regex.Symbol.source + ')"'), 1)
+ static attributeName = Parsernostrum.reg(Grammar.Regex.DotSeparatedSymbols)
+ static attributeNameQuoted = Parsernostrum.reg(new RegExp('"(' + Grammar.Regex.InsideString.source + ')"'), 1)
+ static guid = Parsernostrum.reg(new RegExp(`${Grammar.Regex.HexDigit.source}{32}`))
+ static commaSeparation = Parsernostrum.reg(/\s*,\s*(?!\))/)
+ static commaOrSpaceSeparation = Parsernostrum.reg(/\s*,\s*(?!\))|\s+/)
+ static equalSeparation = Parsernostrum.reg(/\s*=\s*/)
+ static hexColorChannel = Parsernostrum.reg(new RegExp(Grammar.Regex.HexDigit.source + "{2}"))
+
+ /* --- Factory --- */
+
+ /**
+ * @param {typeof IEntity} entityType
+ * @param {String[]} key
+ * @returns {typeof IEntity}
+ */
+ static getAttribute(entityType, [key, ...keys]) {
+ const attribute = entityType?.attributes?.[key];
+ if (!attribute) {
+ return
+ }
+ if (attribute.prototype instanceof AlternativesEntity) {
+ for (const alternative of /** @type {typeof AlternativesEntity} */(attribute).alternatives) {
+ const candidate = this.getAttribute(alternative, keys);
+ if (candidate) {
+ return candidate
+ }
+ }
+ }
+ if (keys.length > 0) {
+ return this.getAttribute(attribute, keys)
+ }
+ return attribute
+ }
+
+ /** @param {typeof IEntity} entityType */
+ static createAttributeGrammar(
+ entityType,
+ attributeNameGrammar = this.attributeName,
+ valueSeparator = this.equalSeparation,
+ handleObjectSet = (values, attributeKey, attributeValue) => { },
+ ) {
+ return Parsernostrum.seq(
+ attributeNameGrammar,
+ valueSeparator,
+ ).chain(([attributeName, _1]) => {
+ const attributeKey = attributeName.split(Configuration.keysSeparator);
+ const attributeValue = this.getAttribute(entityType, attributeKey);
+ const grammar = attributeValue ? attributeValue.grammar : IEntity.unknownEntityGrammar;
+ return grammar.map(attributeValue =>
+ values => {
+ Utility.objectSet(values, attributeKey, attributeValue);
+ handleObjectSet(values, attributeKey, attributeValue);
+ }
+ )
+ })
+ }
+
+ /**
+ * @template {typeof IEntity & (new (...values: any) => InstanceType)} T
+ * @param {T} entityType
+ * @return {Parsernostrum>}
+ */
+ 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(new RegExp(String.raw`(${lookbehind}\s*)\(\s*`), 1),
+ this.createAttributeGrammar(entityType).sepBy(entriesSeparator, minKeys),
+ Parsernostrum.reg(/\s*(,\s*)?\)/, 1), // optional trailing comma
+ )
+ .map(([lookbehind, attributes, trailing]) => {
+ let values = {};
+ if (lookbehind.length) {
+ values["lookbehind"] = lookbehind;
+ }
+ attributes.forEach(attributeSetter => attributeSetter(values));
+ values["trailing"] = trailing !== undefined;
+ return values
+ })
+ // Decide if we accept the entity or not. It is accepted if it doesn't have too many unexpected keys
+ .chain(values => {
+ if (entityType.lookbehind instanceof Array || entityType.lookbehind !== lookbehind) {
+ entityType = entityType.withLookbehind(lookbehind);
+ }
+ const keys = Object.keys(values);
+ 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))
+ })
+ }
+}
+
+class ColorChannelEntity extends IEntity {
+
+ static grammar = this.createGrammar()
+
+ constructor(value = 0) {
+ super();
+ this.value = value;
+ }
+
+ static createGrammar() {
+ return /** @type {P} */(
+ Parsernostrum.number.map(v => new this(v))
+ )
+ }
+
+ serialize(
+ insideString = false,
+ indentation = "",
+ Self = /** @type {typeof IEntity} */(this.constructor),
+ ) {
+ let result = this.value.toFixed(6);
+ if (Self.serialized) {
+ result = `"${result}"`;
+ }
+ return result
+ }
+
+ valueOf() {
+ return this.value
+ }
+}
+
+class LinearColorEntity extends IEntity {
+
+ static attributes = {
+ ...super.attributes,
+ R: ColorChannelEntity.withDefault(),
+ G: ColorChannelEntity.withDefault(),
+ B: ColorChannelEntity.withDefault(),
+ A: ColorChannelEntity.withDefault(type => new type(1)),
+ }
+ static grammar = this.createGrammar()
+
+ #H = new ColorChannelEntity()
+ get H() {
+ return this.#H
+ }
+ set H(value) {
+ this.#H = value;
+ }
+
+ #S = new ColorChannelEntity()
+ get S() {
+ return this.#S
+ }
+ set S(value) {
+ this.#S = value;
+ }
+
+ #V = new ColorChannelEntity()
+ get V() {
+ return this.#V
+ }
+ set V(value) {
+ this.#V = value;
+ }
+
+ constructor(values) {
+ super(values);
+ if (values instanceof Array) {
+ values = {
+ R: values[0] ?? 0,
+ G: values[1] ?? 0,
+ B: values[2] ?? 0,
+ A: values[3] ?? 1,
+ };
+ }
+ /** @type {InstanceType} */ this.R;
+ /** @type {InstanceType} */ this.G;
+ /** @type {InstanceType} */ this.B;
+ /** @type {InstanceType} */ this.A;
+ this.#updateHSV();
+ }
+
+ static createGrammar() {
+ return /** @type {P} */(
+ 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) {
+ return 0
+ } else if (x >= 1) {
+ return 1
+ } else if (x < 0.0031308) {
+ return x * 12.92
+ } else {
+ return Math.pow(x, 1 / 2.4) * 1.055 - 0.055
+ }
+ }
+
+ /** @param {Number} x */
+ static sRGBtoLinear(x) {
+ if (x <= 0) {
+ return 0
+ } else if (x >= 1) {
+ return 1
+ } else if (x < 0.04045) {
+ return x / 12.92
+ } else {
+ return Math.pow((x + 0.055) / 1.055, 2.4)
+ }
+ }
+
+ static getWhite() {
+ return new LinearColorEntity({
+ R: new ColorChannelEntity(1),
+ G: new ColorChannelEntity(1),
+ B: new ColorChannelEntity(1),
+ })
+ }
+
+ static getLinearColorFromHexGrammar() {
+ const hexDigit = /[0-9a-fA-F]/;
+ return Parsernostrum.regArray(new RegExp(
+ "#(" + hexDigit.source + "{2})"
+ + "(" + hexDigit.source + "{2})"
+ + "(" + hexDigit.source + "{2})"
+ + "(" + hexDigit.source + "{2})?"
+ )).map(([m, R, G, B, A]) => new this({
+ R: parseInt(R, 16) / 255,
+ G: parseInt(G, 16) / 255,
+ B: parseInt(B, 16) / 255,
+ A: parseInt(A ?? "FF", 16) / 255,
+ }))
+ }
+
+ static getLinearColorRGBListGrammar() {
+ return Parsernostrum.seq(
+ Parsernostrum.numberByte,
+ Grammar.commaSeparation,
+ Parsernostrum.numberByte,
+ Grammar.commaSeparation,
+ Parsernostrum.numberByte,
+ ).map(([R, _1, G, _3, B]) => new this({
+ R: R / 255,
+ G: G / 255,
+ B: B / 255,
+ A: 1,
+ }))
+ }
+
+ static getLinearColorRGBGrammar() {
+ return Parsernostrum.seq(
+ Parsernostrum.reg(/rgb\s*\(\s*/),
+ this.getLinearColorRGBListGrammar(),
+ Parsernostrum.reg(/\s*\)/)
+ ).map(([_0, linearColor, _2]) => linearColor)
+ }
+
+ static getLinearColorRGBAGrammar() {
+ return Parsernostrum.seq(
+ Parsernostrum.reg(/rgba\s*\(\s*/),
+ this.getLinearColorRGBListGrammar(),
+ Parsernostrum.reg(/\s*\)/)
+ ).map(([_0, linearColor, _2]) => linearColor)
+ }
+
+ static getLinearColorFromAnyFormat() {
+ return Parsernostrum.alt(
+ this.getLinearColorFromHexGrammar(),
+ this.getLinearColorRGBAGrammar(),
+ this.getLinearColorRGBGrammar(),
+ this.getLinearColorRGBListGrammar(),
+ )
+ }
+
+ #updateHSV() {
+ const r = this.R.value;
+ const g = this.G.value;
+ const b = this.B.value;
+ if (Utility.approximatelyEqual(r, g) && Utility.approximatelyEqual(r, b) && Utility.approximatelyEqual(g, b)) {
+ this.S.value = 0;
+ this.V.value = r;
+ return
+ }
+ const max = Math.max(r, g, b);
+ const min = Math.min(r, g, b);
+ const d = max - min;
+ let h;
+ switch (max) {
+ case min:
+ h = 0;
+ break
+ case r:
+ h = (g - b) / d + (g < b ? 6 : 0);
+ break
+ case g:
+ h = (b - r) / d + 2;
+ break
+ case b:
+ h = (r - g) / d + 4;
+ break
+ }
+ h /= 6;
+ this.H.value = h;
+ this.S.value = max == 0 ? 0 : d / max;
+ this.V.value = max;
+ }
+
+ /**
+ * @param {Number} r
+ * @param {Number} g
+ * @param {Number} b
+ * @param {Number} a
+ */
+ setFromRGBA(r, g, b, a = 1) {
+ this.R.value = r;
+ this.G.value = g;
+ this.B.value = b;
+ this.A.value = a;
+ this.#updateHSV();
+ }
+
+ /**
+ * @param {Number} h
+ * @param {Number} s
+ * @param {Number} v
+ * @param {Number} a
+ */
+ setFromHSVA(h, s, v, a = 1) {
+ const i = Math.floor(h * 6);
+ const f = h * 6 - i;
+ const p = v * (1 - s);
+ const q = v * (1 - f * s);
+ const t = v * (1 - (1 - f) * s);
+ const values = [v, q, p, p, t, v];
+ const [r, g, b] = [values[i % 6], values[(i + 4) % 6], values[(i + 2) % 6]];
+ this.R.value = r;
+ this.G.value = g;
+ this.B.value = b;
+ this.A.value = a;
+ this.H.value = h;
+ this.S.value = s;
+ this.V.value = v;
+ }
+
+ /**
+ * @param {Number} x
+ * @param {Number} y
+ * @param {Number} v
+ * @param {Number} a
+ */
+ setFromWheelLocation(x, y, v, a) {
+ const [r, theta] = Utility.getPolarCoordinates(x, y, true);
+ this.setFromHSVA(1 - theta / (2 * Math.PI), r, v, a);
+ }
+
+ toDimmedColor(minV = 0) {
+ const result = new LinearColorEntity();
+ result.setFromRGBANumber(this.toNumber());
+ result.setFromHSVA(
+ result.H.value,
+ result.S.value * 0.6,
+ Math.pow(result.V.value + minV, 0.55) * 0.7
+ );
+ return result
+ }
+
+ toCSSRGBValues() {
+ const r = Math.round(this.R.value * 255);
+ const g = Math.round(this.G.value * 255);
+ const b = Math.round(this.B.value * 255);
+ return i$3`${r}, ${g}, ${b}`
+ }
+
+ toRGBA() {
+ return [
+ Math.round(this.R.value * 255),
+ Math.round(this.G.value * 255),
+ Math.round(this.B.value * 255),
+ Math.round(this.A.value * 255),
+ ]
+ }
+
+ toSRGBA() {
+ return [
+ Math.round(LinearColorEntity.linearToSRGB(this.R.value) * 255),
+ Math.round(LinearColorEntity.linearToSRGB(this.G.value) * 255),
+ Math.round(LinearColorEntity.linearToSRGB(this.B.value) * 255),
+ Math.round(this.A.value * 255),
+ ]
+ }
+
+ toRGBAString() {
+ return this
+ .toRGBA()
+ .map(v => v.toString(16).toUpperCase().padStart(2, "0"))
+ .join("")
+ }
+
+ toSRGBAString() {
+ return this
+ .toSRGBA()
+ .map(v => v.toString(16).toUpperCase().padStart(2, "0"))
+ .join("")
+ }
+
+ toHSVA() {
+ return [this.H.value, this.S.value, this.V.value, this.A.value]
+ }
+
+ toNumber() {
+ return (
+ Math.round(this.R.value * 0xff) << 24)
+ + (Math.round(this.G.value * 0xff) << 16)
+ + (Math.round(this.B.value * 0xff) << 8)
+ + Math.round(this.A.value * 0xff)
+ }
+
+ /** @returns {[Number, Number, Number, Number]} */
+ toArray() {
+ return [this.R.value, this.G.value, this.B.value, this.A.value]
+ }
+
+ /** @param {Number} number */
+ setFromRGBANumber(number) {
+ this.A.value = (number & 0xff) / 0xff;
+ this.B.value = ((number >> 8) & 0xff) / 0xff;
+ this.G.value = ((number >> 16) & 0xff) / 0xff;
+ this.R.value = ((number >> 24) & 0xff) / 0xff;
+ this.#updateHSV();
+ }
+
+ /** @param {Number} number */
+ setFromSRGBANumber(number) {
+ this.A.value = (number & 0xff) / 0xff;
+ this.B.value = LinearColorEntity.sRGBtoLinear(((number >> 8) & 0xff) / 0xff);
+ this.G.value = LinearColorEntity.sRGBtoLinear(((number >> 16) & 0xff) / 0xff);
+ this.R.value = LinearColorEntity.sRGBtoLinear(((number >> 24) & 0xff) / 0xff);
+ this.#updateHSV();
+ }
+
+ toString() {
+ return LinearColorEntity.printLinearColor(this)
+ }
+}
+
+/** @param {ObjectEntity} entity */
+function nodeColor(entity) {
+ switch (entity.getType()) {
+ case Configuration.paths.materialExpressionConstant2Vector:
+ case Configuration.paths.materialExpressionConstant3Vector:
+ case Configuration.paths.materialExpressionConstant4Vector:
+ return Configuration.nodeColors.yellow
+ case Configuration.paths.materialExpressionFunctionInput:
+ case Configuration.paths.materialExpressionTextureCoordinate:
+ case Configuration.paths.materialExpressionWorldPosition:
+ case Configuration.paths.pcgEditorGraphNodeInput:
+ case Configuration.paths.pcgEditorGraphNodeOutput:
+ return Configuration.nodeColors.red
+ case Configuration.paths.makeStruct:
+ return Configuration.nodeColors.darkBlue
+ case Configuration.paths.materialExpressionMaterialFunctionCall:
+ return Configuration.nodeColors.blue
+ case Configuration.paths.materialExpressionTextureSample:
+ return Configuration.nodeColors.darkTurquoise
+ }
+ switch (entity.getClass()) {
+ case Configuration.paths.callFunction:
+ return entity.bIsPureFunc?.valueOf()
+ ? Configuration.nodeColors.green
+ : Configuration.nodeColors.blue
+ case Configuration.paths.niagaraNodeFunctionCall:
+ return Configuration.nodeColors.darkerBlue
+ case Configuration.paths.dynamicCast:
+ return Configuration.nodeColors.turquoise
+ case Configuration.paths.inputDebugKey:
+ case Configuration.paths.inputKey:
+ return Configuration.nodeColors.red
+ case Configuration.paths.createDelegate:
+ case Configuration.paths.enumLiteral:
+ case Configuration.paths.makeArray:
+ case Configuration.paths.makeMap:
+ case Configuration.paths.materialGraphNode:
+ case Configuration.paths.select:
+ return Configuration.nodeColors.green
+ case Configuration.paths.executionSequence:
+ case Configuration.paths.ifThenElse:
+ case Configuration.paths.macro:
+ case Configuration.paths.multiGate:
+ return Configuration.nodeColors.gray
+ case Configuration.paths.functionEntry:
+ case Configuration.paths.functionResult:
+ return Configuration.nodeColors.violet
+ case Configuration.paths.timeline:
+ return Configuration.nodeColors.yellow
+ }
+ if (entity.switchTarget()) {
+ return Configuration.nodeColors.lime
+ }
+ if (entity.isEvent()) {
+ return Configuration.nodeColors.red
+ }
+ if (entity.isComment()) {
+ return (entity.CommentColor ? entity.CommentColor : LinearColorEntity.getWhite())
+ .toDimmedColor()
+ .toCSSRGBValues()
+ }
+ const pcgSubobject = entity.getPcgSubobject();
+ if (pcgSubobject) {
+ if (pcgSubobject.NodeTitleColor) {
+ return pcgSubobject.NodeTitleColor.toDimmedColor(0.1).toCSSRGBValues()
+ }
+ switch (entity.PCGNode?.getName(true)) {
+ case "Branch":
+ case "Select":
+ return Configuration.nodeColors.intenseGreen
+ }
+ }
+ if (entity.bIsPureFunc?.valueOf()) {
+ return Configuration.nodeColors.green
+ }
+ return Configuration.nodeColors.blue
+}
+
+/** @template {typeof IEntity} T */
+class MirroredEntity extends IEntity {
+
+ /** @type {typeof IEntity} */
+ static type
+
+ /** @param {() => InstanceType} getter */
+ constructor(getter = null) {
+ super();
+ const self = /** @type {typeof MirroredEntity} */(this.constructor);
+ getter ??= self.default !== undefined ? /** @type {MirroredEntity} */(self.default(self)).getter : getter;
+ this.getter = getter;
+ }
+
+ static createGrammar(elementGrammar = this.type?.grammar ?? Parsernostrum.lazy(() => this.unknownEntityGrammar)) {
+ return this.type?.grammar.map(v => new this(() => v))
+ }
+
+
+ /**
+ * @template {typeof IEntity} T
+ * @this {T}
+ * @param {(type: T) => (InstanceType | NullEntity)} value
+ * @returns {T}
+ */
+ // @ts-expect-error
+ static withDefault(value = type => new type(() => new (type.type)())) {
+ // @ts-expect-error
+ return super.withDefault(value)
+ }
+
+ /**
+ * @template {typeof IEntity} T
+ * @param {T} type
+ */
+ static of(type) {
+ const result = /** @type {{type: T, grammar: P> } & typeof MirroredEntity} */(
+ this.asUniqueClass()
+ );
+ result.type = type;
+ result.grammar = result.createGrammar();
+ return result
+ }
+
+ doSerialize(
+ insideString = false,
+ indentation = "",
+ Self = /** @type {typeof MirroredEntity} */(this.constructor),
+ printKey = Self.printKey,
+ keySeparator = Self.keySeparator,
+ attributeSeparator = Self.attributeSeparator,
+ wrap = Self.wrap,
+ ) {
+ const value = this.getter();
+ return value.serialize(insideString, indentation, Self.type, printKey, keySeparator, attributeSeparator, wrap)
+ }
+
+ /** @param {IEntity} other */
+ equals(other) {
+ if (other instanceof MirroredEntity) {
+ other = other.getter?.();
+ }
+ return this.getter?.().equals(other)
+ }
+
+ valueOf() {
+ this.valueOf = this.getter().valueOf.bind(this.getter());
+ return this.valueOf()
+ }
+
+ toString() {
+ this.toString = this.getter().toString.bind(this.getter());
+ return this.toString()
+ }
+}
+
+class NumberEntity extends IEntity {
+
+ static numberRegexSource = String.raw`${Grammar.numberRegexSource}(?<=(?:\.(\d*0+))?)`
+ static grammar = this.createGrammar()
+ /** @type {Number} */
+ static precision // Can override this.precision
+
+ #precision
+ get precision() {
+ return /** @type {typeof NumberEntity} */(this.constructor).precision ?? this.#precision
+ }
+ set precision(value) {
+ this.#precision = value;
+ }
+
+ /**
+ * @protected
+ * @type {Number}
+ */
+ _value
+ get value() {
+ return this._value
+ }
+ set value(value) {
+ if (value === -0) {
+ value = 0;
+ }
+ this._value = value;
+ }
+
+ constructor(value = 0, precision = null) {
+ super();
+ this.value = Number(value);
+ if (precision !== null) {
+ this.#precision = Number(precision);
+ }
+ }
+
+ static createGrammar() {
+ return /** @type {P} */(
+ Parsernostrum.regArray(
+ new RegExp(`(?${this.numberRegexSource})|(?\\+?inf)|(?-inf)`)
+ ).map(({ 2: precision, groups: { n, posInf, negInf } }) => new this(
+ n ? Number(n) : posInf ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY,
+ precision?.length
+ )
+ ).label("NumberEntity")
+ )
+ }
+
+ /**
+ * @template {typeof NumberEntity} T
+ * @this {T}
+ * @returns {T}
+ */
+ static withPrecision(value = 0) {
+ const result = this.asUniqueClass();
+ result.precision = value;
+ return result
+ }
+
+ /** @param {Number} num */
+ static printNumber(num) {
+ if (num == Number.POSITIVE_INFINITY) {
+ return "inf"
+ } else if (num == Number.NEGATIVE_INFINITY) {
+ return "-inf"
+ }
+ return Utility.minDecimals(num)
+ }
+
+ serialize(
+ insideString = false,
+ indentation = "",
+ Self = /** @type {typeof NumberEntity} */(this.constructor),
+ ) {
+ if (this.value === Number.POSITIVE_INFINITY) {
+ return "+inf"
+ }
+ if (this.value === Number.NEGATIVE_INFINITY) {
+ return "-inf"
+ }
+ const precision = Self.precision ?? this.precision;
+ let result = precision !== undefined ? this.value.toFixed(precision) : this.value.toString();
+ if (Self.serialized) {
+ result = `"${result}"`;
+ }
+ return result
+ }
+
+ valueOf() {
+ return this.value
+ }
+
+ toString() {
+ return this.value.toString()
+ }
+}
+
+class VectorEntity extends IEntity {
+
+ static attributes = {
+ ...super.attributes,
+ X: NumberEntity.withDefault(),
+ Y: NumberEntity.withDefault(),
+ Z: NumberEntity.withDefault(),
+ }
+ static grammar = this.createGrammar()
+
+ constructor(values) {
+ super(values);
+ /** @type {InstanceType} */ this.X;
+ /** @type {InstanceType} */ this.Y;
+ /** @type {InstanceType} */ this.Z;
+ }
+ c
+ static createGrammar() {
+ return /** @type {P} */(
+ Grammar.createEntityGrammar(this, Grammar.commaSeparation, true).label("VectorEntity")
+ )
+ }
+
+ /** @returns {[Number, Number, Number]} */
+ toArray() {
+ return [this.X.valueOf(), this.Y.valueOf(), this.Z.valueOf()]
+ }
+}
+
const sequencerScriptingNameRegex = /\/Script\/SequencerScripting\.MovieSceneScripting(.+)Channel/;
const keyNameValue = {
"A_AccentGrave": "à",
@@ -3943,43 +3963,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}
+ */
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) {
@@ -3989,7 +4029,7 @@ 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:
@@ -3997,9 +4037,9 @@ 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:
@@ -4010,36 +4050,32 @@ function nodeTitle(entity) {
}
case Configuration.paths.materialExpressionComponentMask: {
const materialObject = entity.getMaterialSubobject();
- return `Mask ( ${Configuration.rgba
- .filter(k => /** @type {MirroredEntity} */(materialObject[k]).get() === true)
- .map(v => v + " ")
- .join("")})`
+ if (materialObject) {
+ return `Mask ( ${Configuration.rgba
+ .filter(k => /** @type {MirroredEntity} */(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: {
@@ -4062,6 +4098,11 @@ 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) {
@@ -4077,7 +4118,7 @@ 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();
@@ -4102,7 +4143,7 @@ function nodeTitle(entity) {
return `Switch on ${switchTarget}`
}
if (entity.isComment()) {
- return entity.NodeComment
+ return entity.NodeComment.toString()
}
const keyNameSymbol = entity.getHIDAttribute();
if (keyNameSymbol) {
@@ -4125,7 +4166,7 @@ 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();
@@ -4144,7 +4185,7 @@ 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)
}
@@ -4154,7 +4195,7 @@ 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) {
@@ -4291,7 +4332,7 @@ 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 "!="
@@ -4304,10 +4345,10 @@ 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()
@@ -4390,8 +4431,112 @@ function nodeIcon(entity) {
return SVGIcon.functionSymbol
}
+/** @template {typeof IEntity} T */
+class ArrayEntity extends IEntity {
+
+ /** @type {typeof IEntity} */
+ static type
+ static grammar = this.createGrammar()
+
+ get length() {
+ return this.values.length
+ }
+
+ /** @param {(ExtractType)[]} values */
+ constructor(values = []) {
+ super();
+ this.values = values;
+ }
+
+ /** @returns {P>} */
+ static createGrammar(elementGrammar = this.type?.grammar ?? Parsernostrum.lazy(() => this.unknownEntityGrammar)) {
+ return this.inlined
+ ? elementGrammar
+ : Parsernostrum.seq(
+ Parsernostrum.reg(/\(\s*/),
+ elementGrammar.sepBy(Grammar.commaSeparation).opt(),
+ Parsernostrum.reg(/\s*(,\s*)?\)/, 1),
+ ).map(([_0, values, trailing]) => {
+ values = values instanceof Array ? values : [];
+ 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} */(result.createGrammar());
+ return result
+ }
+
+ /**
+ * @template {typeof IEntity} T
+ * @param {T} type
+ */
+ static of(type) {
+ const result = /** @type {{type: T, grammar: P> } & typeof ArrayEntity} */(
+ this.asUniqueClass()
+ );
+ result.type = type;
+ result.grammar = /** @type {P} */(result.createGrammar());
+ return result
+ }
+
+ doSerialize(
+ insideString = false,
+ indentation = "",
+ Self = /** @type {typeof ArrayEntity} */(this.constructor),
+ printKey = Self.printKey,
+ keySeparator = Self.keySeparator,
+ attributeSeparator = Self.attributeSeparator,
+ wrap = Self.wrap,
+ ) {
+ if (Self.inlined) {
+ return super.serialize.bind(
+ this.values,
+ insideString,
+ indentation,
+ Self,
+ printKey,
+ keySeparator,
+ attributeSeparator,
+ wrap
+ )()
+ }
+ let result = this.values.map(v => v?.serialize(insideString)).join(Self.attributeSeparator);
+ if (this.trailing) {
+ result += Self.attributeSeparator;
+ }
+ return `(${result})`
+ }
+
+ valueOf() {
+ return this.values
+ }
+
+ /** @param {IEntity} other */
+ equals(other) {
+ if (!(other instanceof ArrayEntity) || this.values.length !== other.values.length) {
+ return false
+ }
+ for (let i = 0; i < this.values.length; ++i) {
+ if (!this.values[i].equals(other.values[i])) {
+ return false
+ }
+ }
+ return true
+ }
+}
+
var crypto;
if (typeof window === "undefined") {
+ // When used in nodejs, mainly for test purpose
import('crypto').then(mod => crypto = mod.default).catch();
} else {
crypto = window.crypto;
@@ -4399,43 +4544,39 @@ if (typeof window === "undefined") {
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} */(
+ Parsernostrum.reg(/[0-9A-F]{32}/i).map(v => new this(v)).label("GuidEntity")
+ )
+ }
+
+ serialize(
+ insideString = false,
+ indentation = "",
+ Self = /** @type {typeof IEntity} */(this.constructor),
+ ) {
+ let result = this.value;
+ if (Self.serialized) {
+ result = `"${result}"`;
+ }
+ return result
}
toString() {
@@ -4443,210 +4584,44 @@ class GuidEntity extends IEntity {
}
}
-class ObjectReferenceEntity extends IEntity {
+class IntegerEntity extends NumberEntity {
- 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(
- `'"(${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(
- 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 grammar = this.createGrammar()
- constructor(values = {}) {
- if (values.constructor === String) {
- values = {
- path: values
- };
+ 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;
}
- 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;
}
static createGrammar() {
- return Parsernostrum.alt(
- this.fullReferenceSerializedGrammar,
- this.fullReferenceGrammar,
- this.typeReferenceGrammar,
+ return /** @type {P} */(
+ Parsernostrum.numberInteger.map(v => new this(v))
)
}
-
- static createNoneInstance() {
- return new ObjectReferenceEntity({ type: "None", path: "" })
- }
-
- getName(dropCounter = false) {
- return Utility.getNameFromPath(this.path.replace(/_C$/, ""), dropCounter)
- }
-
- toString() {
- return this._full
- }
}
-class FunctionReferenceEntity extends IEntity {
+class NaturalNumberEntity extends IntegerEntity {
- static attributes = {
- ...super.attributes,
- MemberParent: AttributeInfo.createType(ObjectReferenceEntity),
- MemberName: AttributeInfo.createType(String),
- MemberGuid: AttributeInfo.createType(GuidEntity),
- }
static grammar = this.createGrammar()
- static createGrammar() {
- return Grammar.createEntityGrammar(this)
+ get value() {
+ return super.value
}
-
- constructor(values) {
- super(values);
- /** @type {ObjectReferenceEntity} */ this.MemberParent;
- /** @type {String} */ this.MemberName;
- /** @type {GuidEntity} */ this.MemberGuid;
+ set value(value) {
+ value = Math.round(Utility.clamp(value, 0));
+ super.value = value;
}
-}
-
-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
- }
-}
-
-class IntegerEntity extends IEntity {
-
- static attributes = {
- ...super.attributes,
- value: new AttributeInfo({
- default: 0,
- predicate: v => v % 1 == 0 && v > 1 << 31 && v < -(1 << 31),
- }),
- }
- static grammar = this.createGrammar()
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()
- }
-}
-
-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(),
- }),
- }
- 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;
- }
-
- getMacroName() {
- const colonIndex = this.MacroGraph.path.search(":");
- return this.MacroGraph.path.substring(colonIndex + 1)
+ return /** @type {P} */(
+ Parsernostrum.numberNatural.map(v => new this(v))
+ )
}
}
@@ -4701,32 +4676,28 @@ const colors = {
const pinColorMaterial = i$3`120, 120, 120`;
-/** @param {PinEntity} entity */
+/** @param {PinEntity} entity */
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"]
}
-/** @param {PinEntity} entity */
+/** @param {PinEntity} entity */
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
}
@@ -4736,49 +4707,115 @@ function pinTitle(entity) {
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} */(
+ // @ts-expect-error
+ Parsernostrum.numberByte.map(v => new this(v))
+ )
}
}
+class StringEntity extends IEntity {
+
+ static grammar = this.createGrammar()
+
+ constructor(value = "") {
+ super();
+ this.value = value;
+ }
+
+ static createGrammar() {
+ return /** @type {P} */(
+ Parsernostrum.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
+ }
+}
+
+class ComputedTypeEntity extends IEntity {
+
+ static grammar = this.createGrammar()
+ /** @type {(entity: IEntity) => typeof IEntity} */
+ static f
+
+ static createGrammar() {
+ return StringEntity.grammar
+ }
+
+ /**
+ * @template {typeof ComputedTypeEntity.f} T
+ * @param {T} producer
+ */
+ static from(producer) {
+ const result = /** @type {(typeof ComputedTypeEntity) & { f: T }} */(this.asUniqueClass());
+ result.f = producer;
+ return result
+ }
+
+ /** @param {IEntity} entity */
+ static compute(entity) {
+ return this.f(entity)
+ }
+}
+
class SymbolEntity extends IEntity {
- static 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} */(
+ 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() {
@@ -4791,7 +4828,9 @@ class EnumEntity extends SymbolEntity {
static grammar = this.createGrammar()
static createGrammar() {
- return Grammar.symbol.map(v => new this(v))
+ return /** @type {P} */(
+ Grammar.symbol.map(v => new this(v))
+ )
}
}
@@ -4800,137 +4839,146 @@ class EnumDisplayValueEntity extends EnumEntity {
static grammar = this.createGrammar()
static createGrammar() {
- return Parsernostrum.reg(Grammar.Regex.InsideString).map(v => new this(v))
+ return /** @type {P} */(
+ Parsernostrum.reg(Grammar.Regex.InsideString).map(v => new this(v))
+ )
}
}
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} */(
+ Parsernostrum.alt(
+ Parsernostrum.seq(
+ Parsernostrum.reg(new RegExp(`${this.lookbehind}\\s*\\(`)),
+ Parsernostrum.doubleQuotedString,
+ Parsernostrum.reg(/\s*\)/)
+ ).map(([_0, value, _2]) => Number(value)),
+ Parsernostrum.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
}
}
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} */ this.namespace;
+ /** @type {InstanceType} */ this.key;
+ /** @type {InstanceType} */ this.value;
}
- constructor(values) {
- super(values);
- /** @type {String} */ this.namespace;
- /** @type {String} */ this.key;
- /** @type {String} */ this.value;
+ static createGrammar() {
+ return /** @type {P} */(
+ Parsernostrum.regArray(new RegExp(
+ String.raw`${LocalizedTextEntity.lookbehind}\s*\(`
+ + String.raw`\s*"(?${Grammar.Regex.InsideString.source})"\s*,`
+ + String.raw`\s*"(?${Grammar.Regex.InsideString.source})"\s*,`
+ + String.raw`\s*"(?${Grammar.Regex.InsideString.source})"\s*`
+ + String.raw`(?,\s+)?`
+ + String.raw`\)`,
+ "m"
+ )).map(({ groups: { namespace, key, value, trailing } }) => {
+ return new this({
+ namespace: new (this.attributes.namespace)(Utility.unescapeString(namespace)),
+ key: new (this.attributes.namespace)(Utility.unescapeString(key)),
+ value: new (this.attributes.namespace)(Utility.unescapeString(value)),
+ trailing: trailing !== undefined,
+ })
+ }).label("LocalizedTextEntity")
+ )
}
toString() {
- return Utility.capitalFirstLetter(this.value)
+ return Utility.capitalFirstLetter(this.value.valueOf())
}
}
class FormatTextEntity extends IEntity {
- static attributes = {
- ...super.attributes,
- value: new AttributeInfo({
- type: [new Union(String, LocalizedTextEntity, InvariantTextEntity, FormatTextEntity)],
- default: [],
- }),
- lookbehind: /** @type {AttributeInfo>} */(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} */
+ static createGrammar() {
+ return Parsernostrum.lazy(() => Parsernostrum.seq(
+ // Resulting regex: /(LOCGEN_FORMAT_NAMED|LOCGEN_FORMAT_ORDERED)\s*/
+ Parsernostrum.reg(new RegExp(String.raw`(${this.lookbehind.join("|")})\s*\(\s*`), 1),
+ Parsernostrum.alt(
+ ...[StringEntity, LocalizedTextEntity, InvariantTextEntity, FormatTextEntity].map(type => type.grammar)
+ ).sepBy(Parsernostrum.reg(/\s*\,\s*/)),
+ Parsernostrum.reg(/\s*\)/)
+ )
+ .map(([lookbehind, values]) => {
+ const result = new this(values);
+ result.lookbehind = lookbehind;
+ return result
+ }))
+ .label("FormatTextEntity")
+ }
+
+ doSerialize(
+ insideString = false,
+ indentation = "",
+ Self = /** @type {typeof FormatTextEntity} */(this.constructor),
+ printKey = Self.printKey,
+ keySeparator = Self.keySeparator,
+ attributeSeparator = Self.attributeSeparator,
+ wrap = Self.wrap,
+ ) {
+ const separator = Self.attributeSeparator;
+ return this.lookbehind + "("
+ + this.values.map(v => v.serialize(insideString)).join(separator)
+ + (Self.trailing ? separator : "")
+ + ")"
}
toString() {
- const pattern = this.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
@@ -4944,37 +4992,51 @@ class FormatTextEntity extends IEntity {
? values[argValue]
: substring
})
- : ""
+ : "";
+ return result
}
}
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} */(
+ Parsernostrum.numberBigInteger.map(v => new this(v))
+ )
+ }
+
+ serialize(
+ insideString = false,
+ indentation = "",
+ Self = /** @type {typeof IEntity} */(this.constructor),
+ ) {
+ let result = this.value.toString();
+ if (Self.serialized) {
+ result = `"${result}"`;
}
- if (values.value === -0) {
- values.value = 0n;
- }
- super(values);
- /** @type {BigInt} */ this.value;
+ return result
}
valueOf() {
@@ -4986,65 +5048,169 @@ class Integer64Entity extends IEntity {
}
}
-class PathSymbolEntity extends IEntity {
+class ObjectReferenceEntity extends IEntity {
- static attributes = {
- ...super.attributes,
- value: new AttributeInfo({
- default: "",
- }),
- }
+ /** @protected */
+ static _quotedParser = Parsernostrum.regArray(new RegExp(
+ `'"(${Grammar.Regex.InsideString.source})"'`
+ + "|"
+ + `'(${Grammar.Regex.InsideSingleQuotedString.source})'`
+ )).map(([_0, a, b]) => a ?? b)
+ static typeReference = Parsernostrum.reg(
+ // @ts-expect-error
+ new RegExp(Grammar.Regex.Path.source + "|" + Grammar.symbol.getParser().regexp.source)
+ )
+ static fullReferenceGrammar = this.createFullReferenceGrammar()
static grammar = this.createGrammar()
+ #type
+ get type() {
+ return this.#type
+ }
+ set type(value) {
+ this.#type = value;
+ }
+
+ #path
+ get path() {
+ return this.#path
+ }
+ set path(value) {
+ this.#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} */
static createGrammar() {
- return Grammar.symbol.map(v => new this(v))
+ return Parsernostrum.alt(
+ this.createFullReferenceSerializedGrammar(),
+ this.createFullReferenceGrammar(),
+ this.createTypeReferenceGrammar(),
+ ).label("ObjectReferenceEntity")
}
- constructor(values) {
- if (values.constructor !== Object) {
- values = {
- value: values,
- };
+ /** @returns {P} */
+ static createFullReferenceGrammar() {
+ return Parsernostrum.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} */
+ static createFullReferenceSerializedGrammar() {
+ return Parsernostrum.regArray(
+ new RegExp(
+ '"(' + Grammar.Regex.InsideString.source + "?)"
+ + "(?:'(" + Grammar.Regex.InsideSingleQuotedString.source + `?)')?"`
+ )
+ ).map(([full, type, path]) => new this(type, path, full))
+ }
+
+ /** @returns {P} */
+ static createTypeReferenceGrammar() {
+ return this.typeReference.map(v => new this(v, "", v))
+ }
+
+ static createNoneInstance() {
+ return new ObjectReferenceEntity("None")
+ }
+
+ getName(dropCounter = false) {
+ return Utility.getNameFromPath(this.path.replace(/_C$/, ""), dropCounter)
+ }
+
+ doSerialize(insideString = false) {
+ if (insideString) {
+ if (this.#fullEscaped === undefined) {
+ this.#fullEscaped = Utility.escapeString(this.#full, false);
+ }
+ return this.#fullEscaped
}
- super(values);
- /** @type {String} */ this.value;
+ return this.full
}
- valueOf() {
- return this.value
- }
-
- toString() {
- return this.value
+ /** @param {IEntity} other */
+ equals(other) {
+ if (!(other instanceof ObjectReferenceEntity)) {
+ return false
+ }
+ return this.type == other.type && this.path == other.path
}
}
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} */(
+ Parsernostrum.seq(
+ SymbolEntity.grammar,
+ Parsernostrum.whitespace,
+ GuidEntity.grammar
+ )
+ .map(([objectName, _1, pinGuid]) => new this(objectName, pinGuid))
+ .label("PinReferenceEntity")
)
}
+ doSerialize() {
+ return this.objectName.serialize() + " " + this.pinGuid.serialize()
+ }
+}
+
+class FunctionReferenceEntity extends IEntity {
+
+ static attributes = {
+ ...super.attributes,
+ MemberParent: ObjectReferenceEntity,
+ MemberName: StringEntity,
+ MemberGuid: GuidEntity,
+ }
+ static grammar = this.createGrammar()
+
constructor(values) {
super(values);
- /** @type {PathSymbolEntity} */ this.objectName;
- /** @type {GuidEntity} */ this.pinGuid;
+ /** @type {InstanceType} */ this.MemberParent;
+ /** @type {InstanceType} */ this.MemberName;
+ /** @type {InstanceType} */ this.MemberGuid;
+ }
+
+ /** @returns {P} */
+ static createGrammar() {
+ return Grammar.createEntityGrammar(this, Grammar.commaSeparation, false, 0)
}
}
@@ -5052,62 +5218,47 @@ 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} */ this.PinCategory;
+ /** @type {InstanceType} */ this.PinSubCategory;
+ /** @type {InstanceType} */ this.PinSubCategoryObject;
+ /** @type {InstanceType} */ this.PinSubCategoryMemberReference;
+ /** @type {InstanceType} */ this.ContainerType;
+ /** @type {InstanceType} */ this.bIsReference;
+ /** @type {InstanceType} */ this.bIsConst;
+ /** @type {InstanceType} */ this.bIsWeakPointer;
+ /** @type {InstanceType} */ this.bIsUObjectWrapper;
+ /** @type {InstanceType} */ this.bIsUObjectWrapper;
+ /** @type {InstanceType} */ this.bSerializeAsSinglePrecisionFloat;
}
- 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} */(
+ 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];
+ }
+ }
}
}
@@ -5115,30 +5266,25 @@ 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} */ this.X;
+ /** @type {InstanceType} */ this.Y;
+ }
+
+ /** @returns {P} */
+ 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()]
}
}
@@ -5147,17 +5293,20 @@ class RBSerializationVector2DEntity extends Vector2DEntity {
static grammar = this.createGrammar()
static createGrammar() {
- return Parsernostrum.alt(
+ return /** @type {P} */(Parsernostrum.alt(
Parsernostrum.regArray(new RegExp(
- /X\s*=\s*/.source + "(?" + Parsernostrum.number.getParser().parser.regexp.source + ")"
+ /X\s*=\s*/.source + "(?" + Grammar.numberRegexSource + ")"
+ "\\s+"
- + /Y\s*=\s*/.source + "(?" + Parsernostrum.number.getParser().parser.regexp.source + ")"
+ + /Y\s*=\s*/.source + "(?" + 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"))
}
}
@@ -5165,30 +5314,23 @@ 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} */ this.R;
+ /** @type {InstanceType} */ this.P;
+ /** @type {InstanceType} */ this.Y;
+ }
+
+ static createGrammar() {
+ return /** @type {P} */(
+ Grammar.createEntityGrammar(this, Grammar.commaSeparation, true).label("RotatorEntity")
+ )
}
getRoll() {
@@ -5206,85 +5348,103 @@ class RotatorEntity extends IEntity {
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} */(
+ Parsernostrum.alt(
+ Parsernostrum.regArray(new RegExp(
+ `(${NumberEntity.numberRegexSource})`
+ + String.raw`\s*,\s*`
+ + `(${NumberEntity.numberRegexSource})`
+ + String.raw`\s*,\s*`
+ + `(${NumberEntity.numberRegexSource})`
+ )).map(([_, p, pPrecision, y, yPrecision, r, rPrecision]) => new this({
+ R: new (RotatorEntity.attributes.R)(r, rPrecision?.length),
+ P: new (RotatorEntity.attributes.P)(p, pPrecision?.length),
+ Y: new (RotatorEntity.attributes.Y)(y, yPrecision?.length),
+ })),
+ RotatorEntity.grammar.map(v => new this({
+ R: v.R,
+ P: v.P,
+ Y: v.Y,
+ }))
+ ).label("SimpleSerializationRotatorEntity")
)
}
+
+ doSerialize() {
+ const attributeSeparator = /** @type {typeof SimpleSerializationRotatorEntity} */(
+ this.constructor
+ ).attributeSeparator;
+ return this.P.serialize() + attributeSeparator
+ + this.Y.serialize() + attributeSeparator
+ + this.R.serialize() + (this.trailing ? attributeSeparator : "")
+ }
}
class SimpleSerializationVector2DEntity extends Vector2DEntity {
+ static attributeSeparator = ", "
static grammar = this.createGrammar()
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} */(
+ Parsernostrum.alt(
+ Parsernostrum.regArray(new RegExp(
+ `(${NumberEntity.numberRegexSource})`
+ + String.raw`\s*,\s*`
+ + `(${NumberEntity.numberRegexSource})`
+ )).map(([_, x, xPrecision, y, yPrecision]) => new this({
+ X: new (Vector2DEntity.attributes.X)(x, xPrecision?.length),
+ Y: new (Vector2DEntity.attributes.Y)(y, yPrecision?.length),
+ })),
+ Vector2DEntity.grammar.map(v => new this({
+ X: v.X,
+ Y: v.Y,
+ }))
+ ).label("SimpleSerializationVector2DEntity")
)
}
+
+ doSerialize() {
+ const attributeSeparator = /** @type {typeof SimpleSerializationVector2DEntity} */(
+ this.constructor
+ ).attributeSeparator;
+ return this.X.serialize() + attributeSeparator
+ + this.Y.serialize() + (this.trailing ? attributeSeparator : "")
+ }
}
class Vector4DEntity extends IEntity {
static attributes = {
...super.attributes,
- X: 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} */ this.X;
+ /** @type {InstanceType} */ this.Y;
+ /** @type {InstanceType} */ this.Z;
+ /** @type {InstanceType} */ this.W;
+ }
+
+ static createGrammar() {
+ return /** @type {P} */(
+ 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()]
}
}
@@ -5292,111 +5452,111 @@ class SimpleSerializationVector4DEntity extends Vector4DEntity {
static grammar = this.createGrammar()
+ /** @returns {P } */
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 + ")"
+ `(${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
)
}
}
-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,
- }),
- }
- 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;
- }
-
- /** @returns {[Number, Number, Number]} */
- toArray() {
- return [this.X, this.Y, this.Z]
- }
-}
-
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} */(
+ Parsernostrum.alt(
+ Parsernostrum.regArray(new RegExp(
+ `(${NumberEntity.numberRegexSource})`
+ // If allow simple serialization then it can parse only a single number ...
+ + (this.allowShortSerialization ? `(?:` : "")
+ + String.raw`\s*,\s*`
+ + `(${NumberEntity.numberRegexSource})`
+ + String.raw`\s*,\s*`
+ + `(${NumberEntity.numberRegexSource})`
+ // ... that will be assigned to X and the rest is optional and set to 0
+ + (this.allowShortSerialization ? `)?` : "")
+ ))
+ .map(([_, x, xPrecision, y, yPrecision, z, zPrecision]) => new this({
+ X: new (VectorEntity.attributes.X)(x, xPrecision?.length),
+ Y: new (VectorEntity.attributes.Y)(y, yPrecision?.length),
+ Z: new (VectorEntity.attributes.Z)(z, zPrecision?.length),
+ })),
+ VectorEntity.grammar.map(v => new this({
+ X: v.X,
+ Y: v.Y,
+ Z: v.Z,
+ }))
+ )
)
}
+
+ /**
+ * @template {typeof SimpleSerializationVectorEntity} T
+ * @this {T}
+ */
+ static flagAllowShortSerialization(value = true) {
+ const result = this.asUniqueClass();
+ if (value !== result.allowShortSerialization) {
+ result.allowShortSerialization = value;
+ result.grammar = result.createGrammar();
+ }
+ return result
+ }
+
+ doSerialize() {
+ const attributeSeparator = /** @type {typeof SimpleSerializationVectorEntity} */(
+ this.constructor
+ ).attributeSeparator;
+ return this.X.serialize() + attributeSeparator
+ + this.Y.serialize() + attributeSeparator
+ + this.Z.serialize() + (this.trailing ? attributeSeparator : "")
+ }
}
-/** @template {TerminalAttribute} T */
+/** @template {IEntity} T */
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,
@@ -5404,50 +5564,34 @@ 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()
@@ -5459,42 +5603,76 @@ 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} */ this.PinId;
+ /** @type {InstanceType} */ this.PinName;
+ /** @type {InstanceType} */ this.PinFriendlyName;
+ /** @type {InstanceType} */ this.PinToolTip;
+ /** @type {InstanceType} */ this.Direction;
+ /** @type {InstanceType} */ this.PinType;
+ /** @type {InstanceType} */ this.LinkedTo;
/** @type {T} */ this.DefaultValue;
- /** @type {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} */ this.AutogeneratedDefaultValue;
+ /** @type {InstanceType} */ this.DefaultObject;
+ /** @type {InstanceType} */ this.PersistentGuid;
+ /** @type {InstanceType} */ this.bHidden;
+ /** @type {InstanceType} */ this.bNotConnectable;
+ /** @type {InstanceType} */ this.bDefaultValueIsReadOnly;
+ /** @type {InstanceType} */ this.bDefaultValueIsIgnored;
+ /** @type {InstanceType} */ this.bAdvancedView;
+ /** @type {InstanceType} */ this.bOrphanedPin;
+ /** @type {ObjectEntity} */ this.objectEntity;
+ }
+
+ static createGrammar() {
+ return /** @type {P} */(
+ 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"
@@ -5502,8 +5680,8 @@ 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)];
@@ -5516,8 +5694,8 @@ 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 += "[]";
}
@@ -5526,7 +5704,8 @@ 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":
@@ -5536,16 +5715,17 @@ 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
@@ -5557,48 +5737,37 @@ 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() {
@@ -5611,15 +5780,12 @@ 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
@@ -5631,14 +5797,14 @@ 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
}
@@ -5654,80 +5820,9 @@ class PinEntity extends IEntity {
}
}
-class ScriptVariableEntity extends IEntity {
-
- static attributes = {
- ...super.attributes,
- ScriptVariable: AttributeInfo.createType(ObjectReferenceEntity),
- OriginalChangeId: AttributeInfo.createType(GuidEntity),
- }
- static grammar = this.createGrammar()
-
- static createGrammar() {
- return Grammar.createEntityGrammar(this)
- }
-
- constructor(values = {}, suppressWarns = false) {
- super(values, suppressWarns);
- /** @type {ObjectReferenceEntity} */ this.ScriptVariable;
- /** @type {GuidEntity} */ this.OriginalChangeId;
- }
-}
-
-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);
- }
-}
-
-class VariableReferenceEntity extends IEntity {
-
- static attributes = {
- ...super.attributes,
- MemberScope: AttributeInfo.createType(String),
- MemberName: AttributeInfo.createValue(""),
- MemberGuid: AttributeInfo.createType(GuidEntity),
- bSelfContext: AttributeInfo.createType(Boolean),
- }
- 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;
- }
-}
-
/** @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 */
function nodeVariadic(entity) {
@@ -5738,11 +5833,12 @@ 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 (
@@ -5773,17 +5869,22 @@ 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())
@@ -5797,26 +5898,26 @@ 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
@@ -5861,333 +5962,400 @@ 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
}
}
}
-class ObjectEntity extends IEntity {
+class MacroGraphReferenceEntity extends IEntity {
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)]),
+ MacroGraph: ObjectReferenceEntity,
+ GraphBlueprint: ObjectReferenceEntity,
+ GraphGuid: GuidEntity,
+ }
+ static grammar = this.createGrammar()
+
+ constructor(values) {
+ super(values);
+ /** @type {InstanceType} */ this.MacroGraph;
+ /** @type {InstanceType} */ this.GraphBlueprint;
+ /** @type {InstanceType} */ this.GraphGuid;
+ }
+
+ static createGrammar() {
+ return /** @type {P} */(
+ Grammar.createEntityGrammar(this)
+ )
+ }
+
+ getMacroName() {
+ const colonIndex = this.MacroGraph.path.search(":");
+ return this.MacroGraph.path.substring(colonIndex + 1)
+ }
+}
+
+class NullEntity extends IEntity {
+
+ static grammar = this.createGrammar()
+
+ static createGrammar() {
+ return /** @type {P} */(
+ // @ts-expect-error
+ Parsernostrum.reg(new RegExp(String.raw`\(${Parsernostrum.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
+ }
+}
+
+class ScriptVariableEntity extends IEntity {
+
+ static attributes = {
+ ...super.attributes,
+ ScriptVariable: ObjectReferenceEntity,
+ OriginalChangeId: GuidEntity,
+ }
+ static grammar = this.createGrammar()
+
+ constructor(values = {}) {
+ super(values);
+ /** @type {InstanceType} */ this.ScriptVariable;
+ /** @type {InstanceType} */ this.OriginalChangeId;
+ }
+
+ static createGrammar() {
+ return /** @type {P} */(
+ Grammar.createEntityGrammar(this).label("ScriptVariableEntity")
+ )
+ }
+}
+
+class UnknownPinEntity extends PinEntity {
+
+ static grammar = this.createGrammar()
+
+ static createGrammar() {
+ return /** @type {P} */(
+ 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)
+ }).label("UnknownPinEntity")
+ )
+ }
+}
+
+class VariableReferenceEntity extends IEntity {
+
+ static attributes = {
+ ...super.attributes,
+ MemberScope: StringEntity,
+ MemberName: StringEntity.withDefault(),
+ MemberGuid: GuidEntity,
+ bSelfContext: BooleanEntity,
+ }
+ static grammar = this.createGrammar()
+
+ constructor(values) {
+ super(values);
+ /** @type {InstanceType} */ this.MemberScope;
+ /** @type {InstanceType} */ this.MemberName;
+ /** @type {InstanceType} */ this.MemberGuid;
+ /** @type {InstanceType} */ this.bSelfContext;
+ }
+
+ static createGrammar() {
+ return /** @type {P} */(
+ Grammar.createEntityGrammar(this).label("VariableReferenceEntity")
+ )
+ }
+}
+
+class ObjectEntity extends IEntity {
+
+ #exported = false
+ get exported() {
+ return this.#exported
+ }
+ set exported(value) {
+ this.#exported = value;
+ }
+
+ static #nameRegex = /^(\w+?)(?:_(\d+))?$/
+ /** @type {(k: String) => String} */
+ static printKey = k => !k.startsWith(Configuration.subObjectAttributeNamePrefix) ? k : ""
+ static attributeSeparator = "\n"
+ static wrap = this.notWrapped
+ static trailing = true
+ static attributes = {
+ ...super.attributes,
+ Class: ObjectReferenceEntity,
+ Name: StringEntity,
+ Archetype: ObjectReferenceEntity,
+ ExportPath: 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]
- ),
+ this.attributes.CustomProperties.type.grammar,
).map(([_0, pin]) => values => {
- if (!values.CustomProperties) {
- values.CustomProperties = [];
- }
- values.CustomProperties.push(pin);
+ /** @type {InstanceType} */(
+ values.CustomProperties ??= new (this.attributes.CustomProperties)()
+ ).values.push(pin);
})
static inlinedArrayEntryGrammar = Parsernostrum.seq(
Parsernostrum.alt(
Grammar.symbolQuoted.map(v => [v, true]),
Grammar.symbol.map(v => [v, false]),
),
- Parsernostrum.reg(
- new RegExp(`\\s*\\(\\s*(\\d+)\\s*\\)\\s*\\=\\s*`),
- 1
- ).map(Number)
+ Parsernostrum.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 = Parsernostrum.seq(
+ Parsernostrum.whitespaceOpt,
+ this.grammar,
+ Parsernostrum.seq(
+ Parsernostrum.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(),
+ Parsernostrum.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} */ this.R;
- /** @type {Number | MirroredEntity} */ this.G;
- /** @type {Number | MirroredEntity} */ this.B;
- /** @type {Number | MirroredEntity} */ 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} */ this.PositionX;
- /** @type {MirroredEntity} */ this.PositionY;
- /** @type {MirroredEntity} */ 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