Calculated serialization type

This commit is contained in:
barsdeveloper
2022-09-11 13:55:01 +02:00
parent 57ef15c943
commit 9f789b3e09
18 changed files with 510 additions and 412 deletions

View File

@@ -5,16 +5,19 @@ import GeneralSerializer from "./GeneralSerializer"
*/
/**
* @template {IEntity} T
* @template {IEntity | Boolean | Number | String} T
*/
export default class CustomSerializer extends GeneralSerializer {
#objectWriter
/**
* @param {(v: T, insideString: Boolean) => String} objectWriter
* @param {new () => T} entityType
*/
constructor(objectWriter, entityType) {
super(undefined, entityType)
this.objectWriter = objectWriter
this.#objectWriter = objectWriter
}
/**
@@ -22,8 +25,8 @@ export default class CustomSerializer extends GeneralSerializer {
* @param {Boolean} insideString
* @returns {String}
*/
write(object, insideString = false) {
let result = this.objectWriter(object, insideString)
write(entity, object, insideString = false) {
let result = this.#objectWriter(object, insideString)
return result
}
}

View File

@@ -37,8 +37,8 @@ export default class GeneralSerializer extends ISerializer {
* @param {Boolean} insideString
* @returns {String}
*/
write(object, insideString = false) {
let result = this.wrap(this.subWrite([], object, insideString))
write(entity, object, insideString = false) {
let result = this.wrap(this.subWrite(entity, [], object, insideString))
return result
}
}

View File

@@ -16,7 +16,6 @@ import SerializedType from "../entity/SerializedType"
import TypeInitialization from "../entity/TypeInitialization"
import Utility from "../Utility"
import VectorEntity from "../entity/VectorEntity"
import CalculatedType from "../entity/CalculatedType"
/**
* @typedef {import("../entity/IEntity").default} IEntity
@@ -30,32 +29,9 @@ export default class Grammar {
static getGrammarForType(r, attributeType, defaultGrammar) {
if (attributeType instanceof TypeInitialization) {
// Unpack TypeInitialization
attributeType = attributeType.type
return Grammar.getGrammarForType(r, attributeType, defaultGrammar)
}
if (attributeType instanceof SerializedType) {
const nonStringTypes = attributeType.types.filter(t => t !== String)
let result = P.alt(
...nonStringTypes.map(t =>
Grammar.getGrammarForType(r, t).wrap(P.string('"'), P.string('"')).map(
/**
* @param {IEntity} entity
*/
entity => {
entity.setShowAsString(true) // Showing as string because it is inside a SerializedType
return entity
}
)
)
)
if (nonStringTypes.length < attributeType.types.length) {
result = result.or(r.String/*.map(v => {
if (attributeType.stringFallback) {
console.log("Unrecognized value, fallback on String")
}
return v
})*/) // Separated because it cannot be wrapped into " and "
let result = Grammar.getGrammarForType(r, attributeType.type, defaultGrammar)
if (attributeType.serialized && !(attributeType.type instanceof String)) {
result = result.wrap(P.string('"'), P.string('"'))
}
return result
}
@@ -149,7 +125,12 @@ export default class Grammar {
Null = r => P.seq(P.string("("), r.InlineOptWhitespace, P.string(")")).map(_ => null).desc("null: ()")
Boolean = r => P.alt(P.string("True"), P.string("False")).map(v => v === "True" ? true : false)
Boolean = r => P.alt(
P.string("True"),
P.string("true"),
P.string("False"),
P.string("false"),
).map(v => v.toLocaleLowerCase() === "true" ? true : false)
.desc("either True or False")
HexDigit = r => P.regex(/[0-9a-fA-f]/).desc("hexadecimal digit")

View File

@@ -34,13 +34,8 @@ export default class ISerializer {
* @param {Boolean} insideString
* @returns {String}
*/
serialize(object, insideString) {
insideString ||= object.isShownAsString()
let result = this.write(object, insideString)
if (object.isShownAsString()) {
result = `"${result}"`
}
return result
serialize(object, insideString, entity = object) {
return this.write(entity, object, insideString)
}
/**
@@ -56,7 +51,7 @@ export default class ISerializer {
* @param {Boolean} insideString
* @returns {String}
*/
write(object, insideString) {
write(entity, object, insideString) {
throw new Error("Not implemented")
}
@@ -64,30 +59,12 @@ export default class ISerializer {
* @param {String[]} fullKey
* @param {Boolean} insideString
*/
writeValue(value, fullKey, insideString) {
if (value === null) {
return "()"
}
const serialize = v => SerializerFactory.getSerializer(Utility.getType(v)).serialize(v)
// This is an exact match (and not instanceof) to hit also primitive types (by accessing value.constructor they are converted to objects automatically)
switch (value?.constructor) {
case Function:
return this.writeValue(value(), fullKey, insideString)
case Boolean:
return Utility.firstCapital(value.toString())
case Number:
return value.toString()
case String:
return insideString
? `\\"${Utility.encodeString(value)}\\"`
: `"${Utility.encodeString(value)}"`
}
if (value instanceof Array) {
return `(${value.map(v => serialize(v) + ",").join("")})`
}
if (value instanceof IEntity) {
return serialize(value)
writeValue(entity, value, fullKey, insideString) {
const serializer = SerializerFactory.getSerializer(Utility.getType(value))
if (!serializer) {
throw new Error("Unknown value type, a serializer must be registered in the SerializerFactory class")
}
return serializer.write(entity, value, insideString)
}
/**
@@ -96,7 +73,7 @@ export default class ISerializer {
* @param {Boolean} insideString
* @returns {String}
*/
subWrite(key, object, insideString) {
subWrite(entity, key, object, insideString) {
let result = ""
let fullKey = key.concat("")
const last = fullKey.length - 1
@@ -106,13 +83,18 @@ export default class ISerializer {
if (value?.constructor === Object) {
// Recursive call when finding an object
result += (result.length ? this.separator : "")
+ this.subWrite(fullKey, value, insideString)
} else if (value !== undefined && this.showProperty(object, fullKey, value)) {
+ this.subWrite(entity, fullKey, value, insideString)
} else if (value !== undefined && this.showProperty(entity, object, fullKey, value)) {
const isSerialized = Utility.isSerialized(entity, fullKey)
result += (result.length ? this.separator : "")
+ this.prefix
+ this.attributeKeyPrinter(fullKey)
+ this.attributeValueConjunctionSign
+ this.writeValue(value, fullKey, insideString)
+ (
isSerialized
? `"${this.writeValue(entity, value, fullKey, true)}"`
: this.writeValue(entity, value, fullKey, insideString)
)
}
}
if (this.trailingSeparator && result.length && fullKey.length === 1) {
@@ -122,7 +104,7 @@ export default class ISerializer {
return result
}
showProperty(object, attributeKey, attributeValue) {
showProperty(entity, object, attributeKey, attributeValue) {
const attributes = this.entityType.attributes
const attribute = Utility.objectGet(attributes, attributeKey)
if (attribute instanceof TypeInitialization) {

View File

@@ -9,7 +9,7 @@ export default class ObjectSerializer extends ISerializer {
super(ObjectEntity, " ", "\n", false)
}
showProperty(object, attributeKey, attributeValue) {
showProperty(entity, object, attributeKey, attributeValue) {
switch (attributeKey.toString()) {
case "Class":
case "Name":
@@ -17,7 +17,7 @@ export default class ObjectSerializer extends ISerializer {
// Serielized separately
return false
}
return super.showProperty(object, attributeKey, attributeValue)
return super.showProperty(entity, object, attributeKey, attributeValue)
}
/**
@@ -46,9 +46,9 @@ export default class ObjectSerializer extends ISerializer {
* @param {ObjectEntity} object
* @param {Boolean} insideString
*/
write(object, insideString) {
let result = `Begin Object Class=${object.Class.path} Name=${this.writeValue(object.Name, ["Name"], insideString)}
${this.subWrite([], object, insideString)
write(entity, object, insideString) {
let result = `Begin Object Class=${object.Class.path} Name=${this.writeValue(entity, object.Name, ["Name"], insideString)}
${this.subWrite(entity, [], object, insideString)
+ object
.CustomProperties.map(pin =>
this.separator

View File

@@ -1,21 +0,0 @@
import PinEntity from "../entity/PinEntity"
import Utility from "../Utility"
import GeneralSerializer from "./GeneralSerializer"
export default class PinSerializer extends GeneralSerializer {
constructor() {
super(v => `${PinEntity.lookbehind} (${v})`, PinEntity, "", ",", true)
}
/**
* @param {String[]} fullKey
* @param {Boolean} insideString
*/
writeValue(value, fullKey, insideString) {
if (value?.constructor === String && fullKey.length == 1 && fullKey[0] == "DefaultValue") {
return `"${Utility.encodeInputString(value)}"`
}
return super.writeValue(value, fullKey, insideString)
}
}

View File

@@ -1,14 +1,25 @@
import Utility from "../Utility"
/** @typedef {import("../entity/IEntity").default} IEntity */
/**
* @template {IEntity} T
* @typedef {import("./ISerializer").default<T>} ISerializer
*/
export default class SerializerFactory {
/** @type {Map<T, ISerializer<T>>} */
static #serializers = new Map()
static registerSerializer(entity, object) {
SerializerFactory.#serializers.set(entity, object)
}
/**
* @template {IEntity} T
* @param {T} entity
*/
static getSerializer(entity) {
return SerializerFactory.#serializers.get(Utility.getType(entity))
return SerializerFactory.#serializers.get(entity)
}
}

View File

@@ -1,3 +1,4 @@
import Utility from "../Utility"
import GeneralSerializer from "./GeneralSerializer"
/**
@@ -20,10 +21,9 @@ export default class ToStringSerializer extends GeneralSerializer {
* @param {T} object
* @param {Boolean} insideString
*/
write(object, insideString) {
let result = insideString || object.isShownAsString()
? `"${object.toString().replaceAll('"', '\\"')}"`
: object.toString()
return result
write(entity, object, insideString) {
return !insideString && object.constructor === String
? `"${Utility.encodeString(object.toString())}"` // String will have quotes if not inside a string already
: Utility.encodeString(object.toString())
}
}

View File

@@ -14,14 +14,52 @@ import ObjectSerializer from "./ObjectSerializer"
import PathSymbolEntity from "../entity/PathSymbolEntity"
import PinEntity from "../entity/PinEntity"
import PinReferenceEntity from "../entity/PinReferenceEntity"
import PinSerializer from "./PinSerializer"
import SerializerFactory from "./SerializerFactory"
import ToStringSerializer from "./ToStringSerializer"
import Utility from "../Utility"
export default function initializeSerializerFactory() {
const bracketsWrapped = v => `(${v})`
SerializerFactory.registerSerializer(
null,
new CustomSerializer(
(nullValue, insideString) => "()",
null
)
)
SerializerFactory.registerSerializer(
Array,
new CustomSerializer(
/** @param {Array} array */
(array, insideString) =>
`(${array
.map(v =>
SerializerFactory.getSerializer(Utility.getType(v)).serialize(v, insideString) + ","
)
.join("")
})`,
Array
)
)
SerializerFactory.registerSerializer(
Boolean,
new CustomSerializer(
/** @param {Boolean} boolean */
(boolean, insideString) => boolean
? insideString
? "true"
: "True"
: insideString
? "false"
: "False",
Boolean
)
)
SerializerFactory.registerSerializer(
FunctionReferenceEntity,
new GeneralSerializer(bracketsWrapped, FunctionReferenceEntity)
@@ -53,6 +91,15 @@ export default function initializeSerializerFactory() {
new GeneralSerializer(v => `${LocalizedTextEntity.lookbehind}(${v})`, LocalizedTextEntity, "", ", ", false, "", _ => "")
)
SerializerFactory.registerSerializer(
Number,
new CustomSerializer(
/** @param {Number} value */
value => value.toString(),
Number
)
)
SerializerFactory.registerSerializer(
ObjectEntity,
new ObjectSerializer()
@@ -73,13 +120,23 @@ export default function initializeSerializerFactory() {
SerializerFactory.registerSerializer(PathSymbolEntity, new ToStringSerializer(PathSymbolEntity))
SerializerFactory.registerSerializer(
PinEntity,
new GeneralSerializer(v => `${PinEntity.lookbehind} (${v})`, PinEntity, "", ",", true)
)
SerializerFactory.registerSerializer(
PinReferenceEntity,
new GeneralSerializer(v => v, PinReferenceEntity, "", " ", false, "", _ => "")
)
SerializerFactory.registerSerializer(
PinEntity,
new PinSerializer()
String,
new CustomSerializer(
(value, insideString) => insideString
? Utility.encodeString(value)
: `"${Utility.encodeString(value)}"`,
String
)
)
}