API Docs for:
Show:

File: js/l-system.js

/**
 * @module gallery-l-system
 */
(function (Y, moduleName) {
    'use strict';

    var _null = null,
        _string_iterations = 'iterations',
        _string_pattern = 'pattern',
        _string_rules = 'rules',
        _string_value = 'value',
        _true = true,
        
        _Array = Y.Array,
        _Base = Y.Base,
        _Lang = Y.Lang,
        _RegExp = RegExp,

        _isArray = _Lang.isArray,
        _isFunction = _Lang.isFunction,
        _isObject = _Lang.isObject,
        _keys = Y.Object.keys,
        _map = _Array.map,
        _some = _Array.some;

    /**
     * @class LSystem
     * @constructor
     * @extends Base
     * @param {Object} config Configuration Object.
     */
    Y.LSystem = _Base.create(moduleName, _Base, [], {
        initializer: function () {
            var me = this;

            me.on([
                'axiomChange',
                'rulesChange'
            ], me.restart, me);

            me.restart();
        },
        /**
         * Iterates the l-system's value.
         * @method iterate
         * @chainable
         * @param {Number} [iterations] Optional.  The number of iterations to
         * perform.  Defaults to 1.  The l-system can only iterate forward so
         * negative values aren't accepted.
         */
        iterate: function (iterations) {
            var i = iterations && iterations >= 1 ? iterations : 1,
                me = this,
                pattern = me.get(_string_pattern),
                rules = me.get(_string_rules),
                value = me.get(_string_value),

                replaceFunction = function (match, index, value) {
                    var replace = me._resolveMatch(rules[match], index, value, match);

                    if (replace === _null) {
                        return match;
                    }

                    return replace;
                };

            while (i > 0) {
                i -= 1;
                value = value.replace(pattern, replaceFunction);
            }

            return me._set(_string_iterations, me.get(_string_iterations) + iterations)._set(_string_value, value);
        },
        /**
         * This method restarts the l-system and resets its value.
         * @method restart
         * @chainable
         */
        restart: function () {
            var me = this;

            return me._set(_string_iterations, 0)._set(_string_pattern, new _RegExp(_map(_keys(me.get(_string_rules)), function (key) {
                if ('\\^$*+?.()|{}[]'.indexOf(key) !== -1) {
                    return '\\' + key;
                }

                return key;
            }).join('|'), 'g'))._set(_string_value, me.get('axiom'));
        },
        /**
         * @method _resolveMatch
         * @param {Array|Object|String|WeightedList} ruleValue
         * @param {Number} index
         * @param {String} value
         * @param {String} match
         * @protected
         * @return {String} Returns null if the rule doesn't apply.
         */
        _resolveMatch: function (ruleValue, index, value, match) {
            var WeightedList = Y.WeightedList,

                last,
                me = this,
                temp;

            if (_isArray(ruleValue)) {
                _some(ruleValue, function (ruleValue) {
                    temp = me._resolveMatch(ruleValue, index, value, match);

                    if (temp !== _null) {
                        return _true;
                    }
                });

                return temp;
            }

            if (_isObject(ruleValue)) {
                if (WeightedList && ruleValue instanceof WeightedList) {
                    return me._resolveMatch(ruleValue.value(), index, value, match);
                }

                temp = ruleValue.fn;

                if (_isFunction(temp)) {
                    return temp.call(ruleValue.ctx, match, index, value);
                }

                temp = ruleValue.first;

                if (temp && index || temp === false && !index) {
                    return _null;
                }

                last = value.length - 1;
                temp = ruleValue.last;

                if (temp && index !== last || temp === false && index ===  last) {
                    return _null;
                }

                temp = ruleValue.next;

                if (temp) {
                    if (WeightedList && temp instanceof WeightedList) {
                        temp = temp.value();
                    }

                    if (value.substr(index + 1, temp.length) !== temp) {
                        return _null;
                    }
                }

                temp = ruleValue.notNext;

                if (temp) {
                    if (WeightedList && temp instanceof WeightedList) {
                        temp = temp.value();
                    }

                    if (value.substr(index + 1, temp.length) === temp) {
                        return _null;
                    }
                }

                temp = ruleValue.notPrev;

                if (temp) {
                    if (!index) {
                        return _null;
                    }

                    if (WeightedList && temp instanceof WeightedList) {
                        temp = temp.value();
                    }

                    if (value.substr(index - temp.length, temp.length) === temp) {
                        return _null;
                    }
                }

                temp = ruleValue.prev;

                if (temp) {
                    if (!index) {
                        return _null;
                    }

                    if (WeightedList && temp instanceof WeightedList) {
                        temp = temp.value();
                    }

                    if (value.substr(index - temp.length, temp.length) !== temp) {
                        return _null;
                    }
                }

                return me._resolveMatch(ruleValue.value, index, value, match);
            }

            return ruleValue;
        }
    }, {
        ATTRS: {
            /**
             * The axiom is the initial value of the l-system.  Note: If the
             * axiom is changed after the l-system has been iterated, the
             * l-system will be restarted.
             * @attribute axiom
             * @default ''
             * @type String
             */
            axiom: {
                value: ''
            },
            /**
             * The number of iterations the l-system has been set to.  This
             * attribute is read only; use the iterate method to iterate the
             * l-system.
             * @attribute iterations
             * @default 0
             * @readonly
             * @type Number
             */
            iterations: {
                readOnly: _true,
                value: 0
            },
            /**
             * The regular expression that is used to help apply the rules
             * during iteration.
             * @attribute pattern
             * @protected
             * @readonly
             * @type RegExp
             */
            pattern: {
                readOnly: _true,
                value: _null
            },
            /**
             * The l-system's rules.  Note: If the rules are changed after the
             * l-system has been iterated, the l-system will be restarted.  If
             * you read this object and manipulate it directly, you must call
             * the restart method before iterating the l-system again otherwise
             * it will yield unknown results.
             *
             * The rules are key/value pairs.  The rules object's keys should be
             * single characters.  In the simplest case, the rules object's
             * values are strings.  For  example, with this rules object
             * {a: 'abc'}, every time the character 'a' is found in the
             * l-system's value, it will be replaced with the string 'abc'.
             *
             * The rules object's values may also be objects for more control
             * over when the rule is applied.  For example, with this rules
             * object {a: {prev: 'ccc', value: 'abc'}}, every time the character
             * 'a' is found in the l-system's value and the previous characters
             * in the l-system's value are 'ccc', the 'a' will be replaced with
             * the string 'abc'.  In the simplest case, the value property is a
             * string but it may be any value that can be one of the rule
             * object's values.  Here is a list of the properties that affect
             * when this rule is applied:
             * <dl>
             *     <dt>
             *         first
             *     </dt>
             *     <dd>
             *         If the first property is defined and is a truthy value,
             *         the rule will only be applied if the matched character is
             *         the first character in the l-system's value.  If the
             *         first property is set to false, the rule will only be
             *         applied if the matched character is not the first
             *         character in the l-system's value.
             *     </dd>
             *     <dt>
             *         last
             *     </dt>
             *     <dd>
             *         If the last property is defined and is a truthy value,
             *         the rule will only be applied if the matched character is
             *         the last character in the l-system's value.  If the last
             *         property is set to false, the rule will only be applied
             *         if the matched character is not the last character in the
             *         l-system's value.
             *     </dd>
             *     <dt>
             *         next
             *     </dt>
             *     <dd>
             *         If the next property is defined, it should be a string of
             *         one or more characters.  The rule will only be applied if
             *         the matched character is followed by this string of
             *         characters exactly.
             *
             *         The next property may also be a weighted list of strings
             *         of one or more characters.  In this case, the value will
             *         be selected from the weighted list at random.
             *     </dd>
             *     <dt>
             *         notNext
             *     </dt>
             *     <dd>
             *         If the notNext property is defined, it should be a string
             *         of one or more characters.  The rule will only be applied
             *         if the matched character is not followed by this string
             *         of characters exactly.
             *
             *         The notNext property may also be a weighted list of
             *         strings of one or more characters.  In this case, the
             *         value will be selected from the weighted list at random.
             *     </dd>
             *     <dt>
             *         notPrev
             *     </dt>
             *     <dd>
             *         If the notPrev property is defined, it should be a string
             *         of one or more characters.  The rule will only be applied
             *         if the matched character is not preceded by this string
             *         of characters exactly.
             *
             *         The notPrev property may also be a weighted list of
             *         strings of one or more characters.  In this case, the
             *         value will be selected from the weighted list at random.
             *     </dd>
             *     <dt>
             *         prev
             *     </dt>
             *     <dd>
             *         If the prev property is defined, it should be a string of
             *         one or more characters.  The rule will only be applied if
             *         the matched character is preceded by this string of
             *         characters exactly.
             *
             *         The prev property may also be a weighted list of strings
             *         of one or more characters.  In this case, the value will
             *         be selected from the weighted list at random.
             *     </dd>
             * </dl>
             *
             * For even further control, the rules object's values may be
             * objects with a fn property.  The fn property should be a function
             * that accepts three arguments:
             * <dl>
             *     <dt>
             *         match
             *     </dt>
             *     <dd>
             *         String.  The single character that was matched.
             *     </dd>
             *     <dt>
             *         index
             *     </dt>
             *     <dd>
             *         Number.  The index of the matched character within the
             *         l-system's value.
             *     </dd>
             *     <dt>
             *         value
             *     </dt>
             *     <dd>
             *         String.  The l-system's current value.
             *     </dd>
             * </dl>
             * The function should return a string or null.  The rule will only
             * be applied if the function does not return null.  The object may
             * also have a ctx property which will be used as the execution
             * context for the function.
             *
             * Multiple rules may be given for a single character by providing
             * an array as a rules object's value.  The array may contain any
             * value that is acceptable as a rules object's value.  The array
             * items will be processed in order; the first rule that applies
             * will be used.  Because of this, the array items should usually be
             * one of the ojects described above.  The array may contain string
             * items, but since string rules are always applied, there should
             * only ever be one string item in the array and it should be the
             * last item in the array.
             *
             * A rules object's value may also be a weighted list of any of the
             * other values described above.  In this case, the values will be
             * selected from the weighted list at random.
             * @attribute rules
             * @default {}
             * @type Object
             */
            rules: {
                value: {}
            },
            /**
             * The current value of the l-system.
             * @attribute value
             * @default ''
             * @readonly
             * @type String
             */
            value: {
                readOnly: _true,
                value: ''
            }
        }
    });
}(Y, arguments[1]));