toosoon-lsystem
Version:
Library providing functionalities for creating and manipulating Lindenmayer systems (L-Systems) using various parameters
179 lines (178 loc) • 5.89 kB
JavaScript
import { PARAMETRIC_SYMBOLS } from './constants';
// *********************
// Normalizers
// *********************
/**
* Normalize parameter into valid axiom
*
* @template {Alphabet} A Alphabet
* @template {Alphabet} I Ignored Alphabet
* @param {AxiomParameter<A|I> | ProductionResult<A|I>} parameter
* @param {A} alphabet
* @param {I} ignoredSymbols
* @param {Defines} defines
* @returns {Axiom<A|I>}
*/
export function normalizeAxiom(parameter, alphabet, ignoredSymbols, defines) {
const axiom = [];
if (typeof parameter === 'string') {
// If parameter is a Phrase, transform and merge it into new axiom
axiom.push(...transformPhraseToAxiom(parameter, alphabet, ignoredSymbols, defines));
}
else if (parameter instanceof Array) {
// If parameter is an Axiom, merge it into new axiom
axiom.push(...parameter);
}
else if (typeof parameter === 'object') {
// If parameter is an AxiomPart, add it to new axiom
axiom.push(parameter);
}
return axiom;
}
/**
* Normalize parameter into valid Symbol & Production
*
* @template {Alphabet} A Alphabet
* @template {Alphabet} I Ignored Alphabet
* @param {SuccessorParameter<A>} successorParameter
* @param {ProductionParameter<A,I>} productionParameter
* @returns { symbol: Symbol<A>; production: Production<A,I> }
*/
export function normalizeProduction(successorParameter, productionParameter) {
// Normalize production by forcing object form
let production;
if (typeof productionParameter === 'string') {
production = { successor: productionParameter };
}
else {
production = productionParameter;
}
// Transform classic context syntax
const contextInfos = transformClassicContext(successorParameter);
let symbol = contextInfos.symbol;
// Set context from classic syntax if no context is found
if (production.context === undefined) {
production.context = contextInfos.context;
}
// Transform parametrical context syntax
const parametricInfos = transformClassicParametric(symbol);
symbol = parametricInfos.symbol;
production.params = parametricInfos.params;
return {
symbol,
production
};
}
// *********************
// Transformers
// *********************
/**
* Transform context classic syntax
*
* @template {Alphabet} A Alphabet
* @param {SuccessorParameter<A>} successorParameter
* @returns { symbol: Symbol<A>; context: Context<A> }
*/
export function transformClassicContext(successorParameter) {
// Initialize values (before<symbol>after)
let symbol = successorParameter;
let context = {};
// Transform 'before' classic syntax (symbol>after)
const before = symbol.match(/(.+)<(.+)/);
if (before) {
context.before = before[1];
symbol = before[2];
}
// Transform 'after' classic syntax (symbol)
const after = symbol.match(/(.+)>(.+)/);
if (after) {
symbol = after[1];
context.after = after[2];
}
return { symbol, context };
}
/**
* Transform parametric classic syntax
*
* @template {Alphabet} A Alphabet
* @param {Symbol<A>|ParametricSymbol<A>} parametricSymbol
* @returns { symbol: Symbol<A>; params: string[] }
*/
export function transformClassicParametric(parametricSymbol) {
// Initialize values (S(0, A))
let symbol = parametricSymbol;
let params = [];
// Transform parametric classic syntax (S, ['0', 'A'])
const split = parametricSymbol.replace(/\s+/g, '').split(/[()]/);
if (split.length > 1) {
symbol = split[0];
params = split[1].split(',');
}
return { symbol, params };
}
/**
* Split a phrase into axiom from an alphabet
*
* @template {Alphabet} A Alphabet
* @template {Alphabet} I Ignored Alphabet
* @param {Phrase} phrase
* @param {A} alphabet
* @param {I} ignoredSymbols
* @param {Defines} defines
* @returns {Axiom<A|I>}
*/
export function transformPhraseToAxiom(phrase, alphabet, ignoredSymbols, defines) {
const split = phrase.replace(/\s+/g, '').split('');
const axiom = [];
let symbol = '';
let params = [];
let index = 0;
for (let i = 0; i < split.length; i++) {
symbol += split[index];
// Transform parametric syntax
const isParametric = split[index + 1] === PARAMETRIC_SYMBOLS[0];
if (isParametric) {
// Loop until next parametric end (')')
while (split[index] !== PARAMETRIC_SYMBOLS[1] && index < split.length) {
index++;
symbol += split[index];
}
const parametricInfos = transformClassicParametric(symbol);
symbol = parametricInfos.symbol;
params = parametricInfos.params.map((param) => {
defines.forEach((define, defineKey) => {
param = param.replace(defineKey, String(define));
});
return eval(param);
});
}
// Add new AxiomPart to Axiom
if (alphabet.includes(symbol) || ignoredSymbols.includes(symbol)) {
axiom.push({ symbol, params });
symbol = '';
params = [];
}
index++;
if (index === split.length)
break;
}
return axiom;
}
/**
* Convert keys & values into a Map
*
* @param {string[]} [keys]
* @param {number[]} [values]
* @returns {Defines}
*/
export function transformParamsToDefines(keys, values) {
// Create defines from from classic parametric syntax
const defines = new Map();
if (keys && values && keys.length === values.length) {
keys.forEach((key, index) => defines.set(key, values[index]));
}
else {
console.warn('transformParamsToDefines', "keys & values lengths don't match:", `\nkeys: ${keys?.length}\nvalues: ${values?.length}`);
}
return defines;
}