/** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * Copyright (C) 2021-2024 Ad5001 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { parseDomain, Expression as Expr, Domain } from "./math/index.mjs" import Objects from "./module/objects.mjs" const NONE = class Empty { } let stringValuesValidators = { "int": [parseInt, (x) => !isNaN(x)], "double": [parseFloat, (x) => !isNaN(x)], "string": [(x) => x, () => true] } let stringValidatorTypes = Object.keys(stringValuesValidators) class PropertyType { /** * Validates if a value corresponds to the current property type, and if so, returns it. * @param value * @returns {null|object} */ parse(value) { throw new TypeError(`validate function of ${typeof this} has not been implemented.`) } /** * Exports the value of this property type. * @param value * @returns {string|number|bool|object} */ export(value) { throw new TypeError(`export function of ${typeof this} has not been implemented.`) } } export class Expression extends PropertyType { constructor(...variables) { super() this.type = "Expression" this.variables = variables } toString() { return this.variables.length === 0 ? "Number" : `Expression(${this.variables.join(", ")})` } parse(value) { let result = NONE if(typeof value == "string") try { result = new Expr(value) } catch(e) { // Silently error and return null console.trace() console.log(`Error parsing expression ${value}:`) console.error(e) } return result } export(value) { if(value instanceof Expr) return value.toEditableString() else throw new TypeError(`Exportation error: ${value} is not an expression.`) } } export class Enum extends PropertyType { constructor(...values) { super() this.type = "Enum" this.values = values this.legacyValues = {} this.translatedValues = values.map(x => qsTranslate("parameters", x)) } toString() { return `${this.type}(${this.values.join(", ")})` } parse(value) { let result = NONE if(this.values.includes(value)) result = value else if(this.legacyValues[value]) result = this.legacyValues[value] return result } export(value) { if(this.values.includes(value)) return value else if(this.legacyValues[value]) return this.legacyValues[value] else throw new TypeError(`Exportation error: ${value} not one of ${this.values.join(", ")}.`) } } export class ObjectType extends PropertyType { constructor(objType, allowNull = false) { super() this.type = "ObjectType" this.objType = objType this.allowNull = allowNull } toString() { return this.objType } parse(name) { let result = NONE if(typeof name == "string" && name in Objects.currentObjectsByName) { let obj = Objects.currentObjectsByName[name] if(obj.type === this.objType || (this.objType === "ExecutableObject" && obj.execute)) { result = obj } else { // Silently error and return null console.trace() console.error(new TypeError(`Object ${name} is of not of type ${this.objType}:`)) } } else if(this.allowNull && (name == null || name === "null")) result = null return result } export(value) { if(value == null && this.allowNull) return null else if(value.type === this.objType || (this.objType === "ExecutableObject" && value.execute)) return value.name else throw new TypeError(`Exportation error: ${value} not a ${this.objType}.`) } } export class List extends PropertyType { constructor(type, format = /^.+$/, label = "", forbidAdding = false) { super() // type can be string, int and double. this.type = "List" this.valueType = type if(!stringValidatorTypes.includes(this.valueType)) throw new TypeError(`${this.valueType} must be one of ${stringValidatorTypes.join(", ")}.`) this.format = format this.label = label this.forbidAdding = forbidAdding } toString() { return `${this.type}(${this.valueType}:${this.format})` } parse(value) { let result = NONE if(typeof value == "object" && value.__proto__ === Array) { let valid = 0 for(let v of value) { if(this.format.test(v)) { v = stringValuesValidators[this.valueType][0](v) if(stringValuesValidators[this.valueType][1](v)) valid++ } } if(valid === value.length) // Ensure every value is valid. result = value } return result } export(value) { if(typeof value == "object" && value.__proto__ === Array) return value else throw new TypeError(`Exportation error: ${value} not a list.`) } } export class Dictionary extends PropertyType { constructor(type, keyType = "string", format = /^.+$/, keyFormat = /^.+$/, preKeyLabel = "", postKeyLabel = ": ", forbidAdding = false) { super() // type & keyType can be string, int and double. this.type = "Dict" this.valueType = type this.keyType = keyType this.format = format this.keyFormat = keyFormat this.preKeyLabel = preKeyLabel this.postKeyLabel = postKeyLabel this.forbidAdding = forbidAdding } toString() { return `${this.type}(${this.keyType}:${this.keyFormat}: ${this.valueType}:${this.format})` } parse(value) { let result = NONE if(typeof value == "object" && value.__proto__ !== Array) { let dict = [] for(let [k, v] of Object.entries(value)) { if(this.format.test(v) && this.keyFormat.test(k)) { k = stringValuesValidators[this.keyType][0](k) v = stringValuesValidators[this.valueType][0](v) if(stringValuesValidators[this.keyType][1](k)) if(stringValuesValidators[this.valueType][1](v)) dict[k] = v } } if(Object.keys(dict).length === Object.keys(value).length) result = value } return result } export(value) { if(typeof value == "object" && value.__proto__ !== Array) return value else throw new TypeError(`Exportation error: ${value} not a dictionary.`) } } // Common parameters for Enums Enum.Position = new Enum( QT_TRANSLATE_NOOP("parameters", "above"), QT_TRANSLATE_NOOP("parameters", "below"), QT_TRANSLATE_NOOP("parameters", "left"), QT_TRANSLATE_NOOP("parameters", "right"), QT_TRANSLATE_NOOP("parameters", "above-left"), QT_TRANSLATE_NOOP("parameters", "above-right"), QT_TRANSLATE_NOOP("parameters", "below-left"), QT_TRANSLATE_NOOP("parameters", "below-right") ) Enum.Position.legacyValues = { "top": "above", "bottom": "below", "top-left": "above-left", "top-right": "above-right", "bottom-left": "below-left", "bottom-right": "below-right" } Enum.Positioning = new Enum( QT_TRANSLATE_NOOP("parameters", "center"), QT_TRANSLATE_NOOP("parameters", "top"), QT_TRANSLATE_NOOP("parameters", "bottom"), QT_TRANSLATE_NOOP("parameters", "left"), QT_TRANSLATE_NOOP("parameters", "right"), QT_TRANSLATE_NOOP("parameters", "top-left"), QT_TRANSLATE_NOOP("parameters", "top-right"), QT_TRANSLATE_NOOP("parameters", "bottom-left"), QT_TRANSLATE_NOOP("parameters", "bottom-right") ) Enum.FunctionDisplayType = new Enum( QT_TRANSLATE_NOOP("parameters", "application"), QT_TRANSLATE_NOOP("parameters", "function") ) Enum.BodePass = new Enum( QT_TRANSLATE_NOOP("parameters", "high"), QT_TRANSLATE_NOOP("parameters", "low") ) Enum.XCursorValuePosition = new Enum( QT_TRANSLATE_NOOP("parameters", "Next to target"), QT_TRANSLATE_NOOP("parameters", "With label"), QT_TRANSLATE_NOOP("parameters", "Hidden") ) /** * Ensures whether a provided value is of the corresponding type. * @param {string|PropertyType} propertyType * @param {string|number|boolean|array|object}value * @returns {Object} */ export function ensureTypeSafety(propertyType, value) { let result let error = false if(typeof propertyType == "string") switch(propertyType) { case "string": result = value error = typeof value !== "string" break case "number": result = parseFloat(value) error = isNaN(result) break case "boolean": result = value error = value !== true && value !== false break case "Domain": try { error = typeof value !== "string" if(!error) result = parseDomain(value) } catch(e) { // Parse domain sometimes returns an empty set when it cannot parse a domain. // It's okay though, it shouldn't be user parsed value, so returning an empty set // is okay for a corrupted file, rather than erroring. console.trace() console.log(`Error parsing domain ${value}:`) console.error(e) error = true } break } else if(propertyType instanceof PropertyType) { result = propertyType.parse(value) error = result === NONE } else throw new TypeError(`Importation error: Unknown property type ${propertyType}.`) if(error) { console.trace() throw new TypeError(`Importation error: Couldn't parse ${JSON.stringify(value)} as ${propertyType}.`) } return result } /** * Serializes a property by its type to export into JSON. * @param {string|PropertyType} propertyType * @param value * @returns {Object} */ export function serializesByPropertyType(propertyType, value) { let result if(typeof propertyType == "string") switch(propertyType) { case "string": result = value.toString() break case "number": result = parseFloat(value) break case "boolean": result = value === true break case "Domain": if(value instanceof Domain) result = value.toString() else throw new TypeError(`Exportation error: ${value} is not a domain.`) break } else if(propertyType instanceof PropertyType) { result = propertyType.export(value) } else throw new TypeError(`Exportation error: Unknown property type ${propertyType}.`) return result }