Split grammar (#15)

* Move grammar parsers to entity classes

* Fix includes

* Fix Entity5 test

* Small detail

* Fix unknown keys entities

* Persistent grammar objects

* Fix grammar

* Grammar from variable
This commit is contained in:
barsdeveloper
2023-09-18 21:13:28 +02:00
committed by GitHub
parent 872bdb7128
commit 78c62ee59a
48 changed files with 1807 additions and 1845 deletions

View File

@@ -1,41 +1,10 @@
import ByteEntity from "../entity/ByteEntity.js"
import ColorChannelEntity from "../entity/ColorChannelEntity.js"
import Configuration from "../Configuration.js"
import EnumDisplayValueEntity from "../entity/EnumDisplayValueEntity.js"
import EnumEntity from "../entity/EnumEntity.js"
import FormatTextEntity from "../entity/FormatTextEntity.js"
import FunctionReferenceEntity from "../entity/FunctionReferenceEntity.js"
import GuidEntity from "../entity/GuidEntity.js"
import IdentifierEntity from "../entity/IdentifierEntity.js"
import IEntity from "../entity/IEntity.js"
import Integer64Entity from "../entity/Integer64Entity.js"
import IntegerEntity from "../entity/IntegerEntity.js"
import InvariantTextEntity from "../entity/InvariantTextEntity.js"
import KeyBindingEntity from "../entity/KeyBindingEntity.js"
import LinearColorEntity from "../entity/LinearColorEntity.js"
import LocalizedTextEntity from "../entity/LocalizedTextEntity.js"
import MacroGraphReferenceEntity from "../entity/MacroGraphReferenceEntity.js"
import MirroredEntity from "../entity/MirroredEntity.js"
import NaturalNumberEntity from "../entity/NaturalNumberEntity.js"
import ObjectEntity from "../entity/ObjectEntity.js"
import ObjectReferenceEntity from "../entity/ObjectReferenceEntity.js"
import Parsimmon from "parsimmon"
import PathSymbolEntity from "../entity/PathSymbolEntity.js"
import PinEntity from "../entity/PinEntity.js"
import PinReferenceEntity from "../entity/PinReferenceEntity.js"
import RotatorEntity from "../entity/RotatorEntity.js"
import SimpleSerializationRotatorEntity from "../entity/SimpleSerializationRotatorEntity.js"
import SimpleSerializationVector2DEntity from "../entity/SimpleSerializationVector2DEntity.js"
import SimpleSerializationVectorEntity from "../entity/SimpleSerializationVectorEntity.js"
import SymbolEntity from "../entity/SymbolEntity.js"
import TerminalTypeEntity from "../entity/TerminalTypeEntity.js"
import Serializable from "./Serializable.js"
import Union from "../entity/Union.js"
import UnknownKeysEntity from "../entity/UnknownKeysEntity.js"
import UnknownPinEntity from "../entity/UnknownPinEntity.js"
import Utility from "../Utility.js"
import VariableReferenceEntity from "../entity/VariableReferenceEntity.js"
import Vector2DEntity from "../entity/Vector2DEntity.js"
import VectorEntity from "../entity/VectorEntity.js"
/**
* @typedef {import ("../entity/IEntity").AnyValue} AnyValue
@@ -204,102 +173,17 @@ export default class Grammar {
case Boolean:
result = this.boolean
break
case ByteEntity:
result = this.byteEntity
break
case ColorChannelEntity:
result = this.colorChannelEntity
break
case EnumDisplayValueEntity:
result = this.enumDisplayValueEntity
break
case EnumEntity:
result = this.enumEntity
break
case FormatTextEntity:
result = this.formatTextEntity
break
case FunctionReferenceEntity:
result = this.functionReferenceEntity
break
case GuidEntity:
result = this.guidEntity
break
case IdentifierEntity:
result = this.identifierEntity
break
case Integer64Entity:
result = this.integer64Entity
break
case IntegerEntity:
result = this.integerEntity
break
case InvariantTextEntity:
result = this.invariantTextEntity
break
case KeyBindingEntity:
result = this.keyBindingEntity
break
case LinearColorEntity:
result = this.linearColorEntity
break
case LocalizedTextEntity:
result = this.localizedTextEntity
break
case MacroGraphReferenceEntity:
result = this.macroGraphReferenceEntity
break
case Number:
result = this.number
break
case ObjectReferenceEntity:
result = this.objectReferenceEntity
break
case PathSymbolEntity:
result = this.pathSymbolEntity
break
case PinEntity:
result = this.pinEntity
break
case PinReferenceEntity:
result = this.pinReferenceEntity
break
case TerminalTypeEntity:
result = this.pinTypeEntity
break
case RotatorEntity:
result = this.rotatorEntity
break
case SimpleSerializationRotatorEntity:
result = this.simpleSerializationRotatorEntity
break
case SimpleSerializationVector2DEntity:
result = this.simpleSerializationVector2DEntity
break
case SimpleSerializationVectorEntity:
result = this.simpleSerializationVectorEntity
break
case String:
result = this.string
break
case SymbolEntity:
result = this.symbolEntity
break
case UnknownKeysEntity:
result = this.unknownKeysEntity
break
case UnknownPinEntity:
result = this.unknownPinEntity
break
case VariableReferenceEntity:
result = this.variableReferenceEntity
break
case Vector2DEntity:
result = this.vector2DEntity
break
case VectorEntity:
result = this.vectorEntity
break
default:
if (type?.prototype instanceof Serializable) {
// @ts-expect-error
return /** @type {typeof Serializable} */(type).grammar
}
}
}
if (attribute?.constructor === Object) {
@@ -371,8 +255,10 @@ export default class Grammar {
}
/**
* @param {EntityConstructor} entityType
* @template {IEntity} T
* @param {new (...args: any) => T} entityType
* @param {Boolean | Number} acceptUnknownKeys Number to specify the limit or true, to let it be a reasonable value
* @returns {Parsimmon.Parser<T>}
*/
static createEntityGrammar = (entityType, acceptUnknownKeys = true) =>
P.seq(
@@ -416,438 +302,5 @@ export default class Grammar {
/* --- Entity --- */
static byteEntity = P.lazy(() => this.byteNumber.map(v => new ByteEntity(v)))
static colorChannelEntity = P.lazy(() => this.number.map(value => new ColorChannelEntity(value)))
static enumDisplayValueEntity = P.lazy(() =>
P.regex(this.Regex.InsideString).map(v => new EnumDisplayValueEntity(v))
)
static enumEntity = P.lazy(() => this.symbol.map(v => new EnumEntity(v)))
static formatTextEntity = P.lazy(() =>
P.seq(
this.regexMap(
// Resulting regex: /(LOCGEN_FORMAT_NAMED|LOCGEN_FORMAT_ORDERED)\s*/
new RegExp(`(${FormatTextEntity.lookbehind.values.reduce((acc, cur) => acc + "|" + cur)})\\s*`),
result => result[1]
),
this.grammarFor(FormatTextEntity.attributes.value)
)
.map(([lookbehind, values]) => {
const result = new FormatTextEntity({
value: values,
})
result.lookbehind = lookbehind
return result
})
)
static functionReferenceEntity = P.lazy(() => this.createEntityGrammar(FunctionReferenceEntity))
static guidEntity = P.lazy(() => this.guid.map(v => new GuidEntity(v)))
static identifierEntity = P.lazy(() => this.symbol.map(v => new IdentifierEntity(v)))
static integer64Entity = P.lazy(() => this.bigInt.map(v => new Integer64Entity(v)))
static integerEntity = P.lazy(() => this.integer.map(v => new IntegerEntity(v)))
static invariantTextEntity = P.lazy(() =>
P.alt(
P.seq(
P.regex(new RegExp(`${InvariantTextEntity.lookbehind}\\s*\\(`)),
this.grammarFor(InvariantTextEntity.attributes.value),
P.regex(/\s*\)/)
)
.map(([_0, value, _2]) => value),
P.regex(new RegExp(InvariantTextEntity.lookbehind)) // InvariantTextEntity can not have arguments
.map(() => "")
).map(value => new InvariantTextEntity(value))
)
static keyBindingEntity = P.lazy(() =>
P.alt(
this.identifierEntity.map(identifier => new KeyBindingEntity({
Key: identifier
})),
this.createEntityGrammar(KeyBindingEntity)
)
)
static linearColorEntity = P.lazy(() => this.createEntityGrammar(LinearColorEntity, false))
static localizedTextEntity = P.lazy(() =>
Grammar.regexMap(
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"
),
matchResult => new LocalizedTextEntity({
namespace: Utility.unescapeString(matchResult[1]),
key: Utility.unescapeString(matchResult[2]),
value: Utility.unescapeString(matchResult[3]),
})
)
)
static macroGraphReferenceEntity = P.lazy(() => this.createEntityGrammar(MacroGraphReferenceEntity))
static naturalNumberEntity = P.lazy(() => this.naturalNumber.map(v => new NaturalNumberEntity(v)))
static noneReferenceEntity = P.lazy(() =>
P.string("None").map(() => ObjectReferenceEntity.createNoneInstance())
)
static typeReferenceEntity = P.lazy(() =>
this.typeReference.map(v =>
new ObjectReferenceEntity({ type: v, path: "" })
)
)
static pathReferenceEntity = P.lazy(() =>
this.path.map(path =>
new ObjectReferenceEntity({ type: "", path: path })
)
)
static fullReferenceEntity = P.lazy(() =>
P.seq(this.typeReference, P.regex(Grammar.Regex.InlineOptWhitespace), this.pathQuotes)
.map(([type, _2, path]) =>
new ObjectReferenceEntity({ type: type, path: path })
)
)
static objectReferenceEntity = P.lazy(() =>
P.alt(
this.noneReferenceEntity,
this.fullReferenceEntity,
this.typeReferenceEntity,
this.pathReferenceEntity,
)
)
static pathSymbolEntity = P.lazy(() => this.symbol.map(v => new PathSymbolEntity(v)))
static pinEntity = P.lazy(() => this.createEntityGrammar(PinEntity))
static pinReferenceEntity = P.lazy(() =>
P.seq(
this.pathSymbolEntity,
P.whitespace,
this.guidEntity
).map(
([objectName, _1, pinGuid]) => new PinReferenceEntity({
objectName: objectName,
pinGuid: pinGuid,
})
)
)
static pinTypeEntity = P.lazy(() => this.createEntityGrammar(TerminalTypeEntity))
static rotatorEntity = P.lazy(() => this.createEntityGrammar(RotatorEntity, false))
static simpleSerializationRotatorEntity = P.lazy(() =>
P.alt(
P.seq(
this.number,
this.commaSeparation,
this.number,
this.commaSeparation,
this.number,
).map(([p, _1, y, _3, r]) =>
new SimpleSerializationRotatorEntity({
R: r,
P: p,
Y: y,
})
),
this.rotatorEntity
)
)
static vector2DEntity = P.lazy(() => this.createEntityGrammar(Vector2DEntity, false))
static simpleSerializationVector2DEntity = P.lazy(() =>
P.alt(
P.seq(
this.number,
this.commaSeparation,
this.number,
).map(([x, _1, y]) => new SimpleSerializationVector2DEntity({
X: x,
Y: y,
})),
this.vector2DEntity
)
)
static vectorEntity = P.lazy(() => this.createEntityGrammar(VectorEntity, false))
static simpleSerializationVectorEntity = P.lazy(() =>
P.alt(
P.seq(
this.number,
this.commaSeparation,
this.number,
this.commaSeparation,
this.number,
).map(([x, _1, y, _3, z]) => new SimpleSerializationVectorEntity({
X: x,
Y: y,
Z: z,
})),
this.vectorEntity
)
)
static symbolEntity = P.lazy(() => this.symbol.map(v => new SymbolEntity(v)))
static variableReferenceEntity = P.lazy(() => this.createEntityGrammar(VariableReferenceEntity))
static unknownKeysEntity = P.lazy(() =>
P.seq(
// Lookbehind
this.regexMap(
new RegExp(`(${this.Regex.Path.source}\\s*)?\\(\\s*`),
result => result[1] ?? ""
),
this.attributeName
.skip(this.equalSeparation)
.chain((attributeName) =>
this.unknownValue
.map(attributeValue =>
values => values[attributeName] = attributeValue
)
)
.sepBy1(this.commaSeparation),
P.regex(/\s*(?:,\s*)?\)/),
)
.map(([lookbehind, attributes, _2]) => {
let values = {}
if (lookbehind.length) {
values.lookbehind = lookbehind
}
attributes.forEach(attributeSetter => attributeSetter(values))
return new UnknownKeysEntity(values)
})
)
static unknownPinEntity = P.lazy(() =>
P.seq(
this.regexMap(
new RegExp(`${this.Regex.Symbol.source}\\s*\\(\\s*`),
result => result[1] ?? ""
),
this.createAttributeGrammar(this.unknownPinEntity).sepBy1(this.commaSeparation),
P.regex(/\s*(?:,\s*)?\)/)
)
.map(([lookbehind, attributes, _2]) => {
let values = {}
if (lookbehind.length) {
values.lookbehind = lookbehind
}
attributes.forEach(attributeSetter => attributeSetter(values))
return new UnknownPinEntity(values)
})
)
static unknownValue = P.lazy(() =>
P.alt(
// Remember to keep the order, otherwise parsing might fail
this.boolean,
this.guidEntity,
this.noneReferenceEntity,
this.null,
this.number,
this.string,
this.fullReferenceEntity,
this.localizedTextEntity,
this.invariantTextEntity,
this.formatTextEntity,
this.pinReferenceEntity,
this.vectorEntity,
this.rotatorEntity,
this.linearColorEntity,
this.vector2DEntity,
this.unknownKeysEntity,
this.symbolEntity,
this.grammarFor(undefined, [PinReferenceEntity]),
this.grammarFor(undefined, [new Union(Number, String, SymbolEntity)]),
)
)
static customProperty = P.lazy(() =>
P.seq(
P.regex(/CustomProperties\s+/),
this.grammarFor(undefined, ObjectEntity.attributes.CustomProperties.type[0]),
).map(([_0, pin]) => values => {
if (!values.CustomProperties) {
values.CustomProperties = []
}
values.CustomProperties.push(pin)
})
)
static inlinedArrayEntry = P.lazy(() =>
P.seq(
P.alt(
this.symbolQuoted.map(v => [v, true]),
this.symbol.map(v => [v, false]),
),
this.regexMap(
new RegExp(`\\s*\\(\\s*(\\d+)\\s*\\)\\s*\\=\\s*`),
v => Number(v[1])
)
)
.chain(
/** @param {[[String, Boolean], Number]} param */
([[symbol, quoted], index]) =>
this.grammarFor(ObjectEntity.attributes[symbol])
.map(currentValue =>
values => {
(values[symbol] ??= [])[index] = currentValue
Utility.objectSet(values, ["attributes", symbol, "quoted"], quoted, true)
if (!ObjectEntity.attributes[symbol]?.inlined) {
if (!values.attributes) {
IEntity.defineAttributes(values, {})
}
Utility.objectSet(values, ["attributes", symbol, "inlined"], true, true)
}
}
)
)
)
static subObjectEntity = P.lazy(() =>
this.objectEntity
.map(object =>
values => values[Configuration.subObjectAttributeNameFromEntity(object)] = object
)
)
/** @type {Parsimmon.Parser<ObjectEntity>} */
static objectEntity = P.lazy(() =>
P.seq(
P.regex(/Begin\s+Object/),
P.seq(
P.whitespace,
P.alt(
this.customProperty,
this.createAttributeGrammar(ObjectEntity),
this.createAttributeGrammar(ObjectEntity, Grammar.attributeNameQuoted, undefined, (obj, k, v) =>
Utility.objectSet(obj, ["attributes", ...k, "quoted"], true, true)
),
this.inlinedArrayEntry,
this.subObjectEntity
)
)
.map(([_0, entry]) => entry)
.many(),
P.regex(/\s+End\s+Object/),
).map(
([_0, attributes, _2]) => {
let values = {}
attributes.forEach(attributeSetter => attributeSetter(values))
return new ObjectEntity(values)
}
)
)
static multipleObject = P.lazy(() =>
P.seq(
P.optWhitespace,
this.objectEntity,
P.seq(
P.whitespace,
this.objectEntity,
)
.map(([_0, object]) => object)
.many(),
P.optWhitespace
).map(([_0, first, remaining, _4]) => [first, ...remaining])
)
/* --- Others --- */
static linearColorFromHex = P.lazy(() =>
Grammar.regexMap(new RegExp(
`#(${Grammar.Regex.HexDigit.source
}{2})(${Grammar.Regex.HexDigit.source
}{2})(${Grammar.Regex.HexDigit.source
}{2})(${this.Regex.HexDigit.source
}{2})?`
),
v => [v[1], v[2], v[3], v[4] ?? "FF"])
.map(([R, G, B, A]) => new LinearColorEntity({
R: parseInt(R, 16) / 255,
G: parseInt(G, 16) / 255,
B: parseInt(B, 16) / 255,
A: parseInt(A, 16) / 255,
}))
)
static linearColorRGBList = P.lazy(() =>
P.seq(
this.byteNumber,
this.commaSeparation,
this.byteNumber,
this.commaSeparation,
this.byteNumber,
).map(([R, _1, G, _3, B]) => new LinearColorEntity({
R: R / 255,
G: G / 255,
B: B / 255,
A: 1,
}))
)
static linearColorRGBAList = P.lazy(() =>
P.seq(
this.byteNumber,
this.commaSeparation,
this.byteNumber,
this.commaSeparation,
this.byteNumber,
this.commaSeparation,
this.byteNumber,
).map(([R, _1, G, _3, B, _5, A]) => new LinearColorEntity({
R: R / 255,
G: G / 255,
B: B / 255,
A: A,
}))
)
static linearColorRGB = P.lazy(() =>
P.seq(
P.regex(/rgb\s*\(\s*/),
this.linearColorRGBList,
P.regex(/\s*\)/)
).map(([_0, linearColor, _2]) => linearColor)
)
static linearColorRGBA = P.lazy(() =>
P.seq(
P.regex(/rgba\s*\(\s*/),
this.linearColorRGBAList,
P.regex(/\s*\)/)
).map(([_0, linearColor, _2]) => linearColor)
)
static linearColorFromAnyFormat = P.lazy(() =>
P.alt(
this.linearColorFromHex,
this.linearColorRGBA,
this.linearColorRGB,
this.linearColorRGBList,
)
)
static unknownValue // Defined in initializeSerializerFactor to avoid circular include
}

View File

@@ -7,8 +7,8 @@ import SerializerFactory from "./SerializerFactory.js"
export default class ObjectSerializer extends Serializer {
constructor() {
super(ObjectEntity, undefined, "\n", true, undefined, Serializer.same)
constructor(entityType = ObjectEntity) {
super(entityType, undefined, "\n", true, undefined, Serializer.same)
}
showProperty(entity, key) {
@@ -31,7 +31,7 @@ export default class ObjectSerializer extends Serializer {
/** @param {String} value */
doRead(value) {
const parseResult = Grammar.objectEntity.parse(value)
const parseResult = Grammar.grammarFor(undefined, this.entityType).parse(value)
if (!parseResult.status) {
throw new Error("Error when trying to parse the object.")
}
@@ -43,7 +43,7 @@ export default class ObjectSerializer extends Serializer {
* @returns {ObjectEntity[]}
*/
readMultiple(value) {
const parseResult = Grammar.multipleObject.parse(value)
const parseResult = ObjectEntity.getMultipleObjectsGrammar().parse(value)
if (!parseResult.status) {
throw new Error("Error when trying to parse the object.")
}

View File

@@ -0,0 +1,15 @@
import Parsimmon from "parsimmon"
const P = Parsimmon
export default class Serializable {
static grammar = this.createGrammar()
/** @protected */
static createGrammar() {
return /** @type {Parsimmon.Parser<Serializable>} */(P.fail(
"Unimplemented createGrammar() method in " + this.name)
)
}
}

View File

@@ -5,6 +5,7 @@ import EnumDisplayValueEntity from "../entity/EnumDisplayValueEntity.js"
import EnumEntity from "../entity/EnumEntity.js"
import FormatTextEntity from "../entity/FormatTextEntity.js"
import FunctionReferenceEntity from "../entity/FunctionReferenceEntity.js"
import Grammar from "./Grammar.js"
import GuidEntity from "../entity/GuidEntity.js"
import IdentifierEntity from "../entity/IdentifierEntity.js"
import Integer64Entity from "../entity/Integer64Entity.js"
@@ -18,6 +19,7 @@ import MirroredEntity from "../entity/MirroredEntity.js"
import ObjectEntity from "../entity/ObjectEntity.js"
import ObjectReferenceEntity from "../entity/ObjectReferenceEntity.js"
import ObjectSerializer from "./ObjectSerializer.js"
import Parsimmon from "parsimmon"
import PathSymbolEntity from "../entity/PathSymbolEntity.js"
import PinEntity from "../entity/PinEntity.js"
import PinReferenceEntity from "../entity/PinReferenceEntity.js"
@@ -30,6 +32,7 @@ import SimpleSerializationVectorEntity from "../entity/SimpleSerializationVector
import SymbolEntity from "../entity/SymbolEntity.js"
import TerminalTypeEntity from "../entity/TerminalTypeEntity.js"
import ToStringSerializer from "./ToStringSerializer.js"
import Union from "../entity/Union.js"
import UnknownKeysEntity from "../entity/UnknownKeysEntity.js"
import Utility from "../Utility.js"
import VariableReferenceEntity from "../entity/VariableReferenceEntity.js"
@@ -38,6 +41,30 @@ import VectorEntity from "../entity/VectorEntity.js"
/** @typedef {import("../entity/IEntity.js").AnySimpleValue} AnySimpleValue */
Grammar.unknownValue =
Parsimmon.alt(
// Remember to keep the order, otherwise parsing might fail
Grammar.boolean,
GuidEntity.createGrammar(),
ObjectReferenceEntity.noneReferenceGrammar,
Grammar.null,
Grammar.number,
Grammar.string,
ObjectReferenceEntity.fullReferenceGrammar,
LocalizedTextEntity.createGrammar(),
InvariantTextEntity.createGrammar(),
FormatTextEntity.createGrammar(),
PinReferenceEntity.createGrammar(),
VectorEntity.createGrammar(),
RotatorEntity.createGrammar(),
LinearColorEntity.createGrammar(),
Vector2DEntity.createGrammar(),
UnknownKeysEntity.createGrammar(),
SymbolEntity.createGrammar(),
Grammar.grammarFor(undefined, [PinReferenceEntity]),
Grammar.grammarFor(undefined, [new Union(Number, String, SymbolEntity)]),
)
export default function initializeSerializerFactory() {
SerializerFactory.registerSerializer(