UNPKG

toosoon-lsystem

Version:

Library providing functionalities for creating and manipulating Lindenmayer systems (L-Systems) using various parameters

179 lines (178 loc) 5.89 kB
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; }