mirror of
https://github.com/barsdeveloper/ueblueprint.git
synced 2026-05-13 15:27:30 +08:00
Grammar refactoring WIP
This commit is contained in:
145
js/serialization/Grammar.js
Normal file
145
js/serialization/Grammar.js
Normal file
@@ -0,0 +1,145 @@
|
||||
import FunctionReferenceEntity from "../entity/FunctionReferenceEntity"
|
||||
import Guid from "../Guid"
|
||||
import Integer from "../entity/Integer"
|
||||
import ObjectReferenceEntity from "../entity/ObjectReferenceEntity"
|
||||
import Parsimmon from "parsimmon"
|
||||
import PinEntity from "../entity/PinEntity"
|
||||
import Utility from "../Utility"
|
||||
import ObjectEntity from "../entity/ObjectEntity"
|
||||
|
||||
let P = Parsimmon
|
||||
|
||||
export default class Grammar {
|
||||
// General
|
||||
InlineWhitespace = _ => P.regex(/[^\S\n]+/)
|
||||
InlineOptWhitespace = _ => P.regex(/[^\S\n]*/)
|
||||
WhitespaceNewline = _ => P.regex(/[^\S\n]*\n\s*/)
|
||||
Null = r => P.seq(P.string("("), r.InlineOptWhitespace, P.string(")")).map(_ => null).desc("null: ()")
|
||||
None = _ => P.string("None").map(_ => new ObjectReferenceEntity({ type: "None" })).desc("none")
|
||||
Boolean = _ => P.alt(P.string("True"), P.string("False")).map(v => v === "True" ? true : false).desc("either True or False")
|
||||
Number = _ => P.regex(/[0-9]+(?:\.[0-9]+)?/).map(Number).desc("a number")
|
||||
Integer = _ => P.regex(/[0-9]+/).map(Integer).desc("an integer")
|
||||
String = _ => P.regex(/(?:[^"\\]|\\")*/).wrap(P.string('"'), P.string('"')).desc('string (with possibility to escape the quote using \")')
|
||||
Word = _ => P.regex(/[a-zA-Z]+/).desc("a word")
|
||||
Guid = _ => P.regex(/[0-9a-zA-Z]{32}/).desc("32 digit hexadecimal (accepts all the letters for safety) value")
|
||||
ReferencePath = _ => P.seq(P.string("/"), P.regex(/[a-zA-Z_]+/).sepBy1(P.string(".")).tieWith("."))
|
||||
.tie()
|
||||
.atLeast(2)
|
||||
.tie()
|
||||
.desc('a path (words with possibly underscore, separated by ".", separated by "/")')
|
||||
Reference = r => P.alt(
|
||||
r.None,
|
||||
r.ReferencePath.map(path => new ObjectReferenceEntity({ path: path })),
|
||||
P.seqMap(
|
||||
r.Word,
|
||||
P.optWhitespace,
|
||||
P.alt(P.string(`"`), P.string(`'"`)).chain(
|
||||
result => r.ReferencePath.skip(
|
||||
P.string(result.split("").reverse().join(""))
|
||||
)
|
||||
),
|
||||
(referenceType, _, referencePath) => new ObjectReferenceEntity({
|
||||
type: referenceType,
|
||||
path: referencePath
|
||||
})
|
||||
)
|
||||
)
|
||||
AttributeName = r => r.Word.sepBy1(P.string(".")).tieWith(".").desc('words separated by ""')
|
||||
AttributeAnyValue = r => P.alt(r.Null, r.None, r.Boolean, r.Number, r.Integer, r.String, r.Guid, r.Reference)
|
||||
static getGrammarForType(r, type, defaultGrammar) {
|
||||
switch (type) {
|
||||
case Boolean:
|
||||
return r.Boolean
|
||||
case Number:
|
||||
return r.Number
|
||||
case Integer:
|
||||
return r.Integer
|
||||
case String:
|
||||
return r.String
|
||||
case Guid:
|
||||
return r.Guid
|
||||
case ObjectReferenceEntity:
|
||||
return r.Reference
|
||||
default:
|
||||
return defaultGrammar
|
||||
}
|
||||
}
|
||||
// Meta grammar
|
||||
static CreateAttributeGrammar = (r, attributeGrammar, attributeSupplier, valueSeparator = P.string("=")) =>
|
||||
attributeGrammar.skip(valueSeparator.trim(P.optWhitespace))
|
||||
.chain(attributeName => {
|
||||
const attributeKey = attributeName.split(".")
|
||||
const attribute = attributeSupplier(attributeKey)
|
||||
const type = Utility.getType(attribute)
|
||||
let attributeValueGrammar = type === Array
|
||||
? attribute
|
||||
.map(v => Grammar.getGrammarForType(r, Utility.getType(v)))
|
||||
.reduce((accum, cur) =>
|
||||
!cur || accum === r.AttributeAnyValue
|
||||
? r.AttributeAnyValue
|
||||
: accum.or(cur)
|
||||
)
|
||||
: Grammar.getGrammarForType(r, type, r.AttributeAnyValue)
|
||||
// After the attribute name (already parsed at this point, we continue with an equal sign (possibly surrounded by whitespace) then the expected attribute value)
|
||||
return attributeValueGrammar.map(attributeValue => type === Array
|
||||
? entity => {
|
||||
/** @type {Array} */
|
||||
let array = Utility.objectGet(entity, attributeKey, [])
|
||||
array.push(attributeValue)
|
||||
return Utility.objectSet(entity, attributeKey, array)
|
||||
}
|
||||
: entity => Utility.objectSet(entity, attributeKey, attributeValue)
|
||||
) // returns attributeSetter
|
||||
})
|
||||
// Meta grammar
|
||||
static CreateMultiAttributeGrammar = (r, keyGrammar, entityType, attributeSupplier) =>
|
||||
/**
|
||||
* Basically this creates a parser that looks for a string like 'AttributeName (A=False,B="Something",)'
|
||||
* Then it populates an object of type EntityType with the attribute values found inside the parentheses.
|
||||
*/
|
||||
P.seqObj(
|
||||
keyGrammar,
|
||||
P.optWhitespace,
|
||||
P.string("("),
|
||||
[
|
||||
"attributes", // this is the name of the attribute of object passed to map chained next
|
||||
Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeSupplier)
|
||||
.trim(P.optWhitespace)
|
||||
.sepBy(P.string(","))
|
||||
.skip(P.regex(/,?/).then(P.optWhitespace)) // Optional trailing comma
|
||||
],
|
||||
P.string(')')
|
||||
).map(object => {
|
||||
let result = new entityType()
|
||||
object.attributes.forEach(attributeSetter => attributeSetter(result))
|
||||
return result
|
||||
})
|
||||
FunctionReference = r => Grammar.CreateMultiAttributeGrammar(
|
||||
r,
|
||||
P.succeed(),
|
||||
FunctionReferenceEntity,
|
||||
attributeKey => Utility.objectGet(FunctionReferenceEntity.attributes, attributeKey)
|
||||
)
|
||||
Pin = r => Grammar.CreateMultiAttributeGrammar(
|
||||
r,
|
||||
P.string("Pin"),
|
||||
PinEntity,
|
||||
attributeKey => Utility.objectGet(PinEntity.attributes, attributeKey)
|
||||
)
|
||||
Object = r => P.seqMap(
|
||||
P.seq(P.string("Begin"), P.whitespace, P.string("Object"), P.whitespace),
|
||||
P.alt(
|
||||
Grammar.CreateAttributeGrammar(r, P.string("CustomProperties"), _ => ObjectEntity.attributes.CustomProperties, P.string(" ")),
|
||||
Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeKey => Utility.objectGet(ObjectEntity, attributeKey))
|
||||
)
|
||||
.trim(r.InlineOptWhitespace) // whitespace which is NOT newline
|
||||
.sepBy(P.string("\n"))
|
||||
.skip(r.WhitespaceNewline), // Optional trailing comma
|
||||
P.seq(P.string("End"), P.whitespace, P.string("Object")),
|
||||
(_, attributes, __) => {
|
||||
let result = new ObjectEntity()
|
||||
attributes.forEach(attributeSetter => attributeSetter(result))
|
||||
return result
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -2,8 +2,31 @@ import Serializer from "./Serializer";
|
||||
|
||||
export default class ObjectSerializer extends Serializer {
|
||||
|
||||
showProperty(attributeKey, attributeValue) {
|
||||
switch (attributeKey.toString()) {
|
||||
case "Class":
|
||||
case "Name":
|
||||
// Serielized separately
|
||||
return false
|
||||
}
|
||||
return super.showProperty(attributeKey, attributeValue)
|
||||
}
|
||||
|
||||
read(value) {
|
||||
const parseResult = Serializer.grammar.Object.parse(value)
|
||||
if (!parseResult.status) {
|
||||
console.error("Error when trying to parse the object.")
|
||||
return parseResult
|
||||
}
|
||||
return parseResult.value
|
||||
}
|
||||
|
||||
write(object) {
|
||||
let result = `Pin (${this.constructor.subWrite('', this)})`
|
||||
let result = `
|
||||
Begin Object Class=${object.Class} Name=${object.Name}
|
||||
${this.subWrite([], object, "\n", " ")}
|
||||
End Object
|
||||
`
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import Parsimmon from "parsimmon"
|
||||
import PinEntity from "../entity/PinEntity"
|
||||
import Utility from "../Utility"
|
||||
import ReferenceTypeName from "./ReferenceTypeName"
|
||||
|
||||
let P = Parsimmon
|
||||
|
||||
export default class PinGrammar {
|
||||
static pinEntity = new PinEntity()
|
||||
Null = _ => P.string("()").map(() => null).desc("null value.")
|
||||
None = _ => P.string("None").map(() => new ReferenceTypeName("None")).desc('None value')
|
||||
ReferencePath = _ => P.regex(/[a-zA-Z_]/).sepBy1(P.string(".")).tieWith(".").sepBy1(Parsimmon.string("/")).tieWith("/")
|
||||
Reference = r => r.Word.skip(P.optWhitespace).then(r.ReferencePath)
|
||||
Word = _ => P.regex(/[a-zA-Z]+/)
|
||||
Guid = _ => P.regex(/[0-9a-zA-Z]{32}/).desc("a 32 digit hexadecimal value")
|
||||
String = _ => P.regex(/(?:[^"\\]|\\")*/).wrap(P.string('"'), P.string('"')).desc('a string value, with possibility to escape the quote symbol (") using \"')
|
||||
Boolean = _ => P.string("True").or(P.string("False")).map(v => v === "True" ? true : false).desc("either True or False")
|
||||
AttributeName = r => r.Word.sepBy1(P.string(".")).tieWith(".")
|
||||
AttributeValue = r => P.alt(r.Null, r.None, r.Boolean, Reference, r.String, r.Guid)
|
||||
Attribute = r => P.seqMap(
|
||||
r.AttributeName,
|
||||
P.string("=").trim(P.optWhitespace),
|
||||
r.AttributeValue,
|
||||
/**
|
||||
*
|
||||
* @param {String} name The key PinEntity
|
||||
* @param {*} _
|
||||
* @param {*} value
|
||||
* @returns
|
||||
*/
|
||||
(name, _, value) =>
|
||||
/**
|
||||
* Sets the property name name in the object pinEntity to the value provided
|
||||
* @param {PinEntity} pinEntity
|
||||
*/
|
||||
(pinEntity) => Utility.objectSet(name.split('.'), value, pinEntity)
|
||||
)
|
||||
Pin = r => {
|
||||
return P.seqObj(
|
||||
P.string("Pin"),
|
||||
P.optWhitespace,
|
||||
P.string("("),
|
||||
[
|
||||
"attributes",
|
||||
r.Attribute
|
||||
.trim(P.optWhitespace)
|
||||
.sepBy(P.string(","))
|
||||
.skip(P.regex(/,?/).then(P.optWhitespace)) // Optional trailing comma
|
||||
],
|
||||
P.string(')')
|
||||
).map(object => {
|
||||
let result = new PinEntity()
|
||||
object.attributes.forEach(attributeSetter => attributeSetter(result))
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import Parsimmon from "parsimmon"
|
||||
import PinGrammar from "./PinGrammar"
|
||||
import PinEntity from "../entity/PinEntity"
|
||||
import Serializer from "./Serializer"
|
||||
|
||||
export default class PinSerializer extends Serializer {
|
||||
|
||||
static pinGrammar = Parsimmon.createLanguage(new PinGrammar())
|
||||
getAttributes() {
|
||||
return PinEntity.attributes
|
||||
}
|
||||
|
||||
read(value) {
|
||||
const parseResult = PinSerializer.pinGrammar.Pin.parse(value)
|
||||
const parseResult = Serializer.grammar.Pin.parse(value)
|
||||
if (!parseResult.status) {
|
||||
console.error("Error when trying to parse the pin.")
|
||||
return parseResult
|
||||
@@ -16,7 +17,7 @@ export default class PinSerializer extends Serializer {
|
||||
}
|
||||
|
||||
write(object) {
|
||||
let result = `Pin (${Serializer.subWrite('', object)})`
|
||||
let result = `Pin (${this.subWrite([], object, ",")})`
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
export default class ReferenceTypeName {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} reference
|
||||
* @returns
|
||||
*/
|
||||
static splitReference(reference) {
|
||||
const pathBegin = reference.search(/['"]/)
|
||||
const referenceType = reference.substr(0, pathBegin > 0 ? pathBegin : undefined) // reference example Class'"/Script/Engine.PlayerCameraManager"'
|
||||
const referencePath = pathBegin > 0 ? reference.substr(pathBegin) : ""
|
||||
switch (referenceType) {
|
||||
case "None":
|
||||
if (referencePath.length > 0) {
|
||||
return false // None cannot have a path
|
||||
}
|
||||
default:
|
||||
return [referenceType, referencePath]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} reference
|
||||
*/
|
||||
constructor(reference) {
|
||||
reference = ReferenceTypeName.splitReference(reference)
|
||||
if (!reference) {
|
||||
throw new Error('Invalid reference: ' + reference)
|
||||
}
|
||||
this.reference = reference
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.value
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,81 @@
|
||||
import Parsimmon from "parsimmon"
|
||||
import PinGrammar from "./PinGrammar"
|
||||
import Grammar from "./Grammar"
|
||||
import Utility from "../Utility"
|
||||
import TypeInitialization from "../entity/TypeInitialization"
|
||||
import ObjectReferenceEntity from "../entity/ObjectReferenceEntity"
|
||||
import Guid from "../Guid"
|
||||
|
||||
|
||||
export default class Serializer {
|
||||
|
||||
static writeValue(value) {
|
||||
if (value?.constructor?.name === 'Function') {
|
||||
return this.writeValue(value())
|
||||
}
|
||||
// No quotes
|
||||
static grammar = Parsimmon.createLanguage(new Grammar())
|
||||
|
||||
writeValue(value) {
|
||||
if (value === null) {
|
||||
return '()'
|
||||
return "()"
|
||||
}
|
||||
if (value?.constructor?.name === 'Boolean') {
|
||||
return value ? 'True' : 'False'
|
||||
}
|
||||
if (value?.constructor?.name === 'ETypesNames' || value?.constructor?.name === 'FGuid') {
|
||||
return value.toString()
|
||||
}
|
||||
// Quotes
|
||||
if (value?.constructor?.name === 'String') {
|
||||
return `"${value}"`
|
||||
switch (value?.constructor) {
|
||||
case Function:
|
||||
return this.writeValue(value())
|
||||
case Boolean:
|
||||
return Utility.FirstCapital(value.toString())
|
||||
case ObjectReferenceEntity:
|
||||
case Guid:
|
||||
return value.toString()
|
||||
case String:
|
||||
return `"${value}"`
|
||||
}
|
||||
}
|
||||
|
||||
static subWrite(prefix, object) {
|
||||
/**
|
||||
*
|
||||
* @param {String[]} prefix
|
||||
* @param {Object} object
|
||||
* @param {String} separator
|
||||
* @returns
|
||||
*/
|
||||
subWrite(key, object, separator = "\n", prefix = "") {
|
||||
let result = ""
|
||||
prefix += prefix != "" ? "." : ""
|
||||
const fullPropertyName = prefix + property
|
||||
let fullKey = key.concat("")
|
||||
const last = fullKey.length - 1
|
||||
for (const property in object) {
|
||||
if (object[property]?.constructor?.name === 'Object') {
|
||||
result += Serializer.subWrite(fullPropertyName, object[property])
|
||||
} else if (!object.constructor.optionalKeys.contains(fullPropertyName)) {
|
||||
result += `${fullPropertyName}=${Serializer.writeValue(object[property])},`
|
||||
fullKey[last] = property
|
||||
const value = object[property]
|
||||
if (object[property]?.constructor === Object) {
|
||||
// Recursive call when finding an object
|
||||
result += this.subWrite(fullKey, value, separator, prefix)
|
||||
} else if (this.showProperty(fullKey, value)) {
|
||||
result += prefix + fullKey.join(".") + "=" + this.writeValue(value) + separator
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
getAttributes() {
|
||||
return PinEntity.attributes
|
||||
}
|
||||
|
||||
showProperty(attributeKey, attributeValue) {
|
||||
const attributes = this.getAttributes()
|
||||
const attribute = Utility.objectGet(attributes, attributeKey)
|
||||
if (attribute instanceof TypeInitialization) {
|
||||
return !Utility.equals(attribute.value, attributeValue) || attribute.showDefault
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} value
|
||||
*/
|
||||
read(value) {
|
||||
//Parsimmon.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representing the object (serialization)
|
||||
* @param {*} object
|
||||
* @returns The serialized string
|
||||
*/
|
||||
write(object) {
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import ObjectEntity from "../entity/ObjectEntity";
|
||||
import PinEntity from "../entity/PinEntity";
|
||||
import SerializeObject from "./ObjectSerialize";
|
||||
import PinSerializer from "./PinSerializer";
|
||||
import PinEntity from "../entity/PinEntity"
|
||||
import Utility from "../Utility"
|
||||
import PinSerializer from "./PinSerializer"
|
||||
import ObjectEntity from "../entity/ObjectEntity"
|
||||
import ObjectSerialize from "./ObjectSerialize"
|
||||
|
||||
|
||||
export default class SerializerFactory {
|
||||
static serializers = new Map([
|
||||
[PinEntity.prototype.constructor.name, PinSerializer],
|
||||
[ObjectEntity.prototype.constructor.name, SerializeObject]
|
||||
[PinEntity, PinSerializer],
|
||||
[ObjectEntity, ObjectSerialize]
|
||||
])
|
||||
|
||||
createSerializer(object) {
|
||||
return SerializerFactory.serializers.get(object.constructor.name)
|
||||
static createSerializer(object) {
|
||||
return new SerializerFactory.serializers.get(Utility.getType(object))()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user