Skip to content

Commit

Permalink
one step further for better parametric L-System support: make match()…
Browse files Browse the repository at this point in the history
… usable with them. Which makes context sensitive parametric Lsys' possible. See new test for an example.
  • Loading branch information
Tom committed Jun 19, 2016
1 parent d9ab6ba commit 212c6d7
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 108 deletions.
40 changes: 28 additions & 12 deletions dist/lindenmayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
// if using objects in axioms, as used in parametric L-Systems
this.getString = function (onlySymbols = true) {
if (typeof this.axiom === 'string') return this.axiom;

if (onlySymbols === true) {
return this.axiom.reduce((prev, current) => prev + current.symbol, '');
return this.axiom.reduce((prev, current) => {
if (current.symbol === undefined) {
console.log('found:', current);
throw new Error('L-Systems that use only objects as symbols (eg: {symbol: \'F\', params: []}), cant use string symbols (eg. \'F\')! Check if you always return objects in your productions and no strings.');
}
return prev + current.symbol;
}, '');
} else {
return JSON.stringify(this.axiom);
}
Expand Down Expand Up @@ -88,14 +93,14 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl

// Replace whitespaces, then split between square brackets.
let splitAxiom = axiom.replace(/\s+/g, '').split(/[\(\)]/);
console.log('parts:', splitAxiom);
// console.log('parts:', splitAxiom)
let newAxiom = [];
// Construct new axiom by getting the params and symbol.
for (let i = 0; i < splitAxiom.length - 1; i += 2) {
let params = splitAxiom[i + 1].split(',').map(Number);
newAxiom.push({ symbol: splitAxiom[i], params: params });
}
console.log('parsed axiom:', newAxiom);
// console.log('parsed axiom:', newAxiom)
};

// transform a classic syntax production into valid JS production
Expand Down Expand Up @@ -132,16 +137,16 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
// which can't be interpreted by the JS engine)
let transformedFunction = ({ index: _index, part: _part, currentAxiom: _axiom, params: _params }) => {

let leftMatch = true;
let rightMatch = true;
let leftMatch = { result: true };
let rightMatch = { result: true };

// this can possibly be optimized (see: https://developers.google.com/speed/articles/optimizing-javascript#avoiding-pitfalls-with-closures)
if (left !== null) {
leftMatch = this.match({ direction: 'left', match: left[1], index: _index, branchSymbols: '[]', ignoredSymbols: '+-&' });
}

// don't match with right side if left already false or no right match necessary
if (leftMatch === false || leftMatch === true && right === null) return leftMatch ? p[1] : indexSymbol;
if (leftMatch.result === false || leftMatch.result === true && right === null) return leftMatch.result ? p[1] : _part;

// see left!== null. could be optimized. Creating 3 variations of function
// so left/right are not checked here, which improves speed, as left/right
Expand All @@ -150,7 +155,13 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
rightMatch = this.match({ direction: 'right', match: right[2], index: _index, branchSymbols: '[]', ignoredSymbols: '+-&' });
}

return leftMatch && rightMatch ? p[1] : indexSymbol;
// Match! On a match return either the result of given production function
// or simply return the symbol itself if its no function.
if (leftMatch.result && rightMatch.result) {
return typeof p[1] === 'function' ? p[1]({ index: _index, part: _part, currentAxiom: _axiom, params: _params, leftMatchIndices: leftMatch.matchIndices, rightMatchIndices: rightMatch.matchIndices }) : p[1];
} else {
return _part;
}
};

let transformedProduction = [indexSymbol, transformedFunction];
Expand Down Expand Up @@ -197,7 +208,7 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
This assumes, it's a list of functions.
*/
for (let _p of p) {
let _result = typeof _p === 'function' ? _p({ index, currentAxiom: this.axiom, part, params }) : _p;
let _result = typeof _p === 'function' ? _p({ index, currentAxiom: newAxiom, part, params }) : _p;
if (_result !== undefined && _result !== false) {
result = _result;
break;
Expand Down Expand Up @@ -282,12 +293,12 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
*/

this.match = function ({ axiom_, match, ignoredSymbols, branchSymbols, index, direction }) {

let branchCount = 0;
let explicitBranchCount = 0;
axiom_ = axiom || this.axiom;
if (branchSymbols === undefined) branchSymbols = this.branchSymbols !== undefined ? this.branchSymbols : [];
if (ignoredSymbols === undefined) ignoredSymbols = this.ignoredSymbols !== undefined ? this.ignoredSymbols : [];
let returnMatchIndices = [];

let branchStart, branchEnd, axiomIndex, loopIndexChange, matchIndex, matchIndexChange, matchIndexOverflow;
// set some variables depending on the direction to match
Expand All @@ -309,7 +320,10 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl

for (; axiomIndex < axiom_.length && axiomIndex >= 0; axiomIndex += loopIndexChange) {
// FIXME: what about objects with .symbol

let axiomSymbol = axiom_[axiomIndex];
// For objects match for objects `symbol`
if (typeof axiomSymbol === 'object') axiomSymbol = axiomSymbol.symbol;
let matchSymbol = match[matchIndex];

// compare current symbol of axiom with current symbol of match
Expand All @@ -329,9 +343,11 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
// only increase match if we are out of explicit branch

if (explicitBranchCount === 0) {

matchIndex += matchIndexChange;
}
} else {
returnMatchIndices.push(axiomIndex);
matchIndex += matchIndexChange;
}
}
Expand All @@ -340,7 +356,7 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
// -> no more matches to do. return with true, as everything matched until here
// *yay*
if (matchIndex === matchIndexOverflow) {
return true;
return { result: true, matchIndices: returnMatchIndices };
}
} else if (axiomSymbol === branchStart) {
branchCount++;
Expand All @@ -351,7 +367,7 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
} else if ((branchCount === 0 || explicitBranchCount > 0 && matchSymbol !== branchEnd) && ignoredSymbols.includes(axiomSymbol) === false) {
// not in branchSymbols/branch? or if in explicit branch, and not at the very end of
// condition (at the ]), and symbol not in ignoredSymbols ? then false
return false;
return { result: false, matchIndices: returnMatchIndices };
}
}
};
Expand Down
40 changes: 28 additions & 12 deletions examples/lindenmayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
// if using objects in axioms, as used in parametric L-Systems
this.getString = function (onlySymbols = true) {
if (typeof this.axiom === 'string') return this.axiom;

if (onlySymbols === true) {
return this.axiom.reduce((prev, current) => prev + current.symbol, '');
return this.axiom.reduce((prev, current) => {
if (current.symbol === undefined) {
console.log('found:', current);
throw new Error('L-Systems that use only objects as symbols (eg: {symbol: \'F\', params: []}), cant use string symbols (eg. \'F\')! Check if you always return objects in your productions and no strings.');
}
return prev + current.symbol;
}, '');
} else {
return JSON.stringify(this.axiom);
}
Expand Down Expand Up @@ -88,14 +93,14 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl

// Replace whitespaces, then split between square brackets.
let splitAxiom = axiom.replace(/\s+/g, '').split(/[\(\)]/);
console.log('parts:', splitAxiom);
// console.log('parts:', splitAxiom)
let newAxiom = [];
// Construct new axiom by getting the params and symbol.
for (let i = 0; i < splitAxiom.length - 1; i += 2) {
let params = splitAxiom[i + 1].split(',').map(Number);
newAxiom.push({ symbol: splitAxiom[i], params: params });
}
console.log('parsed axiom:', newAxiom);
// console.log('parsed axiom:', newAxiom)
};

// transform a classic syntax production into valid JS production
Expand Down Expand Up @@ -132,16 +137,16 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
// which can't be interpreted by the JS engine)
let transformedFunction = ({ index: _index, part: _part, currentAxiom: _axiom, params: _params }) => {

let leftMatch = true;
let rightMatch = true;
let leftMatch = { result: true };
let rightMatch = { result: true };

// this can possibly be optimized (see: https://developers.google.com/speed/articles/optimizing-javascript#avoiding-pitfalls-with-closures)
if (left !== null) {
leftMatch = this.match({ direction: 'left', match: left[1], index: _index, branchSymbols: '[]', ignoredSymbols: '+-&' });
}

// don't match with right side if left already false or no right match necessary
if (leftMatch === false || leftMatch === true && right === null) return leftMatch ? p[1] : indexSymbol;
if (leftMatch.result === false || leftMatch.result === true && right === null) return leftMatch.result ? p[1] : _part;

// see left!== null. could be optimized. Creating 3 variations of function
// so left/right are not checked here, which improves speed, as left/right
Expand All @@ -150,7 +155,13 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
rightMatch = this.match({ direction: 'right', match: right[2], index: _index, branchSymbols: '[]', ignoredSymbols: '+-&' });
}

return leftMatch && rightMatch ? p[1] : indexSymbol;
// Match! On a match return either the result of given production function
// or simply return the symbol itself if its no function.
if (leftMatch.result && rightMatch.result) {
return typeof p[1] === 'function' ? p[1]({ index: _index, part: _part, currentAxiom: _axiom, params: _params, leftMatchIndices: leftMatch.matchIndices, rightMatchIndices: rightMatch.matchIndices }) : p[1];
} else {
return _part;
}
};

let transformedProduction = [indexSymbol, transformedFunction];
Expand Down Expand Up @@ -197,7 +208,7 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
This assumes, it's a list of functions.
*/
for (let _p of p) {
let _result = typeof _p === 'function' ? _p({ index, currentAxiom: this.axiom, part, params }) : _p;
let _result = typeof _p === 'function' ? _p({ index, currentAxiom: newAxiom, part, params }) : _p;
if (_result !== undefined && _result !== false) {
result = _result;
break;
Expand Down Expand Up @@ -282,12 +293,12 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
*/

this.match = function ({ axiom_, match, ignoredSymbols, branchSymbols, index, direction }) {

let branchCount = 0;
let explicitBranchCount = 0;
axiom_ = axiom || this.axiom;
if (branchSymbols === undefined) branchSymbols = this.branchSymbols !== undefined ? this.branchSymbols : [];
if (ignoredSymbols === undefined) ignoredSymbols = this.ignoredSymbols !== undefined ? this.ignoredSymbols : [];
let returnMatchIndices = [];

let branchStart, branchEnd, axiomIndex, loopIndexChange, matchIndex, matchIndexChange, matchIndexOverflow;
// set some variables depending on the direction to match
Expand All @@ -309,7 +320,10 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl

for (; axiomIndex < axiom_.length && axiomIndex >= 0; axiomIndex += loopIndexChange) {
// FIXME: what about objects with .symbol

let axiomSymbol = axiom_[axiomIndex];
// For objects match for objects `symbol`
if (typeof axiomSymbol === 'object') axiomSymbol = axiomSymbol.symbol;
let matchSymbol = match[matchIndex];

// compare current symbol of axiom with current symbol of match
Expand All @@ -329,9 +343,11 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
// only increase match if we are out of explicit branch

if (explicitBranchCount === 0) {

matchIndex += matchIndexChange;
}
} else {
returnMatchIndices.push(axiomIndex);
matchIndex += matchIndexChange;
}
}
Expand All @@ -340,7 +356,7 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
// -> no more matches to do. return with true, as everything matched until here
// *yay*
if (matchIndex === matchIndexOverflow) {
return true;
return { result: true, matchIndices: returnMatchIndices };
}
} else if (axiomSymbol === branchStart) {
branchCount++;
Expand All @@ -351,7 +367,7 @@ function LSystem({ axiom, productions, finals, branchSymbols, ignoredSymbols, cl
} else if ((branchCount === 0 || explicitBranchCount > 0 && matchSymbol !== branchEnd) && ignoredSymbols.includes(axiomSymbol) === false) {
// not in branchSymbols/branch? or if in explicit branch, and not at the very end of
// condition (at the ]), and symbol not in ignoredSymbols ? then false
return false;
return { result: false, matchIndices: returnMatchIndices };
}
}
};
Expand Down
Loading

0 comments on commit 212c6d7

Please sign in to comment.